Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:56:56 +01:00
parent 75dc487a7a
commit 39c29d175b
6317 changed files with 388324 additions and 2 deletions

View file

@ -0,0 +1,17 @@
plugins {
id(ThunderbirdPlugins.Library.kmp)
}
android {
namespace = "net.thunderbird.core.preference.impl"
}
kotlin {
sourceSets {
commonMain.dependencies {
api(projects.core.preference.api)
implementation(projects.core.logging.api)
}
}
}

View file

@ -0,0 +1,28 @@
package net.thunderbird.core.preference
class DefaultPreferenceChangeBroker(
private val subscribers: MutableSet<PreferenceChangeSubscriber> = mutableSetOf(),
) : PreferenceChangeBroker, PreferenceChangePublisher {
private val lock = Any()
override fun subscribe(subscriber: PreferenceChangeSubscriber) {
synchronized(lock) {
subscribers.add(subscriber)
}
}
override fun unsubscribe(subscriber: PreferenceChangeSubscriber) {
synchronized(lock) {
subscribers.remove(subscriber)
}
}
override fun publish() {
val currentSubscribers = synchronized(lock) { HashSet(subscribers) }
for (subscriber in currentSubscribers) {
subscriber.receive()
}
}
}

View file

@ -0,0 +1,72 @@
package net.thunderbird.core.preference.debugging
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.thunderbird.core.logging.LogLevel
import net.thunderbird.core.logging.LogLevelManager
import net.thunderbird.core.logging.Logger
import net.thunderbird.core.preference.storage.Storage
import net.thunderbird.core.preference.storage.StorageEditor
private const val TAG = "DefaultDebuggingSettingsPreferenceManager"
class DefaultDebuggingSettingsPreferenceManager(
private val logger: Logger,
private val storage: Storage,
private val storageEditor: StorageEditor,
private val logLevelManager: LogLevelManager,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
private var scope: CoroutineScope = CoroutineScope(SupervisorJob()),
) : DebuggingSettingsPreferenceManager {
private val configState: MutableStateFlow<DebuggingSettings> = MutableStateFlow(value = loadConfig())
private val mutex = Mutex()
override fun getConfig(): DebuggingSettings = configState.value
override fun getConfigFlow(): Flow<DebuggingSettings> = configState
override fun save(config: DebuggingSettings) {
logger.debug(TAG) { "save() called with: config = $config" }
writeConfig(config)
configState.update { config.also(::updateDebugLogLevel) }
}
private fun loadConfig(): DebuggingSettings = DebuggingSettings(
isDebugLoggingEnabled = storage.getBoolean(
KEY_ENABLE_DEBUG_LOGGING,
DEBUGGING_SETTINGS_DEFAULT_IS_DEBUGGING_LOGGING_ENABLED,
),
isSyncLoggingEnabled = storage.getBoolean(
KEY_ENABLE_SYNC_DEBUG_LOGGING,
DEBUGGING_SETTINGS_DEFAULT_IS_SYNC_LOGGING_ENABLED,
),
).also(::updateDebugLogLevel)
private fun writeConfig(config: DebuggingSettings) {
logger.debug(TAG) { "writeConfig() called with: config = $config" }
scope.launch(ioDispatcher) {
mutex.withLock {
storageEditor.putBoolean(KEY_ENABLE_DEBUG_LOGGING, config.isDebugLoggingEnabled)
storageEditor.putBoolean(KEY_ENABLE_SYNC_DEBUG_LOGGING, config.isSyncLoggingEnabled)
storageEditor.commit().also { commited ->
logger.verbose(TAG) { "writeConfig: storageEditor.commit() resulted in: $commited" }
}
}
}
}
private fun updateDebugLogLevel(config: DebuggingSettings) {
if (config.isDebugLoggingEnabled) {
logLevelManager.override(LogLevel.DEBUG)
} else {
logLevelManager.restoreDefault()
}
}
}

View file

@ -0,0 +1,131 @@
package net.thunderbird.core.preference.display
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.thunderbird.core.logging.Logger
import net.thunderbird.core.preference.display.coreSettings.DisplayCoreSettingsPreferenceManager
import net.thunderbird.core.preference.storage.Storage
import net.thunderbird.core.preference.storage.StorageEditor
private const val TAG = "DefaultDisplaySettingsPreferenceManager"
class DefaultDisplaySettingsPreferenceManager(
private val logger: Logger,
private val storage: Storage,
private val storageEditor: StorageEditor,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
private var scope: CoroutineScope = CoroutineScope(SupervisorJob()),
private val coreSettingsPreferenceManager: DisplayCoreSettingsPreferenceManager,
) : DisplaySettingsPreferenceManager {
private val configState: MutableStateFlow<DisplaySettings> = MutableStateFlow(value = loadConfig())
private val mutex = Mutex()
override fun getConfig(): DisplaySettings = configState.value
override fun getConfigFlow(): Flow<DisplaySettings> = configState
override fun save(config: DisplaySettings) {
logger.debug(TAG) { "save() called with: config = $config" }
coreSettingsPreferenceManager.save(config.coreSettings)
writeConfig(config)
configState.update { config }
}
private fun loadConfig(): DisplaySettings = DisplaySettings(
coreSettings = coreSettingsPreferenceManager.getConfig(),
showRecentChanges = storage.getBoolean(
KEY_SHOW_RECENT_CHANGES,
DISPLAY_SETTINGS_DEFAULT_SHOW_RECENT_CHANGES,
),
shouldShowSetupArchiveFolderDialog = storage.getBoolean(
KEY_SHOULD_SHOW_SETUP_ARCHIVE_FOLDER_DIALOG,
DISPLAY_SETTINGS_DEFAULT_SHOULD_SHOW_SETUP_ARCHIVE_FOLDER_DIALOG,
),
isColorizeMissingContactPictures = storage.getBoolean(
KEY_COLORIZE_MISSING_CONTACT_PICTURE,
DISPLAY_SETTINGS_DEFAULT_IS_COLORIZE_MISSING_CONTACT_PICTURE,
),
isChangeContactNameColor = storage.getBoolean(
KEY_CHANGE_REGISTERED_NAME_COLOR,
DISPLAY_SETTINGS_DEFAULT_IS_CHANGE_CONTACT_NAME_COLOR,
),
isUseBackgroundAsUnreadIndicator = storage.getBoolean(
KEY_USE_BACKGROUND_AS_UNREAD_INDICATOR,
DISPLAY_SETTINGS_DEFAULT_IS_USE_BACKGROUND_AS_INDICATOR,
),
isUseMessageViewFixedWidthFont = storage.getBoolean(
KEY_MESSAGE_VIEW_FIXED_WIDTH_FONT,
DISPLAY_SETTINGS_DEFAULT_IS_USE_MESSAGE_VIEW_FIXED_WIDTH_FONT,
),
isAutoFitWidth = storage.getBoolean(
KEY_AUTO_FIT_WIDTH,
DISPLAY_SETTINGS_DEFAULT_IS_AUTO_FIT_WIDTH,
),
isShowAnimations = storage.getBoolean(
KEY_ANIMATION,
DISPLAY_SETTINGS_DEFAULT_IS_SHOW_ANIMATION,
),
isShowCorrespondentNames = storage.getBoolean(
KEY_SHOW_CORRESPONDENT_NAMES,
DISPLAY_SETTINGS_DEFAULT_IS_SHOW_CORRESPONDENT_NAMES,
),
isShowContactName = storage.getBoolean(
KEY_SHOW_CONTACT_NAME,
DISPLAY_SETTINGS_DEFAULT_IS_SHOW_CONTACT_NAME,
),
isShowContactPicture = storage.getBoolean(
KEY_SHOW_CONTACT_PICTURE,
DISPLAY_SETTINGS_DEFAULT_IS_SHOW_CONTACT_PICTURE,
),
)
private fun writeConfig(config: DisplaySettings) {
logger.debug(TAG) { "writeConfig() called with: config = $config" }
scope.launch(ioDispatcher) {
mutex.withLock {
storageEditor.putBoolean(
KEY_CHANGE_REGISTERED_NAME_COLOR,
config.isChangeContactNameColor,
)
storageEditor.putBoolean(
KEY_COLORIZE_MISSING_CONTACT_PICTURE,
config.isColorizeMissingContactPictures,
)
storageEditor.putBoolean(
KEY_SHOULD_SHOW_SETUP_ARCHIVE_FOLDER_DIALOG,
config.shouldShowSetupArchiveFolderDialog,
)
storageEditor.putBoolean(KEY_SHOW_CONTACT_NAME, config.isShowContactName)
storageEditor.putBoolean(
KEY_SHOW_CORRESPONDENT_NAMES,
config.isShowCorrespondentNames,
)
storageEditor.putBoolean(KEY_SHOW_RECENT_CHANGES, config.showRecentChanges)
storageEditor.putBoolean(KEY_ANIMATION, config.isShowAnimations)
storageEditor.putBoolean(
KEY_SHOW_CONTACT_PICTURE,
config.isShowContactPicture,
)
storageEditor.putBoolean(
KEY_USE_BACKGROUND_AS_UNREAD_INDICATOR,
config.isUseBackgroundAsUnreadIndicator,
)
storageEditor.putBoolean(
KEY_MESSAGE_VIEW_FIXED_WIDTH_FONT,
config.isUseMessageViewFixedWidthFont,
)
storageEditor.putBoolean(KEY_AUTO_FIT_WIDTH, config.isAutoFitWidth)
storageEditor.commit().also { commited ->
logger.verbose(TAG) { "writeConfig: storageEditor.commit() resulted in: $commited" }
}
}
}
}
}

View file

@ -0,0 +1,89 @@
package net.thunderbird.core.preference.display.coreSettings
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.thunderbird.core.logging.Logger
import net.thunderbird.core.preference.display.KEY_THEME
import net.thunderbird.core.preference.storage.Storage
import net.thunderbird.core.preference.storage.StorageEditor
import net.thunderbird.core.preference.storage.getEnumOrDefault
import net.thunderbird.core.preference.storage.putEnum
private const val TAG = "DefaultDisplayCoreSettingsPreferenceManager"
class DefaultDisplayCoreSettingsPreferenceManager(
private val logger: Logger,
private val storage: Storage,
private val storageEditor: StorageEditor,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
private var scope: CoroutineScope = CoroutineScope(SupervisorJob()),
) : DisplayCoreSettingsPreferenceManager {
private val configState: MutableStateFlow<DisplayCoreSettings> = MutableStateFlow(value = loadConfig())
private val mutex = Mutex()
override fun getConfig(): DisplayCoreSettings = configState.value
override fun getConfigFlow(): Flow<DisplayCoreSettings> = configState
override fun save(config: DisplayCoreSettings) {
logger.debug(TAG) { "save() called with: config = $config" }
writeConfig(config)
configState.update { config }
}
private fun loadConfig(): DisplayCoreSettings = DisplayCoreSettings(
fixedMessageViewTheme = storage.getBoolean(
KEY_FIXED_MESSAGE_VIEW_THEME,
DISPLAY_SETTINGS_DEFAULT_FIXED_MESSAGE_VIEW_THEME,
),
appTheme = storage.getEnumOrDefault(KEY_THEME, DISPLAY_SETTINGS_DEFAULT_APP_THEME),
messageViewTheme = storage.getEnumOrDefault(
KEY_MESSAGE_VIEW_THEME,
DISPLAY_SETTINGS_DEFAULT_MESSAGE_VIEW_THEME,
),
messageComposeTheme = storage.getEnumOrDefault(
KEY_MESSAGE_COMPOSE_THEME,
DISPLAY_SETTINGS_DEFAULT_MESSAGE_COMPOSE_THEME,
),
appLanguage = storage.getStringOrDefault(
KEY_APP_LANGUAGE,
DISPLAY_SETTINGS_DEFAULT_APP_LANGUAGE,
),
splitViewMode = storage.getEnumOrDefault(
KEY_SPLIT_VIEW_MODE,
DISPLAY_SETTINGS_DEFAULT_SPLIT_VIEW_MODE,
),
)
private fun writeConfig(config: DisplayCoreSettings) {
logger.debug(TAG) { "writeConfig() called with: config = $config" }
scope.launch(ioDispatcher) {
mutex.withLock {
storageEditor.putEnum(KEY_THEME, config.appTheme)
storageEditor.putEnum(KEY_MESSAGE_VIEW_THEME, config.messageViewTheme)
storageEditor.putEnum(
KEY_MESSAGE_COMPOSE_THEME,
config.messageComposeTheme,
)
storageEditor.putBoolean(
KEY_FIXED_MESSAGE_VIEW_THEME,
config.fixedMessageViewTheme,
)
storageEditor.putString(KEY_APP_LANGUAGE, config.appLanguage)
storageEditor.putEnum(KEY_SPLIT_VIEW_MODE, config.splitViewMode)
storageEditor.commit().also { commited ->
logger.verbose(TAG) { "writeConfig: storageEditor.commit() resulted in: $commited" }
}
}
}
}
}

View file

@ -0,0 +1,95 @@
package net.thunderbird.core.preference.display.inboxSettings
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.thunderbird.core.logging.Logger
import net.thunderbird.core.preference.storage.Storage
import net.thunderbird.core.preference.storage.StorageEditor
private const val TAG = "DefaultDisplayInboxSettingsPreferenceManager"
class DefaultDisplayInboxSettingsPreferenceManager(
private val logger: Logger,
private val storage: Storage,
private val storageEditor: StorageEditor,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
private var scope: CoroutineScope = CoroutineScope(SupervisorJob()),
) : DisplayInboxSettingsPreferenceManager {
private val configState: MutableStateFlow<DisplayInboxSettings> = MutableStateFlow(value = loadConfig())
private val mutex = Mutex()
override fun getConfig(): DisplayInboxSettings = configState.value
override fun getConfigFlow(): Flow<DisplayInboxSettings> = configState
override fun save(config: DisplayInboxSettings) {
logger.debug(TAG) { "save() called with: config = $config" }
writeConfig(config)
configState.update { config }
}
private fun loadConfig(): DisplayInboxSettings = DisplayInboxSettings(
isShowUnifiedInbox = storage.getBoolean(
KEY_SHOW_UNIFIED_INBOX,
DISPLAY_SETTINGS_DEFAULT_IS_SHOW_UNIFIED_INBOX,
),
isShowComposeButtonOnMessageList = storage.getBoolean(
KEY_SHOW_COMPOSE_BUTTON_ON_MESSAGE_LIST,
DISPLAY_SETTINGS_DEFAULT_IS_SHOW_COMPOSE_BUTTON_ON_MESSAGE_LIST,
),
isThreadedViewEnabled = storage.getBoolean(
KEY_THREAD_VIEW_ENABLED,
DISPLAY_SETTINGS_DEFAULT_IS_THREAD_VIEW_ENABLED,
),
isShowStarredCount = storage.getBoolean(
KEY_SHOW_STAR_COUNT,
DISPLAY_SETTINGS_DEFAULT_IS_SHOW_STAR_COUNT,
),
isShowMessageListStars = storage.getBoolean(
KEY_SHOW_MESSAGE_LIST_STARS,
DISPLAY_SETTINGS_DEFAULT_IS_SHOW_MESSAGE_LIST_STAR,
),
isMessageListSenderAboveSubject = storage.getBoolean(
KEY_MESSAGE_LIST_SENDER_ABOVE_SUBJECT,
DISPLAY_SETTINGS_DEFAULT_IS_MESSAGE_LIST_SENDER_ABOVE_SUBJECT,
),
)
private fun writeConfig(config: DisplayInboxSettings) {
logger.debug(TAG) { "writeConfig() called with: config = $config" }
scope.launch(ioDispatcher) {
mutex.withLock {
storageEditor.putBoolean(
KEY_MESSAGE_LIST_SENDER_ABOVE_SUBJECT,
config.isMessageListSenderAboveSubject,
)
storageEditor.putBoolean(
KEY_SHOW_MESSAGE_LIST_STARS,
config.isShowMessageListStars,
)
storageEditor.putBoolean(
KEY_SHOW_COMPOSE_BUTTON_ON_MESSAGE_LIST,
config.isShowComposeButtonOnMessageList,
)
storageEditor.putBoolean(
KEY_THREAD_VIEW_ENABLED,
config.isThreadedViewEnabled,
)
storageEditor.putBoolean(KEY_SHOW_UNIFIED_INBOX, config.isShowUnifiedInbox)
storageEditor.putBoolean(KEY_SHOW_STAR_COUNT, config.isShowStarredCount)
storageEditor.commit().also { commited ->
logger.verbose(TAG) { "writeConfig: storageEditor.commit() resulted in: $commited" }
}
}
}
}
}

View file

@ -0,0 +1,55 @@
package net.thunderbird.core.preference.network
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.thunderbird.core.logging.Logger
import net.thunderbird.core.preference.storage.Storage
import net.thunderbird.core.preference.storage.StorageEditor
import net.thunderbird.core.preference.storage.getEnumOrDefault
import net.thunderbird.core.preference.storage.putEnum
private const val TAG = "DefaultNetworkSettingsPreferenceManager"
class DefaultNetworkSettingsPreferenceManager(
private val logger: Logger,
private val storage: Storage,
private val storageEditor: StorageEditor,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
private var scope: CoroutineScope = CoroutineScope(SupervisorJob()),
) : NetworkSettingsPreferenceManager {
private val configState: MutableStateFlow<NetworkSettings> = MutableStateFlow(value = loadConfig())
private val mutex = Mutex()
override fun getConfig(): NetworkSettings = configState.value
override fun getConfigFlow(): Flow<NetworkSettings> = configState
override fun save(config: NetworkSettings) {
logger.debug(TAG) { "save() called with: config = $config" }
writeConfig(config)
configState.update { config }
}
private fun loadConfig(): NetworkSettings = NetworkSettings(
backgroundOps = storage.getEnumOrDefault(KEY_BG_OPS, NETWORK_SETTINGS_DEFAULT_BACKGROUND_OPS),
)
private fun writeConfig(config: NetworkSettings) {
logger.debug(TAG) { "writeConfig() called with: config = $config" }
scope.launch(ioDispatcher) {
mutex.withLock {
storageEditor.putEnum(KEY_BG_OPS, config.backgroundOps)
storageEditor.commit().also { commited ->
logger.verbose(TAG) { "writeConfig: storageEditor.commit() resulted in: $commited" }
}
}
}
}
}

View file

@ -0,0 +1,64 @@
package net.thunderbird.core.preference.notification
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.thunderbird.core.logging.Logger
import net.thunderbird.core.preference.storage.Storage
import net.thunderbird.core.preference.storage.StorageEditor
private const val TAG = "DefaultNotificationPreferenceManager"
class DefaultNotificationPreferenceManager(
private val logger: Logger,
storage: Storage,
private val storageEditor: StorageEditor,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
private var scope: CoroutineScope = CoroutineScope(SupervisorJob()),
) : NotificationPreferenceManager {
private val mutex = Mutex()
private val configState = MutableStateFlow(
value = NotificationPreference(
isQuietTimeEnabled = storage.getBoolean(
key = KEY_QUIET_TIME_ENABLED,
defValue = NOTIFICATION_PREFERENCE_DEFAULT_IS_QUIET_TIME_ENABLED,
),
quietTimeStarts = storage.getStringOrDefault(
key = KEY_QUIET_TIME_STARTS,
defValue = NOTIFICATION_PREFERENCE_DEFAULT_QUIET_TIME_STARTS,
),
quietTimeEnds = storage.getStringOrDefault(
key = KEY_QUIET_TIME_ENDS,
defValue = NOTIFICATION_PREFERENCE_DEFAULT_QUIET_TIME_END,
),
),
)
override fun getConfig(): NotificationPreference = configState.value
override fun getConfigFlow(): Flow<NotificationPreference> = configState
override fun save(config: NotificationPreference) {
logger.debug(TAG) { "writeConfig() called with: config = $config" }
scope.launch(ioDispatcher) {
mutex.withLock {
storageEditor.putString(KEY_QUIET_TIME_ENDS, config.quietTimeEnds)
storageEditor.putString(KEY_QUIET_TIME_STARTS, config.quietTimeStarts)
storageEditor.putBoolean(
KEY_QUIET_TIME_ENABLED,
config.isQuietTimeEnabled,
)
storageEditor.commit().also { commited ->
logger.verbose(TAG) { "writeConfig: storageEditor.commit() resulted in: $commited" }
}
}
configState.update { config }
}
}
}

View file

@ -0,0 +1,61 @@
package net.thunderbird.core.preference.privacy
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.thunderbird.core.logging.Logger
import net.thunderbird.core.preference.storage.Storage
import net.thunderbird.core.preference.storage.StorageEditor
private const val TAG = "DefaultPrivacySettingsPreferenceManager"
class DefaultPrivacySettingsPreferenceManager(
private val logger: Logger,
private val storage: Storage,
private val storageEditor: StorageEditor,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
private var scope: CoroutineScope = CoroutineScope(SupervisorJob()),
) : PrivacySettingsPreferenceManager {
private val configState: MutableStateFlow<PrivacySettings> = MutableStateFlow(value = loadConfig())
private val mutex = Mutex()
override fun getConfig(): PrivacySettings = configState.value
override fun getConfigFlow(): Flow<PrivacySettings> = configState
override fun save(config: PrivacySettings) {
logger.debug(TAG) { "save() called with: config = $config" }
writeConfig(config)
configState.update { config }
}
private fun loadConfig(): PrivacySettings = PrivacySettings(
isHideTimeZone = storage.getBoolean(
key = KEY_HIDE_TIME_ZONE,
defValue = PRIVACY_SETTINGS_DEFAULT_HIDE_TIME_ZONE,
),
isHideUserAgent = storage.getBoolean(
key = KEY_HIDE_USER_AGENT,
defValue = PRIVACY_SETTINGS_DEFAULT_HIDE_USER_AGENT,
),
)
private fun writeConfig(config: PrivacySettings) {
logger.debug(TAG) { "writeConfig() called with: config = $config" }
scope.launch(ioDispatcher) {
mutex.withLock {
storageEditor.putBoolean(KEY_HIDE_TIME_ZONE, config.isHideTimeZone)
storageEditor.putBoolean(KEY_HIDE_USER_AGENT, config.isHideUserAgent)
storageEditor.commit().also { commited ->
logger.verbose(TAG) { "writeConfig: storageEditor.commit() resulted in: $commited" }
}
}
}
}
}

View file

@ -0,0 +1,56 @@
package net.thunderbird.core.preference.storage
import net.thunderbird.core.logging.Logger
class InMemoryStorage(
private val values: Map<String, String>,
private val logger: Logger,
) : Storage {
override fun isEmpty(): Boolean = values.isEmpty()
override fun contains(key: String): Boolean = values.contains(key)
override fun getAll(): Map<String, String> = values
override fun getBoolean(key: String, defValue: Boolean): Boolean =
values[key]
?.toBoolean()
?: defValue
override fun getInt(key: String, defValue: Int): Int {
val value = values[key] ?: return defValue
return try {
value.toInt()
} catch (e: NumberFormatException) {
logger.error(
message = { "Could not parse int" },
throwable = e,
)
defValue
}
}
override fun getLong(key: String, defValue: Long): Long {
val value = values[key] ?: return defValue
return try {
value.toLong()
} catch (e: NumberFormatException) {
logger.error(
message = { "Could not parse long" },
throwable = e,
)
defValue
}
}
@Throws(NoSuchElementException::class)
override fun getString(key: String): String =
values.getValue(key)
override fun getStringOrDefault(key: String, defValue: String): String =
getStringOrNull(key) ?: defValue
override fun getStringOrNull(key: String): String? =
values[key]
}

View file

@ -0,0 +1,49 @@
package net.thunderbird.core.preference
import assertk.assertThat
import assertk.assertions.contains
import assertk.assertions.doesNotContain
import assertk.assertions.isEqualTo
import kotlin.test.Test
class DefaultPreferenceChangeBrokerTest {
@Test
fun `subscribe should add subscriber`() {
val subscriber = PreferenceChangeSubscriber { }
val subscribers = mutableSetOf<PreferenceChangeSubscriber>()
val broker = DefaultPreferenceChangeBroker(subscribers)
broker.subscribe(subscriber)
assertThat(subscribers.size).isEqualTo(1)
assertThat(subscribers).contains(subscriber)
}
@Test
fun `unsubscribe should remove subscriber`() {
val subscriber = PreferenceChangeSubscriber { }
val subscribers = mutableSetOf<PreferenceChangeSubscriber>(subscriber)
val broker = DefaultPreferenceChangeBroker(subscribers)
broker.unsubscribe(subscriber)
assertThat(subscribers.size).isEqualTo(0)
assertThat(subscribers).doesNotContain(subscriber)
}
@Test
fun `publish should notify subscribers`() {
var received = false
val subscriber = PreferenceChangeSubscriber { received = true }
var receivedOther = false
val otherSubscriber = PreferenceChangeSubscriber { receivedOther = true }
val subscribers = mutableSetOf<PreferenceChangeSubscriber>(subscriber, otherSubscriber)
val broker = DefaultPreferenceChangeBroker(subscribers)
broker.publish()
assertThat(received).isEqualTo(true)
assertThat(receivedOther).isEqualTo(true)
}
}