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:
Claude
2026-09-24 12:53:20 +00:00
parent 766c38fc89
commit 0387ef3306
27 changed files with 459 additions and 298 deletions
@@ -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,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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 =
@@ -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
@@ -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) }
}
}
},
)
}
}
@@ -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
}