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