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