1 /**
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 package com.android.healthconnect.controller.entrydetails
15 
16 import android.os.Bundle
17 import android.view.LayoutInflater
18 import android.view.View
19 import android.view.ViewGroup
20 import androidx.core.os.bundleOf
21 import androidx.core.view.isVisible
22 import androidx.fragment.app.Fragment
23 import androidx.fragment.app.viewModels
24 import androidx.navigation.fragment.findNavController
25 import androidx.recyclerview.widget.LinearLayoutManager
26 import androidx.recyclerview.widget.RecyclerView
27 import com.android.healthconnect.controller.R
28 import com.android.healthconnect.controller.data.entries.FormattedEntry.ExercisePerformanceGoalEntry
29 import com.android.healthconnect.controller.data.entries.FormattedEntry.ExerciseSessionEntry
30 import com.android.healthconnect.controller.data.entries.FormattedEntry.FormattedSectionContent
31 import com.android.healthconnect.controller.data.entries.FormattedEntry.FormattedSectionTitle
32 import com.android.healthconnect.controller.data.entries.FormattedEntry.FormattedSessionDetail
33 import com.android.healthconnect.controller.data.entries.FormattedEntry.ItemDataEntrySeparator
34 import com.android.healthconnect.controller.data.entries.FormattedEntry.PlannedExerciseBlockEntry
35 import com.android.healthconnect.controller.data.entries.FormattedEntry.PlannedExerciseSessionEntry
36 import com.android.healthconnect.controller.data.entries.FormattedEntry.PlannedExerciseStepEntry
37 import com.android.healthconnect.controller.data.entries.FormattedEntry.ReverseSessionDetail
38 import com.android.healthconnect.controller.data.entries.FormattedEntry.SeriesDataEntry
39 import com.android.healthconnect.controller.data.entries.FormattedEntry.SessionHeader
40 import com.android.healthconnect.controller.data.entries.FormattedEntry.SleepSessionEntry
41 import com.android.healthconnect.controller.dataentries.ExerciseSessionItemViewBinder
42 import com.android.healthconnect.controller.dataentries.OnDeleteEntryListener
43 import com.android.healthconnect.controller.dataentries.PlannedExerciseSessionItemViewBinder
44 import com.android.healthconnect.controller.dataentries.SeriesDataItemViewBinder
45 import com.android.healthconnect.controller.dataentries.SleepSessionItemViewBinder
46 import com.android.healthconnect.controller.deletion.DeletionConstants.DELETION_TYPE
47 import com.android.healthconnect.controller.deletion.DeletionConstants.END_TIME
48 import com.android.healthconnect.controller.deletion.DeletionConstants.START_DELETION_EVENT
49 import com.android.healthconnect.controller.deletion.DeletionConstants.START_TIME
50 import com.android.healthconnect.controller.deletion.DeletionType
51 import com.android.healthconnect.controller.entrydetails.DataEntryDetailsViewModel.DateEntryFragmentState
52 import com.android.healthconnect.controller.entrydetails.DataEntryDetailsViewModel.DateEntryFragmentState.Loading
53 import com.android.healthconnect.controller.entrydetails.DataEntryDetailsViewModel.DateEntryFragmentState.LoadingFailed
54 import com.android.healthconnect.controller.entrydetails.DataEntryDetailsViewModel.DateEntryFragmentState.WithData
55 import com.android.healthconnect.controller.permissions.data.HealthPermissionType
56 import com.android.healthconnect.controller.permissiontypes.HealthPermissionTypesFragment.Companion.PERMISSION_TYPE_KEY
57 import com.android.healthconnect.controller.shared.DataType
58 import com.android.healthconnect.controller.shared.recyclerview.RecyclerViewAdapter
59 import com.android.healthconnect.controller.utils.logging.HealthConnectLogger
60 import com.android.healthconnect.controller.utils.logging.PageName
61 import com.android.healthconnect.controller.utils.logging.ToolbarElement
62 import com.android.healthconnect.controller.utils.setupMenu
63 import dagger.hilt.android.AndroidEntryPoint
64 import java.time.Instant
65 import javax.inject.Inject
66 
67 @AndroidEntryPoint(Fragment::class)
68 class DataEntryDetailsFragment : Hilt_DataEntryDetailsFragment() {
69 
70     companion object {
71         private const val ENTRY_ID_KEY = "entry_id_key"
72         const val SHOW_DATA_ORIGIN_KEY = "show_data_origin_key"
73 
74         fun createBundle(
75             permissionType: HealthPermissionType,
76             entryId: String,
77             showDataOrigin: Boolean
78         ): Bundle {
79             return bundleOf(
80                 PERMISSION_TYPE_KEY to permissionType,
81                 ENTRY_ID_KEY to entryId,
82                 SHOW_DATA_ORIGIN_KEY to showDataOrigin)
83         }
84     }
85 
86     @Inject lateinit var logger: HealthConnectLogger
87 
88     private val viewModel: DataEntryDetailsViewModel by viewModels()
89 
90     private lateinit var permissionType: HealthPermissionType
91     private lateinit var recyclerView: RecyclerView
92     private lateinit var entryId: String
93     private lateinit var loadingView: View
94     private lateinit var errorView: View
95     private lateinit var detailsAdapter: RecyclerViewAdapter
96     private var showDataOrigin: Boolean = false
97     private val onDeleteEntryListener by lazy {
98         object : OnDeleteEntryListener {
99             override fun onDeleteEntry(
100                 id: String,
101                 dataType: DataType,
102                 index: Int,
103                 startTime: Instant?,
104                 endTime: Instant?
105             ) {
106                 deleteEntry(id, dataType, index, startTime, endTime)
107             }
108         }
109     }
110     private val sleepSessionViewBinder by lazy {
111         SleepSessionItemViewBinder(
112             showSecondAction = false,
113             onItemClickedListener = null,
114             onDeleteEntryListenerClicked = onDeleteEntryListener)
115     }
116     private val plannedExerciseSessionViewBinder by lazy {
117         PlannedExerciseSessionItemViewBinder(
118             showSecondAction = false, onItemClickedListener = null, onDeleteEntryClicked = null)
119     }
120     private val plannedExerciseBlockViewBinder by lazy { PlannedExerciseBlockViewBinder() }
121     private val plannedExerciseStepViewBinder by lazy { PlannedExerciseStepViewBinder() }
122     private val exercisePerformanceGoalViewBinder by lazy { ExercisePerformanceGoalViewBinder() }
123     private val formattedSectionContentViewBinder by lazy { FormattedSectionContentViewBinder() }
124     private val exerciseSessionItemViewBinder by lazy {
125         ExerciseSessionItemViewBinder(
126             showSecondAction = false,
127             onItemClickedListener = null,
128             onDeleteEntryClicked = onDeleteEntryListener)
129     }
130     private val heartRateItemViewBinder by lazy {
131         SeriesDataItemViewBinder(
132             showSecondAction = false,
133             onItemClickedListener = null,
134             onDeleteEntryClicked = onDeleteEntryListener)
135     }
136     private val itemDataEntrySeparatorViewBinder by lazy { ItemDataEntrySeparatorViewBinder() }
137     private val sessionDetailViewBinder by lazy { SessionDetailViewBinder() }
138     private val sessionHeaderViewBinder by lazy { SessionHeaderViewBinder() }
139     private val reverseSessionDetailViewBinder by lazy { ReverseSessionDetailViewBinder() }
140     private val formattedSectionTitleViewBinder by lazy { FormattedSectionTitleViewBinder() }
141 
142     private val pageName = PageName.ENTRY_DETAILS_PAGE
143 
144     override fun onCreate(savedInstanceState: Bundle?) {
145         super.onCreate(savedInstanceState)
146         logger.setPageId(pageName)
147     }
148 
149     override fun onResume() {
150         super.onResume()
151         logger.setPageId(pageName)
152         logger.logPageImpression()
153     }
154 
155     override fun onCreateView(
156         inflater: LayoutInflater,
157         container: ViewGroup?,
158         savedInstanceState: Bundle?
159     ): View? {
160         logger.setPageId(pageName)
161         val view = inflater.inflate(R.layout.fragment_data_entry_details, container, false)
162         permissionType =
163             requireArguments()
164                 .getSerializable(PERMISSION_TYPE_KEY, HealthPermissionType::class.java)
165                 ?: throw IllegalArgumentException("PERMISSION_TYPE_KEY can't be null!")
166 
167         entryId =
168             requireArguments().getString(ENTRY_ID_KEY)
169                 ?: throw IllegalArgumentException("ENTRY_ID_KEY can't be null!")
170 
171         showDataOrigin =
172             requireArguments().getBoolean(SHOW_DATA_ORIGIN_KEY)
173                 ?: throw IllegalArgumentException("SHOW_DATA_ORIGIN_KEY can't be null!")
174         errorView = view.findViewById(R.id.error_view)
175         loadingView = view.findViewById(R.id.loading)
176         detailsAdapter =
177             RecyclerViewAdapter.Builder()
178                 .setViewBinder(SleepSessionEntry::class.java, sleepSessionViewBinder)
179                 .setViewBinder(ExerciseSessionEntry::class.java, exerciseSessionItemViewBinder)
180                 .setViewBinder(SeriesDataEntry::class.java, heartRateItemViewBinder)
181                 .setViewBinder(FormattedSessionDetail::class.java, sessionDetailViewBinder)
182                 .setViewBinder(SessionHeader::class.java, sessionHeaderViewBinder)
183                 .setViewBinder(ReverseSessionDetail::class.java, reverseSessionDetailViewBinder)
184                 .setViewBinder(FormattedSectionTitle::class.java, formattedSectionTitleViewBinder)
185                 .setViewBinder(
186                     FormattedSectionContent::class.java, formattedSectionContentViewBinder)
187                 .setViewBinder(
188                     PlannedExerciseSessionEntry::class.java, plannedExerciseSessionViewBinder)
189                 .setViewBinder(
190                     PlannedExerciseBlockEntry::class.java, plannedExerciseBlockViewBinder)
191                 .setViewBinder(PlannedExerciseStepEntry::class.java, plannedExerciseStepViewBinder)
192                 .setViewBinder(
193                     ExercisePerformanceGoalEntry::class.java, exercisePerformanceGoalViewBinder)
194                 .setViewBinder(ItemDataEntrySeparator::class.java, itemDataEntrySeparatorViewBinder)
195                 .build()
196         recyclerView =
197             view.findViewById<RecyclerView?>(R.id.data_entries_list).apply {
198                 layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
199                 adapter = detailsAdapter
200             }
201         viewModel.loadEntryData(permissionType, entryId, showDataOrigin)
202         setupMenu(R.menu.set_data_units_with_send_feedback_and_help, viewLifecycleOwner, logger) {
203             menuItem ->
204             when (menuItem.itemId) {
205                 R.id.menu_open_units -> {
206                     logger.logInteraction(ToolbarElement.TOOLBAR_UNITS_BUTTON)
207                     findNavController()
208                         .navigate(R.id.action_dataEntryDetailsFragment_to_unitFragment)
209                     true
210                 }
211                 else -> false
212             }
213         }
214         logger.logImpression(ToolbarElement.TOOLBAR_SETTINGS_BUTTON)
215         return view
216     }
217 
218     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
219         super.onViewCreated(view, savedInstanceState)
220         viewModel.sessionData.observe(viewLifecycleOwner) { state -> updateUI(state) }
221     }
222 
223     private fun updateUI(state: DateEntryFragmentState) {
224         when (state) {
225             is Loading -> {
226                 loadingView.isVisible = true
227                 errorView.isVisible = false
228                 recyclerView.isVisible = false
229             }
230             is LoadingFailed -> {
231                 errorView.isVisible = true
232                 loadingView.isVisible = false
233                 recyclerView.isVisible = false
234             }
235             is WithData -> {
236                 recyclerView.isVisible = true
237                 detailsAdapter.updateData(state.data)
238                 errorView.isVisible = false
239                 loadingView.isVisible = false
240             }
241         }
242     }
243 
244     private fun deleteEntry(
245         uuid: String,
246         dataType: DataType,
247         index: Int,
248         startTime: Instant?,
249         endTime: Instant?
250     ) {
251         val deletionType = DeletionType.DeleteDataEntry(uuid, dataType, index)
252 
253         if (deletionType.dataType == DataType.MENSTRUATION_PERIOD) {
254             childFragmentManager.setFragmentResult(
255                 START_DELETION_EVENT,
256                 bundleOf(
257                     DELETION_TYPE to deletionType, START_TIME to startTime, END_TIME to endTime))
258         } else {
259             childFragmentManager.setFragmentResult(
260                 START_DELETION_EVENT, bundleOf(DELETION_TYPE to deletionType))
261         }
262     }
263 }
264