mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 11:48:24 +00:00
refactor: move the UI settings model and store into commons
UiSettings could not follow the other preference groups into commons because its eight enums each carried a `resourceId: StringResource` pointing at commonsUI's generated `Res` class. commonsUI depends on commons, not the reverse, so commons structurally cannot see it and verifyKmpPurity would reject it either way. The settings themselves are plain data any front end may read, so the data moves and the labels stay. - commons/model: UiSettings, UiSettingsFlow and the nine enums, without the resource ids. Pure data, CLI-safe, per commons/ARCHITECTURE.md §1. - commons/model/preferences/UiSettingsStore: the key table, the defaults and the one-shot copy out of the old shared_settings blob. The legacy reader is injected, because that file is Android's and this store is not. - commonsUI/ui/settings/UiSettingsLabels: the labels, as extension properties keeping the `resourceId` name so no call site changes spelling. Each `when` is exhaustive, so a new enum constant is still a compile error rather than a missing label at runtime. - amethyst keeps what is genuinely platform work: the per-application night mode override the launch splash reads, and AppCompat's locale list. Neither has a desktop equivalent. The shared_settings migration moves to the file's delegate rather than sitting inside the UI store: seven other stores share that file, and DataStore runs a file's migrations once, on whichever store opens it first. Hanging the copy off the UI store alone would have made it depend on construction order. Three of the nine labels went unused — ConnectivityType, FeatureSetType and FontFamilyType are picked through the narrower `shortLabelRes` helpers in AppSettingsScreen — and BooleanType's was misspelled `reourceId`, which is how it stayed unread. They are dropped rather than carried across; the strings they named are still used by those short labels. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXvKXakvup4inNFfAhhr4L
This commit is contained in:
+1
-1
@@ -56,6 +56,7 @@ import com.halilibo.richtext.ui.material3.RichText
|
||||
import com.halilibo.richtext.ui.resolveDefaults
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.UiSettingsFlow
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.error_dialog_button_ok
|
||||
import com.vitorpamplona.amethyst.commons.resources.push_server_explainer
|
||||
@@ -71,7 +72,6 @@ import com.vitorpamplona.amethyst.commons.ui.components.SpinnerSelectionDialog
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.model.UiSettingsFlow
|
||||
import com.vitorpamplona.amethyst.service.notifications.PushDistributorHandler
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsBlockTile
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
|
||||
@@ -28,6 +28,7 @@ import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import coil3.disk.DiskCache
|
||||
import coil3.memory.MemoryCache
|
||||
import com.vitorpamplona.amethyst.commons.model.NoteState
|
||||
import com.vitorpamplona.amethyst.commons.model.UiSettings
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.model.nip03Timestamp.BitcoinExplorerEndpoint
|
||||
import com.vitorpamplona.amethyst.commons.model.nip03Timestamp.IncomingOtsEventVerifier
|
||||
@@ -65,7 +66,6 @@ import com.vitorpamplona.amethyst.commons.tor.TorSettings
|
||||
import com.vitorpamplona.amethyst.connectedApps.DataStoreNostrSignerPermissionStore
|
||||
import com.vitorpamplona.amethyst.connectedApps.nip46.DataStoreNip46ClientStore
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.UiSettings
|
||||
import com.vitorpamplona.amethyst.model.accountsCache.AccountCacheState
|
||||
import com.vitorpamplona.amethyst.model.nip11RelayInfo.Nip11CachedRetriever
|
||||
import com.vitorpamplona.amethyst.model.preferences.DrawerSectionCollapsePreferences
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.content.SharedPreferences
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.core.content.edit
|
||||
import com.vitorpamplona.amethyst.commons.model.HomeFeedType
|
||||
import com.vitorpamplona.amethyst.commons.model.UiSettings
|
||||
import com.vitorpamplona.amethyst.commons.model.chats.ChatFeedType
|
||||
import com.vitorpamplona.amethyst.commons.model.clink.ClinkDebitWalletEntry
|
||||
import com.vitorpamplona.amethyst.commons.model.concord.ConcordViewMode
|
||||
@@ -59,7 +60,6 @@ import com.vitorpamplona.amethyst.commons.model.preferences.readLegacyAccountSec
|
||||
import com.vitorpamplona.amethyst.commons.model.topNavFeeds.TopFilter
|
||||
import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthPolicy
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.model.UiSettings
|
||||
import com.vitorpamplona.amethyst.model.backups.BackupConflictStorage
|
||||
import com.vitorpamplona.amethyst.model.nip60Cashu.CashuPreferences
|
||||
import com.vitorpamplona.amethyst.model.preferences.UiSharedPreferences
|
||||
|
||||
@@ -27,8 +27,8 @@ import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp
|
||||
import com.vitorpamplona.amethyst.commons.model.ThemeType
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.ThemeType
|
||||
import com.vitorpamplona.amethyst.napplet.NappletLauncher
|
||||
import com.vitorpamplona.amethyst.napplet.NappletWebViewProfiles
|
||||
import com.vitorpamplona.amethyst.napplet.WebAppNetworkRegistry
|
||||
|
||||
+27
-188
@@ -27,26 +27,14 @@ import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.datastore.core.DataMigration
|
||||
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.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.vitorpamplona.amethyst.LocalPreferences
|
||||
import com.vitorpamplona.amethyst.commons.model.preferences.CopyOnceMigration
|
||||
import com.vitorpamplona.amethyst.model.AccentColorType
|
||||
import com.vitorpamplona.amethyst.model.BooleanType
|
||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||
import com.vitorpamplona.amethyst.model.FontFamilyType
|
||||
import com.vitorpamplona.amethyst.model.FontSizeType
|
||||
import com.vitorpamplona.amethyst.model.ProfileGalleryType
|
||||
import com.vitorpamplona.amethyst.model.ThemeType
|
||||
import com.vitorpamplona.amethyst.model.UiSettings
|
||||
import com.vitorpamplona.amethyst.model.UiSettingsFlow
|
||||
import com.vitorpamplona.amethyst.commons.model.ThemeType
|
||||
import com.vitorpamplona.amethyst.commons.model.UiSettings
|
||||
import com.vitorpamplona.amethyst.commons.model.UiSettingsFlow
|
||||
import com.vitorpamplona.amethyst.commons.model.preferences.UiSettingsStore
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -54,25 +42,43 @@ import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
/** The UI settings store. See [UiSharedPreferences.migrations] for the copy it carries. */
|
||||
/**
|
||||
* The file UI, Tor, Namecoin, OTS and the Buzz stores all share, each under its
|
||||
* own key prefix.
|
||||
*
|
||||
* The migration is attached here, at the file, rather than inside
|
||||
* [UiSettingsStore]: whichever of those stores is constructed first is the one
|
||||
* that opens the file, and DataStore runs a file's migrations once, on that
|
||||
* first open. Hanging it off the UI store alone would make the copy depend on
|
||||
* load order.
|
||||
*/
|
||||
val Context.sharedPreferencesDataStore: DataStore<Preferences> by preferencesDataStore(
|
||||
name = "shared_settings",
|
||||
produceMigrations = { UiSharedPreferences.migrations() },
|
||||
produceMigrations = { UiSettingsStore.migrations { LocalPreferences.loadSharedSettings() } },
|
||||
)
|
||||
|
||||
/**
|
||||
* The Android half of the UI settings: the flows the app observes, and the two
|
||||
* platform side effects that a theme or language change has to perform.
|
||||
*
|
||||
* Persistence is [UiSettingsStore] in `commons`, which every front end shares.
|
||||
* What stays here is the part that has no desktop equivalent — the per-app night
|
||||
* mode override that the launch splash reads, and AppCompat's locale list.
|
||||
*/
|
||||
@Stable
|
||||
class UiSharedPreferences(
|
||||
prefs: UiSettings,
|
||||
val context: Context,
|
||||
val scope: CoroutineScope,
|
||||
) {
|
||||
private val store = UiSettingsStore(context.sharedPreferencesDataStore) { LocalPreferences.loadSharedSettings() }
|
||||
|
||||
// UI Preferences. Makes sure to wait for it to avoid blinking themes and language preferences
|
||||
val value = UiSettingsFlow.build(prefs)
|
||||
|
||||
@@ -182,7 +188,7 @@ class UiSharedPreferences(
|
||||
.debounce(1000)
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
save(it, context)
|
||||
store.save(it)
|
||||
}.flowOn(Dispatchers.IO)
|
||||
.stateIn(
|
||||
scope,
|
||||
@@ -191,173 +197,6 @@ class UiSharedPreferences(
|
||||
)
|
||||
|
||||
companion object {
|
||||
// loads faster when individualized
|
||||
val UI_THEME = stringPreferencesKey("ui.theme")
|
||||
val UI_LANGUAGE = stringPreferencesKey("ui.language")
|
||||
val UI_SHOW_IMAGES = stringPreferencesKey("ui.show_images")
|
||||
val UI_START_PLAYBACK = stringPreferencesKey("ui.start_playback")
|
||||
val UI_PLAY_VIDEOS = stringPreferencesKey("ui.play_videos")
|
||||
val UI_SHOW_URL_PREVIEW = stringPreferencesKey("ui.show_url_preview")
|
||||
val UI_HIDE_NAVIGATION_BARS = stringPreferencesKey("ui.hide_navigation_bars")
|
||||
val UI_SHOW_PROFILE_PICTURES = stringPreferencesKey("ui.show_profile_pictures")
|
||||
val UI_DONT_SHOW_PUSH_NOTIFICATION_SELECTOR = booleanPreferencesKey("ui.dont_show_push_notification_selector")
|
||||
val UI_DONT_ASK_FOR_NOTIFICATION_PERMISSIONS = booleanPreferencesKey("ui.dont_ask_for_notification_permissions")
|
||||
val UI_FEATURE_SET = stringPreferencesKey("ui.feature_set")
|
||||
val UI_GALLERY_SET = stringPreferencesKey("ui.gallery_set")
|
||||
val UI_PROPOSE_AI_IMPROVEMENTS = stringPreferencesKey("ui.propose_ai_improvements")
|
||||
val UI_USE_TRACKED_BROADCASTS = stringPreferencesKey("ui.use_tracked_broadcasts")
|
||||
val UI_AUTOMATICALLY_CREATE_DRAFTS = stringPreferencesKey("ui.automatically_create_drafts")
|
||||
val UI_SHOW_HOME_NEW_THREADS_TAB = booleanPreferencesKey("ui.show_home_new_threads_tab")
|
||||
val UI_SHOW_HOME_CONVERSATIONS_TAB = booleanPreferencesKey("ui.show_home_conversations_tab")
|
||||
val UI_SHOW_HOME_EVERYTHING_TAB = booleanPreferencesKey("ui.show_home_everything_tab")
|
||||
val UI_SHOW_PROFILE_BADGES = booleanPreferencesKey("ui.show_profile_badges")
|
||||
val UI_SHOW_PROFILE_APP_RECOMMENDATIONS = booleanPreferencesKey("ui.show_profile_app_recommendations")
|
||||
val UI_SHOW_PROFILE_ZAP_RECEIVED_FEED = booleanPreferencesKey("ui.show_profile_zap_received_feed")
|
||||
val UI_SHOW_PROFILE_FOLLOWERS_FEED = booleanPreferencesKey("ui.show_profile_followers_feed")
|
||||
val UI_DONT_SHOW_ONCHAIN_PUBLIC_WARNING = booleanPreferencesKey("ui.dont_show_onchain_public_warning")
|
||||
val UI_SUGGEST_WORKOUTS_FROM_HEALTH_CONNECT = stringPreferencesKey("ui.suggest_workouts_from_health_connect")
|
||||
val UI_ACCENT_COLOR = stringPreferencesKey("ui.accent_color")
|
||||
val UI_FONT_FAMILY = stringPreferencesKey("ui.font_family")
|
||||
val UI_FONT_SIZE = stringPreferencesKey("ui.font_size")
|
||||
val UI_COMPOSE_SIGNATURE = stringPreferencesKey("ui.compose_signature")
|
||||
val UI_SHOW_ONCHAIN_WALLET = booleanPreferencesKey("ui.show_onchain_wallet")
|
||||
val UI_SHOW_PAYTO_ZAP_CHIP = booleanPreferencesKey("ui.show_payto_zap_chip")
|
||||
|
||||
suspend fun uiPreferences(context: Context): UiSettings? =
|
||||
try {
|
||||
// Get the preference flow and take the first value.
|
||||
val preferences = context.sharedPreferencesDataStore.data.first()
|
||||
|
||||
val featureSet = preferences[UI_FEATURE_SET]?.let { FeatureSetType.valueOf(it) } ?: FeatureSetType.SIMPLIFIED
|
||||
|
||||
UiSettings(
|
||||
theme = preferences[UI_THEME]?.let { ThemeType.valueOf(it) } ?: ThemeType.SYSTEM,
|
||||
preferredLanguage = preferences[UI_LANGUAGE]?.ifBlank { null },
|
||||
automaticallyShowImages = preferences[UI_SHOW_IMAGES]?.let { ConnectivityType.valueOf(it) } ?: ConnectivityType.ALWAYS,
|
||||
automaticallyStartPlayback = preferences[UI_START_PLAYBACK]?.let { ConnectivityType.valueOf(it) } ?: ConnectivityType.ALWAYS,
|
||||
automaticallyPlayVideos = preferences[UI_PLAY_VIDEOS]?.let { BooleanType.valueOf(it) } ?: BooleanType.ALWAYS,
|
||||
automaticallyShowUrlPreview = preferences[UI_SHOW_URL_PREVIEW]?.let { ConnectivityType.valueOf(it) } ?: ConnectivityType.ALWAYS,
|
||||
automaticallyHideNavigationBars = preferences[UI_HIDE_NAVIGATION_BARS]?.let { BooleanType.valueOf(it) } ?: BooleanType.ALWAYS,
|
||||
automaticallyShowProfilePictures = preferences[UI_SHOW_PROFILE_PICTURES]?.let { ConnectivityType.valueOf(it) } ?: ConnectivityType.ALWAYS,
|
||||
dontShowPushNotificationSelector = preferences[UI_DONT_SHOW_PUSH_NOTIFICATION_SELECTOR] ?: false,
|
||||
dontAskForNotificationPermissions = preferences[UI_DONT_ASK_FOR_NOTIFICATION_PERMISSIONS] ?: false,
|
||||
featureSet = featureSet,
|
||||
gallerySet = preferences[UI_GALLERY_SET]?.let { ProfileGalleryType.valueOf(it) } ?: ProfileGalleryType.CLASSIC,
|
||||
automaticallyProposeAiImprovements = preferences[UI_PROPOSE_AI_IMPROVEMENTS]?.let { BooleanType.valueOf(it) } ?: BooleanType.ALWAYS,
|
||||
useTrackedBroadcasts =
|
||||
preferences[UI_USE_TRACKED_BROADCASTS]?.let { BooleanType.valueOf(it) }
|
||||
?: if (featureSet == FeatureSetType.COMPLETE) BooleanType.ALWAYS else BooleanType.NEVER,
|
||||
automaticallyCreateDrafts = preferences[UI_AUTOMATICALLY_CREATE_DRAFTS]?.let { BooleanType.valueOf(it) } ?: BooleanType.ALWAYS,
|
||||
showHomeNewThreadsTab = preferences[UI_SHOW_HOME_NEW_THREADS_TAB] ?: true,
|
||||
showHomeConversationsTab = preferences[UI_SHOW_HOME_CONVERSATIONS_TAB] ?: true,
|
||||
showHomeEverythingTab = preferences[UI_SHOW_HOME_EVERYTHING_TAB] ?: false,
|
||||
showProfileBadges = preferences[UI_SHOW_PROFILE_BADGES] ?: true,
|
||||
showProfileAppRecommendations = preferences[UI_SHOW_PROFILE_APP_RECOMMENDATIONS] ?: true,
|
||||
showProfileZapReceivedFeed = preferences[UI_SHOW_PROFILE_ZAP_RECEIVED_FEED] ?: true,
|
||||
showProfileFollowersFeed = preferences[UI_SHOW_PROFILE_FOLLOWERS_FEED] ?: true,
|
||||
dontShowOnchainPublicWarning = preferences[UI_DONT_SHOW_ONCHAIN_PUBLIC_WARNING] ?: false,
|
||||
suggestWorkoutsFromHealthConnect =
|
||||
preferences[UI_SUGGEST_WORKOUTS_FROM_HEALTH_CONNECT]?.let { BooleanType.valueOf(it) } ?: BooleanType.ALWAYS,
|
||||
accentColor = preferences[UI_ACCENT_COLOR]?.let { AccentColorType.valueOf(it) } ?: AccentColorType.PURPLE,
|
||||
fontFamily = preferences[UI_FONT_FAMILY]?.let { FontFamilyType.valueOf(it) } ?: FontFamilyType.SYSTEM,
|
||||
fontSize = preferences[UI_FONT_SIZE]?.let { FontSizeType.valueOf(it) } ?: FontSizeType.NORMAL,
|
||||
composeSignature = preferences[UI_COMPOSE_SIGNATURE] ?: "",
|
||||
showOnchainWallet = preferences[UI_SHOW_ONCHAIN_WALLET] ?: true,
|
||||
showPayToZapChip = preferences[UI_SHOW_PAYTO_ZAP_CHIP] ?: true,
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
// Log any errors that occur while reading the DataStore.
|
||||
Log.e("SharedPreferences") { "Error reading DataStore preferences: ${e.message}" }
|
||||
|
||||
try {
|
||||
val oldVersion = LocalPreferences.loadSharedSettings()
|
||||
if (oldVersion != null) {
|
||||
save(oldVersion, context)
|
||||
}
|
||||
oldVersion
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes every UI setting into [preferences].
|
||||
*
|
||||
* Shared by [save] and by the one-shot copy out of the old
|
||||
* `shared_settings` blob, so the two cannot come to disagree about
|
||||
* which keys a complete set of UI settings has.
|
||||
*/
|
||||
internal fun MutablePreferences.write(sharedSettings: UiSettings) {
|
||||
val preferences = this
|
||||
preferences[UI_THEME] = sharedSettings.theme.name
|
||||
preferences[UI_LANGUAGE] = sharedSettings.preferredLanguage ?: ""
|
||||
preferences[UI_SHOW_IMAGES] = sharedSettings.automaticallyShowImages.name
|
||||
preferences[UI_START_PLAYBACK] = sharedSettings.automaticallyStartPlayback.name
|
||||
preferences[UI_PLAY_VIDEOS] = sharedSettings.automaticallyPlayVideos.name
|
||||
preferences[UI_SHOW_URL_PREVIEW] = sharedSettings.automaticallyShowUrlPreview.name
|
||||
preferences[UI_HIDE_NAVIGATION_BARS] = sharedSettings.automaticallyHideNavigationBars.name
|
||||
preferences[UI_SHOW_PROFILE_PICTURES] = sharedSettings.automaticallyShowProfilePictures.name
|
||||
preferences[UI_DONT_SHOW_PUSH_NOTIFICATION_SELECTOR] = sharedSettings.dontShowPushNotificationSelector
|
||||
preferences[UI_DONT_ASK_FOR_NOTIFICATION_PERMISSIONS] = sharedSettings.dontAskForNotificationPermissions
|
||||
preferences[UI_FEATURE_SET] = sharedSettings.featureSet.name
|
||||
preferences[UI_GALLERY_SET] = sharedSettings.gallerySet.name
|
||||
preferences[UI_PROPOSE_AI_IMPROVEMENTS] = sharedSettings.automaticallyProposeAiImprovements.name
|
||||
preferences[UI_USE_TRACKED_BROADCASTS] = sharedSettings.useTrackedBroadcasts.name
|
||||
preferences[UI_AUTOMATICALLY_CREATE_DRAFTS] = sharedSettings.automaticallyCreateDrafts.name
|
||||
preferences[UI_SHOW_HOME_NEW_THREADS_TAB] = sharedSettings.showHomeNewThreadsTab
|
||||
preferences[UI_SHOW_HOME_CONVERSATIONS_TAB] = sharedSettings.showHomeConversationsTab
|
||||
preferences[UI_SHOW_HOME_EVERYTHING_TAB] = sharedSettings.showHomeEverythingTab
|
||||
preferences[UI_SHOW_PROFILE_BADGES] = sharedSettings.showProfileBadges
|
||||
preferences[UI_SHOW_PROFILE_APP_RECOMMENDATIONS] = sharedSettings.showProfileAppRecommendations
|
||||
preferences[UI_SHOW_PROFILE_ZAP_RECEIVED_FEED] = sharedSettings.showProfileZapReceivedFeed
|
||||
preferences[UI_SHOW_PROFILE_FOLLOWERS_FEED] = sharedSettings.showProfileFollowersFeed
|
||||
preferences[UI_DONT_SHOW_ONCHAIN_PUBLIC_WARNING] = sharedSettings.dontShowOnchainPublicWarning
|
||||
preferences[UI_SUGGEST_WORKOUTS_FROM_HEALTH_CONNECT] = sharedSettings.suggestWorkoutsFromHealthConnect.name
|
||||
preferences[UI_ACCENT_COLOR] = sharedSettings.accentColor.name
|
||||
preferences[UI_FONT_FAMILY] = sharedSettings.fontFamily.name
|
||||
preferences[UI_FONT_SIZE] = sharedSettings.fontSize.name
|
||||
preferences[UI_COMPOSE_SIGNATURE] = sharedSettings.composeSignature
|
||||
preferences[UI_SHOW_ONCHAIN_WALLET] = sharedSettings.showOnchainWallet
|
||||
preferences[UI_SHOW_PAYTO_ZAP_CHIP] = sharedSettings.showPayToZapChip
|
||||
}
|
||||
|
||||
/**
|
||||
* The one-shot copy out of the single `shared_settings` JSON blob these
|
||||
* settings used to be kept as, in the global encrypted file.
|
||||
*
|
||||
* Guarded, and it has to be. Unlike the per-account migrations, this
|
||||
* store has been the real home of these settings for a while, so most
|
||||
* installs already have a populated one — and copying an old blob over
|
||||
* it would undo every UI change the user has made since. [UI_THEME] is
|
||||
* the test: [save] writes every key unconditionally and is the only
|
||||
* writer, so its absence means this store has never been saved, which
|
||||
* is exactly the install whose settings are still only in the legacy
|
||||
* file.
|
||||
*/
|
||||
internal fun migrations(): List<DataMigration<Preferences>> =
|
||||
listOf(
|
||||
CopyOnceMigration("migrated.sharedSettings") { out ->
|
||||
if (out[UI_THEME] == null) {
|
||||
withContext(Dispatchers.IO) {
|
||||
LocalPreferences.loadSharedSettings()?.let { out.write(it) }
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
suspend fun save(
|
||||
sharedSettings: UiSettings,
|
||||
context: Context,
|
||||
) {
|
||||
try {
|
||||
context.sharedPreferencesDataStore.edit { preferences -> preferences.write(sharedSettings) }
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
// Log any errors that occur while reading the DataStore.
|
||||
Log.e("SharedPreferences") { "Error saving DataStore preferences: ${e.message}" }
|
||||
}
|
||||
}
|
||||
suspend fun uiPreferences(context: Context): UiSettings? = UiSettingsStore(context.sharedPreferencesDataStore) { LocalPreferences.loadSharedSettings() }.load()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.model.ThemeType
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletArtifactPolicy
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletIdentity
|
||||
import com.vitorpamplona.amethyst.model.ThemeType
|
||||
import com.vitorpamplona.amethyst.napplethost.HostProfile
|
||||
import com.vitorpamplona.amethyst.napplethost.NappletHostActivity
|
||||
import com.vitorpamplona.amethyst.napplethost.NappletHostContract
|
||||
|
||||
+1
-1
@@ -38,9 +38,9 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.model.BooleanType
|
||||
import com.vitorpamplona.amethyst.commons.service.broadcast.BroadcastEvent
|
||||
import com.vitorpamplona.amethyst.commons.service.pow.PoWJobState
|
||||
import com.vitorpamplona.amethyst.model.BooleanType
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
+1
-1
@@ -29,12 +29,12 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.model.FeatureSetType
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||
import com.vitorpamplona.amethyst.service.location.CachedReversedGeoLocations
|
||||
import com.vitorpamplona.amethyst.ui.note.FirstUserInfoRow
|
||||
import com.vitorpamplona.amethyst.ui.note.types.EditState
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.model.BooleanType
|
||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||
import com.vitorpamplona.amethyst.model.ProfileGalleryType
|
||||
import com.vitorpamplona.amethyst.model.UiSettingsFlow
|
||||
import com.vitorpamplona.amethyst.commons.model.BooleanType
|
||||
import com.vitorpamplona.amethyst.commons.model.ConnectivityType
|
||||
import com.vitorpamplona.amethyst.commons.model.FeatureSetType
|
||||
import com.vitorpamplona.amethyst.commons.model.ProfileGalleryType
|
||||
import com.vitorpamplona.amethyst.commons.model.UiSettingsFlow
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
+1
-1
@@ -46,6 +46,7 @@ import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.model.Dao
|
||||
import com.vitorpamplona.amethyst.commons.model.LiveHiddenUsers
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.UiSettingsFlow
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.backups.ReplaceableBackupConflict
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
@@ -111,7 +112,6 @@ import com.vitorpamplona.amethyst.logTime
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.model.LatestKeyPackageOwner
|
||||
import com.vitorpamplona.amethyst.model.UiSettingsFlow
|
||||
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
|
||||
import com.vitorpamplona.amethyst.model.privacyOptions.RoleBasedHttpClientBuilder
|
||||
import com.vitorpamplona.amethyst.service.ClinkDebitPayer
|
||||
|
||||
+1
-1
@@ -27,9 +27,9 @@ import android.os.Bundle
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp
|
||||
import com.vitorpamplona.amethyst.commons.model.ThemeType
|
||||
import com.vitorpamplona.amethyst.commons.tor.TorType
|
||||
import com.vitorpamplona.amethyst.favorites.FavoriteAppLauncher
|
||||
import com.vitorpamplona.amethyst.model.ThemeType
|
||||
import com.vitorpamplona.amethyst.napplet.WebAppNetworkRegistry
|
||||
import com.vitorpamplona.amethyst.napplethost.NappletHostContract
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.browser.EmbeddedWebAppController
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.model.ThemeType
|
||||
import com.vitorpamplona.amethyst.commons.model.ThemeType
|
||||
|
||||
/**
|
||||
* Keeps the warm embedded tabs in sync with the app's DARK/LIGHT theme. An embed WebView resolves its
|
||||
|
||||
+1
-1
@@ -81,6 +81,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.BooleanType
|
||||
import com.vitorpamplona.amethyst.commons.model.composer.AudienceSelection
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.nip30CustomEmojis.ui.ShowEmojiSuggestionList
|
||||
@@ -134,7 +135,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightPa
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier
|
||||
import com.vitorpamplona.amethyst.model.BooleanType
|
||||
import com.vitorpamplona.amethyst.ui.actions.StrippingFailureDialog
|
||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.FileServerSelectionRow
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.MAX_VOICE_RECORD_SECONDS
|
||||
|
||||
+1
-1
@@ -35,6 +35,7 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.model.BooleanType
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
@@ -69,7 +70,6 @@ import com.vitorpamplona.amethyst.commons.ui.text.onUiThread
|
||||
import com.vitorpamplona.amethyst.commons.ui.text.replaceCurrentWord
|
||||
import com.vitorpamplona.amethyst.commons.ui.text.setTextAndPlaceCursorAtBeginning
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.BooleanType
|
||||
import com.vitorpamplona.amethyst.model.accountsCache.AccountCacheState
|
||||
import com.vitorpamplona.amethyst.service.ai.WritingAssistantFactory
|
||||
import com.vitorpamplona.amethyst.service.location.LocationState
|
||||
|
||||
+1
-1
@@ -32,6 +32,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.commons.model.UiSettingsFlow
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.notification_tab_everyone
|
||||
@@ -44,7 +45,6 @@ import com.vitorpamplona.amethyst.commons.ui.feeds.rememberForeverPagerState
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.TabRowHeight
|
||||
import com.vitorpamplona.amethyst.model.UiSettingsFlow
|
||||
import com.vitorpamplona.amethyst.ui.components.SelectNotificationProvider
|
||||
import com.vitorpamplona.amethyst.ui.feeds.WatchScrollToTop
|
||||
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
||||
|
||||
+9
-8
@@ -73,6 +73,14 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.AccentColorType
|
||||
import com.vitorpamplona.amethyst.commons.model.BooleanType
|
||||
import com.vitorpamplona.amethyst.commons.model.ConnectivityType
|
||||
import com.vitorpamplona.amethyst.commons.model.FeatureSetType
|
||||
import com.vitorpamplona.amethyst.commons.model.FontFamilyType
|
||||
import com.vitorpamplona.amethyst.commons.model.FontSizeType
|
||||
import com.vitorpamplona.amethyst.commons.model.ThemeType
|
||||
import com.vitorpamplona.amethyst.commons.model.UiSettingsFlow
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.accent_color
|
||||
import com.vitorpamplona.amethyst.commons.resources.accent_color_description
|
||||
@@ -118,18 +126,11 @@ import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton
|
||||
import com.vitorpamplona.amethyst.commons.ui.settings.resourceId
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.contentColorOnAccent
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.isLight
|
||||
import com.vitorpamplona.amethyst.model.AccentColorType
|
||||
import com.vitorpamplona.amethyst.model.BooleanType
|
||||
import com.vitorpamplona.amethyst.model.ConnectivityType
|
||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||
import com.vitorpamplona.amethyst.model.FontFamilyType
|
||||
import com.vitorpamplona.amethyst.model.FontSizeType
|
||||
import com.vitorpamplona.amethyst.model.ThemeType
|
||||
import com.vitorpamplona.amethyst.model.UiSettingsFlow
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.previewColor
|
||||
import com.vitorpamplona.amethyst.ui.theme.toFontFamily
|
||||
|
||||
+2
-2
@@ -45,6 +45,8 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.BooleanType
|
||||
import com.vitorpamplona.amethyst.commons.model.UiSettingsFlow
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.add_client_tag_explainer
|
||||
import com.vitorpamplona.amethyst.commons.resources.add_client_tag_title
|
||||
@@ -96,8 +98,6 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackBu
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.model.AccountPoWPreferences
|
||||
import com.vitorpamplona.amethyst.model.BooleanType
|
||||
import com.vitorpamplona.amethyst.model.UiSettingsFlow
|
||||
import com.vitorpamplona.amethyst.service.ai.WritingAssistantFactory
|
||||
import com.vitorpamplona.amethyst.service.pow.deviceHashesPerSecond
|
||||
import com.vitorpamplona.amethyst.service.pow.loadApproxDuration
|
||||
|
||||
+1
-1
@@ -36,6 +36,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.HomeFeedType
|
||||
import com.vitorpamplona.amethyst.commons.model.UiSettingsFlow
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.conversations
|
||||
import com.vitorpamplona.amethyst.commons.resources.home_content_type_articles
|
||||
@@ -72,7 +73,6 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow
|
||||
import com.vitorpamplona.amethyst.model.UiSettingsFlow
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
|
||||
+3
-2
@@ -34,6 +34,8 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.ProfileGalleryType
|
||||
import com.vitorpamplona.amethyst.commons.model.UiSettingsFlow
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.gallery_style
|
||||
import com.vitorpamplona.amethyst.commons.resources.gallery_style_description
|
||||
@@ -48,10 +50,9 @@ import com.vitorpamplona.amethyst.commons.resources.settings_section_profile_sec
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton
|
||||
import com.vitorpamplona.amethyst.commons.ui.settings.resourceId
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow
|
||||
import com.vitorpamplona.amethyst.model.ProfileGalleryType
|
||||
import com.vitorpamplona.amethyst.model.UiSettingsFlow
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
|
||||
|
||||
+3
-2
@@ -38,7 +38,9 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.WarningType
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.model.parseWarningType
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.blocked_users
|
||||
import com.vitorpamplona.amethyst.commons.resources.filter_spam_from_strangers_explainer
|
||||
@@ -63,10 +65,9 @@ import com.vitorpamplona.amethyst.commons.resources.warn_when_posts_have_reports
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton
|
||||
import com.vitorpamplona.amethyst.commons.ui.settings.resourceId
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.model.WarningType
|
||||
import com.vitorpamplona.amethyst.model.parseWarningType
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
|
||||
|
||||
+1
-1
@@ -60,6 +60,7 @@ import com.vitorpamplona.amethyst.commons.fitness.TrainingLog
|
||||
import com.vitorpamplona.amethyst.commons.fitness.WorkoutOrigin
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.BooleanType
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.workout_from_health_connect
|
||||
@@ -71,7 +72,6 @@ import com.vitorpamplona.amethyst.commons.resources.workout_suggestion_connect_t
|
||||
import com.vitorpamplona.amethyst.commons.resources.workout_suggestion_distance_km
|
||||
import com.vitorpamplona.amethyst.commons.ui.pluralStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.model.BooleanType
|
||||
import com.vitorpamplona.amethyst.service.workouts.health.HealthConnectManager
|
||||
import com.vitorpamplona.amethyst.service.workouts.health.publishedWorkoutsOf
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@@ -44,6 +44,10 @@ import com.patrykandpatrick.vico.compose.common.VicoTheme
|
||||
import com.patrykandpatrick.vico.compose.common.VicoTheme.CandlestickCartesianLayerColors
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.ProvideAppIcons
|
||||
import com.vitorpamplona.amethyst.commons.model.AccentColorType
|
||||
import com.vitorpamplona.amethyst.commons.model.FontFamilyType
|
||||
import com.vitorpamplona.amethyst.commons.model.FontSizeType
|
||||
import com.vitorpamplona.amethyst.commons.model.ThemeType
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.AccentBlueDark
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.AccentBlueLight
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.AccentGreenDark
|
||||
@@ -64,10 +68,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.amethystLightColorScheme
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.isLight
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.transparentBackground
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.withFontFamily
|
||||
import com.vitorpamplona.amethyst.model.AccentColorType
|
||||
import com.vitorpamplona.amethyst.model.FontFamilyType
|
||||
import com.vitorpamplona.amethyst.model.FontSizeType
|
||||
import com.vitorpamplona.amethyst.model.ThemeType
|
||||
|
||||
// The accent color (primary/secondary/tertiary) is user-selectable in Settings -> Accent Color.
|
||||
// Purple keeps the original Amethyst look (purple primary + teal secondary). Every other accent
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import com.vitorpamplona.amethyst.model.UiSettingsFlow
|
||||
import com.vitorpamplona.amethyst.commons.model.UiSettingsFlow
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
|
||||
+31
-70
@@ -18,40 +18,10 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model
|
||||
package com.vitorpamplona.amethyst.commons.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.accent_color_blue
|
||||
import com.vitorpamplona.amethyst.commons.resources.accent_color_green
|
||||
import com.vitorpamplona.amethyst.commons.resources.accent_color_orange
|
||||
import com.vitorpamplona.amethyst.commons.resources.accent_color_pink
|
||||
import com.vitorpamplona.amethyst.commons.resources.accent_color_purple
|
||||
import com.vitorpamplona.amethyst.commons.resources.accent_color_red
|
||||
import com.vitorpamplona.amethyst.commons.resources.connectivity_type_always
|
||||
import com.vitorpamplona.amethyst.commons.resources.connectivity_type_never
|
||||
import com.vitorpamplona.amethyst.commons.resources.connectivity_type_unmetered_wifi_only
|
||||
import com.vitorpamplona.amethyst.commons.resources.content_warning_hide_all_sensitive_content_option
|
||||
import com.vitorpamplona.amethyst.commons.resources.content_warning_see_warnings_option
|
||||
import com.vitorpamplona.amethyst.commons.resources.content_warning_show_all_sensitive_content_option
|
||||
import com.vitorpamplona.amethyst.commons.resources.dark
|
||||
import com.vitorpamplona.amethyst.commons.resources.font_family_monospace
|
||||
import com.vitorpamplona.amethyst.commons.resources.font_family_sans_serif
|
||||
import com.vitorpamplona.amethyst.commons.resources.font_family_serif
|
||||
import com.vitorpamplona.amethyst.commons.resources.font_family_system
|
||||
import com.vitorpamplona.amethyst.commons.resources.font_size_huge
|
||||
import com.vitorpamplona.amethyst.commons.resources.font_size_large
|
||||
import com.vitorpamplona.amethyst.commons.resources.font_size_normal
|
||||
import com.vitorpamplona.amethyst.commons.resources.font_size_small
|
||||
import com.vitorpamplona.amethyst.commons.resources.gallery_type_classic
|
||||
import com.vitorpamplona.amethyst.commons.resources.gallery_type_modern
|
||||
import com.vitorpamplona.amethyst.commons.resources.light
|
||||
import com.vitorpamplona.amethyst.commons.resources.system
|
||||
import com.vitorpamplona.amethyst.commons.resources.ui_feature_set_type_complete
|
||||
import com.vitorpamplona.amethyst.commons.resources.ui_feature_set_type_performance
|
||||
import com.vitorpamplona.amethyst.commons.resources.ui_feature_set_type_simplified
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
|
||||
@Stable
|
||||
@Serializable
|
||||
@@ -98,11 +68,10 @@ data class UiSettings(
|
||||
|
||||
enum class ThemeType(
|
||||
val screenCode: Int,
|
||||
val resourceId: StringResource,
|
||||
) {
|
||||
SYSTEM(0, Res.string.system),
|
||||
LIGHT(1, Res.string.light),
|
||||
DARK(2, Res.string.dark),
|
||||
SYSTEM(0),
|
||||
LIGHT(1),
|
||||
DARK(2),
|
||||
}
|
||||
|
||||
fun parseThemeType(code: Int?): ThemeType =
|
||||
@@ -115,14 +84,13 @@ fun parseThemeType(code: Int?): ThemeType =
|
||||
|
||||
enum class AccentColorType(
|
||||
val screenCode: Int,
|
||||
val resourceId: StringResource,
|
||||
) {
|
||||
PURPLE(0, Res.string.accent_color_purple),
|
||||
BLUE(1, Res.string.accent_color_blue),
|
||||
GREEN(2, Res.string.accent_color_green),
|
||||
ORANGE(3, Res.string.accent_color_orange),
|
||||
RED(4, Res.string.accent_color_red),
|
||||
PINK(5, Res.string.accent_color_pink),
|
||||
PURPLE(0),
|
||||
BLUE(1),
|
||||
GREEN(2),
|
||||
ORANGE(3),
|
||||
RED(4),
|
||||
PINK(5),
|
||||
}
|
||||
|
||||
fun parseAccentColorType(screenCode: Int): AccentColorType =
|
||||
@@ -138,12 +106,11 @@ fun parseAccentColorType(screenCode: Int): AccentColorType =
|
||||
|
||||
enum class FontFamilyType(
|
||||
val screenCode: Int,
|
||||
val resourceId: StringResource,
|
||||
) {
|
||||
SYSTEM(0, Res.string.font_family_system),
|
||||
SANS_SERIF(1, Res.string.font_family_sans_serif),
|
||||
SERIF(2, Res.string.font_family_serif),
|
||||
MONOSPACE(3, Res.string.font_family_monospace),
|
||||
SYSTEM(0),
|
||||
SANS_SERIF(1),
|
||||
SERIF(2),
|
||||
MONOSPACE(3),
|
||||
}
|
||||
|
||||
fun parseFontFamilyType(screenCode: Int): FontFamilyType =
|
||||
@@ -158,12 +125,11 @@ fun parseFontFamilyType(screenCode: Int): FontFamilyType =
|
||||
enum class FontSizeType(
|
||||
val scale: Float,
|
||||
val screenCode: Int,
|
||||
val resourceId: StringResource,
|
||||
) {
|
||||
SMALL(0.85f, 0, Res.string.font_size_small),
|
||||
NORMAL(1.0f, 1, Res.string.font_size_normal),
|
||||
LARGE(1.15f, 2, Res.string.font_size_large),
|
||||
HUGE(1.3f, 3, Res.string.font_size_huge),
|
||||
SMALL(0.85f, 0),
|
||||
NORMAL(1.0f, 1),
|
||||
LARGE(1.15f, 2),
|
||||
HUGE(1.3f, 3),
|
||||
}
|
||||
|
||||
fun parseFontSizeType(screenCode: Int): FontSizeType =
|
||||
@@ -178,28 +144,25 @@ fun parseFontSizeType(screenCode: Int): FontSizeType =
|
||||
enum class ConnectivityType(
|
||||
val prefCode: Boolean?,
|
||||
val screenCode: Int,
|
||||
val resourceId: StringResource,
|
||||
) {
|
||||
ALWAYS(null, 0, Res.string.connectivity_type_always),
|
||||
WIFI_ONLY(true, 1, Res.string.connectivity_type_unmetered_wifi_only),
|
||||
NEVER(false, 2, Res.string.connectivity_type_never),
|
||||
ALWAYS(null, 0),
|
||||
WIFI_ONLY(true, 1),
|
||||
NEVER(false, 2),
|
||||
}
|
||||
|
||||
enum class FeatureSetType(
|
||||
val screenCode: Int,
|
||||
val resourceId: StringResource,
|
||||
) {
|
||||
COMPLETE(0, Res.string.ui_feature_set_type_complete),
|
||||
SIMPLIFIED(1, Res.string.ui_feature_set_type_simplified),
|
||||
PERFORMANCE(2, Res.string.ui_feature_set_type_performance),
|
||||
COMPLETE(0),
|
||||
SIMPLIFIED(1),
|
||||
PERFORMANCE(2),
|
||||
}
|
||||
|
||||
enum class ProfileGalleryType(
|
||||
val screenCode: Int,
|
||||
val resourceId: StringResource,
|
||||
) {
|
||||
CLASSIC(0, Res.string.gallery_type_classic),
|
||||
MODERN(1, Res.string.gallery_type_modern),
|
||||
CLASSIC(0),
|
||||
MODERN(1),
|
||||
}
|
||||
|
||||
fun parseConnectivityType(code: Boolean?): ConnectivityType =
|
||||
@@ -236,10 +199,9 @@ fun parseGalleryType(screenCode: Int): ProfileGalleryType =
|
||||
enum class BooleanType(
|
||||
val prefCode: Boolean?,
|
||||
val screenCode: Int,
|
||||
val reourceId: StringResource,
|
||||
) {
|
||||
ALWAYS(null, 0, Res.string.connectivity_type_always),
|
||||
NEVER(false, 1, Res.string.connectivity_type_never),
|
||||
ALWAYS(null, 0),
|
||||
NEVER(false, 1),
|
||||
}
|
||||
|
||||
fun parseBooleanType(code: Boolean?): BooleanType =
|
||||
@@ -259,11 +221,10 @@ fun parseBooleanType(screenCode: Int): BooleanType =
|
||||
enum class WarningType(
|
||||
val prefCode: Boolean?,
|
||||
val screenCode: Int,
|
||||
val resourceId: StringResource,
|
||||
) {
|
||||
WARN(null, 0, Res.string.content_warning_see_warnings_option),
|
||||
SHOW(true, 1, Res.string.content_warning_show_all_sensitive_content_option),
|
||||
HIDE(false, 2, Res.string.content_warning_hide_all_sensitive_content_option),
|
||||
WARN(null, 0),
|
||||
SHOW(true, 1),
|
||||
HIDE(false, 2),
|
||||
}
|
||||
|
||||
fun parseWarningType(screenCode: Int): WarningType =
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model
|
||||
package com.vitorpamplona.amethyst.commons.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.model.preferences
|
||||
|
||||
import androidx.datastore.core.DataMigration
|
||||
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.stringPreferencesKey
|
||||
import com.vitorpamplona.amethyst.commons.model.AccentColorType
|
||||
import com.vitorpamplona.amethyst.commons.model.BooleanType
|
||||
import com.vitorpamplona.amethyst.commons.model.ConnectivityType
|
||||
import com.vitorpamplona.amethyst.commons.model.FeatureSetType
|
||||
import com.vitorpamplona.amethyst.commons.model.FontFamilyType
|
||||
import com.vitorpamplona.amethyst.commons.model.FontSizeType
|
||||
import com.vitorpamplona.amethyst.commons.model.ProfileGalleryType
|
||||
import com.vitorpamplona.amethyst.commons.model.ThemeType
|
||||
import com.vitorpamplona.amethyst.commons.model.UiSettings
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.IO
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
/**
|
||||
* The app-wide UI settings — theme, language, what loads on cellular, which
|
||||
* profile and home tabs are shown.
|
||||
*
|
||||
* Lives on the [AppPreferenceStores.SHARED_SETTINGS] file under the `ui.` key
|
||||
* prefix, one key per setting rather than a single blob: a DataStore read is
|
||||
* whole-file anyway, but individual keys mean a setting added later does not
|
||||
* invalidate the ones already stored.
|
||||
*
|
||||
* Headless on purpose. Applying a theme or a locale is platform work — on
|
||||
* Android it is `UiModeManager` and `AppCompatDelegate`, which have no desktop
|
||||
* equivalent — so that half stays in each front end and only the persistence
|
||||
* is shared.
|
||||
*
|
||||
* @param loadLegacy reads the single `shared_settings` JSON blob these settings
|
||||
* used to live in. Injected because the legacy file is Android's, and this
|
||||
* store is not. Front ends without one pass nothing.
|
||||
*/
|
||||
class UiSettingsStore(
|
||||
private val store: DataStore<Preferences>,
|
||||
private val loadLegacy: suspend () -> UiSettings? = { null },
|
||||
) {
|
||||
/**
|
||||
* Reads every setting, falling back to the legacy blob if the store itself
|
||||
* cannot be read.
|
||||
*
|
||||
* Returns null when neither can be read, which the caller shows as "still
|
||||
* loading" rather than as defaults — writing defaults over an unreadable
|
||||
* store would make a transient failure permanent on the next save.
|
||||
*/
|
||||
suspend fun load(): UiSettings? =
|
||||
try {
|
||||
read(store.data.first())
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("UiSettingsStore") { "Error reading DataStore preferences: ${e.message}" }
|
||||
|
||||
try {
|
||||
loadLegacy()?.also { save(it) }
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun save(settings: UiSettings) {
|
||||
try {
|
||||
store.edit { it.write(settings) }
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("UiSettingsStore") { "Error saving DataStore preferences: ${e.message}" }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
// loads faster when individualized
|
||||
val UI_THEME = stringPreferencesKey("ui.theme")
|
||||
val UI_LANGUAGE = stringPreferencesKey("ui.language")
|
||||
val UI_SHOW_IMAGES = stringPreferencesKey("ui.show_images")
|
||||
val UI_START_PLAYBACK = stringPreferencesKey("ui.start_playback")
|
||||
val UI_PLAY_VIDEOS = stringPreferencesKey("ui.play_videos")
|
||||
val UI_SHOW_URL_PREVIEW = stringPreferencesKey("ui.show_url_preview")
|
||||
val UI_HIDE_NAVIGATION_BARS = stringPreferencesKey("ui.hide_navigation_bars")
|
||||
val UI_SHOW_PROFILE_PICTURES = stringPreferencesKey("ui.show_profile_pictures")
|
||||
val UI_DONT_SHOW_PUSH_NOTIFICATION_SELECTOR = booleanPreferencesKey("ui.dont_show_push_notification_selector")
|
||||
val UI_DONT_ASK_FOR_NOTIFICATION_PERMISSIONS = booleanPreferencesKey("ui.dont_ask_for_notification_permissions")
|
||||
val UI_FEATURE_SET = stringPreferencesKey("ui.feature_set")
|
||||
val UI_GALLERY_SET = stringPreferencesKey("ui.gallery_set")
|
||||
val UI_PROPOSE_AI_IMPROVEMENTS = stringPreferencesKey("ui.propose_ai_improvements")
|
||||
val UI_USE_TRACKED_BROADCASTS = stringPreferencesKey("ui.use_tracked_broadcasts")
|
||||
val UI_AUTOMATICALLY_CREATE_DRAFTS = stringPreferencesKey("ui.automatically_create_drafts")
|
||||
val UI_SHOW_HOME_NEW_THREADS_TAB = booleanPreferencesKey("ui.show_home_new_threads_tab")
|
||||
val UI_SHOW_HOME_CONVERSATIONS_TAB = booleanPreferencesKey("ui.show_home_conversations_tab")
|
||||
val UI_SHOW_HOME_EVERYTHING_TAB = booleanPreferencesKey("ui.show_home_everything_tab")
|
||||
val UI_SHOW_PROFILE_BADGES = booleanPreferencesKey("ui.show_profile_badges")
|
||||
val UI_SHOW_PROFILE_APP_RECOMMENDATIONS = booleanPreferencesKey("ui.show_profile_app_recommendations")
|
||||
val UI_SHOW_PROFILE_ZAP_RECEIVED_FEED = booleanPreferencesKey("ui.show_profile_zap_received_feed")
|
||||
val UI_SHOW_PROFILE_FOLLOWERS_FEED = booleanPreferencesKey("ui.show_profile_followers_feed")
|
||||
val UI_DONT_SHOW_ONCHAIN_PUBLIC_WARNING = booleanPreferencesKey("ui.dont_show_onchain_public_warning")
|
||||
val UI_SUGGEST_WORKOUTS_FROM_HEALTH_CONNECT = stringPreferencesKey("ui.suggest_workouts_from_health_connect")
|
||||
val UI_ACCENT_COLOR = stringPreferencesKey("ui.accent_color")
|
||||
val UI_FONT_FAMILY = stringPreferencesKey("ui.font_family")
|
||||
val UI_FONT_SIZE = stringPreferencesKey("ui.font_size")
|
||||
val UI_COMPOSE_SIGNATURE = stringPreferencesKey("ui.compose_signature")
|
||||
val UI_SHOW_ONCHAIN_WALLET = booleanPreferencesKey("ui.show_onchain_wallet")
|
||||
val UI_SHOW_PAYTO_ZAP_CHIP = booleanPreferencesKey("ui.show_payto_zap_chip")
|
||||
|
||||
/**
|
||||
* Every setting's default matches what the old `getBoolean(key, default)`
|
||||
* call returned for a missing key. Several of them are `true`, so reading
|
||||
* a default of `false` here would silently turn features off for every
|
||||
* install that never touched them.
|
||||
*/
|
||||
fun read(preferences: Preferences): UiSettings {
|
||||
val featureSet = preferences[UI_FEATURE_SET]?.let { FeatureSetType.valueOf(it) } ?: FeatureSetType.SIMPLIFIED
|
||||
|
||||
return UiSettings(
|
||||
theme = preferences[UI_THEME]?.let { ThemeType.valueOf(it) } ?: ThemeType.SYSTEM,
|
||||
preferredLanguage = preferences[UI_LANGUAGE]?.ifBlank { null },
|
||||
automaticallyShowImages = preferences[UI_SHOW_IMAGES]?.let { ConnectivityType.valueOf(it) } ?: ConnectivityType.ALWAYS,
|
||||
automaticallyStartPlayback = preferences[UI_START_PLAYBACK]?.let { ConnectivityType.valueOf(it) } ?: ConnectivityType.ALWAYS,
|
||||
automaticallyPlayVideos = preferences[UI_PLAY_VIDEOS]?.let { BooleanType.valueOf(it) } ?: BooleanType.ALWAYS,
|
||||
automaticallyShowUrlPreview = preferences[UI_SHOW_URL_PREVIEW]?.let { ConnectivityType.valueOf(it) } ?: ConnectivityType.ALWAYS,
|
||||
automaticallyHideNavigationBars = preferences[UI_HIDE_NAVIGATION_BARS]?.let { BooleanType.valueOf(it) } ?: BooleanType.ALWAYS,
|
||||
automaticallyShowProfilePictures = preferences[UI_SHOW_PROFILE_PICTURES]?.let { ConnectivityType.valueOf(it) } ?: ConnectivityType.ALWAYS,
|
||||
dontShowPushNotificationSelector = preferences[UI_DONT_SHOW_PUSH_NOTIFICATION_SELECTOR] ?: false,
|
||||
dontAskForNotificationPermissions = preferences[UI_DONT_ASK_FOR_NOTIFICATION_PERMISSIONS] ?: false,
|
||||
featureSet = featureSet,
|
||||
gallerySet = preferences[UI_GALLERY_SET]?.let { ProfileGalleryType.valueOf(it) } ?: ProfileGalleryType.CLASSIC,
|
||||
automaticallyProposeAiImprovements = preferences[UI_PROPOSE_AI_IMPROVEMENTS]?.let { BooleanType.valueOf(it) } ?: BooleanType.ALWAYS,
|
||||
useTrackedBroadcasts =
|
||||
preferences[UI_USE_TRACKED_BROADCASTS]?.let { BooleanType.valueOf(it) }
|
||||
?: if (featureSet == FeatureSetType.COMPLETE) BooleanType.ALWAYS else BooleanType.NEVER,
|
||||
automaticallyCreateDrafts = preferences[UI_AUTOMATICALLY_CREATE_DRAFTS]?.let { BooleanType.valueOf(it) } ?: BooleanType.ALWAYS,
|
||||
showHomeNewThreadsTab = preferences[UI_SHOW_HOME_NEW_THREADS_TAB] ?: true,
|
||||
showHomeConversationsTab = preferences[UI_SHOW_HOME_CONVERSATIONS_TAB] ?: true,
|
||||
showHomeEverythingTab = preferences[UI_SHOW_HOME_EVERYTHING_TAB] ?: false,
|
||||
showProfileBadges = preferences[UI_SHOW_PROFILE_BADGES] ?: true,
|
||||
showProfileAppRecommendations = preferences[UI_SHOW_PROFILE_APP_RECOMMENDATIONS] ?: true,
|
||||
showProfileZapReceivedFeed = preferences[UI_SHOW_PROFILE_ZAP_RECEIVED_FEED] ?: true,
|
||||
showProfileFollowersFeed = preferences[UI_SHOW_PROFILE_FOLLOWERS_FEED] ?: true,
|
||||
dontShowOnchainPublicWarning = preferences[UI_DONT_SHOW_ONCHAIN_PUBLIC_WARNING] ?: false,
|
||||
suggestWorkoutsFromHealthConnect =
|
||||
preferences[UI_SUGGEST_WORKOUTS_FROM_HEALTH_CONNECT]?.let { BooleanType.valueOf(it) } ?: BooleanType.ALWAYS,
|
||||
accentColor = preferences[UI_ACCENT_COLOR]?.let { AccentColorType.valueOf(it) } ?: AccentColorType.PURPLE,
|
||||
fontFamily = preferences[UI_FONT_FAMILY]?.let { FontFamilyType.valueOf(it) } ?: FontFamilyType.SYSTEM,
|
||||
fontSize = preferences[UI_FONT_SIZE]?.let { FontSizeType.valueOf(it) } ?: FontSizeType.NORMAL,
|
||||
composeSignature = preferences[UI_COMPOSE_SIGNATURE] ?: "",
|
||||
showOnchainWallet = preferences[UI_SHOW_ONCHAIN_WALLET] ?: true,
|
||||
showPayToZapChip = preferences[UI_SHOW_PAYTO_ZAP_CHIP] ?: true,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes every UI setting into [this].
|
||||
*
|
||||
* Shared by [save] and by the one-shot copy out of the old
|
||||
* `shared_settings` blob, so the two cannot come to disagree about
|
||||
* which keys a complete set of UI settings has.
|
||||
*/
|
||||
fun MutablePreferences.write(sharedSettings: UiSettings) {
|
||||
val preferences = this
|
||||
preferences[UI_THEME] = sharedSettings.theme.name
|
||||
preferences[UI_LANGUAGE] = sharedSettings.preferredLanguage ?: ""
|
||||
preferences[UI_SHOW_IMAGES] = sharedSettings.automaticallyShowImages.name
|
||||
preferences[UI_START_PLAYBACK] = sharedSettings.automaticallyStartPlayback.name
|
||||
preferences[UI_PLAY_VIDEOS] = sharedSettings.automaticallyPlayVideos.name
|
||||
preferences[UI_SHOW_URL_PREVIEW] = sharedSettings.automaticallyShowUrlPreview.name
|
||||
preferences[UI_HIDE_NAVIGATION_BARS] = sharedSettings.automaticallyHideNavigationBars.name
|
||||
preferences[UI_SHOW_PROFILE_PICTURES] = sharedSettings.automaticallyShowProfilePictures.name
|
||||
preferences[UI_DONT_SHOW_PUSH_NOTIFICATION_SELECTOR] = sharedSettings.dontShowPushNotificationSelector
|
||||
preferences[UI_DONT_ASK_FOR_NOTIFICATION_PERMISSIONS] = sharedSettings.dontAskForNotificationPermissions
|
||||
preferences[UI_FEATURE_SET] = sharedSettings.featureSet.name
|
||||
preferences[UI_GALLERY_SET] = sharedSettings.gallerySet.name
|
||||
preferences[UI_PROPOSE_AI_IMPROVEMENTS] = sharedSettings.automaticallyProposeAiImprovements.name
|
||||
preferences[UI_USE_TRACKED_BROADCASTS] = sharedSettings.useTrackedBroadcasts.name
|
||||
preferences[UI_AUTOMATICALLY_CREATE_DRAFTS] = sharedSettings.automaticallyCreateDrafts.name
|
||||
preferences[UI_SHOW_HOME_NEW_THREADS_TAB] = sharedSettings.showHomeNewThreadsTab
|
||||
preferences[UI_SHOW_HOME_CONVERSATIONS_TAB] = sharedSettings.showHomeConversationsTab
|
||||
preferences[UI_SHOW_HOME_EVERYTHING_TAB] = sharedSettings.showHomeEverythingTab
|
||||
preferences[UI_SHOW_PROFILE_BADGES] = sharedSettings.showProfileBadges
|
||||
preferences[UI_SHOW_PROFILE_APP_RECOMMENDATIONS] = sharedSettings.showProfileAppRecommendations
|
||||
preferences[UI_SHOW_PROFILE_ZAP_RECEIVED_FEED] = sharedSettings.showProfileZapReceivedFeed
|
||||
preferences[UI_SHOW_PROFILE_FOLLOWERS_FEED] = sharedSettings.showProfileFollowersFeed
|
||||
preferences[UI_DONT_SHOW_ONCHAIN_PUBLIC_WARNING] = sharedSettings.dontShowOnchainPublicWarning
|
||||
preferences[UI_SUGGEST_WORKOUTS_FROM_HEALTH_CONNECT] = sharedSettings.suggestWorkoutsFromHealthConnect.name
|
||||
preferences[UI_ACCENT_COLOR] = sharedSettings.accentColor.name
|
||||
preferences[UI_FONT_FAMILY] = sharedSettings.fontFamily.name
|
||||
preferences[UI_FONT_SIZE] = sharedSettings.fontSize.name
|
||||
preferences[UI_COMPOSE_SIGNATURE] = sharedSettings.composeSignature
|
||||
preferences[UI_SHOW_ONCHAIN_WALLET] = sharedSettings.showOnchainWallet
|
||||
preferences[UI_SHOW_PAYTO_ZAP_CHIP] = sharedSettings.showPayToZapChip
|
||||
}
|
||||
|
||||
/**
|
||||
* The one-shot copy out of the single `shared_settings` JSON blob these
|
||||
* settings used to be kept as, in the global encrypted file.
|
||||
*
|
||||
* Guarded, and it has to be. Unlike the per-account migrations, this
|
||||
* store has been the real home of these settings for a while, so most
|
||||
* installs already have a populated one — and copying an old blob over
|
||||
* it would undo every UI change the user has made since. [UI_THEME] is
|
||||
* the test: [write] sets every key unconditionally and is the only
|
||||
* writer, so its absence means this store has never been saved, which
|
||||
* is exactly the install whose settings are still only in the legacy
|
||||
* file.
|
||||
*
|
||||
* Must be handed to the store at construction, so every consumer of the
|
||||
* shared file gets it no matter which one opens the file first.
|
||||
*/
|
||||
fun migrations(loadLegacy: suspend () -> UiSettings?): List<DataMigration<Preferences>> =
|
||||
listOf(
|
||||
CopyOnceMigration("migrated.sharedSettings") { out ->
|
||||
if (out[UI_THEME] == null) {
|
||||
withContext(Dispatchers.IO) {
|
||||
loadLegacy()?.let { out.write(it) }
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.ui.settings
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.AccentColorType
|
||||
import com.vitorpamplona.amethyst.commons.model.FontSizeType
|
||||
import com.vitorpamplona.amethyst.commons.model.ProfileGalleryType
|
||||
import com.vitorpamplona.amethyst.commons.model.ThemeType
|
||||
import com.vitorpamplona.amethyst.commons.model.WarningType
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.accent_color_blue
|
||||
import com.vitorpamplona.amethyst.commons.resources.accent_color_green
|
||||
import com.vitorpamplona.amethyst.commons.resources.accent_color_orange
|
||||
import com.vitorpamplona.amethyst.commons.resources.accent_color_pink
|
||||
import com.vitorpamplona.amethyst.commons.resources.accent_color_purple
|
||||
import com.vitorpamplona.amethyst.commons.resources.accent_color_red
|
||||
import com.vitorpamplona.amethyst.commons.resources.content_warning_hide_all_sensitive_content_option
|
||||
import com.vitorpamplona.amethyst.commons.resources.content_warning_see_warnings_option
|
||||
import com.vitorpamplona.amethyst.commons.resources.content_warning_show_all_sensitive_content_option
|
||||
import com.vitorpamplona.amethyst.commons.resources.dark
|
||||
import com.vitorpamplona.amethyst.commons.resources.font_size_huge
|
||||
import com.vitorpamplona.amethyst.commons.resources.font_size_large
|
||||
import com.vitorpamplona.amethyst.commons.resources.font_size_normal
|
||||
import com.vitorpamplona.amethyst.commons.resources.font_size_small
|
||||
import com.vitorpamplona.amethyst.commons.resources.gallery_type_classic
|
||||
import com.vitorpamplona.amethyst.commons.resources.gallery_type_modern
|
||||
import com.vitorpamplona.amethyst.commons.resources.light
|
||||
import com.vitorpamplona.amethyst.commons.resources.system
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
|
||||
/**
|
||||
* The display label for each UI-settings enum.
|
||||
*
|
||||
* These used to be a constructor argument on the enums themselves, which pinned
|
||||
* `UiSettings` to `commonsUI` — a [StringResource] comes from the generated `Res`
|
||||
* class, and `commons` cannot see it. The settings are plain data that the CLI and
|
||||
* any headless front end may read, so the data moved to `commons/model` and the
|
||||
* labels stayed here, following the same extension-property shape the Tor settings
|
||||
* already use (`ui.tor.resourceId`).
|
||||
*
|
||||
* Each `when` is exhaustive over its enum, so a new constant is a compile error
|
||||
* here rather than a missing label at runtime — the same guarantee the constructor
|
||||
* argument gave.
|
||||
*
|
||||
* Only the enums something actually labels are here. `ConnectivityType`,
|
||||
* `FeatureSetType` and `FontFamilyType` carried a `resourceId` that nothing read —
|
||||
* their pickers are segmented rows and use the narrower `shortLabelRes` helpers in
|
||||
* `AppSettingsScreen` instead — and `BooleanType`'s was misspelled `reourceId`,
|
||||
* which is how it went unnoticed. They are dropped rather than carried over; the
|
||||
* strings they pointed at are still used by those short labels.
|
||||
*/
|
||||
val ThemeType.resourceId: StringResource
|
||||
get() =
|
||||
when (this) {
|
||||
ThemeType.SYSTEM -> Res.string.system
|
||||
ThemeType.LIGHT -> Res.string.light
|
||||
ThemeType.DARK -> Res.string.dark
|
||||
}
|
||||
|
||||
val AccentColorType.resourceId: StringResource
|
||||
get() =
|
||||
when (this) {
|
||||
AccentColorType.PURPLE -> Res.string.accent_color_purple
|
||||
AccentColorType.BLUE -> Res.string.accent_color_blue
|
||||
AccentColorType.GREEN -> Res.string.accent_color_green
|
||||
AccentColorType.ORANGE -> Res.string.accent_color_orange
|
||||
AccentColorType.RED -> Res.string.accent_color_red
|
||||
AccentColorType.PINK -> Res.string.accent_color_pink
|
||||
}
|
||||
|
||||
val FontSizeType.resourceId: StringResource
|
||||
get() =
|
||||
when (this) {
|
||||
FontSizeType.SMALL -> Res.string.font_size_small
|
||||
FontSizeType.NORMAL -> Res.string.font_size_normal
|
||||
FontSizeType.LARGE -> Res.string.font_size_large
|
||||
FontSizeType.HUGE -> Res.string.font_size_huge
|
||||
}
|
||||
|
||||
val ProfileGalleryType.resourceId: StringResource
|
||||
get() =
|
||||
when (this) {
|
||||
ProfileGalleryType.CLASSIC -> Res.string.gallery_type_classic
|
||||
ProfileGalleryType.MODERN -> Res.string.gallery_type_modern
|
||||
}
|
||||
|
||||
val WarningType.resourceId: StringResource
|
||||
get() =
|
||||
when (this) {
|
||||
WarningType.WARN -> Res.string.content_warning_see_warnings_option
|
||||
WarningType.SHOW -> Res.string.content_warning_show_all_sensitive_content_option
|
||||
WarningType.HIDE -> Res.string.content_warning_hide_all_sensitive_content_option
|
||||
}
|
||||
Reference in New Issue
Block a user