change (media preview config) : final refactoring and tests
This commit is contained in:
parent
4b4cfa341e
commit
ca46166c67
27 changed files with 676 additions and 165 deletions
|
|
@ -34,6 +34,7 @@ import io.element.android.features.roomdirectory.api.RoomDescription
|
|||
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.core.coroutine.mapState
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
|
@ -49,7 +50,6 @@ import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
|||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
|
||||
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Optional
|
||||
|
|
@ -67,7 +67,6 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
private val forgetRoom: ForgetRoom,
|
||||
private val acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
||||
private val buildMeta: BuildMeta,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val seenInvitesStore: SeenInvitesStore,
|
||||
) : Presenter<JoinRoomState> {
|
||||
interface Factory {
|
||||
|
|
@ -94,8 +93,11 @@ class JoinRoomPresenter @AssistedInject constructor(
|
|||
var knockMessage by rememberSaveable { mutableStateOf("") }
|
||||
var isDismissingContent by remember { mutableStateOf(false) }
|
||||
val hideInviteAvatars by remember {
|
||||
appPreferencesStore.getHideInviteAvatarsFlow()
|
||||
}.collectAsState(initial = false)
|
||||
matrixClient
|
||||
.mediaPreviewService()
|
||||
.mediaPreviewConfigFlow
|
||||
.mapState { config -> config.hideInviteAvatar }
|
||||
}.collectAsState()
|
||||
val canReportRoom by produceState(false) { value = matrixClient.canReportRoom() }
|
||||
|
||||
val contentState by produceState<ContentState>(
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
|||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRoom
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import java.util.Optional
|
||||
|
||||
@Module
|
||||
|
|
@ -37,7 +36,6 @@ object JoinRoomModule {
|
|||
forgetRoom: ForgetRoom,
|
||||
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
||||
buildMeta: BuildMeta,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
seenInvitesStore: SeenInvitesStore,
|
||||
): JoinRoomPresenter.Factory {
|
||||
return object : JoinRoomPresenter.Factory {
|
||||
|
|
@ -61,7 +59,6 @@ object JoinRoomModule {
|
|||
cancelKnockRoom = cancelKnockRoom,
|
||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
|
||||
buildMeta = buildMeta,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
seenInvitesStore = seenInvitesStore,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,8 +52,6 @@ import io.element.android.libraries.matrix.test.room.aRoomPreviewInfo
|
|||
import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom
|
||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.lambda.any
|
||||
import io.element.android.tests.testutils.lambda.assert
|
||||
|
|
@ -1057,7 +1055,6 @@ class JoinRoomPresenterTest {
|
|||
forgetRoom: ForgetRoom = FakeForgetRoom(),
|
||||
buildMeta: BuildMeta = aBuildMeta(applicationName = "AppName"),
|
||||
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState> = Presenter { anAcceptDeclineInviteState() },
|
||||
appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(),
|
||||
seenInvitesStore: SeenInvitesStore = InMemorySeenInvitesStore(),
|
||||
): JoinRoomPresenter {
|
||||
return JoinRoomPresenter(
|
||||
|
|
@ -1073,7 +1070,6 @@ class JoinRoomPresenterTest {
|
|||
forgetRoom = forgetRoom,
|
||||
buildMeta = buildMeta,
|
||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
seenInvitesStore = seenInvitesStore,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,16 +14,16 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.mapState
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
|
||||
import io.element.android.libraries.matrix.api.media.MediaPreviewService
|
||||
import io.element.android.libraries.matrix.api.media.isPreviewEnabled
|
||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineProtectionPresenter @Inject constructor(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val mediaPreviewService: MediaPreviewService,
|
||||
private val room: BaseRoom,
|
||||
) : Presenter<TimelineProtectionState> {
|
||||
private val allowedEvents = mutableStateOf<Set<EventId>>(setOf())
|
||||
|
|
@ -31,8 +31,8 @@ class TimelineProtectionPresenter @Inject constructor(
|
|||
@Composable
|
||||
override fun present(): TimelineProtectionState {
|
||||
val mediaPreviewValue = remember {
|
||||
appPreferencesStore.getTimelineMediaPreviewValueFlow()
|
||||
}.collectAsState(initial = MediaPreviewValue.On)
|
||||
mediaPreviewService.mediaPreviewConfigFlow.mapState { config -> config.mediaPreviewValue }
|
||||
}.collectAsState()
|
||||
val roomInfo = room.roomInfoFlow.collectAsState()
|
||||
val protectionState by remember {
|
||||
derivedStateOf {
|
||||
|
|
|
|||
|
|
@ -8,17 +8,19 @@
|
|||
package io.element.android.features.messages.impl.timeline.protection
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.media.MediaPreviewConfig
|
||||
import io.element.android.libraries.matrix.api.media.MediaPreviewService
|
||||
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
|
||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.media.FakeMediaPreviewService
|
||||
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
|
@ -38,10 +40,10 @@ class TimelineProtectionPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - media preview value off`() = runTest {
|
||||
val appPreferencesStore = InMemoryAppPreferencesStore(timelineMediaPreviewValue = MediaPreviewValue.Off)
|
||||
val presenter = createPresenter(appPreferencesStore)
|
||||
val mediaPreviewConfig = MediaPreviewConfig(mediaPreviewValue = MediaPreviewValue.Off, hideInviteAvatar = false)
|
||||
val mediaPreviewService = FakeMediaPreviewService(mediaPreviewConfigFlow = MutableStateFlow(mediaPreviewConfig))
|
||||
val presenter = createPresenter(mediaPreviewService = mediaPreviewService)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.protectionState).isEqualTo(ProtectionState.RenderOnly(persistentSetOf()))
|
||||
// ShowContent with null should have no effect.
|
||||
|
|
@ -54,11 +56,11 @@ class TimelineProtectionPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - media preview value private in public room`() = runTest {
|
||||
val appPreferencesStore = InMemoryAppPreferencesStore(timelineMediaPreviewValue = MediaPreviewValue.Private)
|
||||
val mediaPreviewConfig = MediaPreviewConfig(mediaPreviewValue = MediaPreviewValue.Private, hideInviteAvatar = false)
|
||||
val mediaPreviewService = FakeMediaPreviewService(mediaPreviewConfigFlow = MutableStateFlow(mediaPreviewConfig))
|
||||
val room = FakeBaseRoom(initialRoomInfo = aRoomInfo(joinRule = JoinRule.Public))
|
||||
val presenter = createPresenter(appPreferencesStore, room)
|
||||
val presenter = createPresenter(mediaPreviewService = mediaPreviewService, room = room)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.protectionState).isEqualTo(ProtectionState.RenderOnly(persistentSetOf()))
|
||||
// ShowContent with null should have no effect.
|
||||
|
|
@ -71,9 +73,10 @@ class TimelineProtectionPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - media preview value private in non public room`() = runTest {
|
||||
val appPreferencesStore = InMemoryAppPreferencesStore(timelineMediaPreviewValue = MediaPreviewValue.Private)
|
||||
val mediaPreviewConfig = MediaPreviewConfig(mediaPreviewValue = MediaPreviewValue.Private, hideInviteAvatar = false)
|
||||
val mediaPreviewService = FakeMediaPreviewService(mediaPreviewConfigFlow = MutableStateFlow(mediaPreviewConfig))
|
||||
val room = FakeBaseRoom(initialRoomInfo = aRoomInfo(joinRule = JoinRule.Invite))
|
||||
val presenter = createPresenter(appPreferencesStore, room)
|
||||
val presenter = createPresenter(mediaPreviewService = mediaPreviewService, room = room)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.protectionState).isEqualTo(ProtectionState.RenderAll)
|
||||
|
|
@ -84,10 +87,10 @@ class TimelineProtectionPresenterTest {
|
|||
}
|
||||
|
||||
private fun createPresenter(
|
||||
appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(),
|
||||
room: BaseRoom = FakeBaseRoom(),
|
||||
mediaPreviewService: MediaPreviewService = FakeMediaPreviewService(),
|
||||
) = TimelineProtectionPresenter(
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
mediaPreviewService = mediaPreviewService,
|
||||
room = room,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,14 +11,12 @@ import app.cash.molecule.RecompositionMode
|
|||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.media.MediaPreviewConfig
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
|
@ -38,6 +36,10 @@ class AdvancedSettingsPresenterTest {
|
|||
assertThat(isSharePresenceEnabled).isTrue()
|
||||
assertThat(doesCompressMedia).isTrue()
|
||||
assertThat(theme).isEqualTo(ThemeOption.System)
|
||||
assertThat(mediaPreviewConfigState.hideInviteAvatars).isFalse()
|
||||
assertThat(mediaPreviewConfigState.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.On)
|
||||
assertThat(mediaPreviewConfigState.setHideInviteAvatarsAction).isEqualTo(AsyncAction.Uninitialized)
|
||||
assertThat(mediaPreviewConfigState.setTimelineMediaPreviewAction).isEqualTo(AsyncAction.Uninitialized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -128,56 +130,92 @@ class AdvancedSettingsPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - hide invite avatars`() = runTest {
|
||||
val presenter = createAdvancedSettingsPresenter()
|
||||
val mediaPreviewStore = FakeMediaPreviewConfigStateStore()
|
||||
val presenter = createAdvancedSettingsPresenter(mediaPreviewConfigStateStore = mediaPreviewStore)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
with(awaitItem()) {
|
||||
assertThat(hideInviteAvatars).isFalse()
|
||||
assertThat(mediaPreviewConfigState.hideInviteAvatars).isFalse()
|
||||
eventSink(AdvancedSettingsEvents.SetHideInviteAvatars(true))
|
||||
}
|
||||
with(awaitItem()) {
|
||||
assertThat(hideInviteAvatars).isTrue()
|
||||
assertThat(mediaPreviewConfigState.hideInviteAvatars).isTrue()
|
||||
eventSink(AdvancedSettingsEvents.SetHideInviteAvatars(false))
|
||||
}
|
||||
with(awaitItem()) {
|
||||
assertThat(hideInviteAvatars).isFalse()
|
||||
assertThat(mediaPreviewConfigState.hideInviteAvatars).isFalse()
|
||||
}
|
||||
}
|
||||
assertThat(mediaPreviewStore.getSetHideInviteAvatarsEvents()).isEqualTo(listOf(true, false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - timeline media preview value`() = runTest {
|
||||
val mediaPreviewStore = FakeMediaPreviewConfigStateStore()
|
||||
val presenter = createAdvancedSettingsPresenter(mediaPreviewConfigStateStore = mediaPreviewStore)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
with(awaitItem()) {
|
||||
assertThat(mediaPreviewConfigState.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.On)
|
||||
eventSink(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Off))
|
||||
}
|
||||
with(awaitItem()) {
|
||||
assertThat(mediaPreviewConfigState.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.Off)
|
||||
eventSink(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Private))
|
||||
}
|
||||
with(awaitItem()) {
|
||||
assertThat(mediaPreviewConfigState.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.Private)
|
||||
}
|
||||
}
|
||||
assertThat(mediaPreviewStore.getSetTimelineMediaPreviewValueEvents()).isEqualTo(
|
||||
listOf(MediaPreviewValue.Off, MediaPreviewValue.Private)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - media preview state with custom initial values`() = runTest {
|
||||
val mediaPreviewStore = FakeMediaPreviewConfigStateStore(
|
||||
hideInviteAvatarsValue = true,
|
||||
timelineMediaPreviewValue = MediaPreviewValue.Private
|
||||
)
|
||||
val presenter = createAdvancedSettingsPresenter(mediaPreviewConfigStateStore = mediaPreviewStore)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
with(awaitItem()) {
|
||||
assertThat(mediaPreviewConfigState.hideInviteAvatars).isTrue()
|
||||
assertThat(mediaPreviewConfigState.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.Private)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - timeline media preview value`() = runTest {
|
||||
val mediaPreviewConfigFlow = MutableStateFlow<MediaPreviewConfig?>(null)
|
||||
val presenter = createAdvancedSettingsPresenter(
|
||||
matrixClient = FakeMatrixClient(
|
||||
mediaPreviewConfigFlow = mediaPreviewConfigFlow
|
||||
)
|
||||
fun `present - async actions state`() = runTest {
|
||||
val mediaPreviewStore = FakeMediaPreviewConfigStateStore(
|
||||
setHideInviteAvatarsActionValue = AsyncAction.Loading,
|
||||
setTimelineMediaPreviewActionValue = AsyncAction.Success(Unit)
|
||||
)
|
||||
val presenter = createAdvancedSettingsPresenter(mediaPreviewConfigStateStore = mediaPreviewStore)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
with(awaitItem()) {
|
||||
assertThat(timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.On)
|
||||
eventSink(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Off))
|
||||
}
|
||||
with(awaitItem()) {
|
||||
assertThat(timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.Off)
|
||||
eventSink(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Private))
|
||||
}
|
||||
with(awaitItem()) {
|
||||
assertThat(timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.Private)
|
||||
assertThat(mediaPreviewConfigState.setHideInviteAvatarsAction).isEqualTo(AsyncAction.Loading)
|
||||
assertThat(mediaPreviewConfigState.setTimelineMediaPreviewAction).isEqualTo(AsyncAction.Success(Unit))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAdvancedSettingsPresenter(
|
||||
private fun CoroutineScope.createAdvancedSettingsPresenter(
|
||||
appPreferencesStore: InMemoryAppPreferencesStore = InMemoryAppPreferencesStore(),
|
||||
sessionPreferencesStore: InMemorySessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||
matrixClient: MatrixClient = FakeMatrixClient(),
|
||||
mediaPreviewConfigStateStore: MediaPreviewConfigStateStore = FakeMediaPreviewConfigStateStore(),
|
||||
) = AdvancedSettingsPresenter(
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
sessionPreferencesStore = sessionPreferencesStore,
|
||||
matrixClient = matrixClient,
|
||||
mediaPreviewConfigStateStore = mediaPreviewConfigStateStore,
|
||||
sessionCoroutineScope = this,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
|||
import com.google.common.truth.Truth.assertThat
|
||||
import im.vector.app.features.analytics.plan.Interaction
|
||||
import io.element.android.features.preferences.impl.R
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
|
|
@ -131,7 +132,7 @@ class AdvancedSettingsViewTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "h640dp")
|
||||
@Config(qualifiers = "h1080dp")
|
||||
fun `clicking on hide invite avatars emits the expected event`() {
|
||||
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
||||
rule.setAdvancedSettingsView(
|
||||
|
|
@ -145,8 +146,8 @@ class AdvancedSettingsViewTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "h640dp")
|
||||
fun `clicking on timeline media preview emits the expected event`() {
|
||||
@Config(qualifiers = "h1080dp")
|
||||
fun `clicking on timeline media preview always hide emits the expected event`() {
|
||||
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
||||
rule.setAdvancedSettingsView(
|
||||
state = aAdvancedSettingsState(
|
||||
|
|
@ -157,6 +158,65 @@ class AdvancedSettingsViewTest {
|
|||
rule.clickOn(R.string.screen_advanced_settings_show_media_timeline_always_hide)
|
||||
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Off))
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "h1080dp")
|
||||
fun `clicking on timeline media preview private rooms emits the expected event`() {
|
||||
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
||||
rule.setAdvancedSettingsView(
|
||||
state = aAdvancedSettingsState(
|
||||
eventSink = eventsRecorder,
|
||||
timelineMediaPreviewValue = MediaPreviewValue.On
|
||||
),
|
||||
)
|
||||
rule.clickOn(R.string.screen_advanced_settings_show_media_timeline_private_rooms)
|
||||
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Private))
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "h1080dp")
|
||||
fun `clicking on timeline media preview always show emits the expected event`() {
|
||||
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
||||
rule.setAdvancedSettingsView(
|
||||
state = aAdvancedSettingsState(
|
||||
eventSink = eventsRecorder,
|
||||
timelineMediaPreviewValue = MediaPreviewValue.Off
|
||||
),
|
||||
)
|
||||
rule.clickOn(R.string.screen_advanced_settings_show_media_timeline_always_show)
|
||||
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.On))
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "h1080dp")
|
||||
fun `hide invite avatars toggle is disabled when action is loading`() {
|
||||
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>(expectEvents = false)
|
||||
rule.setAdvancedSettingsView(
|
||||
state = aAdvancedSettingsState(
|
||||
eventSink = eventsRecorder,
|
||||
hideInviteAvatars = false,
|
||||
setHideInviteAvatarsAction = AsyncAction.Loading
|
||||
),
|
||||
)
|
||||
// The toggle should be disabled, so clicking should not emit any events
|
||||
rule.clickOn(R.string.screen_advanced_settings_hide_invite_avatars_toggle_title)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "h1080dp")
|
||||
fun `timeline media preview options are disabled when action is loading`() {
|
||||
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>(expectEvents = false)
|
||||
rule.setAdvancedSettingsView(
|
||||
state = aAdvancedSettingsState(
|
||||
eventSink = eventsRecorder,
|
||||
timelineMediaPreviewValue = MediaPreviewValue.On,
|
||||
setTimelineMediaPreviewAction = AsyncAction.Loading
|
||||
),
|
||||
)
|
||||
// The options should be disabled, so clicking should not emit any events
|
||||
rule.clickOn(R.string.screen_advanced_settings_show_media_timeline_always_hide)
|
||||
rule.clickOn(R.string.screen_advanced_settings_show_media_timeline_private_rooms)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setAdvancedSettingsView(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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.features.preferences.impl.advanced
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
|
||||
|
||||
class FakeMediaPreviewConfigStateStore(
|
||||
hideInviteAvatarsValue: Boolean = false,
|
||||
timelineMediaPreviewValue: MediaPreviewValue = MediaPreviewValue.On,
|
||||
setHideInviteAvatarsActionValue: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
setTimelineMediaPreviewActionValue: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
) : MediaPreviewConfigStateStore {
|
||||
private val hideInviteAvatars = mutableStateOf(hideInviteAvatarsValue)
|
||||
private val timelineMediaPreviewValue = mutableStateOf(timelineMediaPreviewValue)
|
||||
private val setHideInviteAvatarsAction = mutableStateOf(setHideInviteAvatarsActionValue)
|
||||
private val setTimelineMediaPreviewAction = mutableStateOf(setTimelineMediaPreviewActionValue)
|
||||
|
||||
private val setHideInviteAvatarsEvents = mutableListOf<Boolean>()
|
||||
private val setTimelineMediaPreviewValueEvents = mutableListOf<MediaPreviewValue>()
|
||||
|
||||
@Composable
|
||||
override fun state(): MediaPreviewConfigState {
|
||||
return MediaPreviewConfigState(
|
||||
hideInviteAvatars = hideInviteAvatars.value,
|
||||
timelineMediaPreviewValue = timelineMediaPreviewValue.value,
|
||||
setHideInviteAvatarsAction = setHideInviteAvatarsAction.value,
|
||||
setTimelineMediaPreviewAction = setTimelineMediaPreviewAction.value,
|
||||
)
|
||||
}
|
||||
|
||||
override fun setHideInviteAvatars(hide: Boolean) {
|
||||
setHideInviteAvatarsEvents.add(hide)
|
||||
hideInviteAvatars.value = hide
|
||||
}
|
||||
|
||||
override fun setTimelineMediaPreviewValue(value: MediaPreviewValue) {
|
||||
setTimelineMediaPreviewValueEvents.add(value)
|
||||
timelineMediaPreviewValue.value = value
|
||||
}
|
||||
|
||||
fun getSetHideInviteAvatarsEvents(): List<Boolean> = setHideInviteAvatarsEvents.toList()
|
||||
fun getSetTimelineMediaPreviewValueEvents(): List<MediaPreviewValue> = setTimelineMediaPreviewValueEvents.toList()
|
||||
}
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* 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.features.preferences.impl.advanced
|
||||
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.matrix.api.media.MediaPreviewConfig
|
||||
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
|
||||
import io.element.android.libraries.matrix.test.media.FakeMediaPreviewService
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.lambda.assert
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class MediaPreviewConfigStateStoreTest {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
fun `initial state is correct with default values`() = runTest {
|
||||
val store = createMediaPreviewConfigStateStore()
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
store.state()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.hideInviteAvatars).isFalse()
|
||||
assertThat(initialState.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.On)
|
||||
assertThat(initialState.setHideInviteAvatarsAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||
assertThat(initialState.setTimelineMediaPreviewAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `state updates when config flow emits new values`() = runTest {
|
||||
val configFlow = MutableStateFlow(MediaPreviewConfig.DEFAULT)
|
||||
val mediaPreviewService = FakeMediaPreviewService(configFlow)
|
||||
val store = createMediaPreviewConfigStateStore(mediaPreviewService = mediaPreviewService)
|
||||
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
store.state()
|
||||
}.test {
|
||||
// Initial state
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.hideInviteAvatars).isFalse()
|
||||
assertThat(initialState.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.On)
|
||||
|
||||
// Update config
|
||||
configFlow.value = MediaPreviewConfig(hideInviteAvatar = true, mediaPreviewValue = MediaPreviewValue.Private)
|
||||
|
||||
skipItems(1)
|
||||
// Updated state
|
||||
val updatedState = awaitItem()
|
||||
assertThat(updatedState.hideInviteAvatars).isTrue()
|
||||
assertThat(updatedState.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.Private)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setHideInviteAvatars updates state and calls service on success`() = runTest {
|
||||
val setHideInviteAvatarsValueLambda = lambdaRecorder<Boolean, Result<Unit>> { Result.success(Unit) }
|
||||
val mediaPreviewService = FakeMediaPreviewService(
|
||||
setHideInviteAvatarsResult = setHideInviteAvatarsValueLambda
|
||||
)
|
||||
val store = createMediaPreviewConfigStateStore(mediaPreviewService = mediaPreviewService)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
store.state()
|
||||
}.test {
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.hideInviteAvatars).isFalse()
|
||||
}
|
||||
store.setHideInviteAvatars(true)
|
||||
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.hideInviteAvatars).isTrue()
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.hideInviteAvatars).isTrue()
|
||||
assertThat(state.setHideInviteAvatarsAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.hideInviteAvatars).isTrue()
|
||||
assertThat(state.setHideInviteAvatarsAction).isInstanceOf(AsyncAction.Success::class.java)
|
||||
}
|
||||
assert(setHideInviteAvatarsValueLambda).isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setHideInviteAvatars reverts state on failure`() = runTest {
|
||||
val setHideInviteAvatarsValueLambda = lambdaRecorder<Boolean, Result<Unit>> {
|
||||
Result.failure(Exception())
|
||||
}
|
||||
val mediaPreviewService = FakeMediaPreviewService(
|
||||
setHideInviteAvatarsResult = setHideInviteAvatarsValueLambda
|
||||
)
|
||||
val store = createMediaPreviewConfigStateStore(mediaPreviewService = mediaPreviewService)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
store.state()
|
||||
}.test {
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.hideInviteAvatars).isFalse()
|
||||
}
|
||||
store.setHideInviteAvatars(true)
|
||||
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.hideInviteAvatars).isTrue()
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.hideInviteAvatars).isTrue()
|
||||
assertThat(state.setHideInviteAvatarsAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
}
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.hideInviteAvatars).isFalse()
|
||||
assertThat(state.setHideInviteAvatarsAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||
}
|
||||
assert(setHideInviteAvatarsValueLambda).isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setTimelineMediaPreviewValue updates state and calls service on success`() = runTest {
|
||||
val setMediaPreviewValueLambda = lambdaRecorder<MediaPreviewValue, Result<Unit>> { Result.success(Unit) }
|
||||
val mediaPreviewService = FakeMediaPreviewService(
|
||||
setMediaPreviewValueResult = setMediaPreviewValueLambda
|
||||
)
|
||||
val store = createMediaPreviewConfigStateStore(mediaPreviewService = mediaPreviewService)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
store.state()
|
||||
}.test {
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.On)
|
||||
}
|
||||
store.setTimelineMediaPreviewValue(MediaPreviewValue.Off)
|
||||
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.Off)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.Off)
|
||||
assertThat(state.setTimelineMediaPreviewAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.Off)
|
||||
assertThat(state.setTimelineMediaPreviewAction).isInstanceOf(AsyncAction.Success::class.java)
|
||||
}
|
||||
assert(setMediaPreviewValueLambda).isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setTimelineMediaPreviewValue reverts state on failure`() = runTest {
|
||||
val setMediaPreviewValueLambda = lambdaRecorder<MediaPreviewValue, Result<Unit>> {
|
||||
Result.failure(Exception())
|
||||
}
|
||||
val mediaPreviewService = FakeMediaPreviewService(
|
||||
setMediaPreviewValueResult = setMediaPreviewValueLambda
|
||||
)
|
||||
val store = createMediaPreviewConfigStateStore(mediaPreviewService = mediaPreviewService)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
store.state()
|
||||
}.test {
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.On)
|
||||
}
|
||||
store.setTimelineMediaPreviewValue(MediaPreviewValue.Off)
|
||||
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.Off)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.Off)
|
||||
assertThat(state.setTimelineMediaPreviewAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
}
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.On)
|
||||
assertThat(state.setTimelineMediaPreviewAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||
}
|
||||
assert(setMediaPreviewValueLambda).isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createMediaPreviewConfigStateStore(
|
||||
mediaPreviewService: FakeMediaPreviewService = FakeMediaPreviewService(),
|
||||
snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher()
|
||||
): MediaPreviewConfigStateStore = DefaultMediaPreviewConfigStateStore(
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
mediaPreviewService = mediaPreviewService,
|
||||
snackbarDispatcher = snackbarDispatcher
|
||||
)
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@ import io.element.android.features.roomlist.impl.search.RoomListSearchEvents
|
|||
import io.element.android.features.roomlist.impl.search.RoomListSearchState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.mapState
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
|
|
@ -120,8 +121,11 @@ class RoomListPresenter @Inject constructor(
|
|||
// Avatar indicator
|
||||
val showAvatarIndicator by indicatorService.showRoomListTopBarIndicator()
|
||||
val hideInvitesAvatar by remember {
|
||||
appPreferencesStore.getHideInviteAvatarsFlow()
|
||||
}.collectAsState(initial = false)
|
||||
client
|
||||
.mediaPreviewService()
|
||||
.mediaPreviewConfigFlow
|
||||
.mapState { config -> config.hideInviteAvatar }
|
||||
}.collectAsState()
|
||||
|
||||
val contextMenu = remember { mutableStateOf<RoomListState.ContextMenu>(RoomListState.ContextMenu.Hidden) }
|
||||
val declineInviteMenu = remember { mutableStateOf<RoomListState.DeclineInviteMenu>(RoomListState.DeclineInviteMenu.Hidden) }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue