Search code examples
androidkotlindatepickerandroid-jetpack-compose

How to allow only a few dates to be selected in this date picker?


I'm using this compose library for my date picker: https://github.com/maxkeppeler/sheets-compose-dialogs

The composable accepts only disallowed dates as follows:

val disabledDates = listOf(
    LocalDate.now().plusDays(3),
    LocalDate.now().plusDays(5),
    LocalDate.now().plusDays(7),
)

CalendarView(
    useCaseState = rememberUseCaseState(),
    config = CalendarConfig(
        style = CalendarStyle.MONTH,
        disabledDates = disabledDates,
        boundary = LocalDate.now()..LocalDate.now().plusYears(10),
    ),
    selection = CalendarSelection.Date { newDate ->
        selectedDate.value = newDate
    },
)

The issue is that I need to pass only a few allowed dates instead. How can I achieve this? Is there a way to convert the allowed dates into a list of disallowed dates or something?


Solution

  • Since this composabe only allows to disable certain days from the given boundary, there is no way around to provide a list of all dates minus the ones that you want to allow.

    Let's assume the disabledDates from your example are actually the allowed dates. Then you would need to remove these dates from the range that the calendar should display (boundary) so it can be passed as disabledDates.

    There is just one problem: boundary is a ClosedRange. It just consists of a start and an end value, there are no values inbetween so it cannot be converted to a list. To fill that gap we need an Iterator for such a range:

    fun ClosedRange<LocalDate>.toIterable(): Iterable<LocalDate> = Iterable {
        object : Iterator<LocalDate> {
            private var nextDate = start
    
            override fun hasNext(): Boolean = nextDate <= endInclusive
    
            override fun next(): LocalDate = nextDate
                .also { nextDate = nextDate.plusDays(1) }
        }
    }
    

    Now we can just remove the allowed dates from the range of dates like this:

    boundary.toIterable() - allowedDates
    

    With everything put together it would look like this:

    val boundary = LocalDate.now()..LocalDate.now().plusYears(10)
    
    val allowedDates = setOf(
        LocalDate.now().plusDays(3),
        LocalDate.now().plusDays(5),
        LocalDate.now().plusDays(7),
    )
    
    val disabledDates = remember(boundary, allowedDates) {
        boundary.toIterable() - allowedDates
    }
    
    CalendarView(
        useCaseState = rememberUseCaseState(),
        config = CalendarConfig(
            style = CalendarStyle.MONTH,
            disabledDates = disabledDates,
            boundary = boundary,
        ),
        selection = CalendarSelection.Date { newDate ->
            selectedDate.value = newDate
        },
    )
    

    The disabledDates should be remembered because converting the range to a list can be a costly operation when boundary is very large. So this shouldn't be done on every recomposition, only when either boundary or allowedDates changes.