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.testapps.toolbox.fieldviews
17 
18 import android.annotation.SuppressLint
19 import android.content.Context
20 import android.health.connect.datatypes.CyclingPedalingCadenceRecord.CyclingPedalingCadenceRecordSample
21 import android.health.connect.datatypes.ExerciseLap
22 import android.health.connect.datatypes.ExerciseSegment
23 import android.health.connect.datatypes.ExerciseSegmentType
24 import android.health.connect.datatypes.HeartRateRecord.HeartRateSample
25 import android.health.connect.datatypes.PlannedExerciseBlock
26 import android.health.connect.datatypes.PowerRecord.PowerRecordSample
27 import android.health.connect.datatypes.SkinTemperatureRecord
28 import android.health.connect.datatypes.SleepSessionRecord
29 import android.health.connect.datatypes.SpeedRecord.SpeedRecordSample
30 import android.health.connect.datatypes.StepsCadenceRecord.StepsCadenceRecordSample
31 import android.health.connect.datatypes.units.Length
32 import android.health.connect.datatypes.units.Power
33 import android.health.connect.datatypes.units.TemperatureDelta
34 import android.health.connect.datatypes.units.Velocity
35 import android.widget.CheckBox
36 import android.widget.LinearLayout
37 import android.widget.TextView
38 import com.android.healthconnect.testapps.toolbox.Constants.INPUT_TYPE_DOUBLE
39 import com.android.healthconnect.testapps.toolbox.Constants.INPUT_TYPE_INT
40 import com.android.healthconnect.testapps.toolbox.Constants.INPUT_TYPE_LONG
41 import com.android.healthconnect.testapps.toolbox.Constants.INPUT_TYPE_SIGNED_DOUBLE
42 import com.android.healthconnect.testapps.toolbox.Constants.INPUT_TYPE_TEXT
43 import com.android.healthconnect.testapps.toolbox.R
44 import com.android.healthconnect.testapps.toolbox.utils.GeneralUtils.Companion.getStaticFieldNamesAndValues
45 import com.google.android.material.floatingactionbutton.FloatingActionButton
46 import java.lang.reflect.ParameterizedType
47 import java.lang.reflect.Type
48 
49 @SuppressLint("ViewConstructor")
50 class ListInputField(context: Context, fieldName: String, inputFieldType: ParameterizedType) :
51     InputFieldView(context) {
52 
53     data class Row(val context: Context) {
54         val startTime = DateTimePicker(context, "Start Time", true)
55         val endTime = DateTimePicker(context, "End Time")
56         lateinit var dataPointField: InputFieldView
57     }
58 
59     private var mLinearLayout: LinearLayout
60     private var mDataTypeClass: Type
61     private var mRowsData: ArrayList<Row>
62 
63     init {
64         inflate(context, R.layout.fragment_list_input_view, this)
65         requireViewById<TextView>(R.id.field_name).text = fieldName
66         mLinearLayout = requireViewById(R.id.list_input_linear_layout)
67         mDataTypeClass = inputFieldType.actualTypeArguments[0]
68         mRowsData = ArrayList()
69         setupAddRowButtonListener()
70     }
71 
setupAddRowButtonListenernull72     private fun setupAddRowButtonListener() {
73         val buttonView = requireViewById<FloatingActionButton>(R.id.add_row)
74 
75         buttonView.setOnClickListener { addRow() }
76     }
77 
addRownull78     private fun addRow() {
79         val rowLayout = LinearLayout(context)
80         rowLayout.orientation = VERTICAL
81         val row = Row(context)
82         rowLayout.addView(row.startTime)
83         val dataPointField: InputFieldView =
84             when (mDataTypeClass) {
85                 SpeedRecordSample::class.java -> {
86                     EditableTextView(context, "Velocity", INPUT_TYPE_DOUBLE)
87                 }
88                 HeartRateSample::class.java -> {
89                     EditableTextView(context, "Beats per minute", INPUT_TYPE_LONG)
90                 }
91                 PowerRecordSample::class.java -> {
92                     EditableTextView(context, "Power", INPUT_TYPE_DOUBLE)
93                 }
94                 CyclingPedalingCadenceRecordSample::class.java -> {
95                     EditableTextView(context, "Revolutions Per Minute", INPUT_TYPE_DOUBLE)
96                 }
97                 SleepSessionRecord.Stage::class.java -> {
98                     rowLayout.addView(row.endTime)
99                     EnumDropDown(
100                         context,
101                         "Sleep Stage",
102                         getStaticFieldNamesAndValues(SleepSessionRecord.StageType::class),
103                     )
104                 }
105                 StepsCadenceRecordSample::class.java -> {
106                     EditableTextView(context, "Steps Cadence", INPUT_TYPE_DOUBLE)
107                 }
108                 ExerciseSegment::class.java -> {
109                     rowLayout.addView(row.endTime)
110                     EnumDropDown(
111                         context,
112                         "Segment Type",
113                         getStaticFieldNamesAndValues(ExerciseSegmentType::class),
114                     )
115                 }
116                 ExerciseLap::class.java -> {
117                     rowLayout.addView(row.endTime)
118                     EditableTextView(context, "Length", INPUT_TYPE_DOUBLE)
119                 }
120                 SkinTemperatureRecord.Delta::class.java -> {
121                     EditableTextView(context, "Delta", INPUT_TYPE_SIGNED_DOUBLE)
122                 }
123                 PlannedExerciseBlock::class.java -> {
124                     rowLayout.removeView(row.startTime)
125                     ExerciseBlockInputField(
126                         context,
127                         EditableTextView(context, "Repetitions", INPUT_TYPE_INT),
128                         EditableTextView(context, "Description", INPUT_TYPE_TEXT),
129                         CheckBox(context),
130                     )
131                 }
132                 else -> {
133                     return
134                 }
135             }
136         row.dataPointField = dataPointField
137         rowLayout.addView(dataPointField)
138         mRowsData.add(row)
139         mLinearLayout.addView(rowLayout, 0)
140     }
141 
getFieldValuenull142     override fun getFieldValue(): List<Any> {
143         val samples: ArrayList<Any> = ArrayList()
144 
145         for (row in mRowsData) {
146             val dataPoint = row.dataPointField
147             val dataPointString = dataPoint.getFieldValue().toString()
148             val instant = row.startTime
149             when (mDataTypeClass) {
150                 SpeedRecordSample::class.java -> {
151                     samples.add(
152                         SpeedRecordSample(
153                             Velocity.fromMetersPerSecond(dataPointString.toDouble()),
154                             instant.getFieldValue(),
155                         )
156                     )
157                 }
158                 HeartRateSample::class.java -> {
159                     samples.add(HeartRateSample(dataPointString.toLong(), instant.getFieldValue()))
160                 }
161                 PowerRecordSample::class.java -> {
162                     samples.add(
163                         PowerRecordSample(
164                             Power.fromWatts(dataPointString.toDouble()),
165                             instant.getFieldValue(),
166                         )
167                     )
168                 }
169                 CyclingPedalingCadenceRecordSample::class.java -> {
170                     samples.add(
171                         CyclingPedalingCadenceRecordSample(
172                             dataPointString.toDouble(),
173                             instant.getFieldValue(),
174                         )
175                     )
176                 }
177                 StepsCadenceRecordSample::class.java -> {
178                     samples.add(
179                         StepsCadenceRecordSample(
180                             dataPointString.toDouble(),
181                             instant.getFieldValue(),
182                         )
183                     )
184                 }
185                 SleepSessionRecord.Stage::class.java -> {
186                     samples.add(
187                         SleepSessionRecord.Stage(
188                             instant.getFieldValue(),
189                             row.endTime.getFieldValue(),
190                             dataPointString.toInt(),
191                         )
192                     )
193                 }
194                 ExerciseSegment::class.java -> {
195                     samples.add(
196                         ExerciseSegment.Builder(
197                                 instant.getFieldValue(),
198                                 row.endTime.getFieldValue(),
199                                 dataPointString.toInt(),
200                             )
201                             .build()
202                     )
203                 }
204                 ExerciseLap::class.java -> {
205                     samples.add(
206                         ExerciseLap.Builder(instant.getFieldValue(), row.endTime.getFieldValue())
207                             .apply {
208                                 if (dataPointString.isNotEmpty()) {
209                                     setLength(Length.fromMeters(dataPointString.toDouble()))
210                                 }
211                             }
212                             .build()
213                     )
214                 }
215                 PlannedExerciseBlock::class.java -> {
216                     samples.add(dataPoint.getFieldValue())
217                 }
218                 SkinTemperatureRecord.Delta::class.java -> {
219                     if (dataPointString.isNotEmpty()) {
220                         samples.add(
221                             SkinTemperatureRecord.Delta(
222                                 TemperatureDelta.fromCelsius(dataPointString.toDouble()),
223                                 instant.getFieldValue(),
224                             )
225                         )
226                     }
227                 }
228             }
229         }
230         return samples
231     }
232 
isEmptynull233     override fun isEmpty(): Boolean {
234         return getFieldValue().isEmpty()
235     }
236 }
237