change (media preview config) : manage migration of local data

This commit is contained in:
ganfra 2025-06-26 21:44:03 +02:00
parent 42af43ea7a
commit 7fb0b36a9e
5 changed files with 99 additions and 11 deletions

View file

@ -41,6 +41,7 @@ import dagger.assisted.AssistedInject
import im.vector.app.features.analytics.plan.JoinedRoom import im.vector.app.features.analytics.plan.JoinedRoom
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.appnav.loggedin.LoggedInNode import io.element.android.appnav.loggedin.LoggedInNode
import io.element.android.appnav.loggedin.MediaPreviewConfigMigration
import io.element.android.appnav.loggedin.SendQueues import io.element.android.appnav.loggedin.SendQueues
import io.element.android.appnav.room.RoomFlowNode import io.element.android.appnav.room.RoomFlowNode
import io.element.android.appnav.room.RoomNavigationTarget import io.element.android.appnav.room.RoomNavigationTarget
@ -114,6 +115,7 @@ class LoggedInFlowNode @AssistedInject constructor(
private val sendingQueue: SendQueues, private val sendingQueue: SendQueues,
private val logoutEntryPoint: LogoutEntryPoint, private val logoutEntryPoint: LogoutEntryPoint,
private val incomingVerificationEntryPoint: IncomingVerificationEntryPoint, private val incomingVerificationEntryPoint: IncomingVerificationEntryPoint,
private val mediaPreviewConfigMigration: MediaPreviewConfigMigration,
snackbarDispatcher: SnackbarDispatcher, snackbarDispatcher: SnackbarDispatcher,
) : BaseFlowNode<LoggedInFlowNode.NavTarget>( ) : BaseFlowNode<LoggedInFlowNode.NavTarget>(
backstack = BackStack( backstack = BackStack(
@ -179,6 +181,7 @@ class LoggedInFlowNode @AssistedInject constructor(
appNavigationStateService.onNavigateToSpace(id, MAIN_SPACE) appNavigationStateService.onNavigateToSpace(id, MAIN_SPACE)
loggedInFlowProcessor.observeEvents(sessionCoroutineScope) loggedInFlowProcessor.observeEvents(sessionCoroutineScope)
matrixClient.sessionVerificationService().setListener(verificationListener) matrixClient.sessionVerificationService().setListener(verificationListener)
mediaPreviewConfigMigration()
ftueService.state ftueService.state
.onEach { ftueState -> .onEach { ftueState ->

View file

@ -0,0 +1,56 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.appnav.loggedin
import io.element.android.libraries.di.annotations.SessionCoroutineScope
import io.element.android.libraries.matrix.api.media.MediaPreviewService
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
/**
* This migration is temporary, will be safe to remove after some time.
* The goal is to set the server config if it's not set, and remove the local data.
*/
class MediaPreviewConfigMigration @Inject constructor(
private val mediaPreviewService: MediaPreviewService,
private val appPreferencesStore: AppPreferencesStore,
@SessionCoroutineScope
private val sessionCoroutineScope: CoroutineScope,
) {
operator fun invoke() = sessionCoroutineScope.launch {
val hideInviteAvatars = appPreferencesStore.getHideInviteAvatarsFlow().first()
val mediaPreviewValue = appPreferencesStore.getTimelineMediaPreviewValueFlow().first()
if (hideInviteAvatars == null && mediaPreviewValue == null) {
// No local data, abort.
return@launch
}
mediaPreviewService
.fetchMediaPreviewConfig()
.onSuccess { config ->
if (config != null) {
appPreferencesStore.setHideInviteAvatars(null)
appPreferencesStore.setTimelineMediaPreviewValue(null)
} else {
if (hideInviteAvatars != null) {
mediaPreviewService.setHideInviteAvatars(hideInviteAvatars)
appPreferencesStore.setHideInviteAvatars(null)
}
if (mediaPreviewValue != null) {
mediaPreviewService.setMediaPreviewValue(mediaPreviewValue)
appPreferencesStore.setTimelineMediaPreviewValue(null)
}
}
}.onFailure {
Timber.d("Couldn't perform migration, failed to fetch media preview config.")
}
}
}

View file

@ -22,9 +22,10 @@ interface AppPreferencesStore {
suspend fun setTheme(theme: String) suspend fun setTheme(theme: String)
fun getThemeFlow(): Flow<String?> fun getThemeFlow(): Flow<String?>
fun getHideInviteAvatarsFlow(): Flow<Boolean> suspend fun setHideInviteAvatars(hide: Boolean?)
fun getHideInviteAvatarsFlow(): Flow<Boolean?>
fun getTimelineMediaPreviewValueFlow(): Flow<MediaPreviewValue> suspend fun setTimelineMediaPreviewValue(mediaPreviewValue: MediaPreviewValue?)
fun getTimelineMediaPreviewValueFlow(): Flow<MediaPreviewValue?>
suspend fun setTracingLogLevel(logLevel: LogLevel) suspend fun setTracingLogLevel(logLevel: LogLevel)
fun getTracingLogLevelFlow(): Flow<LogLevel> fun getTracingLogLevelFlow(): Flow<LogLevel>

View file

@ -85,15 +85,35 @@ class DefaultAppPreferencesStore @Inject constructor(
} }
} }
override fun getHideInviteAvatarsFlow(): Flow<Boolean> { override fun getHideInviteAvatarsFlow(): Flow<Boolean?> {
return store.data.map { prefs -> return store.data.map { prefs ->
prefs[hideInviteAvatarsKey] == true prefs[hideInviteAvatarsKey]
} }
} }
override fun getTimelineMediaPreviewValueFlow(): Flow<MediaPreviewValue> { override suspend fun setHideInviteAvatars(hide: Boolean?) {
store.edit { prefs ->
if (hide != null) {
prefs[hideInviteAvatarsKey] = hide
} else {
prefs.remove(hideInviteAvatarsKey)
}
}
}
override suspend fun setTimelineMediaPreviewValue(mediaPreviewValue: MediaPreviewValue?) {
store.edit { prefs ->
if (mediaPreviewValue != null) {
prefs[timelineMediaPreviewValueKey] = mediaPreviewValue.name
} else {
prefs.remove(timelineMediaPreviewValueKey)
}
}
}
override fun getTimelineMediaPreviewValueFlow(): Flow<MediaPreviewValue?> {
return store.data.map { prefs -> return store.data.map { prefs ->
prefs[timelineMediaPreviewValueKey]?.let { MediaPreviewValue.valueOf(it) } ?: MediaPreviewValue.On prefs[timelineMediaPreviewValueKey]?.let { MediaPreviewValue.valueOf(it) }
} }
} }

View file

@ -17,8 +17,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
class InMemoryAppPreferencesStore( class InMemoryAppPreferencesStore(
isDeveloperModeEnabled: Boolean = false, isDeveloperModeEnabled: Boolean = false,
customElementCallBaseUrl: String? = null, customElementCallBaseUrl: String? = null,
hideInviteAvatars: Boolean = false, hideInviteAvatars: Boolean? = null,
timelineMediaPreviewValue: MediaPreviewValue = MediaPreviewValue.On, timelineMediaPreviewValue: MediaPreviewValue? = null,
theme: String? = null, theme: String? = null,
logLevel: LogLevel = LogLevel.INFO, logLevel: LogLevel = LogLevel.INFO,
traceLockPacks: Set<TraceLogPack> = emptySet(), traceLockPacks: Set<TraceLogPack> = emptySet(),
@ -55,14 +55,22 @@ class InMemoryAppPreferencesStore(
return theme return theme
} }
override fun getHideInviteAvatarsFlow(): Flow<Boolean> { override fun getHideInviteAvatarsFlow(): Flow<Boolean?> {
return hideInviteAvatars return hideInviteAvatars
} }
override fun getTimelineMediaPreviewValueFlow(): Flow<MediaPreviewValue> { override fun getTimelineMediaPreviewValueFlow(): Flow<MediaPreviewValue?> {
return timelineMediaPreviewValue return timelineMediaPreviewValue
} }
override suspend fun setHideInviteAvatars(hide: Boolean?) {
hideInviteAvatars.value = hide
}
override suspend fun setTimelineMediaPreviewValue(mediaPreviewValue: MediaPreviewValue?) {
timelineMediaPreviewValue.value = mediaPreviewValue
}
override suspend fun setTracingLogLevel(logLevel: LogLevel) { override suspend fun setTracingLogLevel(logLevel: LogLevel) {
this.logLevel.value = logLevel this.logLevel.value = logLevel
} }