From dcc2cad9a97270125fbb13f14decf5bb37d619b7 Mon Sep 17 00:00:00 2001 From: Rajat Biswas Date: Thu, 1 Feb 2024 16:43:32 +0530 Subject: [PATCH] Represent the invalid dates in DatePicker in grey color. --- .../horologist/composables/DatePicker.kt | 193 +++++++++++------- .../horologist/composables/TimePicker.kt | 35 ++-- .../composables/DatePickerInteractionTest.kt | 100 ++++----- .../android/horologist/sample/MenuChips.kt | 44 ++++ .../android/horologist/sample/MenuScreen.kt | 6 + .../horologist/sample/SampleWearApp.kt | 25 ++- .../android/horologist/sample/Screen.kt | 2 + .../horologist/screensizes/DatePickerTest.kt | 23 ++- ...t_fromDatePicker[0]_mobvoiticwatchpro5.png | 3 + ..._fromDatePicker[1]_samsunggalaxywatch5.png | 3 + ...DatePicker[2]_samsunggalaxywatch6large.png | 3 + ...est_fromDatePicker[3]_googlepixelwatch.png | 3 + ...st_fromDatePicker[4]_genericsmallround.png | 3 + ...st_fromDatePicker[5]_genericlargeround.png | 3 + ..._fromDatePicker[6]_smalldevicebigfonts.png | 3 + ...romDatePicker[7]_largedevicesmallfonts.png | 3 + ...est_toDatePicker[0]_mobvoiticwatchpro5.png | 3 + ...st_toDatePicker[1]_samsunggalaxywatch5.png | 3 + ...DatePicker[2]_samsunggalaxywatch6large.png | 3 + ...rTest_toDatePicker[3]_googlepixelwatch.png | 3 + ...Test_toDatePicker[4]_genericsmallround.png | 3 + ...Test_toDatePicker[5]_genericlargeround.png | 3 + ...st_toDatePicker[6]_smalldevicebigfonts.png | 3 + ..._toDatePicker[7]_largedevicesmallfonts.png | 3 + 24 files changed, 327 insertions(+), 149 deletions(-) create mode 100644 sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[0]_mobvoiticwatchpro5.png create mode 100644 sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[1]_samsunggalaxywatch5.png create mode 100644 sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[2]_samsunggalaxywatch6large.png create mode 100644 sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[3]_googlepixelwatch.png create mode 100644 sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[4]_genericsmallround.png create mode 100644 sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[5]_genericlargeround.png create mode 100644 sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[6]_smalldevicebigfonts.png create mode 100644 sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[7]_largedevicesmallfonts.png create mode 100644 sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[0]_mobvoiticwatchpro5.png create mode 100644 sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[1]_samsunggalaxywatch5.png create mode 100644 sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[2]_samsunggalaxywatch6large.png create mode 100644 sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[3]_googlepixelwatch.png create mode 100644 sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[4]_genericsmallround.png create mode 100644 sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[5]_genericlargeround.png create mode 100644 sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[6]_smalldevicebigfonts.png create mode 100644 sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[7]_largedevicesmallfonts.png diff --git a/composables/src/main/java/com/google/android/horologist/composables/DatePicker.kt b/composables/src/main/java/com/google/android/horologist/composables/DatePicker.kt index 2b09e7c8f2..3a3c8bd779 100644 --- a/composables/src/main/java/com/google/android/horologist/composables/DatePicker.kt +++ b/composables/src/main/java/com/google/android/horologist/composables/DatePicker.kt @@ -14,12 +14,9 @@ * limitations under the License. */ -@file:OptIn(ExperimentalFoundationApi::class) - package com.google.android.horologist.composables import androidx.compose.animation.core.Animatable -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.BoxWithConstraints @@ -95,13 +92,7 @@ public fun DatePicker( } val datePickerState = remember(date) { - if (fromDate != null && toDate == null) { - DatePickerState(date = date, fromDate = fromDate, toDate = fromDate.plusYears(3000)) - } else if (fromDate == null && toDate != null) { - DatePickerState(date = date, fromDate = toDate.minusYears(3000), toDate = toDate) - } else { - DatePickerState(date, fromDate, toDate) - } + DatePickerState(date, fromDate, toDate) } val touchExplorationStateProvider = remember { DefaultTouchExplorationStateProvider() } @@ -130,14 +121,11 @@ public fun DatePicker( datePickerState.yearState.selectedOption, datePickerState.monthState.selectedOption, ) { - if (datePickerState.numOfMonths != datePickerState.monthState.numberOfOptions) { - datePickerState.monthState.numberOfOptions = datePickerState.numOfMonths - } - if (datePickerState.numOfDays != datePickerState.dayState.numberOfOptions) { - if (datePickerState.dayState.selectedOption >= datePickerState.numOfDays) { - datePickerState.dayState.animateScrollToOption(datePickerState.numOfDays - 1) + if (datePickerState.maxDaysInMonth != datePickerState.dayState.numberOfOptions) { + if (datePickerState.dayState.selectedOption >= datePickerState.maxDaysInMonth) { + datePickerState.dayState.animateScrollToOption(datePickerState.maxDaysInMonth - 1) } - datePickerState.dayState.numberOfOptions = datePickerState.numOfDays + datePickerState.dayState.numberOfOptions = datePickerState.maxDaysInMonth } } val shortMonthNames = remember { getMonthNames("MMM") } @@ -237,7 +225,6 @@ public fun DatePicker( dayWidth, monthWidth, yearWidth, - spacerWidth, touchExplorationServicesEnabled, pickerGroupState, ), @@ -256,9 +243,15 @@ public fun DatePicker( ) }, contentDescription = dayContentDescription, - option = pickerTextOption(textStyle) { - "%d".format(datePickerState.currentDay(it)) - }, + option = pickerTextOption( + textStyle = textStyle, + indexToText = { + "%d".format(datePickerState.currentDay(it)) + }, + isValid = { + datePickerState.isDayValid(datePickerState.currentDay(it)) + }, + ), ), pickerGroupItemWithRSB( pickerState = datePickerState.monthState, @@ -270,9 +263,15 @@ public fun DatePicker( ) }, contentDescription = monthContentDescription, - option = pickerTextOption(textStyle) { - shortMonthNames[(datePickerState.currentMonth(it) - 1) % 12] - }, + option = pickerTextOption( + textStyle = textStyle, + indexToText = { + shortMonthNames[(datePickerState.currentMonth(it) - 1) % 12] + }, + isValid = { + datePickerState.isMonthValid(datePickerState.currentMonth(it)) + }, + ), ), pickerGroupItemWithRSB( pickerState = datePickerState.yearState, @@ -284,9 +283,15 @@ public fun DatePicker( ) }, contentDescription = yearContentDescription, - option = pickerTextOption(textStyle) { - "%4d".format(datePickerState.currentYear(it)) - }, + option = pickerTextOption( + textStyle = textStyle, + indexToText = { + "%4d".format(datePickerState.currentYear(it)) + }, + isValid = { + datePickerState.isYearValid(datePickerState.currentYear(it)) + }, + ), ), pickerGroupState = pickerGroupState, autoCenter = true, @@ -332,6 +337,11 @@ public fun DatePicker( } .focusRequester(focusRequesterConfirmButton) .focusable(), + enabled = if (pickerGroupState.selectedIndex >= 2) { + datePickerState.isDayValid() + } else { + true + }, ) { Icon( imageVector = @@ -385,14 +395,10 @@ private fun getPickerGroupRowOffset( dayPickerWidth: Dp, monthPickerWidth: Dp, yearPickerWidth: Dp, - spacerWidth: Dp, touchExplorationServicesEnabled: Boolean, pickerGroupState: PickerGroupState, ): Dp { - val currentOffset = ( - rowWidth - - (dayPickerWidth + monthPickerWidth + yearPickerWidth + spacerWidth.times(2)) - ) / 2 + val currentOffset = (rowWidth - (dayPickerWidth + monthPickerWidth + yearPickerWidth)) / 2 return if (touchExplorationServicesEnabled && pickerGroupState.selectedIndex < 0 @@ -401,8 +407,7 @@ private fun getPickerGroupRowOffset( } else if (touchExplorationServicesEnabled && pickerGroupState.selectedIndex > 2 ) { - ((rowWidth - yearPickerWidth) / 2) - - (dayPickerWidth + monthPickerWidth + spacerWidth.times(2) + currentOffset) + ((rowWidth - yearPickerWidth) / 2) - (dayPickerWidth + monthPickerWidth + currentOffset) } else { 0.dp } @@ -413,72 +418,116 @@ internal class DatePickerState constructor( private val fromDate: LocalDate?, private val toDate: LocalDate?, ) { - private val yearOffset = fromDate?.year ?: 1 - val numOfYears = (toDate?.year ?: 3000) - (yearOffset - 1) + // Year range 1900 - 2100 was suggested in b/277885199 + private val startYear = if (fromDate != null && fromDate.year < 1900) { + fromDate.year + } else { + 1900 + } + private val numOfYears = if (toDate != null && (toDate.year - startYear) > 200) { + toDate.year - startYear + 1 + } else { + 201 + } val yearState = PickerState( - initialNumberOfOptions = numOfYears, - initiallySelectedOption = date.year - yearOffset, + initialNumberOfOptions = 201, + initiallySelectedOption = date.year - startYear, ) + val selectedYearEqualsFromYear: Boolean - get() = (yearState.selectedOption == 0) + get() = fromDate?.year == currentYear() + val selectedYearEqualsToYear: Boolean - get() = (yearState.selectedOption == yearState.numberOfOptions - 1) - - private val monthOffset: Int - get() = (if (selectedYearEqualsFromYear) (fromDate?.monthValue ?: 1) else 1) - private val maxMonths: Int - get() = (if (selectedYearEqualsToYear) (toDate?.monthValue ?: 12) else 12) - val numOfMonths: Int - get() = (maxMonths.minus(monthOffset - 1)) + get() = toDate?.year == currentYear() + + fun isYearValid(year: Int = currentYear()): Boolean { + return ( + if (fromDate?.year != null) { + fromDate.year <= year + } else { + true + } + ) && ( + if (toDate?.year != null) { + year <= toDate.year + } else { + true + } + ) + } + val monthState = PickerState( - initialNumberOfOptions = numOfMonths, - initiallySelectedOption = date.monthValue - monthOffset, + initialNumberOfOptions = 12, + initiallySelectedOption = date.monthValue - 1, ) + val selectedMonthEqualsFromMonth: Boolean - get() = (selectedYearEqualsFromYear && monthState.selectedOption == 0) + get() = selectedYearEqualsFromYear && fromDate?.monthValue == currentMonth() + val selectedMonthEqualsToMonth: Boolean - get() = ( - selectedYearEqualsToYear && - monthState.selectedOption == monthState.numberOfOptions - 1 - ) + get() = selectedYearEqualsToYear && toDate?.monthValue == currentMonth() + + fun isMonthValid(month: Int = currentMonth()): Boolean { + return isYearValid() && + ( + if (fromDate != null && selectedYearEqualsFromYear) { + fromDate.monthValue <= month + } else { + true + } + ) && ( + if (toDate != null && selectedYearEqualsToYear) { + month <= toDate.monthValue + } else { + true + } + ) + } - private val dayOffset: Int - get() = (if (selectedMonthEqualsFromMonth) (fromDate?.dayOfMonth ?: 1) else 1) private val firstDayOfMonth: LocalDate get() = LocalDate.of( currentYear(), currentMonth(), 1, ) - private val maxDaysInMonth: Int - get() = ( - if (toDate != null && selectedMonthEqualsToMonth) { - toDate.dayOfMonth - } else { - firstDayOfMonth.with(TemporalAdjusters.lastDayOfMonth()).dayOfMonth - } - ) - val numOfDays: Int - get() = maxDaysInMonth.minus(dayOffset - 1) + val maxDaysInMonth: Int + get() = firstDayOfMonth.with(TemporalAdjusters.lastDayOfMonth()).dayOfMonth val dayState = PickerState( - initialNumberOfOptions = numOfDays, - initiallySelectedOption = date.dayOfMonth - dayOffset, + initialNumberOfOptions = maxDaysInMonth, + initiallySelectedOption = date.dayOfMonth - 1, ) + fun isDayValid(day: Int = currentDay()): Boolean { + return isMonthValid() && + ( + if (fromDate != null && selectedMonthEqualsFromMonth) { + fromDate.dayOfMonth <= day + } else { + true + } + ) && ( + if (toDate != null && selectedMonthEqualsToMonth) { + day <= toDate.dayOfMonth + } else { + true + } + ) + } + fun currentYear(year: Int = yearState.selectedOption): Int { - return year + yearOffset + return year + startYear } fun currentMonth(month: Int = monthState.selectedOption): Int { - return month + monthOffset + return month + 1 } fun currentDay(day: Int = dayState.selectedOption): Int { - return day + dayOffset + return day + 1 } } @@ -502,7 +551,7 @@ private enum class FocusableElementDatePicker(val index: Int) { ; companion object { - private val map = FocusableElementDatePicker.values().associateBy { it.index } + private val map = entries.associateBy { it.index } operator fun get(value: Int) = map[value] } } diff --git a/composables/src/main/java/com/google/android/horologist/composables/TimePicker.kt b/composables/src/main/java/com/google/android/horologist/composables/TimePicker.kt index cd62da2e6b..b4e82a10d7 100644 --- a/composables/src/main/java/com/google/android/horologist/composables/TimePicker.kt +++ b/composables/src/main/java/com/google/android/horologist/composables/TimePicker.kt @@ -53,6 +53,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalInspectionMode @@ -146,7 +147,7 @@ public fun TimePicker( } val optionColor = MaterialTheme.colors.secondary - val pickerOption = pickerTextOption(textStyle) { "%02d".format(it) } + val pickerOption = pickerTextOption(textStyle, { "%02d".format(it) }) val focusRequesterConfirmButton = remember { FocusRequester() } val hourString = stringResource(R.string.horologist_time_picker_hour) @@ -465,7 +466,7 @@ public fun TimePickerWith12HourClock( ) }, contentDescription = hoursContentDescription, - option = pickerTextOption(textStyle) { "%02d".format(it + 1) }, + option = pickerTextOption(textStyle, { "%02d".format(it + 1) }), ), pickerGroupItemWithRSB( pickerState = minuteState, @@ -479,7 +480,7 @@ public fun TimePickerWith12HourClock( ) }, contentDescription = minutesContentDescription, - option = pickerTextOption(textStyle) { "%02d".format(it) }, + option = pickerTextOption(textStyle, { "%02d".format(it) }), ), pickerGroupItemWithRSB( pickerState = periodState, @@ -493,9 +494,9 @@ public fun TimePickerWith12HourClock( FocusableElement12Hour.CONFIRM_BUTTON, ) }, - option = pickerTextOption(textStyle) { + option = pickerTextOption(textStyle, { if (it == 0) amString else pmString - }, + }), ), autoCenter = false, pickerGroupState = pickerGroupState, @@ -588,19 +589,23 @@ internal fun pickerGroupItemWithRSB( ) } -internal fun pickerTextOption(textStyle: TextStyle, indexToText: (Int) -> String): - (@Composable PickerScope.(optionIndex: Int, pickerSelected: Boolean) -> Unit) = { value: Int, pickerSelected: Boolean -> +internal fun pickerTextOption( + textStyle: TextStyle, + indexToText: (Int) -> String, + isValid: (Int) -> Boolean = { true }, +): (@Composable PickerScope.(optionIndex: Int, pickerSelected: Boolean) -> Unit) = { value: Int, pickerSelected: Boolean -> Box(modifier = Modifier.fillMaxSize()) { Text( text = indexToText(value), maxLines = 1, style = textStyle, - color = - if (pickerSelected) { - MaterialTheme.colors.secondary - } else { - MaterialTheme.colors.onBackground - }, + color = if (!isValid(value)) { + Color(0xFF757575) + } else if (pickerSelected) { + MaterialTheme.colors.secondary + } else { + MaterialTheme.colors.onBackground + }, modifier = Modifier .align(Alignment.Center) .wrapContentSize(), @@ -719,7 +724,7 @@ private enum class FocusableElementsTimePicker(val index: Int) { ; companion object { - private val map = FocusableElementsTimePicker.values().associateBy { it.index } + private val map = entries.associateBy { it.index } operator fun get(value: Int) = map[value] } } @@ -733,7 +738,7 @@ private enum class FocusableElement12Hour(val index: Int) { ; companion object { - private val map = FocusableElement12Hour.values().associateBy { it.index } + private val map = entries.associateBy { it.index } operator fun get(value: Int) = map[value] } } diff --git a/composables/src/test/kotlin/com/google/android/horologist/composables/DatePickerInteractionTest.kt b/composables/src/test/kotlin/com/google/android/horologist/composables/DatePickerInteractionTest.kt index fc92436787..a073d9b9ac 100644 --- a/composables/src/test/kotlin/com/google/android/horologist/composables/DatePickerInteractionTest.kt +++ b/composables/src/test/kotlin/com/google/android/horologist/composables/DatePickerInteractionTest.kt @@ -28,9 +28,7 @@ import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick import androidx.wear.compose.material.Text import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.runBlocking import org.junit.Assert -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -150,7 +148,8 @@ class DatePickerInteractionTest { fun year_state_initialised_to_fromYear() { val date = LocalDate.of(2022, 4, 25) - val datePickerState = DatePickerState(date = date, fromDate = date, toDate = date.plusYears(1)) + val datePickerState = + DatePickerState(date = date, fromDate = date, toDate = date.plusYears(1)) assertThat(datePickerState.selectedYearEqualsFromYear).isTrue() assertThat(datePickerState.selectedYearEqualsToYear).isFalse() @@ -160,7 +159,8 @@ class DatePickerInteractionTest { fun month_state_initialised_to_fromMonth() { val date = LocalDate.of(2022, 4, 25) - val datePickerState = DatePickerState(date = date, fromDate = date, toDate = date.plusYears(1)) + val datePickerState = + DatePickerState(date = date, fromDate = date, toDate = date.plusYears(1)) assertThat(datePickerState.selectedMonthEqualsFromMonth).isTrue() assertThat(datePickerState.selectedMonthEqualsToMonth).isFalse() @@ -170,7 +170,8 @@ class DatePickerInteractionTest { fun year_state_initialised_to_toYear() { val date = LocalDate.of(2022, 4, 25) - val datePickerState = DatePickerState(date = date, fromDate = date.minusYears(1), toDate = date) + val datePickerState = + DatePickerState(date = date, fromDate = date.minusYears(1), toDate = date) assertThat(datePickerState.selectedYearEqualsFromYear).isFalse() assertThat(datePickerState.selectedYearEqualsToYear).isTrue() @@ -180,107 +181,90 @@ class DatePickerInteractionTest { fun month_state_initialised_to_toMonth() { val date = LocalDate.of(2022, 4, 25) - val datePickerState = DatePickerState(date = date, fromDate = date.minusYears(1), toDate = date) + val datePickerState = + DatePickerState(date = date, fromDate = date.minusYears(1), toDate = date) assertThat(datePickerState.selectedMonthEqualsFromMonth).isFalse() assertThat(datePickerState.selectedMonthEqualsToMonth).isTrue() } @Test - fun year_options_restricted_correctly_when_fromDate_and_toDate_provided() { + fun current_day_valid_for_date_equal_to_fromDate() { val date = LocalDate.of(2022, 4, 25) - val datePickerState = DatePickerState(date = date, fromDate = date, toDate = date.plusYears(1)) + val datePickerState = + DatePickerState(date = date, fromDate = date, toDate = date.plusYears(1)) - assertThat(datePickerState.numOfYears).isEqualTo(2) + assertThat(datePickerState.isDayValid()).isTrue() } @Test - fun month_options_restricted_correctly_for_fromDate() { + fun current_day_valid_for_date_equal_to_toDate() { val date = LocalDate.of(2022, 4, 25) - val datePickerState = DatePickerState(date = date, fromDate = date, toDate = date.plusYears(1)) + val datePickerState = + DatePickerState(date = date, fromDate = date.minusYears(1), toDate = date) - assertThat(datePickerState.numOfMonths).isEqualTo(9) + assertThat(datePickerState.isDayValid()).isTrue() } @Test - fun month_options_restricted_correctly_for_toDate() { + fun current_day_invalid_for_day_before_fromDate() { val date = LocalDate.of(2022, 4, 25) - val datePickerState = DatePickerState(date = date, fromDate = date.minusYears(1), toDate = date) + val datePickerState = + DatePickerState(date = date.minusDays(1), fromDate = date, toDate = date.plusYears(1)) - assertThat(datePickerState.numOfMonths).isEqualTo(4) + assertThat(datePickerState.isDayValid()).isFalse() } @Test - fun day_options_restricted_correctly_for_fromDate() { + fun current_day_invalid_for_day_after_toDate() { val date = LocalDate.of(2022, 4, 25) - val datePickerState = DatePickerState(date = date, fromDate = date, toDate = date.plusYears(1)) + val datePickerState = + DatePickerState(date = date.plusDays(1), fromDate = date.minusYears(1), toDate = date) - assertThat(datePickerState.numOfDays).isEqualTo(6) + assertThat(datePickerState.isDayValid()).isFalse() } @Test - fun day_options_restricted_correctly_for_toDate() { + fun current_month_invalid_for_month_before_fromDate() { val date = LocalDate.of(2022, 4, 25) - val datePickerState = DatePickerState(date = date, fromDate = date.minusYears(1), toDate = date) + val datePickerState = + DatePickerState(date = date.minusMonths(1), fromDate = date, toDate = date.plusYears(1)) - assertThat(datePickerState.numOfDays).isEqualTo(25) + assertThat(datePickerState.isMonthValid()).isFalse() } @Test - fun picker_options_restricted_correctly_when_fromDate_equals_toDate() { + fun current_month_invalid_for_month_after_toDate() { val date = LocalDate.of(2022, 4, 25) - val datePickerState = DatePickerState(date = date, fromDate = date, toDate = date) + val datePickerState = + DatePickerState(date = date.plusMonths(1), fromDate = date.minusYears(1), toDate = date) - assertThat(datePickerState.numOfYears).isEqualTo(1) - assertThat(datePickerState.numOfMonths).isEqualTo(1) - assertThat(datePickerState.numOfDays).isEqualTo(1) + assertThat(datePickerState.isMonthValid()).isFalse() } - @Ignore("Test is failing") @Test - fun picker_first_options_set_correctly_for_fromDate() { + fun current_year_invalid_for_year_before_fromDate() { val date = LocalDate.of(2022, 4, 25) - val datePickerState = DatePickerState( - date = date, - fromDate = date.minusYears(1), - toDate = date.plusYears(1), - ) - - runBlocking { // to run suspend methods sequentially - datePickerState.yearState.scrollToOption(0) - datePickerState.monthState.scrollToOption(0) - datePickerState.dayState.scrollToOption(0) - } - assertThat(datePickerState.currentYear()).isEqualTo(2021) - assertThat(datePickerState.currentMonth()).isEqualTo(4) - assertThat(datePickerState.currentDay()).isEqualTo(25) + val datePickerState = + DatePickerState(date = date.minusYears(1), fromDate = date, toDate = date.plusYears(1)) + + assertThat(datePickerState.isYearValid()).isFalse() } - @Ignore("Test is failing") @Test - fun picker_last_options_set_correctly_for_toDate() { + fun current_year_invalid_for_year_after_toDate() { val date = LocalDate.of(2022, 4, 25) - val datePickerState = DatePickerState( - date = date, - fromDate = date.minusYears(1), - toDate = date.plusYears(1), - ) - - runBlocking { // to run suspend methods sequentially - datePickerState.yearState.scrollToOption(datePickerState.numOfYears - 1) - datePickerState.monthState.scrollToOption(datePickerState.numOfMonths - 1) - datePickerState.dayState.scrollToOption(datePickerState.numOfDays - 1) - } - assertThat(datePickerState.currentYear()).isEqualTo(2023) - assertThat(datePickerState.currentMonth()).isEqualTo(4) - assertThat(datePickerState.currentDay()).isEqualTo(25) + val datePickerState = + DatePickerState(date = date.plusYears(1), fromDate = date.minusYears(1), toDate = date) + + assertThat(datePickerState.isYearValid()).isFalse() } } diff --git a/sample/src/main/java/com/google/android/horologist/sample/MenuChips.kt b/sample/src/main/java/com/google/android/horologist/sample/MenuChips.kt index 74f2ef5cc2..d30b87ecaa 100644 --- a/sample/src/main/java/com/google/android/horologist/sample/MenuChips.kt +++ b/sample/src/main/java/com/google/android/horologist/sample/MenuChips.kt @@ -138,6 +138,50 @@ fun DatePickerChip( ) } +@Composable +fun FromDatePickerChip( + time: LocalDateTime, + navigateToRoute: (String) -> Unit, +) { + SampleChip( + onClick = { navigateToRoute(Screen.FromDatePicker.route) }, + label = "From Date Picker", + content = { + Text( + text = buildAnnotatedString { + withStyle(SpanStyle(color = Color.Yellow, fontWeight = FontWeight.Bold)) { + append(time.dayOfMonth.toString()) + } + append(" ${time.format(DateTimeFormatter.ofPattern("MMM"))}") + }, + fontSize = 6f.sp, + ) + }, + ) +} + +@Composable +fun ToDatePickerChip( + time: LocalDateTime, + navigateToRoute: (String) -> Unit, +) { + SampleChip( + onClick = { navigateToRoute(Screen.ToDatePicker.route) }, + label = "To Date Picker", + content = { + Text( + text = buildAnnotatedString { + withStyle(SpanStyle(color = Color.Yellow, fontWeight = FontWeight.Bold)) { + append(time.dayOfMonth.toString()) + } + append(" ${time.format(DateTimeFormatter.ofPattern("MMM"))}") + }, + fontSize = 6f.sp, + ) + }, + ) +} + @Composable fun TimeWithSecondsPickerChip( time: LocalDateTime, diff --git a/sample/src/main/java/com/google/android/horologist/sample/MenuScreen.kt b/sample/src/main/java/com/google/android/horologist/sample/MenuScreen.kt index 86d08b7ed1..2b29a27f6a 100644 --- a/sample/src/main/java/com/google/android/horologist/sample/MenuScreen.kt +++ b/sample/src/main/java/com/google/android/horologist/sample/MenuScreen.kt @@ -85,6 +85,12 @@ fun MenuScreen( item { DatePickerChip(time) { navigateToRoute(Screen.DatePicker.route) } } + item { + FromDatePickerChip(time) { navigateToRoute(Screen.FromDatePicker.route) } + } + item { + ToDatePickerChip(time) { navigateToRoute(Screen.ToDatePicker.route) } + } item { TimeWithSecondsPickerChip(time) { navigateToRoute(Screen.TimeWithSecondsPicker.route) } } diff --git a/sample/src/main/java/com/google/android/horologist/sample/SampleWearApp.kt b/sample/src/main/java/com/google/android/horologist/sample/SampleWearApp.kt index a4a090e9a9..be4622c3b1 100644 --- a/sample/src/main/java/com/google/android/horologist/sample/SampleWearApp.kt +++ b/sample/src/main/java/com/google/android/horologist/sample/SampleWearApp.kt @@ -14,11 +14,8 @@ * limitations under the License. */ -@file:OptIn(ExperimentalFoundationApi::class) - package com.google.android.horologist.sample -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState import androidx.compose.runtime.Composable @@ -152,6 +149,28 @@ fun SampleWearApp() { }, ) } + composable(Screen.FromDatePicker.route) { + val date = time.toLocalDate() + DatePicker( + date = date, + fromDate = date, + onDateConfirm = { + time = time.toLocalTime().atDate(it) + navController.popBackStack() + }, + ) + } + composable(Screen.ToDatePicker.route) { + val date = time.toLocalDate() + DatePicker( + date = date, + toDate = date, + onDateConfirm = { + time = time.toLocalTime().atDate(it) + navController.popBackStack() + }, + ) + } composable(Screen.TimePicker.route) { TimePickerWith12HourClock( time = time.toLocalTime(), diff --git a/sample/src/main/java/com/google/android/horologist/sample/Screen.kt b/sample/src/main/java/com/google/android/horologist/sample/Screen.kt index a44ff426dd..466c8488d8 100644 --- a/sample/src/main/java/com/google/android/horologist/sample/Screen.kt +++ b/sample/src/main/java/com/google/android/horologist/sample/Screen.kt @@ -26,6 +26,8 @@ sealed class Screen( object ScrollAwayColumn : Screen("scrollAwayColumn") object Volume : Screen("volume") object DatePicker : Screen("datePicker") + object FromDatePicker : Screen("fromDatePicker") + object ToDatePicker : Screen("toDatePicker") object TimePicker : Screen("timePicker") object TimeWithSecondsPicker : Screen("timeWithSecondsPicker") object TimeWithoutSecondsPicker : Screen("timeWithoutSecondsPicker") diff --git a/sample/src/test/kotlin/com/google/android/horologist/screensizes/DatePickerTest.kt b/sample/src/test/kotlin/com/google/android/horologist/screensizes/DatePickerTest.kt index fa19f62e01..db3e70cc27 100644 --- a/sample/src/test/kotlin/com/google/android/horologist/screensizes/DatePickerTest.kt +++ b/sample/src/test/kotlin/com/google/android/horologist/screensizes/DatePickerTest.kt @@ -19,15 +19,36 @@ package com.google.android.horologist.screensizes import androidx.compose.runtime.Composable import com.google.android.horologist.composables.DatePicker import com.google.android.horologist.compose.tools.Device +import org.junit.Test import java.time.LocalDate class DatePickerTest(device: Device) : ScreenSizeTest(device = device, showTimeText = false) { + private val date: LocalDate = LocalDate.of(2022, 4, 25) + @Composable override fun Content() { DatePicker( onDateConfirm = {}, - date = LocalDate.of(2022, 4, 25), + date = date, + ) + } + + @Test + fun fromDatePicker() = runTest { + DatePicker( + onDateConfirm = {}, + date = date, + fromDate = date, + ) + } + + @Test + fun toDatePicker() = runTest { + DatePicker( + onDateConfirm = {}, + date = date, + toDate = date, ) } } diff --git a/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[0]_mobvoiticwatchpro5.png b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[0]_mobvoiticwatchpro5.png new file mode 100644 index 0000000000..85da6457eb --- /dev/null +++ b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[0]_mobvoiticwatchpro5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f48dc9c78c4e4095b2640d8b06266a7b1b509100139b7fc361017f13d16232f2 +size 25360 diff --git a/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[1]_samsunggalaxywatch5.png b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[1]_samsunggalaxywatch5.png new file mode 100644 index 0000000000..b33a6385ce --- /dev/null +++ b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[1]_samsunggalaxywatch5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37d4e9014897af369ba1a77b5771fc6bdaafb842f3224c66e3b2b4fba61da809 +size 23083 diff --git a/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[2]_samsunggalaxywatch6large.png b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[2]_samsunggalaxywatch6large.png new file mode 100644 index 0000000000..6904b35362 --- /dev/null +++ b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[2]_samsunggalaxywatch6large.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d8580b8dd5a605e1982e88bc8e47ad1c55cd22eeec8351e99353ed258189ef6 +size 27014 diff --git a/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[3]_googlepixelwatch.png b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[3]_googlepixelwatch.png new file mode 100644 index 0000000000..13741011e9 --- /dev/null +++ b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[3]_googlepixelwatch.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b444ff3cb1d379670df7b85650272cedee6aca9e5f78e4c4ede7f07930d5e1b +size 22960 diff --git a/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[4]_genericsmallround.png b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[4]_genericsmallround.png new file mode 100644 index 0000000000..13741011e9 --- /dev/null +++ b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[4]_genericsmallround.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b444ff3cb1d379670df7b85650272cedee6aca9e5f78e4c4ede7f07930d5e1b +size 22960 diff --git a/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[5]_genericlargeround.png b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[5]_genericlargeround.png new file mode 100644 index 0000000000..3a8b38adf9 --- /dev/null +++ b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[5]_genericlargeround.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b7c1c06bf1e43defd49f1c0a88ba782c8e951936db1db2b8ff1aaf6bc205490 +size 25062 diff --git a/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[6]_smalldevicebigfonts.png b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[6]_smalldevicebigfonts.png new file mode 100644 index 0000000000..43eb842852 --- /dev/null +++ b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[6]_smalldevicebigfonts.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b881eb39270b444248fa772c40a98e9051bf88d1cefe7d9573a2b5e9a9e351d1 +size 23026 diff --git a/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[7]_largedevicesmallfonts.png b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[7]_largedevicesmallfonts.png new file mode 100644 index 0000000000..065f567f70 --- /dev/null +++ b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_fromDatePicker[7]_largedevicesmallfonts.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dcfaacfba94aa3a3b1c862906e762146178cc660e9789c2b641afbd647315449 +size 25162 diff --git a/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[0]_mobvoiticwatchpro5.png b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[0]_mobvoiticwatchpro5.png new file mode 100644 index 0000000000..9882ba0ba6 --- /dev/null +++ b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[0]_mobvoiticwatchpro5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b21d76c7671dd42c9b1f60605c859ffc50b7a8b8871cbcd71d47556736d417a +size 25397 diff --git a/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[1]_samsunggalaxywatch5.png b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[1]_samsunggalaxywatch5.png new file mode 100644 index 0000000000..b5462f812b --- /dev/null +++ b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[1]_samsunggalaxywatch5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9963e3909068122e221051131ef8b3753a826ce34aa7f82141bd600ba9391b17 +size 23002 diff --git a/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[2]_samsunggalaxywatch6large.png b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[2]_samsunggalaxywatch6large.png new file mode 100644 index 0000000000..c42634a4fa --- /dev/null +++ b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[2]_samsunggalaxywatch6large.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54e971ec80bb8ba769513607e415042831488805e6783b150bca455a1abb8e5e +size 26969 diff --git a/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[3]_googlepixelwatch.png b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[3]_googlepixelwatch.png new file mode 100644 index 0000000000..a6772bf774 --- /dev/null +++ b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[3]_googlepixelwatch.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d3cdef8c9ffd167b62cf9aff1bbebefdc7b9a30f84be0505a6cb566b3e18e1cb +size 22862 diff --git a/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[4]_genericsmallround.png b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[4]_genericsmallround.png new file mode 100644 index 0000000000..a6772bf774 --- /dev/null +++ b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[4]_genericsmallround.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d3cdef8c9ffd167b62cf9aff1bbebefdc7b9a30f84be0505a6cb566b3e18e1cb +size 22862 diff --git a/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[5]_genericlargeround.png b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[5]_genericlargeround.png new file mode 100644 index 0000000000..5fc23f0031 --- /dev/null +++ b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[5]_genericlargeround.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5919c0509213f438394ff00749a9383f2f49b6c0110124d8fee264b06b3c7848 +size 25094 diff --git a/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[6]_smalldevicebigfonts.png b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[6]_smalldevicebigfonts.png new file mode 100644 index 0000000000..2281798f9a --- /dev/null +++ b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[6]_smalldevicebigfonts.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0626dfae1aae3a1bfd069f305cf5df871a726348b42173db51b3d37c8757dab9 +size 22950 diff --git a/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[7]_largedevicesmallfonts.png b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[7]_largedevicesmallfonts.png new file mode 100644 index 0000000000..314e3a42d6 --- /dev/null +++ b/sample/src/test/snapshots/images/com.google.android.horologist.screensizes_DatePickerTest_toDatePicker[7]_largedevicesmallfonts.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d360ed222e2b67e3ac8d11d13e7ec22026126e628547b4aeea553418127b8ea +size 25057