summaryrefslogtreecommitdiff
path: root/src/com/android/calendar/month/MonthByWeekFragment.kt
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/calendar/month/MonthByWeekFragment.kt')
-rw-r--r--src/com/android/calendar/month/MonthByWeekFragment.kt497
1 files changed, 497 insertions, 0 deletions
diff --git a/src/com/android/calendar/month/MonthByWeekFragment.kt b/src/com/android/calendar/month/MonthByWeekFragment.kt
new file mode 100644
index 00000000..9fe9fe49
--- /dev/null
+++ b/src/com/android/calendar/month/MonthByWeekFragment.kt
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.calendar.month
+
+import android.app.Activity
+import android.app.LoaderManager
+import android.content.ContentUris
+import android.content.CursorLoader
+import android.content.Loader
+import android.content.res.Resources
+import android.database.Cursor
+import android.graphics.drawable.StateListDrawable
+import android.net.Uri
+import android.os.Bundle
+import android.provider.CalendarContract.Attendees
+import android.provider.CalendarContract.Calendars
+import android.provider.CalendarContract.Instances
+import android.text.format.DateUtils
+import android.text.format.Time
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.View
+import android.view.View.OnTouchListener
+import android.view.ViewConfiguration
+import android.view.ViewGroup
+import android.widget.AbsListView
+import android.widget.AbsListView.OnScrollListener
+
+import com.android.calendar.CalendarController
+import com.android.calendar.CalendarController.EventInfo
+import com.android.calendar.CalendarController.EventType
+import com.android.calendar.CalendarController.ViewType
+import com.android.calendar.Event
+import com.android.calendar.R
+import com.android.calendar.Utils
+
+import java.util.ArrayList
+import java.util.Calendar
+import java.util.HashMap
+
+class MonthByWeekFragment @JvmOverloads constructor(
+ initialTime: Long = System.currentTimeMillis(),
+ protected var mIsMiniMonth: Boolean = true
+) : SimpleDayPickerFragment(initialTime), CalendarController.EventHandler,
+ LoaderManager.LoaderCallbacks<Cursor?>, OnScrollListener, OnTouchListener {
+ protected var mMinimumTwoMonthFlingVelocity = 0f
+ protected var mHideDeclined = false
+ protected var mFirstLoadedJulianDay = 0
+ protected var mLastLoadedJulianDay = 0
+ private var mLoader: CursorLoader? = null
+ private var mEventUri: Uri? = null
+ private val mDesiredDay: Time = Time()
+
+ @Volatile
+ private var mShouldLoad = true
+ private var mUserScrolled = false
+ private var mEventsLoadingDelay = 0
+ private var mShowCalendarControls = false
+ private var mIsDetached = false
+ private val mTZUpdater: Runnable = object : Runnable {
+ @Override
+ override fun run() {
+ val tz: String? = Utils.getTimeZone(mContext, this)
+ mSelectedDay.timezone = tz
+ mSelectedDay.normalize(true)
+ mTempTime.timezone = tz
+ mFirstDayOfMonth.timezone = tz
+ mFirstDayOfMonth.normalize(true)
+ mFirstVisibleDay.timezone = tz
+ mFirstVisibleDay.normalize(true)
+ if (mAdapter != null) {
+ mAdapter?.refresh()
+ }
+ }
+ }
+ private val mUpdateLoader: Runnable = object : Runnable {
+ @Override
+ override fun run() {
+ synchronized(this) {
+ if (!mShouldLoad || mLoader == null) {
+ return
+ }
+ // Stop any previous loads while we update the uri
+ stopLoader()
+
+ // Start the loader again
+ mEventUri = updateUri()
+ mLoader?.setUri(mEventUri)
+ mLoader?.startLoading()
+ mLoader?.onContentChanged()
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Started loader with uri: $mEventUri")
+ }
+ }
+ }
+ }
+
+ // Used to load the events when a delay is needed
+ var mLoadingRunnable: Runnable = object : Runnable {
+ @Override
+ override fun run() {
+ if (!mIsDetached) {
+ mLoader = getLoaderManager().initLoader(
+ 0, null,
+ this@MonthByWeekFragment
+ ) as? CursorLoader
+ }
+ }
+ }
+
+ /**
+ * Updates the uri used by the loader according to the current position of
+ * the listview.
+ *
+ * @return The new Uri to use
+ */
+ private fun updateUri(): Uri {
+ val child: SimpleWeekView? = mListView?.getChildAt(0) as? SimpleWeekView
+ if (child != null) {
+ val julianDay: Int = child?.getFirstJulianDay()
+ mFirstLoadedJulianDay = julianDay
+ }
+ // -1 to ensure we get all day events from any time zone
+ mTempTime.setJulianDay(mFirstLoadedJulianDay - 1)
+ val start: Long = mTempTime.toMillis(true)
+ mLastLoadedJulianDay = mFirstLoadedJulianDay + (mNumWeeks + 2 * WEEKS_BUFFER) * 7
+ // +1 to ensure we get all day events from any time zone
+ mTempTime.setJulianDay(mLastLoadedJulianDay + 1)
+ val end: Long = mTempTime.toMillis(true)
+
+ // Create a new uri with the updated times
+ val builder: Uri.Builder = Instances.CONTENT_URI.buildUpon()
+ ContentUris.appendId(builder, start)
+ ContentUris.appendId(builder, end)
+ return builder.build()
+ }
+
+ // Extract range of julian days from URI
+ private fun updateLoadedDays() {
+ val pathSegments = mEventUri?.getPathSegments()
+ val size: Int = pathSegments?.size as Int
+ if (size <= 2) {
+ return
+ }
+ val first: Long = (pathSegments!![size - 2])?.toLong() as Long
+ val last: Long = (pathSegments!![size - 1])?.toLong() as Long
+ mTempTime.set(first)
+ mFirstLoadedJulianDay = Time.getJulianDay(first, mTempTime.gmtoff)
+ mTempTime.set(last)
+ mLastLoadedJulianDay = Time.getJulianDay(last, mTempTime.gmtoff)
+ }
+
+ protected fun updateWhere(): String {
+ // TODO fix selection/selection args after b/3206641 is fixed
+ var where = WHERE_CALENDARS_VISIBLE
+ if (mHideDeclined || !mShowDetailsInMonth) {
+ where += (" AND " + Instances.SELF_ATTENDEE_STATUS.toString() + "!=" +
+ Attendees.ATTENDEE_STATUS_DECLINED)
+ }
+ return where
+ }
+
+ private fun stopLoader() {
+ synchronized(mUpdateLoader) {
+ mHandler.removeCallbacks(mUpdateLoader)
+ if (mLoader != null) {
+ mLoader?.stopLoading()
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Stopped loader from loading")
+ }
+ }
+ }
+ }
+
+ @Override
+ override fun onAttach(activity: Activity) {
+ super.onAttach(activity)
+ mTZUpdater.run()
+ if (mAdapter != null) {
+ mAdapter?.setSelectedDay(mSelectedDay)
+ }
+ mIsDetached = false
+ val viewConfig: ViewConfiguration = ViewConfiguration.get(activity)
+ mMinimumTwoMonthFlingVelocity = viewConfig.getScaledMaximumFlingVelocity().toFloat() / 2f
+ val res: Resources = activity.getResources()
+ mShowCalendarControls = Utils.getConfigBool(activity, R.bool.show_calendar_controls)
+ // Synchronized the loading time of the month's events with the animation of the
+ // calendar controls.
+ if (mShowCalendarControls) {
+ mEventsLoadingDelay = res.getInteger(R.integer.calendar_controls_animation_time)
+ }
+ mShowDetailsInMonth = res.getBoolean(R.bool.show_details_in_month)
+ }
+
+ @Override
+ override fun onDetach() {
+ mIsDetached = true
+ super.onDetach()
+ if (mShowCalendarControls) {
+ if (mListView != null) {
+ mListView?.removeCallbacks(mLoadingRunnable)
+ }
+ }
+ }
+
+ @Override
+ protected override fun setUpAdapter() {
+ mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext)
+ mShowWeekNumber = Utils.getShowWeekNumber(mContext)
+ val weekParams = HashMap<String?, Int?>()
+ weekParams?.put(SimpleWeeksAdapter.WEEK_PARAMS_NUM_WEEKS, mNumWeeks)
+ weekParams?.put(SimpleWeeksAdapter.WEEK_PARAMS_SHOW_WEEK, if (mShowWeekNumber) 1 else 0)
+ weekParams?.put(SimpleWeeksAdapter.WEEK_PARAMS_WEEK_START, mFirstDayOfWeek)
+ weekParams?.put(MonthByWeekAdapter.WEEK_PARAMS_IS_MINI, if (mIsMiniMonth) 1 else 0)
+ weekParams?.put(
+ SimpleWeeksAdapter.WEEK_PARAMS_JULIAN_DAY,
+ Time.getJulianDay(mSelectedDay.toMillis(true), mSelectedDay.gmtoff)
+ )
+ weekParams.put(SimpleWeeksAdapter.WEEK_PARAMS_DAYS_PER_WEEK, mDaysPerWeek)
+ if (mAdapter == null) {
+ mAdapter = MonthByWeekAdapter(getActivity(), weekParams) as SimpleWeeksAdapter?
+ mAdapter?.registerDataSetObserver(mObserver)
+ } else {
+ mAdapter?.updateParams(weekParams)
+ }
+ mAdapter?.notifyDataSetChanged()
+ }
+
+ @Override
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ val v: View
+ v = if (mIsMiniMonth) {
+ inflater.inflate(R.layout.month_by_week, container, false)
+ } else {
+ inflater.inflate(R.layout.full_month_by_week, container, false)
+ }
+ mDayNamesHeader = v.findViewById(R.id.day_names) as? ViewGroup
+ return v
+ }
+
+ @Override
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ super.onActivityCreated(savedInstanceState)
+ mListView?.setSelector(StateListDrawable())
+ mListView?.setOnTouchListener(this)
+ if (!mIsMiniMonth) {
+ mListView?.setBackgroundColor(getResources().getColor(R.color.month_bgcolor))
+ }
+
+ // To get a smoother transition when showing this fragment, delay loading of events until
+ // the fragment is expended fully and the calendar controls are gone.
+ if (mShowCalendarControls) {
+ mListView?.postDelayed(mLoadingRunnable, mEventsLoadingDelay.toLong())
+ } else {
+ mLoader = getLoaderManager().initLoader(0, null, this) as? CursorLoader
+ }
+ mAdapter?.setListView(mListView)
+ }
+
+ @Override
+ protected override fun setUpHeader() {
+ if (mIsMiniMonth) {
+ super.setUpHeader()
+ return
+ }
+ mDayLabels = arrayOfNulls<String>(7)
+ for (i in Calendar.SUNDAY..Calendar.SATURDAY) {
+ mDayLabels[i - Calendar.SUNDAY] = DateUtils.getDayOfWeekString(
+ i,
+ DateUtils.LENGTH_MEDIUM
+ ).toUpperCase()
+ }
+ }
+
+ // TODO
+ @Override
+ override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor?>? {
+ if (mIsMiniMonth) {
+ return null
+ }
+ var loader: CursorLoader?
+ synchronized(mUpdateLoader) {
+ mFirstLoadedJulianDay =
+ (Time.getJulianDay(mSelectedDay.toMillis(true), mSelectedDay.gmtoff) -
+ mNumWeeks * 7 / 2)
+ mEventUri = updateUri()
+ val where = updateWhere()
+ loader = CursorLoader(
+ getActivity(), mEventUri, Event.EVENT_PROJECTION, where,
+ null /* WHERE_CALENDARS_SELECTED_ARGS */, INSTANCES_SORT_ORDER
+ )
+ loader?.setUpdateThrottle(LOADER_THROTTLE_DELAY.toLong())
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Returning new loader with uri: $mEventUri")
+ }
+ return loader
+ }
+
+ @Override
+ override fun doResumeUpdates() {
+ mFirstDayOfWeek = Utils.getFirstDayOfWeek(mContext)
+ mShowWeekNumber = Utils.getShowWeekNumber(mContext)
+ val prevHideDeclined = mHideDeclined
+ mHideDeclined = Utils.getHideDeclinedEvents(mContext)
+ if (prevHideDeclined != mHideDeclined && mLoader != null) {
+ mLoader?.setSelection(updateWhere())
+ }
+ mDaysPerWeek = Utils.getDaysPerWeek(mContext)
+ updateHeader()
+ mAdapter?.setSelectedDay(mSelectedDay)
+ mTZUpdater.run()
+ mTodayUpdater.run()
+ goTo(mSelectedDay.toMillis(true), false, true, false)
+ }
+
+ @Override
+ override fun onLoadFinished(loader: Loader<Cursor?>?, data: Cursor?) {
+ synchronized(mUpdateLoader) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(
+ TAG,
+ "Found " + data?.getCount()?.toString() + " cursor entries for uri " +
+ mEventUri
+ )
+ }
+ val cLoader: CursorLoader = loader as CursorLoader
+ if (mEventUri == null) {
+ mEventUri = cLoader.getUri()
+ updateLoadedDays()
+ }
+ if (cLoader.getUri().compareTo(mEventUri) !== 0) {
+ // We've started a new query since this loader ran so ignore the
+ // result
+ return
+ }
+ val events: ArrayList<Event?>? = ArrayList<Event?>()
+ Event.buildEventsFromCursor(
+ events, data, mContext, mFirstLoadedJulianDay, mLastLoadedJulianDay
+ )
+ (mAdapter as MonthByWeekAdapter).setEvents(
+ mFirstLoadedJulianDay,
+ mLastLoadedJulianDay - mFirstLoadedJulianDay + 1, events as ArrayList<Event>?
+ )
+ }
+ }
+
+ @Override
+ override fun onLoaderReset(loader: Loader<Cursor?>?) {
+ }
+
+ @Override
+ override fun eventsChanged() {
+ // TODO remove this after b/3387924 is resolved
+ if (mLoader != null) {
+ mLoader?.forceLoad()
+ }
+ }
+
+ @get:Override override val supportedEventTypes: Long
+ get() = EventType.GO_TO or EventType.EVENTS_CHANGED
+
+ @Override
+ override fun handleEvent(event: CalendarController.EventInfo?) {
+ if (event?.eventType === EventType.GO_TO) {
+ var animate = true
+ if (mDaysPerWeek * mNumWeeks * 2 < Math.abs(
+ Time.getJulianDay(event?.selectedTime?.toMillis(true) as Long,
+ event?.selectedTime?.gmtoff as Long) -
+ Time.getJulianDay(mFirstVisibleDay?.toMillis(true) as Long,
+ mFirstVisibleDay?.gmtoff as Long) -
+ mDaysPerWeek * mNumWeeks / 2L
+ )
+ ) {
+ animate = false
+ }
+ mDesiredDay.set(event?.selectedTime)
+ mDesiredDay.normalize(true)
+ val animateToday = event?.extraLong and
+ CalendarController.EXTRA_GOTO_TODAY.toLong() != 0L
+ val delayAnimation: Boolean =
+ goTo(event?.selectedTime?.toMillis(true)?.toLong() as Long,
+ animate, true, false)
+ if (animateToday) {
+ // If we need to flash today start the animation after any
+ // movement from listView has ended.
+ mHandler.postDelayed(object : Runnable {
+ @Override
+ override fun run() {
+ (mAdapter as? MonthByWeekAdapter)?.animateToday()
+ mAdapter?.notifyDataSetChanged()
+ }
+ }, if (delayAnimation) GOTO_SCROLL_DURATION.toLong() else 0L)
+ }
+ } else if (event?.eventType == EventType.EVENTS_CHANGED) {
+ eventsChanged()
+ }
+ }
+
+ @Override
+ protected override fun setMonthDisplayed(time: Time, updateHighlight: Boolean) {
+ super.setMonthDisplayed(time, updateHighlight)
+ if (!mIsMiniMonth) {
+ var useSelected = false
+ if (time.year == mDesiredDay.year && time.month == mDesiredDay.month) {
+ mSelectedDay.set(mDesiredDay)
+ mAdapter?.setSelectedDay(mDesiredDay)
+ useSelected = true
+ } else {
+ mSelectedDay.set(time)
+ mAdapter?.setSelectedDay(time)
+ }
+ val controller: CalendarController? = CalendarController.getInstance(mContext)
+ if (mSelectedDay.minute >= 30) {
+ mSelectedDay.minute = 30
+ } else {
+ mSelectedDay.minute = 0
+ }
+ val newTime: Long = mSelectedDay.normalize(true)
+ if (newTime != controller?.time && mUserScrolled) {
+ val offset: Long =
+ if (useSelected) 0 else DateUtils.WEEK_IN_MILLIS * mNumWeeks / 3.toLong()
+ controller?.time = (newTime + offset)
+ }
+ controller?.sendEvent(
+ this as Object?, EventType.UPDATE_TITLE, time, time, time, -1,
+ ViewType.CURRENT, DateUtils.FORMAT_SHOW_DATE.toLong() or
+ DateUtils.FORMAT_NO_MONTH_DAY.toLong() or
+ DateUtils.FORMAT_SHOW_YEAR.toLong(), null, null
+ )
+ }
+ }
+
+ @Override
+ override fun onScrollStateChanged(view: AbsListView?, scrollState: Int) {
+ synchronized(mUpdateLoader) {
+ if (scrollState != OnScrollListener.SCROLL_STATE_IDLE) {
+ mShouldLoad = false
+ stopLoader()
+ mDesiredDay.setToNow()
+ } else {
+ mHandler.removeCallbacks(mUpdateLoader)
+ mShouldLoad = true
+ mHandler.postDelayed(mUpdateLoader, LOADER_DELAY.toLong())
+ }
+ }
+ if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
+ mUserScrolled = true
+ }
+ mScrollStateChangedRunnable.doScrollStateChange(view, scrollState)
+ }
+
+ @Override
+ override fun onTouch(v: View?, event: MotionEvent?): Boolean {
+ mDesiredDay.setToNow()
+ return false
+ }
+
+ companion object {
+ private const val TAG = "MonthFragment"
+ private const val TAG_EVENT_DIALOG = "event_dialog"
+
+ // Selection and selection args for adding event queries
+ private val WHERE_CALENDARS_VISIBLE: String = Calendars.VISIBLE.toString() + "=1"
+ private val INSTANCES_SORT_ORDER: String = (Instances.START_DAY.toString() + "," +
+ Instances.START_MINUTE + "," + Instances.TITLE)
+ protected var mShowDetailsInMonth = false
+ private const val WEEKS_BUFFER = 1
+
+ // How long to wait after scroll stops before starting the loader
+ // Using scroll duration because scroll state changes don't update
+ // correctly when a scroll is triggered programmatically.
+ private const val LOADER_DELAY = 200
+
+ // The minimum time between requeries of the data if the db is
+ // changing
+ private const val LOADER_THROTTLE_DELAY = 500
+ }
+} \ No newline at end of file