• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.healthconnect.controller.shared.preference
17 
18 import android.os.Bundle
19 import android.view.LayoutInflater
20 import android.view.View
21 import android.view.ViewGroup
22 import android.view.accessibility.AccessibilityEvent
23 import android.view.animation.Animation
24 import android.view.animation.Animation.AnimationListener
25 import android.view.animation.AnimationUtils.loadAnimation
26 import android.widget.TextView
27 import androidx.annotation.StringRes
28 import androidx.preference.PreferenceScreen
29 import androidx.recyclerview.widget.RecyclerView
30 import com.android.healthconnect.controller.R
31 import com.android.healthconnect.controller.shared.HealthPreferenceComparisonCallback
32 import com.android.healthconnect.controller.utils.logging.HealthConnectLogger
33 import com.android.healthconnect.controller.utils.logging.HealthConnectLoggerEntryPoint
34 import com.android.healthconnect.controller.utils.logging.PageName
35 import com.android.healthconnect.controller.utils.logging.ToolbarElement
36 import com.android.healthconnect.controller.utils.setupSharedMenu
37 import com.android.settingslib.widget.SettingsBasePreferenceFragment
38 import com.google.android.material.appbar.AppBarLayout
39 import dagger.hilt.android.EntryPointAccessors
40 
41 /** A base fragment that represents a page in Health Connect. */
42 abstract class HealthPreferenceFragment : SettingsBasePreferenceFragment() {
43 
44     private lateinit var logger: HealthConnectLogger
45     private lateinit var loadingView: View
46     private lateinit var errorView: TextView
47     private lateinit var preferenceContainer: ViewGroup
48     private lateinit var prefView: ViewGroup
49     private var pageName: PageName = PageName.UNKNOWN_PAGE
50     private var isLoading: Boolean = false
51     private var hasError: Boolean = false
52 
setPageNamenull53     fun setPageName(pageName: PageName) {
54         this.pageName = pageName
55     }
56 
onCreatenull57     override fun onCreate(savedInstanceState: Bundle?) {
58         setupLogger()
59         super.onCreate(savedInstanceState)
60         val appBarLayout =
61             requireActivity()
62                 .findViewById<AppBarLayout>(com.android.settingslib.collapsingtoolbar.R.id.app_bar)
63         appBarLayout?.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
64         appBarLayout?.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
65     }
66 
onResumenull67     override fun onResume() {
68         super.onResume()
69         logger.setPageId(pageName)
70         logger.logPageImpression()
71     }
72 
onCreateViewnull73     override fun onCreateView(
74         inflater: LayoutInflater,
75         container: ViewGroup?,
76         savedInstanceState: Bundle?,
77     ): View {
78         logger.setPageId(pageName)
79         val rootView =
80             inflater.inflate(R.layout.preference_frame, container, /*attachToRoot */ false)
81         loadingView = rootView.findViewById(R.id.progress_indicator)
82         errorView = rootView.findViewById(R.id.error_view)
83         prefView = rootView.findViewById(R.id.pref_container)
84         preferenceContainer =
85             super.onCreateView(inflater, container, savedInstanceState) as ViewGroup
86         setLoading(isLoading, animate = false, force = true)
87         prefView.addView(preferenceContainer)
88         return rootView
89     }
90 
onViewCreatednull91     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
92         super.onViewCreated(view, savedInstanceState)
93         setupSharedMenu(viewLifecycleOwner, logger)
94         logger.logImpression(ToolbarElement.TOOLBAR_SETTINGS_BUTTON)
95     }
96 
onCreatePreferencesnull97     override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
98         preferenceManager.preferenceComparisonCallback = HealthPreferenceComparisonCallback()
99     }
100 
onCreateAdapternull101     override fun onCreateAdapter(preferenceScreen: PreferenceScreen): RecyclerView.Adapter<*> {
102         val adapter = super.onCreateAdapter(preferenceScreen)
103         /* By default, the PreferenceGroupAdapter does setHasStableIds(true). Since each Preference
104          * is internally allocated with an auto-incremented ID, it does not allow us to gracefully
105          * update only changed preferences based on HealthPreferenceComparisonCallback. In order to
106          * allow the list to track the changes, we need to ignore the Preference IDs. */
107         adapter.setHasStableIds(false)
108         return adapter
109     }
110 
setLoadingnull111     protected fun setLoading(isLoading: Boolean, animate: Boolean = true) {
112         setLoading(isLoading, animate, false)
113     }
114 
setErrornull115     protected fun setError(hasError: Boolean, @StringRes errorText: Int = R.string.default_error) {
116         if (this.hasError != hasError) {
117             this.hasError = hasError
118             // If there is no created view, there is no reason to animate.
119             val canAnimate = view != null
120             setViewShown(preferenceContainer, !hasError, canAnimate)
121             setViewShown(loadingView, !hasError, canAnimate)
122             setViewShown(errorView, hasError, canAnimate)
123             errorView.setText(errorText)
124         }
125     }
126 
setLoadingnull127     private fun setLoading(loading: Boolean, animate: Boolean, force: Boolean) {
128         if (isLoading != loading || force) {
129             isLoading = loading
130             // If there is no created view, there is no reason to animate.
131             val canAnimate = animate && view != null
132             setViewShown(preferenceContainer, !loading, canAnimate)
133             setViewShown(errorView, shown = false, animate = false)
134             setViewShown(loadingView, loading, canAnimate)
135         }
136     }
137 
setViewShownnull138     private fun setViewShown(view: View, shown: Boolean, animate: Boolean) {
139         if (animate) {
140             val animation: Animation =
141                 loadAnimation(
142                     context,
143                     if (shown) android.R.anim.fade_in else android.R.anim.fade_out,
144                 )
145             if (shown) {
146                 view.visibility = View.VISIBLE
147             } else {
148                 animation.setAnimationListener(
149                     object : AnimationListener {
150                         override fun onAnimationStart(animation: Animation) {}
151 
152                         override fun onAnimationRepeat(animation: Animation) {}
153 
154                         override fun onAnimationEnd(animation: Animation) {
155                             view.visibility = View.INVISIBLE
156                         }
157                     }
158                 )
159             }
160             view.startAnimation(animation)
161         } else {
162             view.clearAnimation()
163             view.visibility = if (shown) View.VISIBLE else View.GONE
164         }
165     }
166 
setupLoggernull167     private fun setupLogger() {
168         val hiltEntryPoint =
169             EntryPointAccessors.fromApplication(
170                 requireContext().applicationContext,
171                 HealthConnectLoggerEntryPoint::class.java,
172             )
173         logger = hiltEntryPoint.logger()
174         logger.setPageId(pageName)
175     }
176 }
177