diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt index 4335bf46a1..e086b9ff4a 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt @@ -41,6 +41,7 @@ import java.time.ZoneId import java.time.chrono.IsoChronology import java.time.format.DateTimeFormatterBuilder import java.time.format.FormatStyle +import java.util.Date import java.util.Locale import kotlin.math.abs import kotlin.math.log10 @@ -102,8 +103,12 @@ internal object QuestionnaireItemDatePickerViewHolderFactory : header.bind(questionnaireItemViewItem.questionnaireItem) textInputLayout.hint = localePattern textInputEditText.removeTextChangedListener(textWatcher) - - if (textInputEditText.text.isNullOrEmpty()) { + if (isTextUpdateRequired( + textInputEditText.context, + questionnaireItemViewItem.answers.singleOrNull()?.valueDateType, + textInputEditText.text.toString() + ) + ) { textInputEditText.setText( questionnaireItemViewItem.answers.singleOrNull() ?.valueDateType @@ -157,6 +162,20 @@ internal object QuestionnaireItemDatePickerViewHolderFactory : } } } + + private fun isTextUpdateRequired( + context: Context, + answer: DateType?, + inputText: String? + ): Boolean { + val inputDate = + try { + parseDate(inputText, context) + } catch (e: Exception) { + null + } + return answer?.localDate != inputDate + } } internal const val TAG = "date-picker" @@ -194,14 +213,17 @@ internal val DateType.localDate internal val LocalDate.dateType get() = DateType(year, monthValue - 1, dayOfMonth) +internal val Date.localDate + get() = LocalDate.of(year + 1900, month + 1, date) + internal fun parseDate(text: CharSequence?, context: Context): LocalDate { - val date = + val localDate = if (isAndroidIcuSupported()) { - DateFormat.getDateInstance(DateFormat.SHORT).parse(text.toString()) - } else { - android.text.format.DateFormat.getDateFormat(context).parse(text.toString()) - } - val localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate() + DateFormat.getDateInstance(DateFormat.SHORT).parse(text.toString()) + } else { + android.text.format.DateFormat.getDateFormat(context).parse(text.toString()) + } + .localDate // date/localDate with year more than 4 digit throws data format exception if deep copy // operation get performed on QuestionnaireResponse, // QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent in org.hl7.fhir.r4.model diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt index e8c6926799..75b1197589 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt @@ -146,7 +146,10 @@ internal object QuestionnaireItemDateTimePickerViewHolderFactory : val dateTime = questionnaireItemViewItem.answers.singleOrNull()?.valueDateTimeType updateDateTimeInput( dateTime?.let { - LocalDateTime.of(it.year, it.month + 1, it.day, it.hour, it.minute, it.second) + it.localDateTime.also { + localDate = it.toLocalDate() + localTime = it.toLocalTime() + } } ) textWatcher = @@ -197,7 +200,12 @@ internal object QuestionnaireItemDateTimePickerViewHolderFactory : /** Update the date and time input fields in the UI. */ private fun updateDateTimeInput(localDateTime: LocalDateTime?) { enableOrDisableTimePicker(enableIt = localDateTime != null) - if (dateInputEditText.text.isNullOrEmpty()) { + if (isTextUpdateRequired( + dateInputEditText.context, + localDateTime, + dateInputEditText.text.toString() + ) + ) { dateInputEditText.setText(localDateTime?.localizedDateString ?: "") } timeInputEditText.setText( @@ -281,6 +289,21 @@ internal object QuestionnaireItemDateTimePickerViewHolderFactory : timeInputLayout.isEnabled = enableIt timeInputLayout.isEnabled = enableIt } + + private fun isTextUpdateRequired( + context: Context, + answer: LocalDateTime?, + inputText: String? + ): Boolean { + val inputDate = + try { + generateLocalDateTime(parseDate(inputText, context), localTime) + } catch (e: Exception) { + null + } + if (answer == null || inputDate == null) return true + return answer.toLocalDate() != inputDate.toLocalDate() + } } } @@ -301,3 +324,14 @@ internal val DateTimeType.localTime minute, second, ) + +internal val DateTimeType.localDateTime + get() = + LocalDateTime.of( + year, + month + 1, + day, + hour, + minute, + second, + ) diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactoryTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactoryTest.kt index 66e2914688..065ee41bb1 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactoryTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactoryTest.kt @@ -65,10 +65,7 @@ class QuestionnaireItemDatePickerViewHolderFactoryTest { ) ) - assertThat( - viewHolder.itemView.findViewById(R.id.text_input_edit_text).text.toString() - ) - .isEqualTo("") + assertThat(viewHolder.dateInputView.text.toString()).isEqualTo("") } @Test @@ -86,10 +83,7 @@ class QuestionnaireItemDatePickerViewHolderFactoryTest { answersChangedCallback = { _, _, _ -> }, ) ) - assertThat( - viewHolder.itemView.findViewById(R.id.text_input_edit_text).text.toString() - ) - .isEqualTo("11/19/20") + assertThat(viewHolder.dateInputView.text.toString()).isEqualTo("11/19/20") } @Test @@ -107,10 +101,7 @@ class QuestionnaireItemDatePickerViewHolderFactoryTest { answersChangedCallback = { _, _, _ -> }, ) ) - assertThat( - viewHolder.itemView.findViewById(R.id.text_input_edit_text).text.toString() - ) - .isEqualTo("2020/11/19") + assertThat(viewHolder.dateInputView.text.toString()).isEqualTo("2020/11/19") } @Test @@ -128,10 +119,7 @@ class QuestionnaireItemDatePickerViewHolderFactoryTest { answersChangedCallback = { _, _, _ -> }, ) ) - assertThat( - viewHolder.itemView.findViewById(R.id.text_input_edit_text).text.toString() - ) - .isEqualTo("11/19/20") + assertThat(viewHolder.dateInputView.text.toString()).isEqualTo("11/19/20") } @Test @@ -146,7 +134,7 @@ class QuestionnaireItemDatePickerViewHolderFactoryTest { ) viewHolder.bind(item) - viewHolder.itemView.findViewById(R.id.text_input_edit_text).text = "11/19/2020" + viewHolder.dateInputView.text = "11/19/2020" val answer = item.answers.singleOrNull()?.value as DateType @@ -166,7 +154,7 @@ class QuestionnaireItemDatePickerViewHolderFactoryTest { answersChangedCallback = { _, _, _ -> }, ) viewHolder.bind(item) - viewHolder.itemView.findViewById(R.id.text_input_edit_text).text = "2020/11/19" + viewHolder.dateInputView.text = "2020/11/19" val answer = item.answers.singleOrNull()?.value as DateType assertThat(answer.day).isEqualTo(19) @@ -190,7 +178,7 @@ class QuestionnaireItemDatePickerViewHolderFactoryTest { ) viewHolder.bind(questionnaireItem) - viewHolder.itemView.findViewById(R.id.text_input_edit_text).text = "11/19/" + viewHolder.dateInputView.text = "11/19/" val answer = questionnaireItem.answers.singleOrNull()?.value assertThat(answer).isNull() } @@ -265,12 +253,59 @@ class QuestionnaireItemDatePickerViewHolderFactoryTest { ) ) - assertThat(viewHolder.itemView.findViewById(R.id.text_input_edit_text).isEnabled) - .isFalse() + assertThat(viewHolder.dateInputView.isEnabled).isFalse() + } + + @Test + fun `bind multiple times with different QuestionnaireItemViewItem should show proper date`() { + setLocale(Locale.US) + + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.QuestionnaireItemComponent().apply { text = "Question?" }, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + .addAnswer( + QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent() + .setValue(DateType(2020, 10, 19)) + ), + validationResult = NotValidated, + answersChangedCallback = { _, _, _ -> }, + ) + ) + assertThat(viewHolder.dateInputView.text.toString()).isEqualTo("11/19/20") + + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.QuestionnaireItemComponent().apply { text = "Question?" }, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + .addAnswer( + QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent() + .setValue(DateType(2021, 10, 19)) + ), + validationResult = NotValidated, + answersChangedCallback = { _, _, _ -> }, + ) + ) + assertThat(viewHolder.dateInputView.text.toString()).isEqualTo("11/19/21") + + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.QuestionnaireItemComponent().apply { text = "Question?" }, + QuestionnaireResponse.QuestionnaireResponseItemComponent(), + validationResult = NotValidated, + answersChangedCallback = { _, _, _ -> }, + ) + ) + assertThat(viewHolder.dateInputView.text.toString()).isEmpty() } private fun setLocale(locale: Locale) { Locale.setDefault(locale) context.resources.configuration.setLocale(locale) } + + private val QuestionnaireItemViewHolder.dateInputView: TextView + get() { + return itemView.findViewById(R.id.text_input_edit_text) + } } diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactoryTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactoryTest.kt index 4bbedbc944..633665825f 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactoryTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactoryTest.kt @@ -16,7 +16,6 @@ package com.google.android.fhir.datacapture.views -import android.widget.EditText import android.widget.FrameLayout import android.widget.TextView import com.google.android.fhir.datacapture.R @@ -75,14 +74,8 @@ class QuestionnaireItemDateTimePickerViewHolderFactoryTest { ) ) - assertThat( - viewHolder.itemView.findViewById(R.id.date_input_edit_text).text.toString() - ) - .isEqualTo("") - assertThat( - viewHolder.itemView.findViewById(R.id.time_input_edit_text).text.toString() - ) - .isEqualTo("") + assertThat(viewHolder.dateInputView.text.toString()).isEqualTo("") + assertThat(viewHolder.timeInputView.text.toString()).isEqualTo("") } @Test @@ -100,14 +93,8 @@ class QuestionnaireItemDateTimePickerViewHolderFactoryTest { ) ) - assertThat( - viewHolder.itemView.findViewById(R.id.date_input_edit_text).text.toString() - ) - .isEqualTo("2/5/20") - assertThat( - viewHolder.itemView.findViewById(R.id.time_input_edit_text).text.toString() - ) - .isEqualTo("1:30 AM") + assertThat(viewHolder.dateInputView.text.toString()).isEqualTo("2/5/20") + assertThat(viewHolder.timeInputView.text.toString()).isEqualTo("1:30 AM") } @Test @@ -125,7 +112,7 @@ class QuestionnaireItemDateTimePickerViewHolderFactoryTest { ) viewHolder.bind(itemViewItem) - viewHolder.itemView.findViewById(R.id.date_input_edit_text).text = "11/19/2020" + viewHolder.dateInputView.text = "11/19/2020" val answer = itemViewItem.answers.singleOrNull()?.value as DateTimeType assertThat(answer.day).isEqualTo(19) @@ -149,7 +136,7 @@ class QuestionnaireItemDateTimePickerViewHolderFactoryTest { ) viewHolder.bind(itemViewItem) - viewHolder.itemView.findViewById(R.id.date_input_edit_text).text = "2020/11/19" + viewHolder.dateInputView.text = "2020/11/19" val answer = itemViewItem.answers.singleOrNull()?.value as DateTimeType assertThat(answer.day).isEqualTo(19) @@ -171,7 +158,7 @@ class QuestionnaireItemDateTimePickerViewHolderFactoryTest { answersChangedCallback = { _, _, _ -> }, ) viewHolder.bind(itemViewItem) - viewHolder.itemView.findViewById(R.id.date_input_edit_text).text = "2020/11/" + viewHolder.dateInputView.text = "2020/11/" assertThat(itemViewItem.answers.singleOrNull()).isNull() } @@ -187,7 +174,7 @@ class QuestionnaireItemDateTimePickerViewHolderFactoryTest { ) viewHolder.bind(itemViewItem) - viewHolder.itemView.findViewById(R.id.date_input_edit_text).text = "11/19/" + viewHolder.dateInputView.text = "11/19/" assertThat(viewHolder.itemView.findViewById(R.id.time_input_layout).isEnabled) .isFalse() @@ -204,7 +191,7 @@ class QuestionnaireItemDateTimePickerViewHolderFactoryTest { ) viewHolder.bind(itemViewItem) - viewHolder.itemView.findViewById(R.id.date_input_edit_text).text = "11/19/2020" + viewHolder.dateInputView.text = "11/19/2020" assertThat(viewHolder.itemView.findViewById(R.id.time_input_layout).isEnabled) .isTrue() @@ -267,9 +254,65 @@ class QuestionnaireItemDateTimePickerViewHolderFactoryTest { ) ) - assertThat(viewHolder.itemView.findViewById(R.id.date_input_edit_text).isEnabled) - .isFalse() - assertThat(viewHolder.itemView.findViewById(R.id.time_input_edit_text).isEnabled) - .isFalse() + assertThat(viewHolder.dateInputView.isEnabled).isFalse() + assertThat(viewHolder.timeInputView.isEnabled).isFalse() + } + + @Test + fun `bind multiple times with separate QuestionnaireItemViewItem should show proper date and time`() { + + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.QuestionnaireItemComponent().apply { text = "Question?" }, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + .addAnswer( + QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent() + .setValue(DateTimeType(Date(2020 - 1900, 1, 5, 1, 30, 0))) + ), + validationResult = NotValidated, + answersChangedCallback = { _, _, _ -> }, + ) + ) + + assertThat(viewHolder.dateInputView.text.toString()).isEqualTo("2/5/20") + assertThat(viewHolder.timeInputView.text.toString()).isEqualTo("1:30 AM") + + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.QuestionnaireItemComponent().apply { text = "Question?" }, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + .addAnswer( + QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent() + .setValue(DateTimeType(Date(2021 - 1900, 1, 5, 2, 30, 0))) + ), + validationResult = NotValidated, + answersChangedCallback = { _, _, _ -> }, + ) + ) + + assertThat(viewHolder.dateInputView.text.toString()).isEqualTo("2/5/21") + assertThat(viewHolder.timeInputView.text.toString()).isEqualTo("2:30 AM") + + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.QuestionnaireItemComponent().apply { text = "Question?" }, + QuestionnaireResponse.QuestionnaireResponseItemComponent(), + validationResult = NotValidated, + answersChangedCallback = { _, _, _ -> }, + ) + ) + + assertThat(viewHolder.dateInputView.text.toString()).isEmpty() + assertThat(viewHolder.timeInputView.text.toString()).isEmpty() } + + private val QuestionnaireItemViewHolder.dateInputView: TextView + get() { + return itemView.findViewById(R.id.date_input_edit_text) + } + + private val QuestionnaireItemViewHolder.timeInputView: TextView + get() { + return itemView.findViewById(R.id.time_input_edit_text) + } }