• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2025 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.entrydetails
17 
18 import android.os.Bundle
19 import android.view.LayoutInflater
20 import android.view.View
21 import android.view.ViewGroup
22 import androidx.core.os.bundleOf
23 import androidx.core.view.isVisible
24 import androidx.fragment.app.Fragment
25 import androidx.fragment.app.viewModels
26 import androidx.recyclerview.widget.LinearLayoutManager
27 import androidx.recyclerview.widget.RecyclerView
28 import com.android.healthconnect.controller.R
29 import com.android.healthconnect.controller.data.entries.EntriesAdapter
30 import com.android.healthconnect.controller.data.entries.EntriesViewModel
31 import com.android.healthconnect.controller.data.entries.ExerciseSessionItemViewBinder
32 import com.android.healthconnect.controller.data.entries.ExpressiveEntriesAdapter
33 import com.android.healthconnect.controller.data.entries.FormattedEntry.ExercisePerformanceGoalEntry
34 import com.android.healthconnect.controller.data.entries.FormattedEntry.ExerciseSessionEntry
35 import com.android.healthconnect.controller.data.entries.FormattedEntry.FormattedSectionContent
36 import com.android.healthconnect.controller.data.entries.FormattedEntry.FormattedSectionTitle
37 import com.android.healthconnect.controller.data.entries.FormattedEntry.FormattedSessionDetail
38 import com.android.healthconnect.controller.data.entries.FormattedEntry.ItemDataEntrySeparator
39 import com.android.healthconnect.controller.data.entries.FormattedEntry.PlannedExerciseBlockEntry
40 import com.android.healthconnect.controller.data.entries.FormattedEntry.PlannedExerciseSessionEntry
41 import com.android.healthconnect.controller.data.entries.FormattedEntry.PlannedExerciseStepEntry
42 import com.android.healthconnect.controller.data.entries.FormattedEntry.ReverseSessionDetail
43 import com.android.healthconnect.controller.data.entries.FormattedEntry.SeriesDataEntry
44 import com.android.healthconnect.controller.data.entries.FormattedEntry.SessionHeader
45 import com.android.healthconnect.controller.data.entries.FormattedEntry.SleepSessionEntry
46 import com.android.healthconnect.controller.data.entries.PlannedExerciseSessionItemViewBinder
47 import com.android.healthconnect.controller.data.entries.SeriesDataItemViewBinder
48 import com.android.healthconnect.controller.data.entries.SleepSessionItemViewBinder
49 import com.android.healthconnect.controller.entrydetails.DataEntryDetailsViewModel.DateEntryFragmentState
50 import com.android.healthconnect.controller.entrydetails.DataEntryDetailsViewModel.DateEntryFragmentState.Loading
51 import com.android.healthconnect.controller.entrydetails.DataEntryDetailsViewModel.DateEntryFragmentState.LoadingFailed
52 import com.android.healthconnect.controller.entrydetails.DataEntryDetailsViewModel.DateEntryFragmentState.WithData
53 import com.android.healthconnect.controller.navigation.PERMISSION_TYPE_KEY
54 import com.android.healthconnect.controller.permissions.data.FitnessPermissionType
55 import com.android.healthconnect.controller.shared.recyclerview.RecyclerViewAdapter
56 import com.android.healthconnect.controller.utils.logging.HealthConnectLogger
57 import com.android.healthconnect.controller.utils.logging.PageName
58 import com.android.healthconnect.controller.utils.logging.ToolbarElement
59 import com.android.healthconnect.controller.utils.setupSharedMenu
60 import com.android.settingslib.widget.SettingsThemeHelper
61 import dagger.hilt.android.AndroidEntryPoint
62 import javax.inject.Inject
63 
64 @AndroidEntryPoint(Fragment::class)
65 class DataEntryDetailsFragment : Hilt_DataEntryDetailsFragment() {
66 
67     companion object {
68         private const val ENTRY_ID_KEY = "entry_id_key"
69         const val SHOW_DATA_ORIGIN_KEY = "show_data_origin_key"
70 
71         fun createBundle(
72             permissionType: FitnessPermissionType,
73             entryId: String,
74             showDataOrigin: Boolean,
75         ): Bundle {
76             return bundleOf(
77                 PERMISSION_TYPE_KEY to permissionType,
78                 ENTRY_ID_KEY to entryId,
79                 SHOW_DATA_ORIGIN_KEY to showDataOrigin,
80             )
81         }
82     }
83 
84     @Inject lateinit var logger: HealthConnectLogger
85 
86     private val viewModel: DataEntryDetailsViewModel by viewModels()
87     private val entriesViewModel: EntriesViewModel by viewModels()
88 
89     private lateinit var permissionType: FitnessPermissionType
90     private lateinit var recyclerView: RecyclerView
91     private lateinit var entryId: String
92     private lateinit var loadingView: View
93     private lateinit var errorView: View
94     private lateinit var detailsAdapter: RecyclerViewAdapter
95     private var showDataOrigin: Boolean = false
96     private val sleepSessionViewBinder by lazy {
97         SleepSessionItemViewBinder(onItemClickedListener = null)
98     }
99     private val plannedExerciseSessionViewBinder by lazy {
100         PlannedExerciseSessionItemViewBinder(onItemClickedListener = null)
101     }
102     private val plannedExerciseBlockViewBinder by lazy { PlannedExerciseBlockViewBinder() }
103     private val plannedExerciseStepViewBinder by lazy { PlannedExerciseStepViewBinder() }
104     private val exercisePerformanceGoalViewBinder by lazy { ExercisePerformanceGoalViewBinder() }
105     private val formattedSectionContentViewBinder by lazy { FormattedSectionContentViewBinder() }
106     private val exerciseSessionItemViewBinder by lazy {
107         ExerciseSessionItemViewBinder(onItemClickedListener = null)
108     }
109     private val heartRateItemViewBinder by lazy {
110         SeriesDataItemViewBinder(onItemClickedListener = null)
111     }
112     private val itemDataEntrySeparatorViewBinder by lazy { ItemDataEntrySeparatorViewBinder() }
113     private val sessionDetailViewBinder by lazy { SessionDetailViewBinder() }
114     private val sessionHeaderViewBinder by lazy { SessionHeaderViewBinder() }
115     private val reverseSessionDetailViewBinder by lazy { ReverseSessionDetailViewBinder() }
116     private val formattedSectionTitleViewBinder by lazy { FormattedSectionTitleViewBinder() }
117 
118     private val pageName = PageName.ENTRY_DETAILS_PAGE
119 
120     override fun onCreate(savedInstanceState: Bundle?) {
121         super.onCreate(savedInstanceState)
122         logger.setPageId(pageName)
123     }
124 
125     override fun onResume() {
126         super.onResume()
127         logger.setPageId(pageName)
128         logger.logPageImpression()
129     }
130 
131     override fun onCreateView(
132         inflater: LayoutInflater,
133         container: ViewGroup?,
134         savedInstanceState: Bundle?,
135     ): View? {
136         logger.setPageId(pageName)
137         val view = inflater.inflate(R.layout.fragment_data_entry_details, container, false)
138         permissionType =
139             requireArguments()
140                 .getSerializable(PERMISSION_TYPE_KEY, FitnessPermissionType::class.java)
141                 ?: throw IllegalArgumentException("PERMISSION_TYPE_KEY can't be null!")
142 
143         entryId =
144             requireArguments().getString(ENTRY_ID_KEY)
145                 ?: throw IllegalArgumentException("ENTRY_ID_KEY can't be null!")
146 
147         showDataOrigin =
148             requireArguments().getBoolean(SHOW_DATA_ORIGIN_KEY)
149                 ?: throw IllegalArgumentException("SHOW_DATA_ORIGIN_KEY can't be null!")
150         errorView = view.findViewById(R.id.error_view)
151         loadingView = view.findViewById(R.id.loading)
152         detailsAdapter =
153             if (SettingsThemeHelper.isExpressiveTheme(requireContext())) {
154                 getExpressiveEntriesAdapter()
155             } else {
156                 getEntriesAdapter()
157             }
158 
159         recyclerView =
160             view.findViewById<RecyclerView?>(R.id.data_entries_list).apply {
161                 layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
162                 adapter = detailsAdapter
163             }
164         viewModel.loadEntryData(permissionType, entryId, showDataOrigin)
165         setupSharedMenu(viewLifecycleOwner, logger)
166         logger.logImpression(ToolbarElement.TOOLBAR_SETTINGS_BUTTON)
167         return view
168     }
169 
170     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
171         super.onViewCreated(view, savedInstanceState)
172         viewModel.sessionData.observe(viewLifecycleOwner) { state -> updateUI(state) }
173     }
174 
175     private fun getExpressiveEntriesAdapter(): ExpressiveEntriesAdapter {
176         return ExpressiveEntriesAdapter.Builder()
177             .setViewBinder(SleepSessionEntry::class.java, sleepSessionViewBinder)
178             .setViewBinder(ExerciseSessionEntry::class.java, exerciseSessionItemViewBinder)
179             .setViewBinder(SeriesDataEntry::class.java, heartRateItemViewBinder)
180             .setViewBinder(FormattedSessionDetail::class.java, sessionDetailViewBinder)
181             .setViewBinder(SessionHeader::class.java, sessionHeaderViewBinder)
182             .setViewBinder(ReverseSessionDetail::class.java, reverseSessionDetailViewBinder)
183             .setViewBinder(FormattedSectionTitle::class.java, formattedSectionTitleViewBinder)
184             .setViewBinder(FormattedSectionContent::class.java, formattedSectionContentViewBinder)
185             .setViewBinder(
186                 PlannedExerciseSessionEntry::class.java,
187                 plannedExerciseSessionViewBinder,
188             )
189             .setViewBinder(PlannedExerciseBlockEntry::class.java, plannedExerciseBlockViewBinder)
190             .setViewBinder(PlannedExerciseStepEntry::class.java, plannedExerciseStepViewBinder)
191             .setViewBinder(
192                 ExercisePerformanceGoalEntry::class.java,
193                 exercisePerformanceGoalViewBinder,
194             )
195             .setViewBinder(ItemDataEntrySeparator::class.java, itemDataEntrySeparatorViewBinder)
196             .setViewModel(entriesViewModel)
197             .build(requireContext())
198     }
199 
200     private fun getEntriesAdapter(): EntriesAdapter {
201         return EntriesAdapter.Builder()
202             .setViewBinder(SleepSessionEntry::class.java, sleepSessionViewBinder)
203             .setViewBinder(ExerciseSessionEntry::class.java, exerciseSessionItemViewBinder)
204             .setViewBinder(SeriesDataEntry::class.java, heartRateItemViewBinder)
205             .setViewBinder(FormattedSessionDetail::class.java, sessionDetailViewBinder)
206             .setViewBinder(SessionHeader::class.java, sessionHeaderViewBinder)
207             .setViewBinder(ReverseSessionDetail::class.java, reverseSessionDetailViewBinder)
208             .setViewBinder(FormattedSectionTitle::class.java, formattedSectionTitleViewBinder)
209             .setViewBinder(FormattedSectionContent::class.java, formattedSectionContentViewBinder)
210             .setViewBinder(
211                 PlannedExerciseSessionEntry::class.java,
212                 plannedExerciseSessionViewBinder,
213             )
214             .setViewBinder(PlannedExerciseBlockEntry::class.java, plannedExerciseBlockViewBinder)
215             .setViewBinder(PlannedExerciseStepEntry::class.java, plannedExerciseStepViewBinder)
216             .setViewBinder(
217                 ExercisePerformanceGoalEntry::class.java,
218                 exercisePerformanceGoalViewBinder,
219             )
220             .setViewBinder(ItemDataEntrySeparator::class.java, itemDataEntrySeparatorViewBinder)
221             .setViewModel(entriesViewModel)
222             .build()
223     }
224 
225     private fun updateUI(state: DateEntryFragmentState) {
226         when (state) {
227             is Loading -> {
228                 loadingView.isVisible = true
229                 errorView.isVisible = false
230                 recyclerView.isVisible = false
231             }
232             is LoadingFailed -> {
233                 errorView.isVisible = true
234                 loadingView.isVisible = false
235                 recyclerView.isVisible = false
236             }
237             is WithData -> {
238                 recyclerView.isVisible = true
239                 detailsAdapter.updateData(state.data)
240                 errorView.isVisible = false
241                 loadingView.isVisible = false
242             }
243         }
244     }
245 }
246