kizitonwose / Calendar

A highly customizable calendar view and compose library for Android and Kotlin Multiplatform.
MIT License
4.78k stars 508 forks source link

rememberFirstVisibleWeekAfterScroll #594

Open akrulec opened 3 weeks ago

akrulec commented 3 weeks ago

https://github.com/kizitonwose/Calendar/blob/64b7adbce7c6c7581895575a359fcc8e5d188416/compose-multiplatform/sample/src/commonMain/kotlin/Utils.kt#L123

I've tried using this function the following way. The goal is that if the user scrolls the week calendar, we automatically select the same day in the week the user scrolled to. Then we update the screen below the week calendar.

val currentMonth = remember { YearMonth.of(currentDate.year, currentDate.month) }
    val startDate = remember { currentMonth.minusMonths(24).atStartOfMonth() }
    val endDate = remember { currentMonth.plusMonths(2).atEndOfMonth() }
    val firstDayOfWeek = remember { DayOfWeek.SUNDAY }

    val state = rememberWeekCalendarState(
        startDate = startDate,
        endDate = endDate,
        firstVisibleWeekDate = currentDate,
        firstDayOfWeek = firstDayOfWeek)
    val visibleWeek = rememberFirstVisibleWeekAfterScroll(state)
    // Remember this through UI changes, since the current date can be changed from a different
    // fragment.
    var firstDayOfTheWeek by rememberSaveable { mutableStateOf(currentDate) }

    LaunchedEffect(visibleWeek) {
        Timber.e("$currentDate visible week: $visibleWeek")
        visibleWeek.days.firstOrNull()?.date?.let { firstDateAfterScroll ->
            if (firstDateAfterScroll != firstDayOfTheWeek) {
                firstDayOfTheWeek = firstDateAfterScroll

                // Week changed, make sure to refresh data.
                visibleWeek.days.lastOrNull()?.date?.let { lastDate ->
                    onCalendarScroll(firstDateAfterScroll, lastDate)
                }

                // Select the same day in the new week, by updating the date.
                val dayOfWeek = (currentDate.dayOfWeek.value % NUM_DAYS_TO_SHOW)
                visibleWeek.days.getOrNull(dayOfWeek)?.date?.let { newDate ->
                    Timber.e("scroll new date: $newDate")
                    onDateSelected(newDate)
                }
            }
        }
    }

However, we also at some point show a full calendar, and when the user select the date there, we use scrollToWeek function to scroll the calendar to that week.

LaunchedEffect(currentDate) {
        // Update the state of the calendar if the date changes (by user clicking on a different
        // date in the week, or month calendar).
        state.scrollToWeek(currentDate)
    }

However, if the user scolls way back ( to like a year ago), and selects a date, the visibleWeek is not correct. Printing the values this is what I get:

selected date: 2023-11-01 
visible week: Week { first = WeekDay(date=2024-11-03, position=RangeDate), last = WeekDay(date=2024-11-09, position=RangeDate) } 

The side effect of this odd state seems to be the following. If the user clicks 'today' button, the visible week seems to be at some random date in 2021:

selected date: 2024-11-03 
visible week: Week { first = WeekDay(date=2021-10-31, position=InDate), last = WeekDay(date=2021-11-06, position=RangeDate) } 

Any idea what could be going on here? Is there something in the state that needs to be updated? I assume that if currentDate is updated, then rememberWeekCalendarState will refresh, but maybe not?

Thank you!

kizitonwose commented 2 weeks ago

How is currentDate being updated? There's an example in the sample app with month/week toggle and this seems to work fine. Can you reproduce it there?

akrulec commented 15 hours ago

How is currentDate being updated? There's an example in the sample app with month/week toggle and this seems to work fine. Can you reproduce it there?

Is there a specific case I should try to reproduce this on? I haven't found any examples that would keep the 'current date' state in the view model. Thanks