1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.calendar
17 
18 import com.android.calendar.CalendarController.EventInfo
19 import com.android.calendar.CalendarController.EventType
20 import android.app.Fragment
21 import android.content.Context
22 import android.os.Bundle
23 import android.text.format.Time
24 import android.view.LayoutInflater
25 import android.view.View
26 import android.view.ViewGroup
27 import android.widget.FrameLayout.LayoutParams
28 import android.view.animation.Animation
29 import android.view.animation.AnimationUtils
30 import android.widget.ProgressBar
31 import android.widget.ViewSwitcher
32 import android.widget.ViewSwitcher.ViewFactory
33 
34 /**
35  * This is the base class for Day and Week Activities.
36  */
37 class DayFragment : Fragment, CalendarController.EventHandler, ViewFactory {
38     protected var mProgressBar: ProgressBar? = null
39     protected var mViewSwitcher: ViewSwitcher? = null
40     protected var mInAnimationForward: Animation? = null
41     protected var mOutAnimationForward: Animation? = null
42     protected var mInAnimationBackward: Animation? = null
43     protected var mOutAnimationBackward: Animation? = null
44     var mEventLoader: EventLoader? = null
45     var mSelectedDay: Time = Time()
46     private val mTZUpdater: Runnable = object : Runnable {
runnull47         override fun run() {
48             if (!this@DayFragment.isAdded()) {
49                 return
50             }
51             val tz: String? = Utils.getTimeZone(getActivity(), this)
52             mSelectedDay.timezone = tz
53             mSelectedDay.normalize(true)
54         }
55     }
56     private var mNumDays = 0
57 
58     constructor() {
59         mSelectedDay.setToNow()
60     }
61 
62     constructor(timeMillis: Long, numOfDays: Int) {
63         mNumDays = numOfDays
64         if (timeMillis == 0L) {
65             mSelectedDay.setToNow()
66         } else {
67             mSelectedDay.set(timeMillis)
68         }
69     }
70 
onCreatenull71     override fun onCreate(icicle: Bundle?) {
72         super.onCreate(icicle)
73         val context: Context = getActivity()
74         mInAnimationForward = AnimationUtils.loadAnimation(context, R.anim.slide_left_in)
75         mOutAnimationForward = AnimationUtils.loadAnimation(context, R.anim.slide_left_out)
76         mInAnimationBackward = AnimationUtils.loadAnimation(context, R.anim.slide_right_in)
77         mOutAnimationBackward = AnimationUtils.loadAnimation(context, R.anim.slide_right_out)
78         mEventLoader = EventLoader(context)
79     }
80 
onCreateViewnull81     override fun onCreateView(
82         inflater: LayoutInflater?,
83         container: ViewGroup?,
84         savedInstanceState: Bundle?
85     ): View? {
86         val v: View? = inflater?.inflate(R.layout.day_activity, null)
87         mViewSwitcher = v?.findViewById(R.id.switcher) as? ViewSwitcher
88         mViewSwitcher?.setFactory(this)
89         mViewSwitcher?.getCurrentView()?.requestFocus()
90         (mViewSwitcher?.getCurrentView() as? DayView)?.updateTitle()
91         return v
92     }
93 
makeViewnull94     override fun makeView(): View {
95         mTZUpdater.run()
96         val view = DayView(getActivity(), CalendarController
97                 .getInstance(getActivity()), mViewSwitcher, mEventLoader, mNumDays)
98         view.setId(DayFragment.Companion.VIEW_ID)
99         view.setLayoutParams(LayoutParams(
100                 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
101         view.setSelected(mSelectedDay, false, false)
102         return view
103     }
104 
onResumenull105     override fun onResume() {
106         super.onResume()
107         mEventLoader!!.startBackgroundThread()
108         mTZUpdater.run()
109         eventsChanged()
110         var view: DayView? = mViewSwitcher?.getCurrentView() as? DayView
111         view?.handleOnResume()
112         view?.restartCurrentTimeUpdates()
113         view = mViewSwitcher?.getNextView() as? DayView
114         view?.handleOnResume()
115         view?.restartCurrentTimeUpdates()
116     }
117 
onSaveInstanceStatenull118     override fun onSaveInstanceState(outState: Bundle?) {
119         super.onSaveInstanceState(outState)
120     }
121 
onPausenull122     override fun onPause() {
123         super.onPause()
124         var view: DayView? = mViewSwitcher?.getCurrentView() as? DayView
125         view?.cleanup()
126         view = mViewSwitcher?.getNextView() as? DayView
127         view?.cleanup()
128         mEventLoader!!.stopBackgroundThread()
129 
130         // Stop events cross-fade animation
131         view?.stopEventsAnimation()
132         (mViewSwitcher?.getNextView() as? DayView)?.stopEventsAnimation()
133     }
134 
startProgressSpinnernull135     fun startProgressSpinner() {
136         // start the progress spinner
137         mProgressBar?.setVisibility(View.VISIBLE)
138     }
139 
stopProgressSpinnernull140     fun stopProgressSpinner() {
141         // stop the progress spinner
142         mProgressBar?.setVisibility(View.GONE)
143     }
144 
goTonull145     private fun goTo(goToTime: Time?, ignoreTime: Boolean, animateToday: Boolean) {
146         if (mViewSwitcher == null) {
147             // The view hasn't been set yet. Just save the time and use it later.
148             mSelectedDay.set(goToTime)
149             return
150         }
151         val currentView: DayView? = mViewSwitcher?.getCurrentView() as? DayView
152 
153         // How does goTo time compared to what's already displaying?
154         val diff: Int = currentView?.compareToVisibleTimeRange(goToTime as Time) as Int
155         if (diff == 0) {
156             // In visible range. No need to switch view
157             currentView.setSelected(goToTime, ignoreTime, animateToday)
158         } else {
159             // Figure out which way to animate
160             if (diff > 0) {
161                 mViewSwitcher?.setInAnimation(mInAnimationForward)
162                 mViewSwitcher?.setOutAnimation(mOutAnimationForward)
163             } else {
164                 mViewSwitcher?.setInAnimation(mInAnimationBackward)
165                 mViewSwitcher?.setOutAnimation(mOutAnimationBackward)
166             }
167             val next: DayView? = mViewSwitcher?.getNextView() as? DayView
168             if (ignoreTime) {
169                 next!!.firstVisibleHour = currentView.firstVisibleHour
170             }
171             next?.setSelected(goToTime, ignoreTime, animateToday)
172             next?.reloadEvents()
173             mViewSwitcher?.showNext()
174             next?.requestFocus()
175             next?.updateTitle()
176             next?.restartCurrentTimeUpdates()
177         }
178     }
179 
180     /**
181      * Returns the selected time in milliseconds. The milliseconds are measured
182      * in UTC milliseconds from the epoch and uniquely specifies any selectable
183      * time.
184      *
185      * @return the selected time in milliseconds
186      */
187     val selectedTimeInMillis: Long
188         get() {
189             if (mViewSwitcher == null) {
190                 return -1
191             }
192             val view: DayView = mViewSwitcher?.getCurrentView() as DayView ?: return -1
193             return view.selectedTimeInMillis
194         }
195 
eventsChangednull196     override fun eventsChanged() {
197         if (mViewSwitcher == null) {
198             return
199         }
200         var view: DayView? = mViewSwitcher?.getCurrentView() as? DayView
201         view?.clearCachedEvents()
202         view?.reloadEvents()
203         view = mViewSwitcher?.getNextView() as? DayView
204         view?.clearCachedEvents()
205     }
206 
207     val nextView: DayView?
208         get() = mViewSwitcher?.getNextView() as? DayView
209     override val supportedEventTypes: Long
210         get() = CalendarController.EventType.GO_TO or CalendarController.EventType.EVENTS_CHANGED
211 
handleEventnull212     override fun handleEvent(msg: CalendarController.EventInfo?) {
213         if (msg?.eventType == CalendarController.EventType.GO_TO) {
214 // TODO support a range of time
215 // TODO support event_id
216 // TODO support select message
217             goTo(msg.selectedTime, msg.extraLong and CalendarController.EXTRA_GOTO_DATE != 0L,
218                     msg.extraLong and CalendarController.EXTRA_GOTO_TODAY != 0L)
219         } else if (msg?.eventType == CalendarController.EventType.EVENTS_CHANGED) {
220             eventsChanged()
221         }
222     }
223 
224     companion object {
225         /**
226          * The view id used for all the views we create. It's OK to have all child
227          * views have the same ID. This ID is used to pick which view receives
228          * focus when a view hierarchy is saved / restore
229          */
230         private const val VIEW_ID = 1
231         protected const val BUNDLE_KEY_RESTORE_TIME = "key_restore_time"
232     }
233 }