Move the prefs classes onto floret's PrefStore
This commit is contained in:
@@ -176,6 +176,7 @@ dependencies {
|
||||
implementation("de.jeanlucmakiola.floret:core-locale")
|
||||
implementation("de.jeanlucmakiola.floret:core-crash")
|
||||
implementation("de.jeanlucmakiola.floret:core-di")
|
||||
implementation("de.jeanlucmakiola.floret:core-prefs")
|
||||
implementation("de.jeanlucmakiola.floret:core-reminders")
|
||||
implementation("de.jeanlucmakiola.floret:core-android")
|
||||
implementation("de.jeanlucmakiola.floret:identity")
|
||||
|
||||
@@ -29,7 +29,9 @@ import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import de.jeanlucmakiola.calendula.data.calendar.CalendarRepository
|
||||
import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs
|
||||
import de.jeanlucmakiola.calendula.data.prefs.ThemeMode
|
||||
import de.jeanlucmakiola.floret.prefs.ThemeMode
|
||||
import de.jeanlucmakiola.floret.prefs.isDark
|
||||
import de.jeanlucmakiola.floret.prefs.isPureBlack
|
||||
import de.jeanlucmakiola.calendula.data.prefs.is24Hour
|
||||
import de.jeanlucmakiola.calendula.domain.EventForm
|
||||
import de.jeanlucmakiola.calendula.domain.buildInsertEventForm
|
||||
@@ -137,11 +139,7 @@ class MainActivity : AppCompatActivity() {
|
||||
// backdrop and bar icons instead of the night qualifier's (#181).
|
||||
val cachedTheme = WindowThemeCache.read(this)
|
||||
cachedTheme?.windowBackgroundRes()?.let { window.setBackgroundDrawableResource(it) }
|
||||
systemBarsDark = when (cachedTheme) {
|
||||
null, ThemeMode.SYSTEM -> systemDark
|
||||
ThemeMode.LIGHT -> false
|
||||
ThemeMode.DARK, ThemeMode.BLACK -> true
|
||||
}
|
||||
systemBarsDark = (cachedTheme ?: ThemeMode.SYSTEM).isDark(systemDark)
|
||||
applyEdgeToEdge()
|
||||
lifecycleScope.launch {
|
||||
settingsPrefs.themeMode.collect { mode ->
|
||||
@@ -180,11 +178,7 @@ class MainActivity : AppCompatActivity() {
|
||||
// and the Settings screen, so a theme change applies app-wide at once.
|
||||
val settingsViewModel: SettingsViewModel = hiltViewModel()
|
||||
val settings by settingsViewModel.state.collectAsStateWithLifecycle()
|
||||
val darkTheme = when (settings.themeMode) {
|
||||
ThemeMode.SYSTEM -> isSystemInDarkTheme()
|
||||
ThemeMode.LIGHT -> false
|
||||
ThemeMode.DARK, ThemeMode.BLACK -> true
|
||||
}
|
||||
val darkTheme = settings.themeMode.isDark(isSystemInDarkTheme())
|
||||
// onCreate can only see the night resource qualifier, not the in-app
|
||||
// override — re-apply from the resolved theme so the bar icons follow
|
||||
// the app's light/dark choice.
|
||||
@@ -223,7 +217,7 @@ class MainActivity : AppCompatActivity() {
|
||||
CalendulaTheme(
|
||||
darkTheme = darkTheme,
|
||||
dynamicColor = settings.dynamicColor,
|
||||
pureBlack = settings.themeMode == ThemeMode.BLACK,
|
||||
pureBlack = settings.themeMode.isPureBlack,
|
||||
typography = typography,
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
package de.jeanlucmakiola.calendula.data.prefs
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.MutablePreferences
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.longPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import de.jeanlucmakiola.floret.prefs.Pref
|
||||
import de.jeanlucmakiola.floret.prefs.PrefStore
|
||||
import de.jeanlucmakiola.floret.prefs.nullableBooleanPref
|
||||
import de.jeanlucmakiola.floret.prefs.nullableLongPref
|
||||
import de.jeanlucmakiola.floret.prefs.nullableStringPref
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -26,18 +24,13 @@ import javax.inject.Singleton
|
||||
*/
|
||||
@Singleton
|
||||
class CalendarPrefs @Inject constructor(
|
||||
private val store: DataStore<Preferences>,
|
||||
store: DataStore<Preferences>,
|
||||
) {
|
||||
private val prefs = PrefStore(store)
|
||||
|
||||
// Both id sets are deduped: the store is shared with SettingsPrefs, so any
|
||||
// unrelated write would otherwise re-emit an identical set.
|
||||
val hiddenCalendarIds: Flow<Set<Long>> = store.data
|
||||
.map { prefs -> prefs[HIDDEN_IDS_KEY].parseIds() }
|
||||
.distinctUntilChanged()
|
||||
val hiddenCalendarIds: Flow<Set<Long>> = prefs.flow(HIDDEN_IDS)
|
||||
|
||||
suspend fun setHiddenCalendarIds(ids: Set<Long>) {
|
||||
store.edit { prefs -> prefs.writeIds(HIDDEN_IDS_KEY, ids) }
|
||||
}
|
||||
suspend fun setHiddenCalendarIds(ids: Set<Long>) = prefs.set(HIDDEN_IDS, ids)
|
||||
|
||||
/**
|
||||
* Switch-offs the provider does not know about yet, because writing
|
||||
@@ -48,24 +41,18 @@ class CalendarPrefs @Inject constructor(
|
||||
* second visibility model: `CalendarVisibilityReconciler` drains it entry by
|
||||
* entry as soon as the app may write, and nothing adds to it while it may.
|
||||
*/
|
||||
val pendingDisabledCalendarIds: Flow<Set<Long>> = store.data
|
||||
.map { prefs -> prefs[DISABLED_IDS_KEY].parseIds() }
|
||||
.distinctUntilChanged()
|
||||
val pendingDisabledCalendarIds: Flow<Set<Long>> = prefs.flow(DISABLED_IDS)
|
||||
|
||||
suspend fun addPendingDisabledCalendarIds(ids: Collection<Long>) =
|
||||
editPendingDisabled { it + ids }
|
||||
suspend fun addPendingDisabledCalendarIds(ids: Collection<Long>) {
|
||||
prefs.update(DISABLED_IDS) { it + ids }
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop [ids] from the pending set, one at a time as the reconciler flushes
|
||||
* them, so a run that fails part-way never re-applies what already landed.
|
||||
*/
|
||||
suspend fun removePendingDisabledCalendarIds(ids: Collection<Long>) =
|
||||
editPendingDisabled { it - ids.toSet() }
|
||||
|
||||
private suspend fun editPendingDisabled(transform: (Set<Long>) -> Set<Long>) {
|
||||
store.edit { prefs ->
|
||||
prefs.writeIds(DISABLED_IDS_KEY, transform(prefs[DISABLED_IDS_KEY].parseIds()))
|
||||
}
|
||||
suspend fun removePendingDisabledCalendarIds(ids: Collection<Long>) {
|
||||
prefs.update(DISABLED_IDS) { it - ids.toSet() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,39 +60,28 @@ class CalendarPrefs @Inject constructor(
|
||||
* owed. Null until the reconciler has evaluated it (which needs the calendar
|
||||
* permission), false once it has been shown or was never needed.
|
||||
*/
|
||||
val visibilityNoticePending: Flow<Boolean?> = store.data.map { prefs ->
|
||||
prefs[VISIBILITY_NOTICE_KEY]
|
||||
}
|
||||
val visibilityNoticePending: Flow<Boolean?> = prefs.flow(VISIBILITY_NOTICE)
|
||||
|
||||
suspend fun setVisibilityNoticePending(pending: Boolean) {
|
||||
store.edit { prefs -> prefs[VISIBILITY_NOTICE_KEY] = pending }
|
||||
}
|
||||
suspend fun setVisibilityNoticePending(pending: Boolean) = prefs.set(VISIBILITY_NOTICE, pending)
|
||||
|
||||
/**
|
||||
* The calendar the user last created an event in; preselected in the
|
||||
* event form. Null until the first event is created.
|
||||
*/
|
||||
val lastUsedCalendarId: Flow<Long?> = store.data.map { prefs ->
|
||||
prefs[LAST_USED_CALENDAR_KEY]
|
||||
}
|
||||
val lastUsedCalendarId: Flow<Long?> = prefs.flow(LAST_USED_CALENDAR)
|
||||
|
||||
suspend fun setLastUsedCalendarId(id: Long) {
|
||||
store.edit { prefs -> prefs[LAST_USED_CALENDAR_KEY] = id }
|
||||
}
|
||||
suspend fun setLastUsedCalendarId(id: Long) = prefs.set(LAST_USED_CALENDAR, id)
|
||||
|
||||
companion object {
|
||||
internal val HIDDEN_IDS_KEY = stringPreferencesKey("hidden_calendar_ids")
|
||||
internal val DISABLED_IDS_KEY = stringPreferencesKey("disabled_calendar_ids")
|
||||
internal val VISIBILITY_NOTICE_KEY = booleanPreferencesKey("visibility_notice_pending")
|
||||
internal val LAST_USED_CALENDAR_KEY = longPreferencesKey("last_used_calendar_id")
|
||||
private companion object {
|
||||
val HIDDEN_IDS = idSetPref("hidden_calendar_ids")
|
||||
val DISABLED_IDS = idSetPref("disabled_calendar_ids")
|
||||
val VISIBILITY_NOTICE = nullableBooleanPref("visibility_notice_pending")
|
||||
val LAST_USED_CALENDAR = nullableLongPref("last_used_calendar_id")
|
||||
}
|
||||
}
|
||||
|
||||
private fun String?.parseIds(): Set<Long> = orEmpty()
|
||||
.split(',')
|
||||
.mapNotNull { it.trim().toLongOrNull() }
|
||||
.toSet()
|
||||
|
||||
private fun MutablePreferences.writeIds(key: Preferences.Key<String>, ids: Set<Long>) {
|
||||
if (ids.isEmpty()) remove(key) else set(key, ids.sorted().joinToString(","))
|
||||
}
|
||||
/** A comma-joined id set; non-numeric tokens are dropped, and an empty set removes the key. */
|
||||
private fun idSetPref(name: String): Pref<Set<Long>> = nullableStringPref(name).map(
|
||||
{ stored -> stored.orEmpty().split(',').mapNotNull { it.trim().toLongOrNull() }.toSet() },
|
||||
{ ids -> if (ids.isEmpty()) null else ids.sorted().joinToString(",") },
|
||||
)
|
||||
|
||||
@@ -2,10 +2,8 @@ package de.jeanlucmakiola.calendula.data.prefs
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.longPreferencesKey
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import de.jeanlucmakiola.floret.prefs.PrefStore
|
||||
import de.jeanlucmakiola.floret.prefs.nullableLongPref
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -20,17 +18,16 @@ import javax.inject.Singleton
|
||||
*/
|
||||
@Singleton
|
||||
class ReminderStatePrefs @Inject constructor(
|
||||
private val store: DataStore<Preferences>,
|
||||
store: DataStore<Preferences>,
|
||||
) {
|
||||
private val prefs = PrefStore(store)
|
||||
|
||||
/** The watermark, or `null` before the first scan has ever run. */
|
||||
suspend fun lastScanMillis(): Long? = store.data.map { it[LAST_SCAN_KEY] }.first()
|
||||
suspend fun lastScanMillis(): Long? = prefs.get(LAST_SCAN)
|
||||
|
||||
suspend fun setLastScanMillis(millis: Long) {
|
||||
store.edit { prefs -> prefs[LAST_SCAN_KEY] = millis }
|
||||
}
|
||||
suspend fun setLastScanMillis(millis: Long) = prefs.set(LAST_SCAN, millis)
|
||||
|
||||
private companion object {
|
||||
val LAST_SCAN_KEY = longPreferencesKey("reminder_last_scan_millis")
|
||||
val LAST_SCAN = nullableLongPref("reminder_last_scan_millis")
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+32
-88
@@ -166,15 +166,6 @@ class OnboardingViewModel @Inject constructor(
|
||||
hiddenTouched,
|
||||
) { hidden, touched -> if (touched) null else hidden }
|
||||
|
||||
/** Both calendar steps' inputs: what each applies to, and whether it is answered. */
|
||||
private val calendarsFlags: Flow<CalendarsFlags> = combine(
|
||||
calendarsApplies,
|
||||
prefs.onboardingCalendarsDone,
|
||||
hiddenApplies,
|
||||
prefs.onboardingHiddenCalendarsDone,
|
||||
::CalendarsFlags,
|
||||
)
|
||||
|
||||
/**
|
||||
* The calendars the hidden-calendars step lists, so the user can judge which
|
||||
* to switch on rather than take the app's word that there are some. Live: a
|
||||
@@ -188,47 +179,35 @@ class OnboardingViewModel @Inject constructor(
|
||||
initialValue = emptyList(),
|
||||
)
|
||||
|
||||
private val flags: Flow<OnboardingFlags> = combine(
|
||||
prefs.reminderOnboardingDone,
|
||||
prefs.onboardingWizardArmed,
|
||||
prefs.onboardingBackupDone,
|
||||
prefs.onboardingViewDone,
|
||||
prefs.onboardingMonthStyleDone,
|
||||
::OnboardingFlags,
|
||||
)
|
||||
|
||||
/** The closing steps' own flags, and whether the notice applies at all. */
|
||||
private val closingFlags: Flow<ClosingFlags> = combine(
|
||||
calendarPrefs.visibilityNoticePending.map { it == true },
|
||||
prefs.onboardingVisibilityDone,
|
||||
prefs.onboardingDoneShown,
|
||||
::ClosingFlags,
|
||||
)
|
||||
/** What the two calendar steps apply to (#287). */
|
||||
private val calendarStepsApply: Flow<Pair<Boolean?, Boolean?>> =
|
||||
combine(calendarsApplies, hiddenApplies, ::Pair)
|
||||
|
||||
val plan: StateFlow<OnboardingPlan?> =
|
||||
combine(
|
||||
hasPermission,
|
||||
flags,
|
||||
closingFlags,
|
||||
prefs.onboardingProgress,
|
||||
calendarPrefs.visibilityNoticePending.map { it == true },
|
||||
backupApplies,
|
||||
calendarsFlags,
|
||||
) { granted, stored, closing, backup, calendars ->
|
||||
calendarStepsApply,
|
||||
) { granted, progress, visibilityArmed, backup, (calendars, hidden) ->
|
||||
val answered = progress.answered
|
||||
granted?.let {
|
||||
onboardingPlan(
|
||||
hasPermission = it,
|
||||
remindersDone = stored.remindersDone,
|
||||
wizardArmed = stored.wizardArmed,
|
||||
backupDone = stored.backupDone,
|
||||
viewDone = stored.viewDone,
|
||||
monthStyleDone = stored.monthStyleDone,
|
||||
remindersDone = OnboardingStep.Reminders in answered,
|
||||
wizardArmed = progress.wizardArmed,
|
||||
backupDone = OnboardingStep.Backup in answered,
|
||||
viewDone = OnboardingStep.View in answered,
|
||||
monthStyleDone = OnboardingStep.MonthStyle in answered,
|
||||
backupApplies = backup,
|
||||
hiddenDone = calendars.hiddenDone,
|
||||
hiddenApplies = calendars.hiddenApplies,
|
||||
calendarsDone = calendars.done,
|
||||
calendarsApplies = calendars.applies,
|
||||
visibilityArmed = closing.visibilityArmed,
|
||||
visibilityDone = closing.visibilityDone,
|
||||
doneShown = closing.doneShown,
|
||||
hiddenDone = OnboardingStep.HiddenCalendars in answered,
|
||||
hiddenApplies = hidden,
|
||||
calendarsDone = OnboardingStep.Calendars in answered,
|
||||
calendarsApplies = calendars,
|
||||
visibilityArmed = visibilityArmed,
|
||||
visibilityDone = OnboardingStep.Visibility in answered,
|
||||
doneShown = OnboardingStep.Done in answered,
|
||||
)
|
||||
}
|
||||
}.stateWhileSubscribed(
|
||||
@@ -258,7 +237,7 @@ class OnboardingViewModel @Inject constructor(
|
||||
* to a restarted process, leaving them the reminder step alone.
|
||||
*/
|
||||
private suspend fun armWizardIfFresh() {
|
||||
if (!prefs.reminderOnboardingDone.first()) prefs.armOnboardingWizard()
|
||||
prefs.armOnboardingWizard()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,7 +253,7 @@ class OnboardingViewModel @Inject constructor(
|
||||
fun finishReminders(remindersEnabled: Boolean) {
|
||||
viewModelScope.launch {
|
||||
prefs.setRemindersEnabled(remindersEnabled)
|
||||
prefs.setReminderOnboardingDone()
|
||||
prefs.setOnboardingDone(OnboardingStep.Reminders)
|
||||
// Nothing else re-arms the scan: turning reminders off cancels the
|
||||
// alarm (#75).
|
||||
scanner.scan()
|
||||
@@ -288,24 +267,13 @@ class OnboardingViewModel @Inject constructor(
|
||||
* returned to, which [OnboardingPlan.canGoBack] already refuses.
|
||||
*/
|
||||
fun goBack() {
|
||||
viewModelScope.launch {
|
||||
when (plan.value?.previous) {
|
||||
OnboardingStep.HiddenCalendars -> prefs.setOnboardingHiddenCalendarsDone(false)
|
||||
OnboardingStep.Calendars -> prefs.setOnboardingCalendarsDone(false)
|
||||
OnboardingStep.Reminders -> prefs.setReminderOnboardingDone(false)
|
||||
OnboardingStep.Backup -> prefs.setOnboardingBackupDone(false)
|
||||
OnboardingStep.View -> prefs.setOnboardingViewDone(false)
|
||||
OnboardingStep.MonthStyle -> prefs.setOnboardingMonthStyleDone(false)
|
||||
OnboardingStep.Visibility -> prefs.setOnboardingVisibilityDone(false)
|
||||
OnboardingStep.Done -> prefs.setOnboardingDoneShown(false)
|
||||
OnboardingStep.Permission, null -> Unit
|
||||
}
|
||||
}
|
||||
val previous = plan.value?.previous ?: return
|
||||
viewModelScope.launch { prefs.setOnboardingDone(previous, done = false) }
|
||||
}
|
||||
|
||||
/** Close the backup step without setting anything up. */
|
||||
fun skipBackup() {
|
||||
viewModelScope.launch { prefs.setOnboardingBackupDone() }
|
||||
viewModelScope.launch { prefs.setOnboardingDone(OnboardingStep.Backup) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -337,7 +305,7 @@ class OnboardingViewModel @Inject constructor(
|
||||
/** Close the calendar step once its confirmation has been read. */
|
||||
fun finishCalendars() {
|
||||
viewModelScope.launch {
|
||||
prefs.setOnboardingCalendarsDone()
|
||||
prefs.setOnboardingDone(OnboardingStep.Calendars)
|
||||
_creation.value = null
|
||||
}
|
||||
}
|
||||
@@ -352,7 +320,7 @@ class OnboardingViewModel @Inject constructor(
|
||||
* choice, and so is going off to set up an account and coming back.
|
||||
*/
|
||||
fun skipCalendars() {
|
||||
viewModelScope.launch { prefs.setOnboardingCalendarsDone() }
|
||||
viewModelScope.launch { prefs.setOnboardingDone(OnboardingStep.Calendars) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -378,7 +346,7 @@ class OnboardingViewModel @Inject constructor(
|
||||
)
|
||||
// Immediate feedback: the first backup lands while onboarding runs.
|
||||
BackupScheduler.runNow(context)
|
||||
prefs.setOnboardingBackupDone()
|
||||
prefs.setOnboardingDone(OnboardingStep.Backup)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,11 +360,11 @@ class OnboardingViewModel @Inject constructor(
|
||||
|
||||
/** Close a picker step — the choice itself is saved as it is made. */
|
||||
fun finishView() {
|
||||
viewModelScope.launch { prefs.setOnboardingViewDone() }
|
||||
viewModelScope.launch { prefs.setOnboardingDone(OnboardingStep.View) }
|
||||
}
|
||||
|
||||
fun finishMonthStyle() {
|
||||
viewModelScope.launch { prefs.setOnboardingMonthStyleDone() }
|
||||
viewModelScope.launch { prefs.setOnboardingDone(OnboardingStep.MonthStyle) }
|
||||
}
|
||||
|
||||
/** Switch one listed calendar on or off, from the hidden-calendars step (#287). */
|
||||
@@ -433,7 +401,7 @@ class OnboardingViewModel @Inject constructor(
|
||||
/** Close the hidden-calendars step, switched on or left as they were (#287). */
|
||||
fun finishHiddenCalendars() {
|
||||
viewModelScope.launch {
|
||||
prefs.setOnboardingHiddenCalendarsDone()
|
||||
prefs.setOnboardingDone(OnboardingStep.HiddenCalendars)
|
||||
// The error belongs to the attempt, not to the step: stepping back
|
||||
// into it later must not find a refusal nothing has asked for yet.
|
||||
_hiddenWriteFailed.value = false
|
||||
@@ -448,7 +416,7 @@ class OnboardingViewModel @Inject constructor(
|
||||
/** Acknowledge the calendar-visibility notice (#75). */
|
||||
fun finishVisibility() {
|
||||
viewModelScope.launch {
|
||||
prefs.setOnboardingVisibilityDone()
|
||||
prefs.setOnboardingDone(OnboardingStep.Visibility)
|
||||
// Retiring the notice drops its step, so it waits until nothing
|
||||
// follows — the count must not fall on the way to a later screen.
|
||||
if (plan.value?.steps?.last() == OnboardingStep.Visibility) retireVisibilityNotice()
|
||||
@@ -485,36 +453,12 @@ class OnboardingViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/** The stored answers the plan is derived from. */
|
||||
private data class OnboardingFlags(
|
||||
val remindersDone: Boolean,
|
||||
val wizardArmed: Boolean,
|
||||
val backupDone: Boolean,
|
||||
val viewDone: Boolean,
|
||||
val monthStyleDone: Boolean,
|
||||
)
|
||||
|
||||
/** How the calendar step's create attempt ended (#287). */
|
||||
sealed interface CalendarCreation {
|
||||
data class Created(val name: String) : CalendarCreation
|
||||
data object Failed : CalendarCreation
|
||||
}
|
||||
|
||||
/** Both calendar steps' inputs (#287). */
|
||||
private data class CalendarsFlags(
|
||||
val applies: Boolean?,
|
||||
val done: Boolean,
|
||||
val hiddenApplies: Boolean?,
|
||||
val hiddenDone: Boolean,
|
||||
)
|
||||
|
||||
/** The tail of the flow: the visibility notice and the closing screen. */
|
||||
private data class ClosingFlags(
|
||||
val visibilityArmed: Boolean,
|
||||
val visibilityDone: Boolean,
|
||||
val doneShown: Boolean,
|
||||
)
|
||||
|
||||
/** What the view step starts from, and renders its previews with. */
|
||||
data class ViewChoice(
|
||||
val defaultView: CalendarView,
|
||||
|
||||
@@ -49,7 +49,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import de.jeanlucmakiola.calendula.R
|
||||
import de.jeanlucmakiola.calendula.data.appname.LauncherName
|
||||
import de.jeanlucmakiola.calendula.data.prefs.ThemeMode
|
||||
import de.jeanlucmakiola.floret.prefs.ThemeMode
|
||||
import de.jeanlucmakiola.calendula.domain.FontRole
|
||||
import de.jeanlucmakiola.calendula.ui.theme.BundledFont
|
||||
import de.jeanlucmakiola.calendula.ui.theme.FONT_CUSTOM_TOKEN
|
||||
|
||||
@@ -3,7 +3,7 @@ package de.jeanlucmakiola.calendula.ui.settings
|
||||
import de.jeanlucmakiola.floret.reminders.ReminderOverride
|
||||
import de.jeanlucmakiola.calendula.data.prefs.PastEventDisplay
|
||||
import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs
|
||||
import de.jeanlucmakiola.calendula.data.prefs.ThemeMode
|
||||
import de.jeanlucmakiola.floret.prefs.ThemeMode
|
||||
import de.jeanlucmakiola.calendula.data.prefs.TimeFormatPref
|
||||
import de.jeanlucmakiola.calendula.data.prefs.WeekStartPref
|
||||
import de.jeanlucmakiola.calendula.domain.CalendarSource
|
||||
|
||||
@@ -25,7 +25,7 @@ import de.jeanlucmakiola.floret.reminders.reminderOverrideFor
|
||||
import de.jeanlucmakiola.calendula.data.prefs.PastEventDisplay
|
||||
import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs
|
||||
import de.jeanlucmakiola.calendula.data.prefs.SpecialDatesStalledReason
|
||||
import de.jeanlucmakiola.calendula.data.prefs.ThemeMode
|
||||
import de.jeanlucmakiola.floret.prefs.ThemeMode
|
||||
import de.jeanlucmakiola.calendula.data.prefs.TimeFormatPref
|
||||
import de.jeanlucmakiola.calendula.data.prefs.WeekStartPref
|
||||
import de.jeanlucmakiola.calendula.data.reminders.ReminderScanner
|
||||
|
||||
@@ -4,7 +4,7 @@ import android.content.Context
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.core.content.edit
|
||||
import de.jeanlucmakiola.calendula.R
|
||||
import de.jeanlucmakiola.calendula.data.prefs.ThemeMode
|
||||
import de.jeanlucmakiola.floret.prefs.ThemeMode
|
||||
|
||||
/**
|
||||
* A synchronous copy of the theme choice, for the window backdrop drawn before
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.jeanlucmakiola.calendula.data.prefs
|
||||
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import de.jeanlucmakiola.calendula.testDataStore
|
||||
import de.jeanlucmakiola.calendula.testPrefs
|
||||
@@ -39,7 +40,7 @@ class CalendarPrefsTest {
|
||||
val prefs = CalendarPrefs(store)
|
||||
store.updateData { p ->
|
||||
val mutable = p.toMutablePreferences()
|
||||
mutable[CalendarPrefs.HIDDEN_IDS_KEY] = "1,abc,3"
|
||||
mutable[stringPreferencesKey("hidden_calendar_ids")] = "1,abc,3"
|
||||
mutable
|
||||
}
|
||||
assertThat(prefs.hiddenCalendarIds.first()).isEqualTo(setOf(1L, 3L))
|
||||
@@ -54,7 +55,7 @@ class CalendarPrefsTest {
|
||||
val store = testDataStore(tempDir, "test_prefs")
|
||||
val prefs = CalendarPrefs(store)
|
||||
store.updateData { p ->
|
||||
p.toMutablePreferences().apply { this[CalendarPrefs.DISABLED_IDS_KEY] = "2,9" }
|
||||
p.toMutablePreferences().apply { this[stringPreferencesKey("disabled_calendar_ids")] = "2,9" }
|
||||
}
|
||||
assertThat(prefs.pendingDisabledCalendarIds.first()).isEqualTo(setOf(2L, 9L))
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import de.jeanlucmakiola.calendula.testSettings
|
||||
import de.jeanlucmakiola.floret.reminders.ReminderOverride
|
||||
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import de.jeanlucmakiola.calendula.domain.EventFormField
|
||||
import de.jeanlucmakiola.calendula.domain.FontRole
|
||||
@@ -16,6 +17,8 @@ import de.jeanlucmakiola.calendula.ui.common.QuickSwitchConfig
|
||||
import de.jeanlucmakiola.calendula.ui.common.TimelineHours
|
||||
import de.jeanlucmakiola.calendula.ui.common.TimelineScale
|
||||
import de.jeanlucmakiola.calendula.ui.month.MonthViewStyle
|
||||
import de.jeanlucmakiola.calendula.ui.onboarding.OnboardingStep
|
||||
import de.jeanlucmakiola.floret.prefs.ThemeMode
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.datetime.DayOfWeek
|
||||
@@ -68,7 +71,7 @@ class SettingsPrefsTest {
|
||||
val prefs = SettingsPrefs(store)
|
||||
store.updateData { p ->
|
||||
val m = p.toMutablePreferences()
|
||||
m[SettingsPrefs.WEEK_START_KEY] = "MONDAY"
|
||||
m[stringPreferencesKey("week_start")] = "MONDAY"
|
||||
m
|
||||
}
|
||||
assertThat(prefs.weekStart.first()).isEqualTo(WeekStartPref.Day(DayOfWeek.MONDAY))
|
||||
@@ -80,7 +83,7 @@ class SettingsPrefsTest {
|
||||
val prefs = SettingsPrefs(store)
|
||||
store.updateData { p ->
|
||||
val m = p.toMutablePreferences()
|
||||
m[SettingsPrefs.WEEK_START_KEY] = "FUNDAY"
|
||||
m[stringPreferencesKey("week_start")] = "FUNDAY"
|
||||
m
|
||||
}
|
||||
assertThat(prefs.weekStart.first()).isEqualTo(WeekStartPref.Auto)
|
||||
@@ -191,7 +194,7 @@ class SettingsPrefsTest {
|
||||
val prefs = SettingsPrefs(store)
|
||||
store.updateData { p ->
|
||||
val m = p.toMutablePreferences()
|
||||
m[SettingsPrefs.MONTH_VIEW_STYLE_KEY] = "Carousel"
|
||||
m[stringPreferencesKey("month_view_style")] = "Carousel"
|
||||
m
|
||||
}
|
||||
assertThat(prefs.monthViewStyle.first()).isEqualTo(MonthViewStyle.Paged)
|
||||
@@ -203,7 +206,7 @@ class SettingsPrefsTest {
|
||||
val prefs = SettingsPrefs(store)
|
||||
store.updateData { p ->
|
||||
val m = p.toMutablePreferences()
|
||||
m[SettingsPrefs.THEME_MODE_KEY] = "PLAID"
|
||||
m[stringPreferencesKey("theme_mode")] = "PLAID"
|
||||
m
|
||||
}
|
||||
assertThat(prefs.themeMode.first()).isEqualTo(ThemeMode.SYSTEM)
|
||||
@@ -269,7 +272,7 @@ class SettingsPrefsTest {
|
||||
// Only Day (disabled) and Week stored, plus a bogus name; Month & Agenda absent.
|
||||
store.updateData { p ->
|
||||
val m = p.toMutablePreferences()
|
||||
m[SettingsPrefs.QUICK_SWITCH_VIEWS_KEY] = "!Day,Week,Hologram"
|
||||
m[stringPreferencesKey("quick_switch_views")] = "!Day,Week,Hologram"
|
||||
m
|
||||
}
|
||||
val config = prefs.quickSwitchConfig.first()
|
||||
@@ -322,7 +325,7 @@ class SettingsPrefsTest {
|
||||
val prefs = SettingsPrefs(store)
|
||||
store.updateData { p ->
|
||||
val m = p.toMutablePreferences()
|
||||
m[SettingsPrefs.DRAWER_VIEW_ORDER_KEY] = "Agenda,Nope,Week"
|
||||
m[stringPreferencesKey("drawer_view_order")] = "Agenda,Nope,Week"
|
||||
m
|
||||
}
|
||||
assertThat(prefs.drawerViewOrder.first()).containsExactly(
|
||||
@@ -337,7 +340,7 @@ class SettingsPrefsTest {
|
||||
val prefs = SettingsPrefs(store)
|
||||
store.updateData { p ->
|
||||
val m = p.toMutablePreferences()
|
||||
m[SettingsPrefs.FORM_FIELDS_KEY] = "Location,Hologram"
|
||||
m[stringPreferencesKey("event_form_default_fields")] = "Location,Hologram"
|
||||
m
|
||||
}
|
||||
assertThat(prefs.defaultFormFields.first()).containsExactly(EventFormField.Location)
|
||||
@@ -347,7 +350,7 @@ class SettingsPrefsTest {
|
||||
fun `reminders default to enabled, onboarding to not done`(@TempDir tempDir: Path) = runTest {
|
||||
val prefs = testSettings(tempDir)
|
||||
assertThat(prefs.remindersEnabled.first()).isTrue()
|
||||
assertThat(prefs.reminderOnboardingDone.first()).isFalse()
|
||||
assertThat(prefs.onboardingProgress.first().answered).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -360,24 +363,35 @@ class SettingsPrefsTest {
|
||||
@Test
|
||||
fun `reminder onboarding completes one-way`(@TempDir tempDir: Path) = runTest {
|
||||
val prefs = testSettings(tempDir)
|
||||
prefs.setReminderOnboardingDone()
|
||||
assertThat(prefs.reminderOnboardingDone.first()).isTrue()
|
||||
prefs.setOnboardingDone(OnboardingStep.Reminders)
|
||||
assertThat(prefs.onboardingProgress.first().answered).containsExactly(OnboardingStep.Reminders)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a step's answer can be cleared again`(@TempDir tempDir: Path) = runTest {
|
||||
val prefs = testSettings(tempDir)
|
||||
prefs.setOnboardingDone(OnboardingStep.Backup)
|
||||
prefs.setOnboardingDone(OnboardingStep.View)
|
||||
|
||||
prefs.setOnboardingDone(OnboardingStep.Backup, done = false)
|
||||
|
||||
assertThat(prefs.onboardingProgress.first().answered).containsExactly(OnboardingStep.View)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the wizard arms on a first run`(@TempDir tempDir: Path) = runTest {
|
||||
val prefs = testSettings(tempDir)
|
||||
prefs.armOnboardingWizard()
|
||||
assertThat(prefs.onboardingWizardArmed.first()).isTrue()
|
||||
assertThat(prefs.onboardingProgress.first().wizardArmed).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the wizard does not arm once the reminder step is answered`(@TempDir tempDir: Path) = runTest {
|
||||
// An existing install re-granting the permission is not re-onboarded.
|
||||
val prefs = testSettings(tempDir)
|
||||
prefs.setReminderOnboardingDone()
|
||||
prefs.setOnboardingDone(OnboardingStep.Reminders)
|
||||
prefs.armOnboardingWizard()
|
||||
assertThat(prefs.onboardingWizardArmed.first()).isFalse()
|
||||
assertThat(prefs.onboardingProgress.first().wizardArmed).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -386,8 +400,8 @@ class SettingsPrefsTest {
|
||||
prefs.armOnboardingWizard()
|
||||
|
||||
prefs.finishOnboardingWizard()
|
||||
assertThat(prefs.onboardingDoneShown.first()).isTrue()
|
||||
assertThat(prefs.onboardingWizardArmed.first()).isFalse()
|
||||
assertThat(prefs.onboardingProgress.first().answered).containsExactly(OnboardingStep.Done)
|
||||
assertThat(prefs.onboardingProgress.first().wizardArmed).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -415,7 +429,7 @@ class SettingsPrefsTest {
|
||||
// The pre-multi-reminder format stored a bare integer string.
|
||||
store.updateData { p ->
|
||||
val m = p.toMutablePreferences()
|
||||
m[SettingsPrefs.DEFAULT_REMINDER_KEY] = "30"
|
||||
m[stringPreferencesKey("default_reminder_minutes")] = "30"
|
||||
m
|
||||
}
|
||||
assertThat(prefs.defaultReminderMinutes.first()).containsExactly(30)
|
||||
@@ -427,7 +441,7 @@ class SettingsPrefsTest {
|
||||
val prefs = SettingsPrefs(store)
|
||||
store.updateData { p ->
|
||||
val m = p.toMutablePreferences()
|
||||
m[SettingsPrefs.DEFAULT_REMINDER_KEY] = "soon"
|
||||
m[stringPreferencesKey("default_reminder_minutes")] = "soon"
|
||||
m
|
||||
}
|
||||
assertThat(prefs.defaultReminderMinutes.first()).isEmpty()
|
||||
@@ -464,7 +478,7 @@ class SettingsPrefsTest {
|
||||
// reminders. Partially-valid values keep their salvageable minutes.
|
||||
store.updateData { p ->
|
||||
val m = p.toMutablePreferences()
|
||||
m[SettingsPrefs.CALENDAR_REMINDER_OVERRIDE_KEY] = "5=none;7=abc;8=;9=30,abc;11=15"
|
||||
m[stringPreferencesKey("per_calendar_reminder_override")] = "5=none;7=abc;8=;9=30,abc;11=15"
|
||||
m
|
||||
}
|
||||
assertThat(prefs.perCalendarReminderOverride.first()).containsExactly(
|
||||
@@ -591,7 +605,7 @@ class SettingsPrefsTest {
|
||||
store.updateData { p ->
|
||||
val m = p.toMutablePreferences()
|
||||
// A bad id, a non-numeric length, one out of range, and a good entry.
|
||||
m[SettingsPrefs.CALENDAR_EVENT_DURATION_KEY] = "x=60;7=soon;8=99999;9=45"
|
||||
m[stringPreferencesKey("per_calendar_event_duration")] = "x=60;7=soon;8=99999;9=45"
|
||||
m
|
||||
}
|
||||
assertThat(prefs.perCalendarEventDuration.first()).containsExactly(9L, 45)
|
||||
|
||||
Reference in New Issue
Block a user