Repo created
This commit is contained in:
parent
75dc487a7a
commit
39c29d175b
6317 changed files with 388324 additions and 2 deletions
10
core/preference/api/build.gradle.kts
Normal file
10
core/preference/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
plugins {
|
||||
id(ThunderbirdPlugins.Library.kmp)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "net.thunderbird.core.preference"
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package net.thunderbird.core.preference.debugging
|
||||
|
||||
import net.thunderbird.core.preference.BuildConfig
|
||||
|
||||
actual val isDebug: Boolean = BuildConfig.DEBUG
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package net.thunderbird.core.preference
|
||||
|
||||
import net.thunderbird.core.preference.debugging.DebuggingSettings
|
||||
import net.thunderbird.core.preference.display.DisplaySettings
|
||||
import net.thunderbird.core.preference.network.NetworkSettings
|
||||
import net.thunderbird.core.preference.notification.NotificationPreference
|
||||
import net.thunderbird.core.preference.privacy.PrivacySettings
|
||||
|
||||
/**
|
||||
* Stores a snapshot of the app's general settings.
|
||||
*
|
||||
* When adding a setting here, make sure to also add it in these places:
|
||||
* - [GeneralSettingsManager] (write function)
|
||||
* - [GeneralSettingsDescriptions]
|
||||
*/
|
||||
// TODO: Move over settings from K9
|
||||
data class GeneralSettings(
|
||||
val network: NetworkSettings = NetworkSettings(),
|
||||
val notification: NotificationPreference = NotificationPreference(),
|
||||
val display: DisplaySettings = DisplaySettings(),
|
||||
val privacy: PrivacySettings = PrivacySettings(),
|
||||
val debugging: DebuggingSettings = DebuggingSettings(),
|
||||
)
|
||||
|
||||
enum class BackgroundSync {
|
||||
ALWAYS,
|
||||
NEVER,
|
||||
FOLLOW_SYSTEM_AUTO_SYNC,
|
||||
}
|
||||
|
||||
enum class AppTheme {
|
||||
LIGHT,
|
||||
DARK,
|
||||
FOLLOW_SYSTEM,
|
||||
}
|
||||
|
||||
enum class SubTheme {
|
||||
LIGHT,
|
||||
DARK,
|
||||
USE_GLOBAL,
|
||||
}
|
||||
|
||||
enum class BackgroundOps {
|
||||
ALWAYS,
|
||||
NEVER,
|
||||
WHEN_CHECKED_AUTO_SYNC,
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls when to use the message list split view.
|
||||
*/
|
||||
enum class SplitViewMode {
|
||||
ALWAYS,
|
||||
NEVER,
|
||||
WHEN_IN_LANDSCAPE,
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package net.thunderbird.core.preference
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Retrieve and modify general settings.
|
||||
*
|
||||
*/
|
||||
interface GeneralSettingsManager : PreferenceManager<GeneralSettings> {
|
||||
@Deprecated(
|
||||
message = "Use PreferenceManager<GeneralSettings>.getConfig() instead",
|
||||
replaceWith = ReplaceWith(
|
||||
expression = "getConfig()",
|
||||
),
|
||||
)
|
||||
fun getSettings(): GeneralSettings
|
||||
|
||||
@Deprecated(
|
||||
message = "Use PreferenceManager<GeneralSettings>.getConfigFlow() instead",
|
||||
replaceWith = ReplaceWith(
|
||||
expression = "getConfigFlow()",
|
||||
),
|
||||
)
|
||||
fun getSettingsFlow(): Flow<GeneralSettings>
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package net.thunderbird.core.preference
|
||||
|
||||
/**
|
||||
* Broker to manage subscribers and notify them about changes in the preferences, when the
|
||||
* [PreferenceChangePublisher] publishes a change.
|
||||
*/
|
||||
interface PreferenceChangeBroker {
|
||||
|
||||
/**
|
||||
* Subscribe to preference changes.
|
||||
*
|
||||
* @param subscriber The subscriber to be notified about preference changes.
|
||||
*/
|
||||
fun subscribe(subscriber: PreferenceChangeSubscriber)
|
||||
|
||||
/**
|
||||
* Unsubscribe from preference changes.
|
||||
*
|
||||
* @param subscriber The subscriber that no longer wants to be notified about preference changes.
|
||||
*/
|
||||
fun unsubscribe(subscriber: PreferenceChangeSubscriber)
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package net.thunderbird.core.preference
|
||||
|
||||
/**
|
||||
* Publishes changes of preferences to all subscribers.
|
||||
*/
|
||||
interface PreferenceChangePublisher {
|
||||
|
||||
/**
|
||||
* Publish a change in the preferences.
|
||||
*/
|
||||
fun publish()
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package net.thunderbird.core.preference
|
||||
|
||||
/**
|
||||
* Subscribe to be notified about changes in the preferences.
|
||||
*/
|
||||
fun interface PreferenceChangeSubscriber {
|
||||
|
||||
/**
|
||||
* Called when preferences change.
|
||||
*/
|
||||
fun receive()
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package net.thunderbird.core.preference
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Interface for managing preferences of a specific type.
|
||||
*
|
||||
* This interface provides methods to save, retrieve, and observe changes to a configuration object.
|
||||
*
|
||||
* @param T The type of the configuration object being managed.
|
||||
*/
|
||||
interface PreferenceManager<T> {
|
||||
/**
|
||||
* Saves the given configuration.
|
||||
*
|
||||
* @param config The configuration of type [T] to be saved.
|
||||
*/
|
||||
fun save(config: T)
|
||||
|
||||
/**
|
||||
* Retrieves a snapshot of the current configuration.
|
||||
*
|
||||
* **Note:** This function provides a one-time snapshot of the configuration.
|
||||
* For observing configuration changes reactively, it is recommended to use [getConfigFlow] instead.
|
||||
*
|
||||
* @return The current configuration of type [T].
|
||||
*/
|
||||
fun getConfig(): T
|
||||
|
||||
/**
|
||||
* Returns a [Flow] that emits the configuration whenever it changes.
|
||||
*
|
||||
* This allows observing changes to the configuration in a reactive way.
|
||||
*
|
||||
* @return A [Flow] of [T] representing the configuration.
|
||||
*/
|
||||
fun getConfigFlow(): Flow<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the configuration by applying the given [updater] function to the current configuration.
|
||||
*
|
||||
* This function is an inline extension function for [PreferenceManager].
|
||||
* It retrieves the current configuration, applies the [updater] function to it,
|
||||
* and then saves the updated configuration.
|
||||
*
|
||||
* @param T The type of the configuration.
|
||||
* @param updater A lambda function that takes the current configuration of type [T]
|
||||
* and returns the updated configuration of type [T].
|
||||
*/
|
||||
inline fun <reified T> PreferenceManager<T>.update(updater: (T) -> T) {
|
||||
val config = getConfig()
|
||||
save(updater(config))
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package net.thunderbird.core.preference.debugging
|
||||
|
||||
expect val isDebug: Boolean
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package net.thunderbird.core.preference.debugging
|
||||
|
||||
const val DEBUGGING_SETTINGS_DEFAULT_IS_SYNC_LOGGING_ENABLED = false
|
||||
val DEBUGGING_SETTINGS_DEFAULT_IS_DEBUGGING_LOGGING_ENABLED = isDebug
|
||||
|
||||
data class DebuggingSettings(
|
||||
val isDebugLoggingEnabled: Boolean = DEBUGGING_SETTINGS_DEFAULT_IS_DEBUGGING_LOGGING_ENABLED,
|
||||
val isSyncLoggingEnabled: Boolean = DEBUGGING_SETTINGS_DEFAULT_IS_SYNC_LOGGING_ENABLED,
|
||||
)
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package net.thunderbird.core.preference.debugging
|
||||
|
||||
import net.thunderbird.core.preference.PreferenceManager
|
||||
|
||||
const val KEY_ENABLE_DEBUG_LOGGING = "enableDebugLogging"
|
||||
const val KEY_ENABLE_SYNC_DEBUG_LOGGING = "enableSyncDebugLogging"
|
||||
|
||||
interface DebuggingSettingsPreferenceManager : PreferenceManager<DebuggingSettings>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package net.thunderbird.core.preference.display
|
||||
|
||||
import net.thunderbird.core.preference.display.coreSettings.DisplayCoreSettings
|
||||
import net.thunderbird.core.preference.display.inboxSettings.DisplayInboxSettings
|
||||
|
||||
const val DISPLAY_SETTINGS_DEFAULT_IS_SHOW_ANIMATION = true
|
||||
const val DISPLAY_SETTINGS_DEFAULT_IS_SHOW_CORRESPONDENT_NAMES = true
|
||||
const val DISPLAY_SETTINGS_DEFAULT_SHOW_RECENT_CHANGES = true
|
||||
const val DISPLAY_SETTINGS_DEFAULT_SHOULD_SHOW_SETUP_ARCHIVE_FOLDER_DIALOG = true
|
||||
const val DISPLAY_SETTINGS_DEFAULT_IS_SHOW_CONTACT_NAME = false
|
||||
const val DISPLAY_SETTINGS_DEFAULT_IS_SHOW_CONTACT_PICTURE = true
|
||||
const val DISPLAY_SETTINGS_DEFAULT_IS_CHANGE_CONTACT_NAME_COLOR = true
|
||||
const val DISPLAY_SETTINGS_DEFAULT_IS_COLORIZE_MISSING_CONTACT_PICTURE = false
|
||||
const val DISPLAY_SETTINGS_DEFAULT_IS_USE_BACKGROUND_AS_INDICATOR = false
|
||||
const val DISPLAY_SETTINGS_DEFAULT_IS_USE_MESSAGE_VIEW_FIXED_WIDTH_FONT = false
|
||||
const val DISPLAY_SETTINGS_DEFAULT_IS_AUTO_FIT_WIDTH = true
|
||||
|
||||
data class DisplaySettings(
|
||||
val coreSettings: DisplayCoreSettings = DisplayCoreSettings(),
|
||||
val inboxSettings: DisplayInboxSettings = DisplayInboxSettings(),
|
||||
val isShowAnimations: Boolean = DISPLAY_SETTINGS_DEFAULT_IS_SHOW_ANIMATION,
|
||||
val isShowCorrespondentNames: Boolean = DISPLAY_SETTINGS_DEFAULT_IS_SHOW_CORRESPONDENT_NAMES,
|
||||
val showRecentChanges: Boolean = DISPLAY_SETTINGS_DEFAULT_SHOW_RECENT_CHANGES,
|
||||
val shouldShowSetupArchiveFolderDialog: Boolean = DISPLAY_SETTINGS_DEFAULT_SHOULD_SHOW_SETUP_ARCHIVE_FOLDER_DIALOG,
|
||||
val isShowContactName: Boolean = DISPLAY_SETTINGS_DEFAULT_IS_SHOW_CONTACT_NAME,
|
||||
val isShowContactPicture: Boolean = DISPLAY_SETTINGS_DEFAULT_IS_SHOW_CONTACT_PICTURE,
|
||||
val isChangeContactNameColor: Boolean = DISPLAY_SETTINGS_DEFAULT_IS_CHANGE_CONTACT_NAME_COLOR,
|
||||
val isColorizeMissingContactPictures: Boolean = DISPLAY_SETTINGS_DEFAULT_IS_COLORIZE_MISSING_CONTACT_PICTURE,
|
||||
val isUseBackgroundAsUnreadIndicator: Boolean = DISPLAY_SETTINGS_DEFAULT_IS_USE_BACKGROUND_AS_INDICATOR,
|
||||
val isUseMessageViewFixedWidthFont: Boolean = DISPLAY_SETTINGS_DEFAULT_IS_USE_MESSAGE_VIEW_FIXED_WIDTH_FONT,
|
||||
val isAutoFitWidth: Boolean = DISPLAY_SETTINGS_DEFAULT_IS_AUTO_FIT_WIDTH,
|
||||
)
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package net.thunderbird.core.preference.display
|
||||
|
||||
import net.thunderbird.core.preference.PreferenceManager
|
||||
|
||||
const val KEY_SHOW_CONTACT_NAME = "showContactName"
|
||||
const val KEY_SHOW_CORRESPONDENT_NAMES = "showCorrespondentNames"
|
||||
const val KEY_SHOW_RECENT_CHANGES = "showRecentChanges"
|
||||
const val KEY_THEME = "theme"
|
||||
const val KEY_ANIMATION = "animations"
|
||||
const val KEY_SHOULD_SHOW_SETUP_ARCHIVE_FOLDER_DIALOG = "shouldShowSetupArchiveFolderDialog"
|
||||
const val KEY_CHANGE_REGISTERED_NAME_COLOR = "changeRegisteredNameColor"
|
||||
const val KEY_COLORIZE_MISSING_CONTACT_PICTURE = "colorizeMissingContactPictures"
|
||||
const val KEY_USE_BACKGROUND_AS_UNREAD_INDICATOR = "isUseBackgroundAsUnreadIndicator"
|
||||
const val KEY_MESSAGE_VIEW_FIXED_WIDTH_FONT = "messageViewFixedWidthFont"
|
||||
const val KEY_AUTO_FIT_WIDTH = "autofitWidth"
|
||||
const val KEY_SHOW_CONTACT_PICTURE = "showContactPicture"
|
||||
|
||||
interface DisplaySettingsPreferenceManager : PreferenceManager<DisplaySettings>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package net.thunderbird.core.preference.display.coreSettings
|
||||
|
||||
import net.thunderbird.core.preference.AppTheme
|
||||
import net.thunderbird.core.preference.SplitViewMode
|
||||
import net.thunderbird.core.preference.SubTheme
|
||||
|
||||
const val DISPLAY_SETTINGS_DEFAULT_APP_LANGUAGE = ""
|
||||
const val DISPLAY_SETTINGS_DEFAULT_FIXED_MESSAGE_VIEW_THEME = true
|
||||
val DISPLAY_SETTINGS_DEFAULT_APP_THEME = AppTheme.FOLLOW_SYSTEM
|
||||
val DISPLAY_SETTINGS_DEFAULT_MESSAGE_COMPOSE_THEME = SubTheme.USE_GLOBAL
|
||||
val DISPLAY_SETTINGS_DEFAULT_SPLIT_VIEW_MODE = SplitViewMode.NEVER
|
||||
val DISPLAY_SETTINGS_DEFAULT_MESSAGE_VIEW_THEME = SubTheme.USE_GLOBAL
|
||||
data class DisplayCoreSettings(
|
||||
val fixedMessageViewTheme: Boolean = DISPLAY_SETTINGS_DEFAULT_FIXED_MESSAGE_VIEW_THEME,
|
||||
val appTheme: AppTheme = DISPLAY_SETTINGS_DEFAULT_APP_THEME,
|
||||
val messageViewTheme: SubTheme = DISPLAY_SETTINGS_DEFAULT_MESSAGE_VIEW_THEME,
|
||||
val messageComposeTheme: SubTheme = DISPLAY_SETTINGS_DEFAULT_MESSAGE_COMPOSE_THEME,
|
||||
val appLanguage: String = DISPLAY_SETTINGS_DEFAULT_APP_LANGUAGE,
|
||||
val splitViewMode: SplitViewMode = DISPLAY_SETTINGS_DEFAULT_SPLIT_VIEW_MODE,
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package net.thunderbird.core.preference.display.coreSettings
|
||||
|
||||
import net.thunderbird.core.preference.PreferenceManager
|
||||
|
||||
const val KEY_FIXED_MESSAGE_VIEW_THEME = "fixedMessageViewTheme"
|
||||
const val KEY_MESSAGE_VIEW_THEME = "messageViewTheme"
|
||||
const val KEY_MESSAGE_COMPOSE_THEME = "messageComposeTheme"
|
||||
const val KEY_APP_LANGUAGE = "language"
|
||||
const val KEY_SPLIT_VIEW_MODE = "splitViewMode"
|
||||
|
||||
interface DisplayCoreSettingsPreferenceManager : PreferenceManager<DisplayCoreSettings>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package net.thunderbird.core.preference.display.inboxSettings
|
||||
|
||||
const val DISPLAY_SETTINGS_DEFAULT_IS_SHOW_UNIFIED_INBOX = false
|
||||
const val DISPLAY_SETTINGS_DEFAULT_IS_SHOW_STAR_COUNT = false
|
||||
const val DISPLAY_SETTINGS_DEFAULT_IS_SHOW_MESSAGE_LIST_STAR = true
|
||||
const val DISPLAY_SETTINGS_DEFAULT_IS_MESSAGE_LIST_SENDER_ABOVE_SUBJECT = false
|
||||
const val DISPLAY_SETTINGS_DEFAULT_IS_SHOW_COMPOSE_BUTTON_ON_MESSAGE_LIST = true
|
||||
const val DISPLAY_SETTINGS_DEFAULT_IS_THREAD_VIEW_ENABLED = true
|
||||
|
||||
data class DisplayInboxSettings(
|
||||
val isShowUnifiedInbox: Boolean = DISPLAY_SETTINGS_DEFAULT_IS_SHOW_UNIFIED_INBOX,
|
||||
val isShowStarredCount: Boolean = DISPLAY_SETTINGS_DEFAULT_IS_SHOW_STAR_COUNT,
|
||||
val isShowMessageListStars: Boolean = DISPLAY_SETTINGS_DEFAULT_IS_SHOW_MESSAGE_LIST_STAR,
|
||||
val isMessageListSenderAboveSubject: Boolean = DISPLAY_SETTINGS_DEFAULT_IS_MESSAGE_LIST_SENDER_ABOVE_SUBJECT,
|
||||
val isShowComposeButtonOnMessageList: Boolean = DISPLAY_SETTINGS_DEFAULT_IS_SHOW_COMPOSE_BUTTON_ON_MESSAGE_LIST,
|
||||
val isThreadedViewEnabled: Boolean = DISPLAY_SETTINGS_DEFAULT_IS_THREAD_VIEW_ENABLED,
|
||||
)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package net.thunderbird.core.preference.display.inboxSettings
|
||||
|
||||
import net.thunderbird.core.preference.PreferenceManager
|
||||
|
||||
const val KEY_MESSAGE_LIST_SENDER_ABOVE_SUBJECT = "messageListSenderAboveSubject"
|
||||
const val KEY_SHOW_COMPOSE_BUTTON_ON_MESSAGE_LIST = "showComposeButtonOnMessageList"
|
||||
const val KEY_SHOW_MESSAGE_LIST_STARS = "messageListStars"
|
||||
const val KEY_SHOW_STAR_COUNT = "showStarredCount"
|
||||
const val KEY_SHOW_UNIFIED_INBOX = "showUnifiedInbox"
|
||||
const val KEY_THREAD_VIEW_ENABLED = "isThreadedViewEnabled"
|
||||
|
||||
interface DisplayInboxSettingsPreferenceManager : PreferenceManager<DisplayInboxSettings>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package net.thunderbird.core.preference.network
|
||||
|
||||
import net.thunderbird.core.preference.BackgroundOps
|
||||
|
||||
val NETWORK_SETTINGS_DEFAULT_BACKGROUND_OPS = BackgroundOps.ALWAYS
|
||||
|
||||
data class NetworkSettings(
|
||||
val backgroundOps: BackgroundOps = NETWORK_SETTINGS_DEFAULT_BACKGROUND_OPS,
|
||||
)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package net.thunderbird.core.preference.network
|
||||
|
||||
import net.thunderbird.core.preference.PreferenceManager
|
||||
|
||||
const val KEY_BG_OPS = "backgroundOperations"
|
||||
|
||||
interface NetworkSettingsPreferenceManager : PreferenceManager<NetworkSettings>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package net.thunderbird.core.preference.notification
|
||||
|
||||
const val NOTIFICATION_PREFERENCE_DEFAULT_IS_QUIET_TIME_ENABLED = false
|
||||
const val NOTIFICATION_PREFERENCE_DEFAULT_QUIET_TIME_STARTS = "21:00"
|
||||
const val NOTIFICATION_PREFERENCE_DEFAULT_QUIET_TIME_END = "7:00"
|
||||
|
||||
data class NotificationPreference(
|
||||
val isQuietTimeEnabled: Boolean = NOTIFICATION_PREFERENCE_DEFAULT_IS_QUIET_TIME_ENABLED,
|
||||
val quietTimeStarts: String = NOTIFICATION_PREFERENCE_DEFAULT_QUIET_TIME_STARTS,
|
||||
val quietTimeEnds: String = NOTIFICATION_PREFERENCE_DEFAULT_QUIET_TIME_END,
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package net.thunderbird.core.preference.notification
|
||||
|
||||
import net.thunderbird.core.preference.PreferenceManager
|
||||
|
||||
const val KEY_QUIET_TIME_ENDS = "quietTimeEnds"
|
||||
const val KEY_QUIET_TIME_STARTS = "quietTimeStarts"
|
||||
const val KEY_QUIET_TIME_ENABLED = "quietTimeEnabled"
|
||||
|
||||
interface NotificationPreferenceManager : PreferenceManager<NotificationPreference>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package net.thunderbird.core.preference.privacy
|
||||
|
||||
const val PRIVACY_SETTINGS_DEFAULT_HIDE_TIME_ZONE = false
|
||||
const val PRIVACY_SETTINGS_DEFAULT_HIDE_USER_AGENT = false
|
||||
|
||||
data class PrivacySettings(
|
||||
val isHideTimeZone: Boolean = PRIVACY_SETTINGS_DEFAULT_HIDE_TIME_ZONE,
|
||||
val isHideUserAgent: Boolean = PRIVACY_SETTINGS_DEFAULT_HIDE_USER_AGENT,
|
||||
)
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package net.thunderbird.core.preference.privacy
|
||||
|
||||
import net.thunderbird.core.preference.PreferenceManager
|
||||
|
||||
const val KEY_HIDE_TIME_ZONE = "hideTimeZone"
|
||||
const val KEY_HIDE_USER_AGENT = "hideUserAgent"
|
||||
|
||||
interface PrivacySettingsPreferenceManager : PreferenceManager<PrivacySettings>
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
package net.thunderbird.core.preference.storage
|
||||
|
||||
interface Storage {
|
||||
/**
|
||||
* Checks if the storage is empty.
|
||||
*
|
||||
* @return true if the storage is empty, false otherwise.
|
||||
*/
|
||||
fun isEmpty(): Boolean
|
||||
|
||||
/**
|
||||
* Checks if the storage contains a value for the given key.
|
||||
*
|
||||
* @param key The key to check.
|
||||
* @return true if the storage contains a value for the given key, false otherwise.
|
||||
*/
|
||||
fun contains(key: String): Boolean
|
||||
|
||||
/**
|
||||
* Returns a map of all key-value pairs in the storage.
|
||||
*
|
||||
* @return A map of all key-value pairs.
|
||||
*/
|
||||
fun getAll(): Map<String, String>
|
||||
|
||||
/**
|
||||
* Returns the boolean value for the given key.
|
||||
*
|
||||
* @param key The key to look up.
|
||||
* @param defValue The default value to return if the key is not found.
|
||||
* @return The boolean value for the given key, or the default value if the key is not found.
|
||||
*/
|
||||
fun getBoolean(key: String, defValue: Boolean): Boolean
|
||||
|
||||
/**
|
||||
* Returns the integer value for the given key.
|
||||
*
|
||||
* @param key The key to look up.
|
||||
* @param defValue The default value to return if the key is not found.
|
||||
* @return The integer value for the given key, or the default value if the key is not found.
|
||||
*/
|
||||
fun getInt(key: String, defValue: Int): Int
|
||||
|
||||
/**
|
||||
* Returns the long value for the given key.
|
||||
*
|
||||
* @param key The key to look up.
|
||||
* @param defValue The default value to return if the key is not found.
|
||||
* @return The long value for the given key, or the default value if the key is not found.
|
||||
*/
|
||||
fun getLong(key: String, defValue: Long): Long
|
||||
|
||||
/**
|
||||
* Returns the string value for the given key.
|
||||
*
|
||||
* @param key The key to look up.
|
||||
* @return The string value for the given key.
|
||||
* @throws NoSuchElementException if the key is not found.
|
||||
*/
|
||||
@Throws(NoSuchElementException::class)
|
||||
fun getString(key: String): String
|
||||
|
||||
/**
|
||||
* Returns the string value for the given key.
|
||||
*
|
||||
* @param key The key to look up.
|
||||
* @param defValue The default value to return if the key is not found.
|
||||
* @return The string value for the given key, or the default value if the key is not found.
|
||||
*/
|
||||
fun getStringOrDefault(key: String, defValue: String): String
|
||||
|
||||
/**
|
||||
* Returns the string value for the given key, or null if the key is not found.
|
||||
*
|
||||
* @param key The key to look up.
|
||||
* @return The string value for the given key, or null if the key is not found.
|
||||
*/
|
||||
fun getStringOrNull(key: String): String?
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the enum value for the given key, or the default value if the key is not found or the stored value
|
||||
* is not a valid enum constant.
|
||||
*
|
||||
* @param T The enum type.
|
||||
* @param key The key to look up.
|
||||
* @param default The default enum value to return if the key is not found or the value is invalid.
|
||||
* @return The enum value for the given key, or the default value.
|
||||
* @throws IllegalArgumentException if the stored string value does not match any of the enum constants.
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
inline fun <reified T : Enum<T>> Storage.getEnumOrDefault(key: String, default: T): T =
|
||||
getStringOrNull(key)
|
||||
?.let { value ->
|
||||
try {
|
||||
enumValueOf<T>(value)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
throw IllegalArgumentException(
|
||||
buildString {
|
||||
append("Unable to convert stored key [$key] value [$value] ")
|
||||
appendLine("to enum of type ${T::class.qualifiedName}.")
|
||||
append("Valid values: ${enumValues<T>().joinToString()}")
|
||||
},
|
||||
e,
|
||||
)
|
||||
}
|
||||
}
|
||||
?: default
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package net.thunderbird.core.preference.storage
|
||||
|
||||
/**
|
||||
* Interface for editing the storage.
|
||||
*
|
||||
* This interface provides methods to put various types of values into the storage,
|
||||
* remove values, and commit the changes.
|
||||
*/
|
||||
interface StorageEditor {
|
||||
|
||||
/**
|
||||
* Puts a boolean value into the storage.
|
||||
*
|
||||
* @param key The key for the value.
|
||||
* @param value The boolean value to put.
|
||||
* @return The StorageEditor instance for chaining.
|
||||
*/
|
||||
fun putBoolean(key: String, value: Boolean): StorageEditor
|
||||
|
||||
/**
|
||||
* Puts an integer value into the storage.
|
||||
*
|
||||
* @param key The key for the value.
|
||||
* @param value The integer value to put.
|
||||
* @return The StorageEditor instance for chaining.
|
||||
*/
|
||||
fun putInt(key: String, value: Int): StorageEditor
|
||||
|
||||
/**
|
||||
* Puts a long value into the storage.
|
||||
*
|
||||
* @param key The key for the value.
|
||||
* @param value The long value to put.
|
||||
* @return The StorageEditor instance for chaining.
|
||||
*/
|
||||
fun putLong(key: String, value: Long): StorageEditor
|
||||
|
||||
/**
|
||||
* Puts a string value into the storage.
|
||||
*
|
||||
* @param key The key for the value.
|
||||
* @param value The string value to put. If null, the key will be removed.
|
||||
* @return The StorageEditor instance for chaining.
|
||||
*/
|
||||
fun putString(key: String, value: String?): StorageEditor
|
||||
|
||||
/**
|
||||
* Removes a value from the storage.
|
||||
*
|
||||
* @param key The key for the value to remove.
|
||||
* @return The StorageEditor instance for chaining.
|
||||
*/
|
||||
fun remove(key: String): StorageEditor
|
||||
|
||||
/**
|
||||
* Commits the changes made to the storage.
|
||||
*
|
||||
* @return true if the commit was successful, false otherwise.
|
||||
*/
|
||||
fun commit(): Boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package net.thunderbird.core.preference.storage
|
||||
|
||||
/**
|
||||
* Extension functions for the [StorageEditor] interface to simplify putting enum values.
|
||||
*
|
||||
* @param T The type of the enum.
|
||||
* @param key The key under which the enum value will be stored.
|
||||
* @param value The enum value to be stored.
|
||||
*/
|
||||
inline fun <reified T : Enum<T>> StorageEditor.putEnum(key: String, value: T) {
|
||||
putString(key, value.name)
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package net.thunderbird.core.preference.storage
|
||||
|
||||
/**
|
||||
* Represents a mechanism for persisting storage data.
|
||||
*
|
||||
* This interface provides methods to:
|
||||
* - Load the current storage values.
|
||||
* - Create a storage editor for applying updates to the storage.
|
||||
*/
|
||||
interface StoragePersister {
|
||||
|
||||
/**
|
||||
* Loads the storage values.
|
||||
*
|
||||
* @return The loaded storage.
|
||||
*/
|
||||
fun loadValues(): Storage
|
||||
|
||||
/**
|
||||
* Creates a storage editor for updating the storage.
|
||||
*
|
||||
* @param storageUpdater The updater to apply changes to the storage.
|
||||
* @return A new instance of [StorageEditor].
|
||||
*/
|
||||
fun createStorageEditor(storageUpdater: StorageUpdater): StorageEditor
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package net.thunderbird.core.preference.storage
|
||||
|
||||
/**
|
||||
* Interface for updating the storage.
|
||||
*/
|
||||
fun interface StorageUpdater {
|
||||
|
||||
/**
|
||||
* Updates the storage using the provided updater function.
|
||||
*
|
||||
* @param updater A function that takes the current storage and returns the updated storage.
|
||||
*/
|
||||
fun updateStorage(updater: (currentStorage: Storage) -> Storage)
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package net.thunderbird.core.preference.debugging
|
||||
|
||||
actual val isDebug: Boolean = false
|
||||
17
core/preference/impl/build.gradle.kts
Normal file
17
core/preference/impl/build.gradle.kts
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue