mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Add persistent account provider history to app preferences
This commit is contained in:
+14
@@ -38,6 +38,20 @@ interface AppPreferencesStore {
|
|||||||
suspend fun setTracingLogLevel(logLevel: LogLevel)
|
suspend fun setTracingLogLevel(logLevel: LogLevel)
|
||||||
fun getTracingLogLevelFlow(): Flow<LogLevel>
|
fun getTracingLogLevelFlow(): Flow<LogLevel>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of account providers (homeserver URLs) the user has previously authenticated
|
||||||
|
* against, most recent first. Used to default the account provider and to power autocomplete
|
||||||
|
* suggestions during sign-in. Stored locally only; never synced across devices.
|
||||||
|
*/
|
||||||
|
fun getHomeserverHistoryFlow(): Flow<List<String>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add [url] to the front of the account provider history (see [getHomeserverHistoryFlow]).
|
||||||
|
* The value is normalised (trimmed + lowercased); existing case-insensitive duplicates are
|
||||||
|
* moved to the front rather than duplicated, and the list is capped in size. No-op for blanks.
|
||||||
|
*/
|
||||||
|
suspend fun addHomeserverToHistory(url: String)
|
||||||
|
|
||||||
suspend fun setTracingLogPacks(targets: Set<TraceLogPack>)
|
suspend fun setTracingLogPacks(targets: Set<TraceLogPack>)
|
||||||
fun getTracingLogPacksFlow(): Flow<Set<TraceLogPack>>
|
fun getTracingLogPacksFlow(): Flow<Set<TraceLogPack>>
|
||||||
|
|
||||||
|
|||||||
+27
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
package io.element.android.libraries.preferences.impl.store
|
package io.element.android.libraries.preferences.impl.store
|
||||||
|
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||||
import androidx.datastore.preferences.core.edit
|
import androidx.datastore.preferences.core.edit
|
||||||
import androidx.datastore.preferences.core.intPreferencesKey
|
import androidx.datastore.preferences.core.intPreferencesKey
|
||||||
@@ -36,6 +37,7 @@ private val timelineMediaPreviewValueKey = stringPreferencesKey("timelineMediaPr
|
|||||||
private val liveLocationMinimumDistanceUpdateKey = intPreferencesKey("liveLocationMinimumDistanceUpdate")
|
private val liveLocationMinimumDistanceUpdateKey = intPreferencesKey("liveLocationMinimumDistanceUpdate")
|
||||||
private val logLevelKey = stringPreferencesKey("logLevel")
|
private val logLevelKey = stringPreferencesKey("logLevel")
|
||||||
private val traceLogPacksKey = stringPreferencesKey("traceLogPacks")
|
private val traceLogPacksKey = stringPreferencesKey("traceLogPacks")
|
||||||
|
private val homeserverHistoryKey = stringPreferencesKey("homeserverHistory")
|
||||||
private val messageSoundUriKey = stringPreferencesKey("notificationMessageSoundUri")
|
private val messageSoundUriKey = stringPreferencesKey("notificationMessageSoundUri")
|
||||||
private val messageSoundChannelVersionKey = intPreferencesKey("notificationMessageSoundChannelVersion")
|
private val messageSoundChannelVersionKey = intPreferencesKey("notificationMessageSoundChannelVersion")
|
||||||
private val messageSoundDisplayNameKey = stringPreferencesKey("notificationMessageSoundDisplayName")
|
private val messageSoundDisplayNameKey = stringPreferencesKey("notificationMessageSoundDisplayName")
|
||||||
@@ -43,6 +45,10 @@ private val callRingtoneUriKey = stringPreferencesKey("notificationCallRingtoneU
|
|||||||
private val callRingtoneChannelVersionKey = intPreferencesKey("notificationCallRingtoneChannelVersion")
|
private val callRingtoneChannelVersionKey = intPreferencesKey("notificationCallRingtoneChannelVersion")
|
||||||
private val callRingtoneDisplayNameKey = stringPreferencesKey("notificationCallRingtoneDisplayName")
|
private val callRingtoneDisplayNameKey = stringPreferencesKey("notificationCallRingtoneDisplayName")
|
||||||
|
|
||||||
|
// URLs never contain a newline, so it is a safe delimiter to persist an ordered list in a single String.
|
||||||
|
private const val HOMESERVER_HISTORY_DELIMITER = "\n"
|
||||||
|
private const val MAX_HOMESERVER_HISTORY_SIZE = 10
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultAppPreferencesStore(
|
class DefaultAppPreferencesStore(
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
@@ -168,6 +174,20 @@ class DefaultAppPreferencesStore(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getHomeserverHistoryFlow(): Flow<List<String>> {
|
||||||
|
return store.data.map { prefs -> prefs.readHomeserverHistory() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun addHomeserverToHistory(url: String) {
|
||||||
|
val normalized = url.trim().lowercase()
|
||||||
|
if (normalized.isEmpty()) return
|
||||||
|
store.edit { prefs ->
|
||||||
|
val updated = (listOf(normalized) + prefs.readHomeserverHistory().filter { it != normalized })
|
||||||
|
.take(MAX_HOMESERVER_HISTORY_SIZE)
|
||||||
|
prefs[homeserverHistoryKey] = updated.joinToString(HOMESERVER_HISTORY_DELIMITER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getMessageSoundFlow(): Flow<NotificationSound> {
|
override fun getMessageSoundFlow(): Flow<NotificationSound> {
|
||||||
return store.data.map { prefs -> NotificationSound.fromStored(prefs[messageSoundUriKey]) }
|
return store.data.map { prefs -> NotificationSound.fromStored(prefs[messageSoundUriKey]) }
|
||||||
}
|
}
|
||||||
@@ -242,6 +262,13 @@ class DefaultAppPreferencesStore(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Preferences.readHomeserverHistory(): List<String> {
|
||||||
|
return this[homeserverHistoryKey]
|
||||||
|
?.split(HOMESERVER_HISTORY_DELIMITER)
|
||||||
|
?.filter { it.isNotEmpty() }
|
||||||
|
?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
private fun BuildMeta.defaultLogLevel(): LogLevel {
|
private fun BuildMeta.defaultLogLevel(): LogLevel {
|
||||||
return when (buildType) {
|
return when (buildType) {
|
||||||
BuildType.DEBUG -> LogLevel.TRACE
|
BuildType.DEBUG -> LogLevel.TRACE
|
||||||
|
|||||||
+70
@@ -54,4 +54,74 @@ class DefaultAppPreferencesStoreTest {
|
|||||||
|
|
||||||
assertThat(store.getLiveLocationMinimumDistanceInMetersUpdateFlow().first()).isEqualTo(25)
|
assertThat(store.getLiveLocationMinimumDistanceInMetersUpdateFlow().first()).isEqualTo(25)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `homeserver history is empty by default`() = runTest {
|
||||||
|
val store = createStore()
|
||||||
|
assertThat(store.getHomeserverHistoryFlow().first()).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `adding homeservers keeps the most recent first`() = runTest {
|
||||||
|
val store = createStore()
|
||||||
|
|
||||||
|
store.addHomeserverToHistory("https://matrix.org")
|
||||||
|
store.addHomeserverToHistory("https://example.com")
|
||||||
|
|
||||||
|
assertThat(store.getHomeserverHistoryFlow().first())
|
||||||
|
.containsExactly("https://example.com", "https://matrix.org")
|
||||||
|
.inOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `adding an existing homeserver moves it to the front without duplicating`() = runTest {
|
||||||
|
val store = createStore()
|
||||||
|
|
||||||
|
store.addHomeserverToHistory("https://matrix.org")
|
||||||
|
store.addHomeserverToHistory("https://example.com")
|
||||||
|
store.addHomeserverToHistory("https://matrix.org")
|
||||||
|
|
||||||
|
assertThat(store.getHomeserverHistoryFlow().first())
|
||||||
|
.containsExactly("https://matrix.org", "https://example.com")
|
||||||
|
.inOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `homeservers are normalised and deduplicated case-insensitively`() = runTest {
|
||||||
|
val store = createStore()
|
||||||
|
|
||||||
|
store.addHomeserverToHistory(" HTTPS://Matrix.ORG ")
|
||||||
|
store.addHomeserverToHistory("https://matrix.org")
|
||||||
|
|
||||||
|
assertThat(store.getHomeserverHistoryFlow().first())
|
||||||
|
.containsExactly("https://matrix.org")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `blank homeservers are ignored`() = runTest {
|
||||||
|
val store = createStore()
|
||||||
|
|
||||||
|
store.addHomeserverToHistory(" ")
|
||||||
|
|
||||||
|
assertThat(store.getHomeserverHistoryFlow().first()).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `homeserver history is capped to the most recent entries`() = runTest {
|
||||||
|
val store = createStore()
|
||||||
|
|
||||||
|
repeat(15) { index ->
|
||||||
|
store.addHomeserverToHistory("https://server$index.org")
|
||||||
|
}
|
||||||
|
|
||||||
|
val history = store.getHomeserverHistoryFlow().first()
|
||||||
|
assertThat(history).hasSize(10)
|
||||||
|
assertThat(history.first()).isEqualTo("https://server14.org")
|
||||||
|
assertThat(history.last()).isEqualTo("https://server5.org")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createStore() = DefaultAppPreferencesStore(
|
||||||
|
buildMeta = buildMeta,
|
||||||
|
preferenceDataStoreFactory = FakePreferenceDataStoreFactory(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+15
@@ -16,6 +16,7 @@ import io.element.android.libraries.preferences.api.store.NotificationSound
|
|||||||
import io.element.android.libraries.preferences.api.store.NotificationSoundChannelConfig
|
import io.element.android.libraries.preferences.api.store.NotificationSoundChannelConfig
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.flow.updateAndGet
|
import kotlinx.coroutines.flow.updateAndGet
|
||||||
|
|
||||||
class InMemoryAppPreferencesStore(
|
class InMemoryAppPreferencesStore(
|
||||||
@@ -27,6 +28,7 @@ class InMemoryAppPreferencesStore(
|
|||||||
liveLocationMinimumDistanceUpdate: Int = 10,
|
liveLocationMinimumDistanceUpdate: Int = 10,
|
||||||
logLevel: LogLevel = LogLevel.INFO,
|
logLevel: LogLevel = LogLevel.INFO,
|
||||||
traceLogPacks: Set<TraceLogPack> = emptySet(),
|
traceLogPacks: Set<TraceLogPack> = emptySet(),
|
||||||
|
homeserverHistory: List<String> = emptyList(),
|
||||||
messageSound: NotificationSound = NotificationSound.SystemDefault,
|
messageSound: NotificationSound = NotificationSound.SystemDefault,
|
||||||
messageSoundChannelVersion: Int = 0,
|
messageSoundChannelVersion: Int = 0,
|
||||||
messageSoundDisplayName: String? = null,
|
messageSoundDisplayName: String? = null,
|
||||||
@@ -40,6 +42,7 @@ class InMemoryAppPreferencesStore(
|
|||||||
private val liveLocationMinimumDistanceUpdate = MutableStateFlow(liveLocationMinimumDistanceUpdate)
|
private val liveLocationMinimumDistanceUpdate = MutableStateFlow(liveLocationMinimumDistanceUpdate)
|
||||||
private val logLevel = MutableStateFlow(logLevel)
|
private val logLevel = MutableStateFlow(logLevel)
|
||||||
private val tracingLogPacks = MutableStateFlow(traceLogPacks)
|
private val tracingLogPacks = MutableStateFlow(traceLogPacks)
|
||||||
|
private val homeserverHistory = MutableStateFlow(homeserverHistory)
|
||||||
private val hideInviteAvatars = MutableStateFlow(hideInviteAvatars)
|
private val hideInviteAvatars = MutableStateFlow(hideInviteAvatars)
|
||||||
private val timelineMediaPreviewValue = MutableStateFlow(timelineMediaPreviewValue)
|
private val timelineMediaPreviewValue = MutableStateFlow(timelineMediaPreviewValue)
|
||||||
private val messageSound = MutableStateFlow(messageSound)
|
private val messageSound = MutableStateFlow(messageSound)
|
||||||
@@ -117,6 +120,18 @@ class InMemoryAppPreferencesStore(
|
|||||||
return tracingLogPacks
|
return tracingLogPacks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getHomeserverHistoryFlow(): Flow<List<String>> {
|
||||||
|
return homeserverHistory
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun addHomeserverToHistory(url: String) {
|
||||||
|
val normalized = url.trim().lowercase()
|
||||||
|
if (normalized.isEmpty()) return
|
||||||
|
homeserverHistory.update { current ->
|
||||||
|
(listOf(normalized) + current.filter { it != normalized }).take(10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getMessageSoundFlow(): Flow<NotificationSound> {
|
override fun getMessageSoundFlow(): Flow<NotificationSound> {
|
||||||
return messageSound
|
return messageSound
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user