1 /**
2  * Copyright (C) 2022 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  * ```
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * ```
10  *
11  * Unless required by applicable law or agreed to in writing, software distributed under the License
12  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13  * or implied. See the License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 package com.android.healthconnect.controller.dataentries
17 
18 import android.util.Log
19 import androidx.lifecycle.LiveData
20 import androidx.lifecycle.MutableLiveData
21 import androidx.lifecycle.ViewModel
22 import androidx.lifecycle.viewModelScope
23 import com.android.healthconnect.controller.data.entries.FormattedEntry
24 import com.android.healthconnect.controller.data.entries.FormattedEntry.FormattedAggregation
25 import com.android.healthconnect.controller.permissions.data.HealthPermissionType
26 import com.android.healthconnect.controller.permissions.data.HealthPermissionType.DISTANCE
27 import com.android.healthconnect.controller.permissions.data.HealthPermissionType.STEPS
28 import com.android.healthconnect.controller.permissions.data.HealthPermissionType.TOTAL_CALORIES_BURNED
29 import com.android.healthconnect.controller.shared.usecase.UseCaseResults
30 import dagger.hilt.android.lifecycle.HiltViewModel
31 import java.time.Instant
32 import javax.inject.Inject
33 import kotlinx.coroutines.launch
34 
35 /** View model for {@link DataEntriesFragment} . */
36 @HiltViewModel
37 class DataEntriesFragmentViewModel
38 @Inject
39 constructor(
40     private val loadDataEntriesUseCase: LoadDataEntriesUseCase,
41     private val loadMenstruationDataUseCase: LoadMenstruationDataUseCase,
42     private val loadDataAggregationsUseCase: LoadDataAggregationsUseCase
43 ) : ViewModel() {
44 
45     companion object {
46         private const val TAG = "DataEntriesFragmentView"
47         private val AGGREGATE_HEADER_DATA_TYPES = listOf(STEPS, DISTANCE, TOTAL_CALORIES_BURNED)
48     }
49 
50     private val _dataEntries = MutableLiveData<DataEntriesFragmentState>()
51     val dataEntries: LiveData<DataEntriesFragmentState>
52         get() = _dataEntries
53 
54     val currentSelectedDate = MutableLiveData<Instant>()
55 
loadDatanull56     fun loadData(permissionType: HealthPermissionType, selectedDate: Instant) {
57         _dataEntries.postValue(DataEntriesFragmentState.Loading)
58         currentSelectedDate.postValue(selectedDate)
59         viewModelScope.launch {
60             val list = ArrayList<FormattedEntry>()
61             val entriesResults =
62                 when (permissionType) {
63                     // Special-casing Menstruation as it spans multiple days
64                     HealthPermissionType.MENSTRUATION -> {
65                         loadMenstruationDataUseCase.invoke(selectedDate)
66                     }
67                     else -> {
68                         loadEntries(permissionType, selectedDate)
69                     }
70                 }
71             when (entriesResults) {
72                 is UseCaseResults.Success -> {
73                     list.addAll(entriesResults.data)
74                     if (list.isEmpty()) {
75                         _dataEntries.postValue(DataEntriesFragmentState.Empty)
76                     } else {
77                         addAggregation(permissionType, selectedDate, list)
78                         _dataEntries.postValue(DataEntriesFragmentState.WithData(list))
79                     }
80                 }
81                 is UseCaseResults.Failed -> {
82                     Log.e(TAG, "Loading error ", entriesResults.exception)
83                     _dataEntries.postValue(DataEntriesFragmentState.LoadingFailed)
84                 }
85             }
86         }
87     }
88 
loadAggregationnull89     private suspend fun loadAggregation(
90         permissionType: HealthPermissionType,
91         selectedDate: Instant
92     ): UseCaseResults<FormattedAggregation> {
93         val input = LoadAggregationInput(permissionType, selectedDate)
94         return loadDataAggregationsUseCase.invoke(input)
95     }
96 
loadEntriesnull97     private suspend fun loadEntries(
98         permissionType: HealthPermissionType,
99         selectedDate: Instant
100     ): UseCaseResults<List<FormattedEntry>> {
101         val input = LoadDataEntriesInput(permissionType, selectedDate)
102         return loadDataEntriesUseCase.invoke(input)
103     }
104 
addAggregationnull105     private suspend fun addAggregation(
106         permissionType: HealthPermissionType,
107         selectedDate: Instant,
108         list: ArrayList<FormattedEntry>
109     ) {
110         if (permissionType in AGGREGATE_HEADER_DATA_TYPES) {
111             when (val aggregationResult = loadAggregation(permissionType, selectedDate)) {
112                 is UseCaseResults.Success -> {
113                     list.add(0, aggregationResult.data)
114                 }
115                 is UseCaseResults.Failed -> {
116                     Log.e(TAG, "Failed to load aggregation!", aggregationResult.exception)
117                 }
118             }
119         }
120     }
121 
122     sealed class DataEntriesFragmentState {
123         object Loading : DataEntriesFragmentState()
124 
125         object Empty : DataEntriesFragmentState()
126 
127         object LoadingFailed : DataEntriesFragmentState()
128 
129         data class WithData(val entries: List<FormattedEntry>) : DataEntriesFragmentState()
130     }
131 }
132