• 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.PreferenceFragmentCompat
29 import androidx.preference.PreferenceScreen
30 import androidx.recyclerview.widget.RecyclerView
31 import com.android.healthconnect.controller.R
32 import com.android.healthconnect.controller.shared.HealthPreferenceComparisonCallback
33 import com.android.healthconnect.controller.utils.logging.HealthConnectLogger
34 import com.android.healthconnect.controller.utils.logging.HealthConnectLoggerEntryPoint
35 import com.android.healthconnect.controller.utils.logging.PageName
36 import com.android.healthconnect.controller.utils.logging.ToolbarElement
37 import com.android.healthconnect.controller.utils.setupSharedMenu
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 : PreferenceFragmentCompat() {
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 = requireActivity().findViewById<AppBarLayout>(R.id.app_bar)
61         appBarLayout?.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
62         appBarLayout?.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
63     }
64 
onResumenull65     override fun onResume() {
66         super.onResume()
67         logger.setPageId(pageName)
68         logger.logPageImpression()
69     }
70 
onCreateViewnull71     override fun onCreateView(
72         inflater: LayoutInflater,
73         container: ViewGroup?,
74         savedInstanceState: Bundle?
75     ): View? {
76         logger.setPageId(pageName)
77         val rootView =
78             inflater.inflate(R.layout.preference_frame, container, /*attachToRoot */ false)
79         loadingView = rootView.findViewById(R.id.progress_indicator)
80         errorView = rootView.findViewById(R.id.error_view)
81         prefView = rootView.findViewById(R.id.pref_container)
82         preferenceContainer =
83             super.onCreateView(inflater, container, savedInstanceState) as ViewGroup
84         setLoading(isLoading, animate = false, force = true)
85         prefView.addView(preferenceContainer)
86         return rootView
87     }
88 
onViewCreatednull89     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
90         super.onViewCreated(view, savedInstanceState)
91         setupSharedMenu(viewLifecycleOwner, logger)
92         logger.logImpression(ToolbarElement.TOOLBAR_SETTINGS_BUTTON)
93     }
94 
onCreatePreferencesnull95     override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
96         preferenceManager.preferenceComparisonCallback = HealthPreferenceComparisonCallback()
97     }
98 
onCreateAdapternull99     override fun onCreateAdapter(preferenceScreen: PreferenceScreen?): RecyclerView.Adapter<*> {
100         val adapter = super.onCreateAdapter(preferenceScreen)
101         /* By default, the PreferenceGroupAdapter does setHasStableIds(true). Since each Preference
102          * is internally allocated with an auto-incremented ID, it does not allow us to gracefully
103          * update only changed preferences based on HealthPreferenceComparisonCallback. In order to
104          * allow the list to track the changes, we need to ignore the Preference IDs. */
105         adapter.setHasStableIds(false)
106         return adapter
107     }
108 
setLoadingnull109     protected fun setLoading(isLoading: Boolean, animate: Boolean = true) {
110         setLoading(isLoading, animate, false)
111     }
112 
setErrornull113     protected fun setError(hasError: Boolean, @StringRes errorText: Int = R.string.default_error) {
114         if (this.hasError != hasError) {
115             this.hasError = hasError
116             // If there is no created view, there is no reason to animate.
117             val canAnimate = view != null
118             setViewShown(preferenceContainer, !hasError, canAnimate)
119             setViewShown(loadingView, !hasError, canAnimate)
120             setViewShown(errorView, hasError, canAnimate)
121             errorView.setText(errorText)
122         }
123     }
124 
setLoadingnull125     private fun setLoading(loading: Boolean, animate: Boolean, force: Boolean) {
126         if (isLoading != loading || force) {
127             isLoading = loading
128             // If there is no created view, there is no reason to animate.
129             val canAnimate = animate && view != null
130             setViewShown(preferenceContainer, !loading, canAnimate)
131             setViewShown(errorView, shown = false, animate = false)
132             setViewShown(loadingView, loading, canAnimate)
133         }
134     }
135 
setViewShownnull136     private fun setViewShown(view: View, shown: Boolean, animate: Boolean) {
137         if (animate) {
138             val animation: Animation =
139                 loadAnimation(
140                     context, if (shown) android.R.anim.fade_in else android.R.anim.fade_out)
141             if (shown) {
142                 view.visibility = View.VISIBLE
143             } else {
144                 animation.setAnimationListener(
145                     object : AnimationListener {
146                         override fun onAnimationStart(animation: Animation) {}
147 
148                         override fun onAnimationRepeat(animation: Animation) {}
149 
150                         override fun onAnimationEnd(animation: Animation) {
151                             view.visibility = View.INVISIBLE
152                         }
153                     })
154             }
155             view.startAnimation(animation)
156         } else {
157             view.clearAnimation()
158             view.visibility = if (shown) View.VISIBLE else View.GONE
159         }
160     }
161 
setupLoggernull162     private fun setupLogger() {
163         val hiltEntryPoint =
164             EntryPointAccessors.fromApplication(
165                 requireContext().applicationContext, HealthConnectLoggerEntryPoint::class.java)
166         logger = hiltEntryPoint.logger()
167         logger.setPageId(pageName)
168     }
169 }
170