diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt index bd1ff95..4adf344 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt @@ -19,25 +19,18 @@ import de.jeanlucmakiola.calendula.ui.agenda.AgendaRange import de.jeanlucmakiola.calendula.ui.common.CalendarView import de.jeanlucmakiola.calendula.widget.agenda.AgendaWidget import de.jeanlucmakiola.calendula.widget.month.MonthWidget -import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.drop -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import javax.inject.Inject -private const val WIDGET_REFRESH_DEBOUNCE_MS = 350L - -@OptIn(FlowPreview::class) @HiltViewModel class SettingsViewModel @Inject constructor( private val prefs: SettingsPrefs, @@ -123,30 +116,11 @@ class SettingsViewModel @Inject constructor( initialValue = SettingsUiState(dynamicColorAvailable = dynamicColorAvailable), ) - init { - // Push widget redraws when their backing prefs change, debounced so a - // rapid flip-and-flip-back settles to one update of the final value - // (two racing updateAll calls can otherwise leave the widget stale). - // drop(1) skips the value present at startup — nothing to refresh yet. - prefs.agendaWidgetRange - .drop(1) - .debounce(WIDGET_REFRESH_DEBOUNCE_MS) - .distinctUntilChanged() - .onEach { AgendaWidget().updateAll(appContext) } - .launchIn(viewModelScope) - - // Both widgets depend on the week start (month weekday header; the - // agenda widget's "this week" range). - prefs.weekStart - .drop(1) - .debounce(WIDGET_REFRESH_DEBOUNCE_MS) - .distinctUntilChanged() - .onEach { - AgendaWidget().updateAll(appContext) - MonthWidget().updateAll(appContext) - } - .launchIn(viewModelScope) - } + // Serialises widget redraws so a rapid flip-and-flip-back can't run two + // updateAll calls at once — concurrent calls coalesce around a stale read + // and can leave the widget on the intermediate value. Held sequentially, + // the last call reads the final committed pref and renders it. + private val widgetRefreshMutex = Mutex() private data class ReminderDefaults( val allowColor: Boolean, @@ -179,7 +153,15 @@ class SettingsViewModel @Inject constructor( } fun setWeekStart(pref: WeekStartPref) { - viewModelScope.launch { prefs.setWeekStart(pref) } + viewModelScope.launch { + prefs.setWeekStart(pref) + // Both widgets depend on the week start (month weekday header; the + // agenda widget's "this week" range). + widgetRefreshMutex.withLock { + AgendaWidget().updateAll(appContext) + MonthWidget().updateAll(appContext) + } + } } fun setAgendaScreenRange(range: AgendaRange) { @@ -187,7 +169,10 @@ class SettingsViewModel @Inject constructor( } fun setAgendaWidgetRange(range: AgendaRange) { - viewModelScope.launch { prefs.setAgendaWidgetRange(range) } + viewModelScope.launch { + prefs.setAgendaWidgetRange(range) + widgetRefreshMutex.withLock { AgendaWidget().updateAll(appContext) } + } } fun setAgendaShowRangeBar(enabled: Boolean) {