Merge develop into feature/account-provider-selection

This commit is contained in:
Jenna Vassar
2026-08-14 07:51:17 -07:00
1632 changed files with 11623 additions and 5960 deletions
@@ -13,29 +13,75 @@ import io.element.android.libraries.matrix.api.tracing.LogLevel
import io.element.android.libraries.matrix.api.tracing.TraceLogPack
import kotlinx.coroutines.flow.Flow
/**
* Local, app-wide user preferences that are not tied to a session, stored on the device only.
*
* Each getter returns a flow that emits the current value straight away and then again on every change.
* The default each preference falls back to when it has never been set is given on the getter.
*/
interface AppPreferencesStore {
/**
* @param enabled true to expose the developer options in the settings.
*/
suspend fun setDeveloperModeEnabled(enabled: Boolean)
/** Whether developer mode is on; defaults to `true` on debug builds and `false` otherwise. */
fun isDeveloperModeEnabledFlow(): Flow<Boolean>
/**
* @param string the Element Call deployment to use, or `null` to go back to the one from the homeserver.
*/
suspend fun setCustomElementCallBaseUrl(string: String?)
/** The Element Call URL the user has overridden, or `null` when the homeserver's own value should be used. */
fun getCustomElementCallBaseUrlFlow(): Flow<String?>
/**
* @param theme the name of the theme to apply: light, dark, or follow the system.
*/
suspend fun setTheme(theme: String)
/** The name of the chosen theme, or `null` when the user has not picked one and the system setting should be followed. */
fun getThemeFlow(): Flow<String?>
/**
* @param value the distance in metres the user must move before a new live location is published.
*/
suspend fun setLiveLocationMinimumDistanceInMetersUpdate(value: Int)
/** The minimum distance in metres between two live location updates; defaults to 10. */
fun getLiveLocationMinimumDistanceInMetersUpdateFlow(): Flow<Int>
/**
* Only used to clear the local value once it has been migrated to the server.
*
* @param hide the local setting, or `null` to erase it.
*/
@Deprecated("Use MediaPreviewService instead. Kept only for migration.")
suspend fun setHideInviteAvatars(hide: Boolean?)
/** The local setting to migrate to the server, or `null` once there is nothing left to migrate. */
@Deprecated("Use MediaPreviewService instead. Kept only for migration.")
fun getHideInviteAvatarsFlow(): Flow<Boolean?>
/**
* Only used to clear the local value once it has been migrated to the server.
*
* @param mediaPreviewValue the local setting, or `null` to erase it.
*/
@Deprecated("Use MediaPreviewService instead. Kept only for migration.")
suspend fun setTimelineMediaPreviewValue(mediaPreviewValue: MediaPreviewValue?)
/** The local setting to migrate to the server, or `null` once there is nothing left to migrate. */
@Deprecated("Use MediaPreviewService instead. Kept only for migration.")
fun getTimelineMediaPreviewValueFlow(): Flow<MediaPreviewValue?>
/**
* @param logLevel how verbose the SDK and app logs should be.
*/
suspend fun setTracingLogLevel(logLevel: LogLevel)
/** The configured log level; defaults to the one the build was compiled with. */
fun getTracingLogLevelFlow(): Flow<LogLevel>
/**
@@ -52,23 +98,40 @@ interface AppPreferencesStore {
*/
suspend fun addHomeserverToHistory(url: String)
/**
* @param targets the extra log packs to enable on top of the default targets.
*/
suspend fun setTracingLogPacks(targets: Set<TraceLogPack>)
/** The extra log packs enabled by the user; defaults to an empty set. */
fun getTracingLogPacksFlow(): Flow<Set<TraceLogPack>>
/** The sound played for message notifications. */
fun getMessageSoundFlow(): Flow<NotificationSound>
/**
* Atomically persists [sound] (with copy-time [title] for Custom; cleared otherwise) and
* bumps the channel version. Single transaction so process death can't desync URI and version.
*
* @param sound the sound to persist.
* @param title the display name captured when the sound was copied, or `null` for the built-in choices.
* @return the new channel version, to be passed to the channel recreation.
*/
suspend fun setMessageSoundAndIncrementVersion(sound: NotificationSound, title: String?): Int
/** Title captured at copy time. Null for SystemDefault / Silent or pre-title persisted data. */
fun getMessageSoundDisplayNameFlow(): Flow<String?>
/** The sound played for incoming call notifications. */
fun getCallRingtoneFlow(): Flow<NotificationSound>
/** See [setMessageSoundAndIncrementVersion]. */
/**
* See [setMessageSoundAndIncrementVersion].
*
* @param sound the ringtone to persist.
* @param title the display name captured when the sound was copied, or `null` for the built-in choices.
* @return the new channel version, to be passed to the channel recreation.
*/
suspend fun setCallRingtoneAndIncrementVersion(sound: NotificationSound, title: String?): Int
/** See [getMessageSoundDisplayNameFlow]. */
@@ -77,5 +140,6 @@ interface AppPreferencesStore {
/** Single-snapshot read of all sound prefs; used at boot to seed channels without N reads. */
suspend fun getNotificationSoundChannelConfig(): NotificationSoundChannelConfig
/** Erases every app preference, so they all fall back to their defaults. */
suspend fun reset()
}
@@ -17,5 +17,10 @@ import androidx.datastore.preferences.core.Preferences
* It's a wrapper around AndroidX's `PreferenceDataStoreFactory` to make testing easier.
*/
interface PreferenceDataStoreFactory {
/**
* Creates or opens the preference store with the given name.
*
* @param name the store name, which also determines the file it is persisted to.
*/
fun create(name: String): DataStore<Preferences>
}
@@ -10,30 +10,77 @@ package io.element.android.libraries.preferences.api.store
import kotlinx.coroutines.flow.Flow
/**
* Local, per-session user preferences, stored on the device only and never synced to the homeserver.
*
* Each getter returns a flow that emits the current value straight away and then again on every change.
* The default each preference falls back to when it has never been set is given on the getter.
*/
interface SessionPreferencesStore {
/**
* @param enabled true to let the homeserver share this user's presence with others.
*/
suspend fun setSharePresence(enabled: Boolean)
/** Whether presence is shared; defaults to `true`. */
fun isSharePresenceEnabled(): Flow<Boolean>
/**
* @param enabled true to send read receipts that other members can see.
*/
suspend fun setSendPublicReadReceipts(enabled: Boolean)
/** Whether read receipts are sent publicly rather than privately; defaults to `true`. */
fun isSendPublicReadReceiptsEnabled(): Flow<Boolean>
/**
* @param enabled true to display the read receipts of other members in the timeline.
*/
suspend fun setRenderReadReceipts(enabled: Boolean)
/** Whether other members' read receipts are displayed; defaults to `true`. */
fun isRenderReadReceiptsEnabled(): Flow<Boolean>
/**
* @param enabled true to tell other members when this user is typing.
*/
suspend fun setSendTypingNotifications(enabled: Boolean)
/** Whether typing notifications are sent; defaults to `true`. */
fun isSendTypingNotificationsEnabled(): Flow<Boolean>
/**
* @param enabled true to display which other members are typing.
*/
suspend fun setRenderTypingNotifications(enabled: Boolean)
/** Whether other members' typing notifications are displayed; defaults to `true`. */
fun isRenderTypingNotificationsEnabled(): Flow<Boolean>
/**
* @param skip true to remember that the user dismissed the session verification prompt.
*/
suspend fun setSkipSessionVerification(skip: Boolean)
/** Whether the user chose to skip verifying this session, so they are not asked again; defaults to `false`. */
fun isSessionVerificationSkipped(): Flow<Boolean>
/**
* @param compress true to downscale images before uploading them.
*/
suspend fun setOptimizeImages(compress: Boolean)
/** Whether images are downscaled before upload; defaults to `true`. */
fun doesOptimizeImages(): Flow<Boolean>
/**
* @param preset the quality and size trade-off to apply when compressing videos.
*/
suspend fun setVideoCompressionPreset(preset: VideoCompressionPreset)
/** The video compression preset; defaults to [VideoCompressionPreset.STANDARD], including when the stored value is unreadable. */
fun getVideoCompressionPreset(): Flow<VideoCompressionPreset>
/** Erases every preference of this session, so they all fall back to their defaults. */
suspend fun clear()
}
@@ -11,7 +11,22 @@ package io.element.android.libraries.preferences.api.store
import io.element.android.libraries.matrix.api.core.SessionId
import kotlinx.coroutines.CoroutineScope
/**
* Caches one [SessionPreferencesStore] per session, so that every caller of a given session shares the same instance.
*/
interface SessionPreferencesStoreFactory {
/**
* Returns the store of a session, creating it on first use.
*
* @param sessionId the session whose store is requested.
* @param sessionCoroutineScope the scope the store uses for its own work, only honoured when the store is actually created.
*/
fun get(sessionId: SessionId, sessionCoroutineScope: CoroutineScope): SessionPreferencesStore
/**
* Drops the cached store of a session, to be called when that session goes away.
*
* @param sessionId the session whose store is dropped.
*/
fun remove(sessionId: SessionId)
}