mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 11:48:24 +00:00
refactor: take the last nine DataStores off the Context delegate
Every other store in the app now opens through AppPreferenceStores; these nine were still on androidx's `Context.preferencesDataStore`, which is the pattern the rest of this branch removed. They are the stores behind the napplet sandbox, the in-app browser, favorites, chess dismissals and the two calendar-reminder files. The names are unchanged, and that is the whole safety argument. The holder reproduces `filesDir/datastore/<name>.preferences_pb`, which is exactly what the delegate resolved to, so every one of these opens the file it was already using: nothing migrates, and a rollback finds its data where it left it. A typo in one of these strings would silently orphan that store's data rather than fail, so they were copied across verbatim. The calendar migrations moved with their files. DataStore runs a file's migrations once, when that file is first opened, so they have to be attached to the file by the holder rather than by whichever caller happens to open it first — the same reason shared_settings' migration lives there. Two shapes changed rather than being mechanically translated: - The registries' `Context` receiver is gone. These stores are app-scoped now, so a receiver the body ignores would claim a dependency that is not real. The `appContext ?: return` guards stay: they mean "init() has run", which is still true and still worth keeping. - DataStoreNappletStorage and DataStoreNappletPermissionStore lose their Context secondary constructors instead of keeping an unused parameter. Both call sites pass the store directly. On the process boundary, since Amethyst.instance is deliberately unset in the `:napplet` sandbox and reaching it there throws: all four components declared `android:process=":napplet"` are napplethost.* classes from the :nappletHost module, NappletBrokerService declares no process and so runs in main, and onCreate's sandbox early-return precedes the one WebAppNetworkRegistry.init() call. All nine stores are main-process only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXvKXakvup4inNFfAhhr4L
This commit is contained in:
@@ -79,8 +79,12 @@ import com.vitorpamplona.amethyst.model.privacyOptions.RoleBasedHttpClientBuilde
|
||||
import com.vitorpamplona.amethyst.model.torState.AccountsTorStateConnector
|
||||
import com.vitorpamplona.amethyst.model.torState.TorRelayState
|
||||
import com.vitorpamplona.amethyst.napplet.DataStoreNappletPermissionStore
|
||||
import com.vitorpamplona.amethyst.service.calendar.CALENDAR_REMINDER_LOG_STORE
|
||||
import com.vitorpamplona.amethyst.service.calendar.CALENDAR_REMINDER_SETTINGS_STORE
|
||||
import com.vitorpamplona.amethyst.service.calendar.CalendarReminderWorker
|
||||
import com.vitorpamplona.amethyst.service.calendar.calendarReminderLogMigrations
|
||||
import com.vitorpamplona.amethyst.service.calendar.calendarReminderSettings
|
||||
import com.vitorpamplona.amethyst.service.calendar.calendarReminderSettingsMigrations
|
||||
import com.vitorpamplona.amethyst.service.cast.CastRegistry
|
||||
import com.vitorpamplona.amethyst.service.connectivity.ConnectivityManager
|
||||
import com.vitorpamplona.amethyst.service.crashreports.CrashReportCache
|
||||
@@ -257,6 +261,8 @@ class AppModules(
|
||||
// One file per account, so the migration is per name rather than a constant.
|
||||
name.startsWith(CashuPreferences.FILE_PREFIX) ->
|
||||
listOf(CashuPreferences.legacyMigration(appContext, name.removePrefix(CashuPreferences.FILE_PREFIX)))
|
||||
name == CALENDAR_REMINDER_SETTINGS_STORE -> calendarReminderSettingsMigrations(appContext)
|
||||
name == CALENDAR_REMINDER_LOG_STORE -> calendarReminderLogMigrations(appContext)
|
||||
else -> emptyList()
|
||||
}
|
||||
},
|
||||
@@ -855,8 +861,10 @@ class AppModules(
|
||||
*/
|
||||
val nappletAccountScope: () -> String = { sessionManager.loggedInAccount()?.pubKey ?: "" }
|
||||
|
||||
// Singleton stores for napplet permissions — DataStore v1 enforces one instance per file.
|
||||
val nappletPermissionStore by lazy { DataStoreNappletPermissionStore(appContext, nappletAccountScope) }
|
||||
// Singleton stores for napplet permissions. The holder is what enforces
|
||||
// DataStore's one-instance-per-file rule now; this stays a lazy val so the
|
||||
// ledger below and the broker share one object.
|
||||
val nappletPermissionStore by lazy { DataStoreNappletPermissionStore(appStores.getDataStore("napplet_permissions"), nappletAccountScope) }
|
||||
|
||||
/**
|
||||
* The one napplet permission ledger for the main process. Its persistent half is just the store
|
||||
@@ -1347,7 +1355,7 @@ class AppModules(
|
||||
Filter(kinds = listOf(CalendarDateSlotEvent.KIND, CalendarTimeSlotEvent.KIND)),
|
||||
).conflate()
|
||||
.collect {
|
||||
if (appContext.calendarReminderSettings().load().enabled &&
|
||||
if (calendarReminderSettings().load().enabled &&
|
||||
CalendarReminderWorker.couldStillFire(CalendarReminderWorker.acceptedRsvpsInCache(), TimeUtils.now())
|
||||
) {
|
||||
CalendarReminderWorker.schedule(appContext)
|
||||
|
||||
+15
-5
@@ -21,9 +21,11 @@
|
||||
package com.vitorpamplona.amethyst.favorites
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.browser.OmniboxInput
|
||||
import com.vitorpamplona.quartz.nip01Core.core.JsonMapper
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
@@ -37,7 +39,15 @@ import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private val Context.browserHistoryDataStore by preferencesDataStore(name = "browser_history")
|
||||
/**
|
||||
* The browser-history file, on the app-wide holder rather than a `Context` delegate.
|
||||
* Same path the delegate resolved to, so nothing migrates.
|
||||
*
|
||||
* Main process only: [Amethyst.instance] is deliberately unset in the
|
||||
* `:napplet` sandbox.
|
||||
*/
|
||||
private val browserHistoryDataStore: DataStore<Preferences>
|
||||
get() = Amethyst.instance.appStores.getDataStore("browser_history")
|
||||
|
||||
/**
|
||||
* One device-local visited site, keyed by full [url]. [visitCount]/[lastVisitedAt] drive frecency ranking
|
||||
@@ -83,7 +93,7 @@ object BrowserHistoryRegistry {
|
||||
val ctx = context.applicationContext
|
||||
appContext = ctx
|
||||
scope.launch {
|
||||
val json = ctx.browserHistoryDataStore.data.first()[KEY]
|
||||
val json = browserHistoryDataStore.data.first()[KEY]
|
||||
val loaded = if (json != null) decode(json) else emptyList()
|
||||
// Merge disk under anything already recorded this session (session wins, newest-first).
|
||||
update { current -> dedupeNewestFirst(current + loaded) }
|
||||
@@ -136,9 +146,9 @@ object BrowserHistoryRegistry {
|
||||
}
|
||||
|
||||
private fun persist(json: String) {
|
||||
val ctx = appContext ?: return
|
||||
appContext ?: return
|
||||
scope.launch {
|
||||
ctx.browserHistoryDataStore.edit { it[KEY] = json }
|
||||
browserHistoryDataStore.edit { it[KEY] = json }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-7
@@ -21,9 +21,11 @@
|
||||
package com.vitorpamplona.amethyst.favorites
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp
|
||||
import com.vitorpamplona.quartz.nip01Core.core.JsonMapper
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
@@ -38,7 +40,15 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
private val Context.favoriteAppsDataStore by preferencesDataStore(name = "favorite_apps")
|
||||
/**
|
||||
* The favorite-apps file, on the app-wide holder rather than a `Context` delegate.
|
||||
* Same path the delegate resolved to, so nothing migrates.
|
||||
*
|
||||
* Main process only: [Amethyst.instance] is deliberately unset in the
|
||||
* `:napplet` sandbox.
|
||||
*/
|
||||
private val favoriteAppsDataStore: DataStore<Preferences>
|
||||
get() = Amethyst.instance.appStores.getDataStore("favorite_apps")
|
||||
|
||||
/**
|
||||
* The user's device-local list of [FavoriteApp]s — the single source of truth shared by the bottom
|
||||
@@ -81,7 +91,7 @@ object FavoriteAppsRegistry {
|
||||
val ctx = context.applicationContext
|
||||
appContext = ctx
|
||||
scope.launch {
|
||||
val prefs = ctx.favoriteAppsDataStore.data.first()
|
||||
val prefs = favoriteAppsDataStore.data.first()
|
||||
val loaded = prefs[KEY]?.let { decode(it) } ?: emptyList()
|
||||
// Don't clobber adds made in this session before hydration finished, and don't resurrect
|
||||
// anything the user removed in that same window.
|
||||
@@ -139,16 +149,16 @@ object FavoriteAppsRegistry {
|
||||
}
|
||||
|
||||
private fun persist(json: String) {
|
||||
val ctx = appContext ?: return
|
||||
appContext ?: return
|
||||
scope.launch {
|
||||
ctx.favoriteAppsDataStore.edit { it[KEY] = json }
|
||||
favoriteAppsDataStore.edit { it[KEY] = json }
|
||||
}
|
||||
}
|
||||
|
||||
private fun persistManifests(json: String) {
|
||||
val ctx = appContext ?: return
|
||||
appContext ?: return
|
||||
scope.launch {
|
||||
ctx.favoriteAppsDataStore.edit { it[MANIFESTS_KEY] = json }
|
||||
favoriteAppsDataStore.edit { it[MANIFESTS_KEY] = json }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-7
@@ -20,19 +20,15 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.napplet
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletCapability
|
||||
import com.vitorpamplona.amethyst.commons.napplet.permissions.GrantState
|
||||
import com.vitorpamplona.amethyst.commons.napplet.permissions.NappletPermissionStore
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
private val Context.nappletPermissionsDataStore by preferencesDataStore(name = "napplet_permissions")
|
||||
|
||||
/**
|
||||
* Persists the standing napplet grants ([GrantState.ALLOW_ALWAYS] / [GrantState.DENY]) in a
|
||||
* dedicated DataStore. Keyed by `"<coordinate>\u0000<capability>"` so a coordinate's grants can
|
||||
@@ -43,9 +39,6 @@ class DataStoreNappletPermissionStore(
|
||||
private val dataStore: DataStore<Preferences>,
|
||||
private val accountPubKey: () -> String,
|
||||
) : NappletPermissionStore {
|
||||
constructor(context: Context, accountPubKey: () -> String) :
|
||||
this(context.applicationContext.nappletPermissionsDataStore, accountPubKey)
|
||||
|
||||
/**
|
||||
* Grants belong to one account. [accountPubKey] is read at call time, so an account switch moves
|
||||
* every read and write to that account's namespace with no rebuild — a grant made by one account
|
||||
|
||||
@@ -20,17 +20,13 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.napplet
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletStorage
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
private val Context.nappletStorageDataStore by preferencesDataStore(name = "napplet_storage")
|
||||
|
||||
/**
|
||||
* DataStore-backed [NappletStorage]. Every key is prefixed with the **active account** and then the
|
||||
* applet's coordinate, so one napplet's keys can never collide with another's, one account's data is
|
||||
@@ -44,9 +40,6 @@ class DataStoreNappletStorage(
|
||||
private val dataStore: DataStore<Preferences>,
|
||||
private val accountPubKey: () -> String,
|
||||
) : NappletStorage {
|
||||
constructor(context: Context, accountPubKey: () -> String) :
|
||||
this(context.applicationContext.nappletStorageDataStore, accountPubKey)
|
||||
|
||||
override suspend fun get(
|
||||
coordinate: String,
|
||||
key: String,
|
||||
|
||||
@@ -90,7 +90,7 @@ class NappletBrokerService : Service() {
|
||||
private val signerLedger by lazy { NostrSignerPermissionLedger(Amethyst.instance.signerPermissionStore) }
|
||||
|
||||
// Per-applet sandboxed key-value store (namespaced by account + coordinate inside the impl).
|
||||
private val storage by lazy { DataStoreNappletStorage(applicationContext, Amethyst.instance.nappletAccountScope) }
|
||||
private val storage by lazy { DataStoreNappletStorage(Amethyst.instance.appStores.getDataStore("napplet_storage"), Amethyst.instance.nappletAccountScope) }
|
||||
|
||||
private val incoming by lazy { Messenger(Handler(Looper.getMainLooper(), ::handleMessage)) }
|
||||
|
||||
|
||||
+15
-5
@@ -21,9 +21,11 @@
|
||||
package com.vitorpamplona.amethyst.napplet
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -32,7 +34,15 @@ import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
private val Context.nappletNetworkDataStore by preferencesDataStore(name = "napplet_network")
|
||||
/**
|
||||
* The per-napplet routing file, on the app-wide holder rather than a `Context` delegate.
|
||||
* Same path the delegate resolved to, so nothing migrates.
|
||||
*
|
||||
* Main process only: [Amethyst.instance] is deliberately unset in the
|
||||
* `:napplet` sandbox.
|
||||
*/
|
||||
private val nappletNetworkDataStore: DataStore<Preferences>
|
||||
get() = Amethyst.instance.appStores.getDataStore("napplet_network")
|
||||
|
||||
/**
|
||||
* Per-nSite network-routing preference: whether a site's traffic goes through **Tor** (the default)
|
||||
@@ -69,7 +79,7 @@ object NappletNetworkRegistry {
|
||||
appContext = ctx
|
||||
hydration =
|
||||
scope.launch {
|
||||
ctx.nappletNetworkDataStore.data.first().asMap().forEach { (key, value) ->
|
||||
nappletNetworkDataStore.data.first().asMap().forEach { (key, value) ->
|
||||
// putIfAbsent: never clobber a choice made in this session before hydration finished.
|
||||
modes.putIfAbsent(key.name, value != OPEN_WEB)
|
||||
}
|
||||
@@ -95,9 +105,9 @@ object NappletNetworkRegistry {
|
||||
useTor: Boolean,
|
||||
) {
|
||||
modes[coordinate] = useTor
|
||||
val ctx = appContext ?: return
|
||||
appContext ?: return
|
||||
scope.launch {
|
||||
ctx.nappletNetworkDataStore.edit { it[stringPreferencesKey(coordinate)] = if (useTor) TOR else OPEN_WEB }
|
||||
nappletNetworkDataStore.edit { it[stringPreferencesKey(coordinate)] = if (useTor) TOR else OPEN_WEB }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,11 @@ package com.vitorpamplona.amethyst.napplet
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.net.toUri
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -33,7 +35,16 @@ import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
private val Context.webUrlNetworkDataStore by preferencesDataStore(name = "weburl_network")
|
||||
/**
|
||||
* The per-site routing file, on the app-wide holder rather than a `Context`
|
||||
* delegate. Same path the delegate resolved to, so nothing migrates.
|
||||
*
|
||||
* Main process only: [Amethyst.instance] is deliberately unset in the
|
||||
* `:napplet` sandbox, and this registry is only touched from the browser
|
||||
* chrome that runs in the main process.
|
||||
*/
|
||||
private val webUrlNetworkDataStore: DataStore<Preferences>
|
||||
get() = Amethyst.instance.appStores.getDataStore("weburl_network")
|
||||
|
||||
/**
|
||||
* Per-web-client network-routing preference: whether a favorited URL / browsed site routes through
|
||||
@@ -67,7 +78,7 @@ object WebAppNetworkRegistry {
|
||||
appContext = ctx
|
||||
hydration =
|
||||
scope.launch {
|
||||
ctx.webUrlNetworkDataStore.data.first().asMap().forEach { (key, value) ->
|
||||
webUrlNetworkDataStore.data.first().asMap().forEach { (key, value) ->
|
||||
// putIfAbsent: never clobber a choice made in this session before hydration finished.
|
||||
modes.putIfAbsent(key.name, value != OPEN_WEB)
|
||||
}
|
||||
@@ -98,9 +109,9 @@ object WebAppNetworkRegistry {
|
||||
) {
|
||||
val host = hostKeyOf(url)
|
||||
modes[host] = useTor
|
||||
val ctx = appContext ?: return
|
||||
appContext ?: return
|
||||
scope.launch {
|
||||
ctx.webUrlNetworkDataStore.edit { it[stringPreferencesKey(host)] = if (useTor) TOR else OPEN_WEB }
|
||||
webUrlNetworkDataStore.edit { it[stringPreferencesKey(host)] = if (useTor) TOR else OPEN_WEB }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+40
-34
@@ -21,9 +21,11 @@
|
||||
package com.vitorpamplona.amethyst.service.calendar
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataMigration
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.model.preferences.CalendarReminderLogStore
|
||||
import com.vitorpamplona.amethyst.commons.model.preferences.CalendarReminderSettings
|
||||
import com.vitorpamplona.amethyst.commons.model.preferences.CalendarReminderSettingsStore
|
||||
@@ -34,43 +36,47 @@ import com.vitorpamplona.amethyst.commons.model.preferences.CopyOnceMigration
|
||||
*
|
||||
* The store classes live in commons; only the file location and the one-off
|
||||
* lift out of the legacy SharedPreferences are Android's business.
|
||||
*
|
||||
* Both files sit on `AppPreferenceStores` rather than a `Context` delegate.
|
||||
* The names below are the delegate's names, and the holder reproduces the path
|
||||
* it resolved to, so nothing migrates. The migrations move with them: DataStore
|
||||
* runs a file's migrations once, when that file is first opened, so they have
|
||||
* to be attached to the file by the holder rather than by whoever opens it.
|
||||
*/
|
||||
private const val LEGACY_SETTINGS_FILE = "amethyst_calendar_reminder_prefs"
|
||||
private const val LEGACY_LOG_FILE = "amethyst_calendar_reminders"
|
||||
|
||||
private val Context.calendarReminderSettingsData by preferencesDataStore(
|
||||
name = "calendar_reminder_settings",
|
||||
produceMigrations = { context ->
|
||||
listOf(
|
||||
CopyOnceMigration("migrated.calendarReminderSettings") { out ->
|
||||
val legacy = context.getSharedPreferences(LEGACY_SETTINGS_FILE, Context.MODE_PRIVATE)
|
||||
if (legacy.contains("enabled")) {
|
||||
out[booleanPreferencesKey("enabled")] = legacy.getBoolean("enabled", CalendarReminderSettings.DEFAULT_ENABLED)
|
||||
}
|
||||
if (legacy.contains("lead_minutes")) {
|
||||
out[intPreferencesKey("lead_minutes")] = legacy.getInt("lead_minutes", CalendarReminderSettings.DEFAULT_LEAD_MINUTES)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
/** Store (and file) names, as [Amethyst.appStores] keys them. */
|
||||
const val CALENDAR_REMINDER_SETTINGS_STORE = "calendar_reminder_settings"
|
||||
const val CALENDAR_REMINDER_LOG_STORE = "calendar_reminder_log"
|
||||
|
||||
private val Context.calendarReminderLogData by preferencesDataStore(
|
||||
name = "calendar_reminder_log",
|
||||
produceMigrations = { context ->
|
||||
listOf(
|
||||
CopyOnceMigration("migrated.calendarReminderLog") { out ->
|
||||
val legacy = context.getSharedPreferences(LEGACY_LOG_FILE, Context.MODE_PRIVATE)
|
||||
// Values are the event-start times the reminders fired for; anything
|
||||
// else in the file is not ours and is left behind.
|
||||
legacy.all.forEach { (key, value) ->
|
||||
if (value is Long) out[CalendarReminderLogStore.keyFor(key.removePrefix("notified:"))] = value
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
/** The one-off copy of the reminder settings out of the legacy prefs file. */
|
||||
fun calendarReminderSettingsMigrations(context: Context): List<DataMigration<Preferences>> =
|
||||
listOf(
|
||||
CopyOnceMigration("migrated.calendarReminderSettings") { out ->
|
||||
val legacy = context.getSharedPreferences(LEGACY_SETTINGS_FILE, Context.MODE_PRIVATE)
|
||||
if (legacy.contains("enabled")) {
|
||||
out[booleanPreferencesKey("enabled")] = legacy.getBoolean("enabled", CalendarReminderSettings.DEFAULT_ENABLED)
|
||||
}
|
||||
if (legacy.contains("lead_minutes")) {
|
||||
out[intPreferencesKey("lead_minutes")] = legacy.getInt("lead_minutes", CalendarReminderSettings.DEFAULT_LEAD_MINUTES)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
fun Context.calendarReminderSettings() = CalendarReminderSettingsStore(calendarReminderSettingsData)
|
||||
/** The one-off copy of the fired-reminder log out of the legacy prefs file. */
|
||||
fun calendarReminderLogMigrations(context: Context): List<DataMigration<Preferences>> =
|
||||
listOf(
|
||||
CopyOnceMigration("migrated.calendarReminderLog") { out ->
|
||||
val legacy = context.getSharedPreferences(LEGACY_LOG_FILE, Context.MODE_PRIVATE)
|
||||
// Values are the event-start times the reminders fired for; anything
|
||||
// else in the file is not ours and is left behind.
|
||||
legacy.all.forEach { (key, value) ->
|
||||
if (value is Long) out[CalendarReminderLogStore.keyFor(key.removePrefix("notified:"))] = value
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
fun Context.calendarReminderLog() = CalendarReminderLogStore(calendarReminderLogData)
|
||||
fun calendarReminderSettings() = CalendarReminderSettingsStore(Amethyst.instance.appStores.getDataStore(CALENDAR_REMINDER_SETTINGS_STORE))
|
||||
|
||||
fun calendarReminderLog() = CalendarReminderLogStore(Amethyst.instance.appStores.getDataStore(CALENDAR_REMINDER_LOG_STORE))
|
||||
|
||||
+2
-2
@@ -65,7 +65,7 @@ class CalendarReminderWorker(
|
||||
) : CoroutineWorker(appContext, params) {
|
||||
override suspend fun doWork(): Result {
|
||||
runCatching { Amethyst.instance.resourceUsage.add(UsageKeys.workerRuns("calendarReminder"), 1) }
|
||||
val settings = applicationContext.calendarReminderSettings().load()
|
||||
val settings = calendarReminderSettings().load()
|
||||
if (!settings.enabled) {
|
||||
Log.d(TAG) { "Reminders disabled; ending periodic chain." }
|
||||
// The settings toggle re-schedules on enable; no reason to keep
|
||||
@@ -75,7 +75,7 @@ class CalendarReminderWorker(
|
||||
}
|
||||
val now = TimeUtils.now()
|
||||
val windowEnd = now + settings.leadMinutes * 60L
|
||||
val store = applicationContext.calendarReminderLog()
|
||||
val store = calendarReminderLog()
|
||||
|
||||
// Walk every kind-31925 RSVP authored by an account on this device. We don't have a
|
||||
// multi-account "all logged-in pubkeys" view here, so we accept any RSVP that's
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ import kotlinx.coroutines.launch
|
||||
fun CalendarReminderSettingsScreen(nav: INav) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val store = remember(context) { context.calendarReminderSettings() }
|
||||
val store = remember { calendarReminderSettings() }
|
||||
|
||||
// DataStore reads are suspend, so the first frame renders the defaults and
|
||||
// the stored values arrive right after. Collecting the flow rather than
|
||||
|
||||
+5
-3
@@ -20,8 +20,9 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chess
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
|
||||
/**
|
||||
* Where Android keeps the dismissed-chess-games store.
|
||||
@@ -31,4 +32,5 @@ import androidx.datastore.preferences.preferencesDataStore
|
||||
* data over is not worth the code. Anyone who had dismissed a game sees it once
|
||||
* more and dismisses it again.
|
||||
*/
|
||||
internal val Context.chessDismissedGamesData by preferencesDataStore(name = "chess_dismissed_games_v2")
|
||||
internal val chessDismissedGamesData: DataStore<Preferences>
|
||||
get() = Amethyst.instance.appStores.getDataStore("chess_dismissed_games_v2")
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ class ChessViewModelNew(
|
||||
private val publisher = AndroidChessPublisher(account)
|
||||
private val fetcher = AndroidRelayFetcher(account)
|
||||
private val metadataProvider = AndroidMetadataProvider()
|
||||
private val dismissedStorage = ChessDismissedGamesStore(application.chessDismissedGamesData)
|
||||
private val dismissedStorage = ChessDismissedGamesStore(chessDismissedGamesData)
|
||||
|
||||
// Shared business logic (creates its own ChessLobbyState internally)
|
||||
private val logic =
|
||||
|
||||
Reference in New Issue
Block a user