One rollover-following today flow for the view models

This commit is contained in:
2026-10-16 14:07:39 +02:00
parent 16fe2687e8
commit 3ed12f1fee
4 changed files with 32 additions and 30 deletions
@@ -4,10 +4,14 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import de.jeanlucmakiola.floret.android.broadcastTicks import de.jeanlucmakiola.floret.android.broadcastTicks
import de.jeanlucmakiola.floret.android.stateWhileSubscribed
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.flow.transformLatest
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone
@@ -43,3 +47,21 @@ class AndroidTodaySource @Inject constructor(
private const val MINUTE_MILLIS = 60_000L private const val MINUTE_MILLIS = 60_000L
fun currentDate(): LocalDate = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date fun currentDate(): LocalDate = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
/**
* Today as a hot flow seeded with [initial]. [onRollover] runs with the previous
* and the new date each time the day changes while collected, so a screen left
* open over midnight can move off yesterday (#373).
*/
fun Flow<LocalDate>.stateFollowingDays(
scope: CoroutineScope,
initial: LocalDate,
onRollover: (old: LocalDate, new: LocalDate) -> Unit,
): StateFlow<LocalDate> {
var last = initial
return onEach { date ->
val old = last
last = date
if (old != date) onRollover(old, date)
}.stateWhileSubscribed(scope, initial)
}
@@ -10,6 +10,7 @@ import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs
import de.jeanlucmakiola.calendula.data.prefs.firstDayOfWeek import de.jeanlucmakiola.calendula.data.prefs.firstDayOfWeek
import de.jeanlucmakiola.calendula.data.time.TodaySource import de.jeanlucmakiola.calendula.data.time.TodaySource
import de.jeanlucmakiola.calendula.data.time.currentDate import de.jeanlucmakiola.calendula.data.time.currentDate
import de.jeanlucmakiola.calendula.data.time.stateFollowingDays
import de.jeanlucmakiola.calendula.domain.CalendarSource import de.jeanlucmakiola.calendula.domain.CalendarSource
import de.jeanlucmakiola.calendula.domain.EventInstance import de.jeanlucmakiola.calendula.domain.EventInstance
import de.jeanlucmakiola.calendula.domain.FailureReason import de.jeanlucmakiola.calendula.domain.FailureReason
@@ -26,7 +27,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.DayOfWeek import kotlinx.datetime.DayOfWeek
@@ -98,21 +98,14 @@ class AgendaViewModel @Inject constructor(
var lastTopDate: LocalDate? = null var lastTopDate: LocalDate? = null
private set private set
private var lastToday = todayDate
/** /**
* Today, kept current across midnight (#373). An agenda still starting on * Today, kept current across midnight (#373). An agenda still starting on
* yesterday moves on to the new day; one jumped elsewhere stays put. * yesterday moves on to the new day; one jumped elsewhere stays put.
*/ */
private val today: StateFlow<LocalDate> = todaySource.today private val today: StateFlow<LocalDate> =
.onEach { date -> todaySource.today.stateFollowingDays(viewModelScope, todayDate) { old, date ->
val old = lastToday if (_window.value.anchor == old) _window.value = _window.value.copy(anchor = date)
lastToday = date
if (old != date && _window.value.anchor == old) {
_window.value = _window.value.copy(anchor = date)
}
} }
.stateWhileSubscribed(viewModelScope, todayDate)
// A transient, in-view override of the saved agenda range. Held in memory // A transient, in-view override of the saved agenda range. Held in memory
// (not persisted), so it survives view switches and rotation within the // (not persisted), so it survives view switches and rotation within the
@@ -10,6 +10,7 @@ import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs
import de.jeanlucmakiola.calendula.data.prefs.firstDayOfWeek import de.jeanlucmakiola.calendula.data.prefs.firstDayOfWeek
import de.jeanlucmakiola.calendula.data.time.TodaySource import de.jeanlucmakiola.calendula.data.time.TodaySource
import de.jeanlucmakiola.calendula.data.time.currentDate import de.jeanlucmakiola.calendula.data.time.currentDate
import de.jeanlucmakiola.calendula.data.time.stateFollowingDays
import de.jeanlucmakiola.calendula.domain.CalendarSource import de.jeanlucmakiola.calendula.domain.CalendarSource
import de.jeanlucmakiola.calendula.domain.EventInstance import de.jeanlucmakiola.calendula.domain.EventInstance
import de.jeanlucmakiola.calendula.domain.FailureReason import de.jeanlucmakiola.calendula.domain.FailureReason
@@ -90,16 +91,9 @@ class MonthViewModel @Inject constructor(
private val todayDate: LocalDate private val todayDate: LocalDate
get() = currentDate() get() = currentDate()
private var lastToday = todayDate
/** Today, kept current across midnight so a grid left open doesn't mark yesterday (#373). */ /** Today, kept current across midnight so a grid left open doesn't mark yesterday (#373). */
val today: StateFlow<LocalDate> = todaySource.today val today: StateFlow<LocalDate> =
.onEach { date -> todaySource.today.stateFollowingDays(viewModelScope, todayDate, ::followToday)
val old = lastToday
lastToday = date
if (old != date) followToday(old, date)
}
.stateWhileSubscribed(viewModelScope, todayDate)
private val _month = MutableStateFlow(todayDate.yearMonth) private val _month = MutableStateFlow(todayDate.yearMonth)
val month: StateFlow<YearMonth> = _month val month: StateFlow<YearMonth> = _month
@@ -10,6 +10,7 @@ import de.jeanlucmakiola.calendula.data.prefs.resolveFirstDay
import de.jeanlucmakiola.calendula.data.time.TodaySource import de.jeanlucmakiola.calendula.data.time.TodaySource
import de.jeanlucmakiola.calendula.data.time.ZoneSource import de.jeanlucmakiola.calendula.data.time.ZoneSource
import de.jeanlucmakiola.calendula.data.time.currentDate import de.jeanlucmakiola.calendula.data.time.currentDate
import de.jeanlucmakiola.calendula.data.time.stateFollowingDays
import de.jeanlucmakiola.calendula.domain.CalendarSource import de.jeanlucmakiola.calendula.domain.CalendarSource
import de.jeanlucmakiola.calendula.domain.EventInstance import de.jeanlucmakiola.calendula.domain.EventInstance
import de.jeanlucmakiola.calendula.domain.displayPriority import de.jeanlucmakiola.calendula.domain.displayPriority
@@ -32,7 +33,6 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.DayOfWeek import kotlinx.datetime.DayOfWeek
@@ -124,16 +124,9 @@ abstract class PagedTimelineViewModel(
val rule: StateFlow<(LocalDate) -> PageGrid> = val rule: StateFlow<(LocalDate) -> PageGrid> =
gridRule.stateIn(viewModelScope, SharingStarted.Eagerly, initialRule) gridRule.stateIn(viewModelScope, SharingStarted.Eagerly, initialRule)
private var lastToday = todayDate
/** Today, kept current across midnight so a page left open doesn't mark yesterday (#373). */ /** Today, kept current across midnight so a page left open doesn't mark yesterday (#373). */
private val today: StateFlow<LocalDate> = todaySource.today private val today: StateFlow<LocalDate> =
.onEach { date -> todaySource.today.stateFollowingDays(viewModelScope, todayDate, ::followToday)
val old = lastToday
lastToday = date
if (old != date) followToday(old, date)
}
.stateWhileSubscribed(viewModelScope, todayDate)
private val position: StateFlow<PagerPosition> = private val position: StateFlow<PagerPosition> =
combine(_anchor, rule) { anchor, rule -> positionOf(anchor, rule) } combine(_anchor, rule) { anchor, rule -> positionOf(anchor, rule) }