From 5637c2c1cfe8e4b7c95677b55544c41a6616b23d Mon Sep 17 00:00:00 2001 From: Maximilian Keppeler Date: Mon, 1 Jan 2024 15:13:07 +0100 Subject: [PATCH] (Calendar) Respect disabled dates when selection date range #75 --- .../calendar/functional/CalendarViewTests.kt | 116 ++++++++++++++++++ .../sheets/calendar/CalendarState.kt | 23 +++- 2 files changed, 134 insertions(+), 5 deletions(-) diff --git a/calendar/src/androidTest/java/com/maxkeppeler/sheets/calendar/functional/CalendarViewTests.kt b/calendar/src/androidTest/java/com/maxkeppeler/sheets/calendar/functional/CalendarViewTests.kt index 8e5566ba..1393ee10 100644 --- a/calendar/src/androidTest/java/com/maxkeppeler/sheets/calendar/functional/CalendarViewTests.kt +++ b/calendar/src/androidTest/java/com/maxkeppeler/sheets/calendar/functional/CalendarViewTests.kt @@ -17,6 +17,7 @@ package com.maxkeppeler.sheets.calendar.functional +import android.util.Range import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.ui.test.assertIsNotEnabled import androidx.compose.ui.test.junit4.createComposeRule @@ -145,6 +146,121 @@ class CalendarViewTests { assert(endDate == testEndDate) } + @Test + fun calendarViewDateSelectionStyleMonthConfigDatesDisabled() { + val testDate = LocalDate.now().withDayOfMonth(15) + val newDates = listOf( + testDate.plusDays(2), + testDate, + testDate.minusDays(3) + ) + val disabledDates = listOf( + testDate.minusDays(3), + ) + var selectedDate: LocalDate? = null + rule.setContentAndWaitForIdle { + CalendarView( + useCaseState = UseCaseState(visible = true), + selection = CalendarSelection.Date( + onSelectDate = { dates -> selectedDate = dates } + ), + config = CalendarConfig( + style = CalendarStyle.MONTH, + disabledDates = disabledDates + ) + ) + } + + newDates.forEach { date -> + rule.onNodeWithTags( + TestTags.CALENDAR_DATE_SELECTION, + date.format(DateTimeFormatter.ISO_DATE) + ).performClick() + } + + rule.onPositiveButton().performClick() + assert(selectedDate == testDate) + } + + @Test + fun calendarViewDatesSelectionStyleMonthConfigDatesDisabled() { + val testDate = LocalDate.now().withDayOfMonth(15) + val defaultDates = listOf( + testDate.minusDays(10), + testDate.minusDays(5) + ) + val newDates = listOf( + testDate, + testDate.minusDays(3) + ) + val disabledDates = listOf( + testDate.minusDays(3), + ) + var selectedDates: List? = null + rule.setContentAndWaitForIdle { + CalendarView( + useCaseState = UseCaseState(visible = true), + selection = CalendarSelection.Dates( + selectedDates = defaultDates, + onSelectDates = { dates -> selectedDates = dates } + ), + config = CalendarConfig( + style = CalendarStyle.MONTH, + disabledDates = disabledDates + ) + ) + } + + newDates.forEach { date -> + rule.onNodeWithTags( + TestTags.CALENDAR_DATE_SELECTION, + date.format(DateTimeFormatter.ISO_DATE) + ).performClick() + } + + rule.onPositiveButton().performClick() + assert(selectedDates?.sorted() == (defaultDates + testDate).sorted()) + } + + + @Test + fun calendarViewPeriodSelectionStyleMonthConfigDatesDisabled() { + val testDate = LocalDate.now().withDayOfMonth(15) + val disabledDates = listOf( + testDate.minusDays(1), + testDate.minusDays(2), + testDate.minusDays(3), + ) + val testStartDate = testDate.minusDays(4) + val testEndDate = testDate.plusDays(2) + var selectedDate: Range? = null + rule.setContentAndWaitForIdle { + CalendarView( + useCaseState = UseCaseState(visible = true), + selection = CalendarSelection.Period { startDate, endDate -> + selectedDate = Range(startDate, endDate) + }, + config = CalendarConfig( + style = CalendarStyle.MONTH, + disabledDates = disabledDates + ) + ) + } + + rule.onNodeWithTags( + TestTags.CALENDAR_DATE_SELECTION, + testStartDate.format(DateTimeFormatter.ISO_DATE) + ).performClick() + + rule.onNodeWithTags( + TestTags.CALENDAR_DATE_SELECTION, + testEndDate.format(DateTimeFormatter.ISO_DATE) + ).performClick() + + rule.onPositiveButton().assertIsNotEnabled() + assert(selectedDate == null) + } + @Test fun calendarViewPeriodSelectionInvalid() { rule.setContentAndWaitForIdle { diff --git a/calendar/src/main/java/com/maxkeppeler/sheets/calendar/CalendarState.kt b/calendar/src/main/java/com/maxkeppeler/sheets/calendar/CalendarState.kt index 546767d7..9fcabb45 100644 --- a/calendar/src/main/java/com/maxkeppeler/sheets/calendar/CalendarState.kt +++ b/calendar/src/main/java/com/maxkeppeler/sheets/calendar/CalendarState.kt @@ -245,16 +245,29 @@ internal class CalendarState( } is CalendarSelection.Period -> { - val beforeStart = - range.startValue?.let { newDate.isBefore(it) } ?: false - val containsDisabledDate = range.endValue?.let { startDate -> - config.disabledDates?.any { it.isAfter(startDate) && it.isBefore(newDate) } + // Check if the selected range includes any disabled dates + val includesDisabledDate = range.startValue?.let { startDate -> + config.disabledDates?.any { disabledDate -> + disabledDate.isAfter(startDate) && disabledDate.isBefore(newDate) || disabledDate == newDate + } } ?: false - if (isRangeSelectionStart || beforeStart || containsDisabledDate) { + + // Reset the range if the selection includes a disabled date + if (includesDisabledDate) { + range[Constants.RANGE_START] = newDate + range[Constants.RANGE_END] = null + return + } + + // Check if the selection is the start or the date is before the start + val beforeStart = range.startValue?.let { newDate.isBefore(it) } ?: false + if (isRangeSelectionStart || beforeStart) { + // Reset the range if the selection includes a disabled date range[Constants.RANGE_START] = newDate range[Constants.RANGE_END] = null isRangeSelectionStart = false } else { + // Check if the selection is the end or the date is after the end range[Constants.RANGE_END] = newDate isRangeSelectionStart = true }