change (media preview config) : final refactoring and tests

This commit is contained in:
ganfra 2025-06-30 21:31:58 +02:00
parent 4b4cfa341e
commit ca46166c67
27 changed files with 676 additions and 165 deletions

View file

@ -8,21 +8,15 @@
package io.element.android.features.preferences.impl.advanced
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import io.element.android.compound.theme.Theme
import io.element.android.compound.theme.mapToTheme
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runUpdatingState
import io.element.android.libraries.di.annotations.SessionCoroutineScope
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
import kotlinx.coroutines.CoroutineScope
@ -51,6 +45,8 @@ class AdvancedSettingsPresenter @Inject constructor(
appPreferencesStore.getThemeFlow().mapToTheme()
}.collectAsState(initial = Theme.System)
val mediaPreviewConfigState = mediaPreviewConfigStateStore.state()
val themeOption by remember {
derivedStateOf {
when (theme.value) {
@ -89,10 +85,7 @@ class AdvancedSettingsPresenter @Inject constructor(
isSharePresenceEnabled = isSharePresenceEnabled,
doesCompressMedia = doesCompressMedia,
theme = themeOption,
hideInviteAvatars = mediaPreviewConfigStateStore.hideInviteAvatars.value,
timelineMediaPreviewValue = mediaPreviewConfigStateStore.timelineMediaPreviewValue.value,
setHideInviteAvatarsAction = mediaPreviewConfigStateStore.setHideInviteAvatarsAction.value,
setTimelineMediaPreviewAction = mediaPreviewConfigStateStore.setTimelineMediaPreviewAction.value,
mediaPreviewConfigState = mediaPreviewConfigState,
eventSink = ::handleEvents,
)
}

View file

@ -10,9 +10,7 @@ package io.element.android.features.preferences.impl.advanced
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.res.stringResource
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.designsystem.components.preferences.DropdownOption
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
import io.element.android.libraries.ui.strings.CommonStrings
data class AdvancedSettingsState(
@ -20,10 +18,7 @@ data class AdvancedSettingsState(
val isSharePresenceEnabled: Boolean,
val doesCompressMedia: Boolean,
val theme: ThemeOption,
val hideInviteAvatars: Boolean,
val timelineMediaPreviewValue: MediaPreviewValue,
val setHideInviteAvatarsAction: AsyncAction<Unit>,
val setTimelineMediaPreviewAction: AsyncAction<Unit>,
val mediaPreviewConfigState: MediaPreviewConfigState,
val eventSink: (AdvancedSettingsEvents) -> Unit
)

View file

@ -29,8 +29,8 @@ fun aAdvancedSettingsState(
isDeveloperModeEnabled: Boolean = false,
isSharePresenceEnabled: Boolean = false,
doesCompressMedia: Boolean = false,
hideInviteAvatars: Boolean = false,
theme: ThemeOption = ThemeOption.System,
hideInviteAvatars: Boolean = false,
timelineMediaPreviewValue: MediaPreviewValue = MediaPreviewValue.On,
setTimelineMediaPreviewAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
setHideInviteAvatarsAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
@ -40,9 +40,11 @@ fun aAdvancedSettingsState(
isSharePresenceEnabled = isSharePresenceEnabled,
doesCompressMedia = doesCompressMedia,
theme = theme,
hideInviteAvatars = hideInviteAvatars,
timelineMediaPreviewValue = timelineMediaPreviewValue,
setTimelineMediaPreviewAction = setTimelineMediaPreviewAction,
setHideInviteAvatarsAction = setHideInviteAvatarsAction,
mediaPreviewConfigState = MediaPreviewConfigState(
hideInviteAvatars = hideInviteAvatars,
timelineMediaPreviewValue = timelineMediaPreviewValue,
setTimelineMediaPreviewAction = setTimelineMediaPreviewAction,
setHideInviteAvatarsAction = setHideInviteAvatarsAction
),
eventSink = eventSink
)

View file

@ -133,11 +133,11 @@ private fun ModerationAndSafety(
) {
PreferenceSwitch(
title = stringResource(R.string.screen_advanced_settings_hide_invite_avatars_toggle_title),
isChecked = state.hideInviteAvatars,
isChecked = state.mediaPreviewConfigState.hideInviteAvatars,
onCheckedChange = {
state.eventSink(AdvancedSettingsEvents.SetHideInviteAvatars(it))
},
enabled = !state.setHideInviteAvatarsAction.isLoading()
enabled = !state.mediaPreviewConfigState.setHideInviteAvatarsAction.isLoading()
)
ListSectionHeader(
title = stringResource(R.string.screen_advanced_settings_show_media_timeline_title),
@ -153,27 +153,36 @@ private fun ModerationAndSafety(
)
ListItem(
headlineContent = { Text(text = stringResource(R.string.screen_advanced_settings_show_media_timeline_always_hide)) },
leadingContent = ListItemContent.RadioButton(selected = state.timelineMediaPreviewValue == MediaPreviewValue.Off, compact = true),
leadingContent = ListItemContent.RadioButton(
selected = state.mediaPreviewConfigState.timelineMediaPreviewValue == MediaPreviewValue.Off,
compact = true
),
onClick = {
state.eventSink(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Off))
},
enabled = !state.setTimelineMediaPreviewAction.isLoading()
enabled = !state.mediaPreviewConfigState.setTimelineMediaPreviewAction.isLoading()
)
ListItem(
headlineContent = { Text(text = stringResource(R.string.screen_advanced_settings_show_media_timeline_private_rooms)) },
leadingContent = ListItemContent.RadioButton(selected = state.timelineMediaPreviewValue == MediaPreviewValue.Private, compact = true),
leadingContent = ListItemContent.RadioButton(
selected = state.mediaPreviewConfigState.timelineMediaPreviewValue == MediaPreviewValue.Private,
compact = true
),
onClick = {
state.eventSink(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Private))
},
enabled = !state.setTimelineMediaPreviewAction.isLoading()
enabled = !state.mediaPreviewConfigState.setTimelineMediaPreviewAction.isLoading()
)
ListItem(
headlineContent = { Text(text = stringResource(R.string.screen_advanced_settings_show_media_timeline_always_show)) },
leadingContent = ListItemContent.RadioButton(selected = state.timelineMediaPreviewValue == MediaPreviewValue.On, compact = true),
leadingContent = ListItemContent.RadioButton(
selected = state.mediaPreviewConfigState.timelineMediaPreviewValue == MediaPreviewValue.On,
compact = true
),
onClick = {
state.eventSink(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.On))
},
enabled = !state.setTimelineMediaPreviewAction.isLoading()
enabled = !state.mediaPreviewConfigState.setTimelineMediaPreviewAction.isLoading()
)
}
}

View file

@ -7,7 +7,7 @@
package io.element.android.features.preferences.impl.advanced
import androidx.compose.runtime.State
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.architecture.AsyncAction
@ -21,27 +21,29 @@ import io.element.android.libraries.matrix.api.media.MediaPreviewService
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
interface MediaPreviewConfigStateStore {
val hideInviteAvatars: State<Boolean>
val timelineMediaPreviewValue: State<MediaPreviewValue>
val setHideInviteAvatarsAction: State<AsyncAction<Unit>>
val setTimelineMediaPreviewAction: State<AsyncAction<Unit>>
data class MediaPreviewConfigState(
val hideInviteAvatars: Boolean,
val timelineMediaPreviewValue: MediaPreviewValue,
val setHideInviteAvatarsAction: AsyncAction<Unit>,
val setTimelineMediaPreviewAction: AsyncAction<Unit>,
)
interface MediaPreviewConfigStateStore {
@Composable
fun state(): MediaPreviewConfigState
fun setHideInviteAvatars(hide: Boolean)
fun setTimelineMediaPreviewValue(value: MediaPreviewValue)
}
@ContributesBinding(SessionScope::class, boundType = MediaPreviewConfigStateStore::class)
@ContributesBinding(SessionScope::class)
@SingleIn(SessionScope::class)
class DefaultMediaPreviewConfigStateStore @Inject constructor(
@SessionCoroutineScope
@ -49,19 +51,19 @@ class DefaultMediaPreviewConfigStateStore @Inject constructor(
private val mediaPreviewService: MediaPreviewService,
private val snackbarDispatcher: SnackbarDispatcher,
) : MediaPreviewConfigStateStore {
override val hideInviteAvatars = mutableStateOf(false)
override val timelineMediaPreviewValue = mutableStateOf(MediaPreviewValue.On)
override val setHideInviteAvatarsAction = mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized)
override val setTimelineMediaPreviewAction = mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized)
private val hideInviteAvatars = mutableStateOf(false)
private val timelineMediaPreviewValue = mutableStateOf(MediaPreviewValue.On)
private val setHideInviteAvatarsAction = mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized)
private val setTimelineMediaPreviewAction = mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized)
init {
val configFlow = mediaPreviewService.getMediaPreviewConfigFlow().shareIn(sessionCoroutineScope, SharingStarted.Eagerly)
val hideInviteAvatarsFlow = configFlow.mapNotNull { it?.hideInviteAvatar }.distinctUntilChanged()
val timelineMediaPreviewFlow = configFlow.mapNotNull { it?.mediaPreviewValue }.distinctUntilChanged()
val configFlow = mediaPreviewService.mediaPreviewConfigFlow
val hideInviteAvatarsFlow = configFlow.map { it.hideInviteAvatar }.distinctUntilChanged()
val timelineMediaPreviewFlow = configFlow.map { it.mediaPreviewValue }.distinctUntilChanged()
hideInviteAvatarsFlow
.onEach {
Timber.d("Hide invi@te avatars changed to $it")
Timber.d("Hide invite avatars changed to $it")
hideInviteAvatars.value = it
}
.launchIn(sessionCoroutineScope)
@ -74,6 +76,16 @@ class DefaultMediaPreviewConfigStateStore @Inject constructor(
.launchIn(sessionCoroutineScope)
}
@Composable
override fun state(): MediaPreviewConfigState {
return MediaPreviewConfigState(
hideInviteAvatars = hideInviteAvatars.value,
timelineMediaPreviewValue = timelineMediaPreviewValue.value,
setHideInviteAvatarsAction = setHideInviteAvatarsAction.value,
setTimelineMediaPreviewAction = setTimelineMediaPreviewAction.value,
)
}
override fun setHideInviteAvatars(hide: Boolean) {
sessionCoroutineScope.launch {
Timber.d("Setting hide invite avatars to $hide")
@ -106,4 +118,3 @@ class DefaultMediaPreviewConfigStateStore @Inject constructor(
}
}
}