change (preferences) : use PreferenceDropdown for appearance (and add some tests)

This commit is contained in:
ganfra 2025-04-11 17:09:31 +02:00
parent ce0bac55c5
commit dffdb15cf0
9 changed files with 198 additions and 138 deletions

View file

@ -7,16 +7,13 @@
package io.element.android.features.preferences.impl.advanced package io.element.android.features.preferences.impl.advanced
import io.element.android.compound.theme.Theme
import io.element.android.libraries.matrix.api.media.MediaPreviewValue import io.element.android.libraries.matrix.api.media.MediaPreviewValue
sealed interface AdvancedSettingsEvents { sealed interface AdvancedSettingsEvents {
data class SetDeveloperModeEnabled(val enabled: Boolean) : AdvancedSettingsEvents data class SetDeveloperModeEnabled(val enabled: Boolean) : AdvancedSettingsEvents
data class SetSharePresenceEnabled(val enabled: Boolean) : AdvancedSettingsEvents data class SetSharePresenceEnabled(val enabled: Boolean) : AdvancedSettingsEvents
data class SetCompressMedia(val compress: Boolean) : AdvancedSettingsEvents data class SetCompressMedia(val compress: Boolean) : AdvancedSettingsEvents
data object ChangeTheme : AdvancedSettingsEvents data class SetTheme(val theme: ThemeOption) : AdvancedSettingsEvents
data object CancelChangeTheme : AdvancedSettingsEvents
data class SetTheme(val theme: Theme) : AdvancedSettingsEvents
data class SetTimelineMediaPreviewValue(val value: MediaPreviewValue) : AdvancedSettingsEvents data class SetTimelineMediaPreviewValue(val value: MediaPreviewValue) : AdvancedSettingsEvents
data class SetHideInviteAvatars(val value: Boolean) : AdvancedSettingsEvents data class SetHideInviteAvatars(val value: Boolean) : AdvancedSettingsEvents
} }

View file

@ -9,11 +9,10 @@ package io.element.android.features.preferences.impl.advanced
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import io.element.android.compound.theme.Theme import io.element.android.compound.theme.Theme
import io.element.android.compound.theme.mapToTheme import io.element.android.compound.theme.mapToTheme
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
@ -39,11 +38,9 @@ class AdvancedSettingsPresenter @Inject constructor(
val doesCompressMedia by remember { val doesCompressMedia by remember {
sessionPreferencesStore.doesCompressMedia() sessionPreferencesStore.doesCompressMedia()
}.collectAsState(initial = true) }.collectAsState(initial = true)
val theme by remember { val theme = remember {
appPreferencesStore.getThemeFlow().mapToTheme() appPreferencesStore.getThemeFlow().mapToTheme()
}.collectAsState(initial = Theme.System) }.collectAsState(initial = Theme.System)
var showChangeThemeDialog by remember { mutableStateOf(false) }
val hideInviteAvatars by remember { val hideInviteAvatars by remember {
appPreferencesStore.getHideInviteAvatarsFlow() appPreferencesStore.getHideInviteAvatarsFlow()
}.collectAsState(false) }.collectAsState(false)
@ -52,6 +49,16 @@ class AdvancedSettingsPresenter @Inject constructor(
appPreferencesStore.getTimelineMediaPreviewValueFlow() appPreferencesStore.getTimelineMediaPreviewValueFlow()
}.collectAsState(initial = MediaPreviewValue.On) }.collectAsState(initial = MediaPreviewValue.On)
val themeOption by remember {
derivedStateOf {
when (theme.value) {
Theme.System -> ThemeOption.System
Theme.Dark -> ThemeOption.Dark
Theme.Light -> ThemeOption.Light
}
}
}
fun handleEvents(event: AdvancedSettingsEvents) { fun handleEvents(event: AdvancedSettingsEvents) {
when (event) { when (event) {
is AdvancedSettingsEvents.SetDeveloperModeEnabled -> localCoroutineScope.launch { is AdvancedSettingsEvents.SetDeveloperModeEnabled -> localCoroutineScope.launch {
@ -63,11 +70,12 @@ class AdvancedSettingsPresenter @Inject constructor(
is AdvancedSettingsEvents.SetCompressMedia -> localCoroutineScope.launch { is AdvancedSettingsEvents.SetCompressMedia -> localCoroutineScope.launch {
sessionPreferencesStore.setCompressMedia(event.compress) sessionPreferencesStore.setCompressMedia(event.compress)
} }
AdvancedSettingsEvents.CancelChangeTheme -> showChangeThemeDialog = false
AdvancedSettingsEvents.ChangeTheme -> showChangeThemeDialog = true
is AdvancedSettingsEvents.SetTheme -> localCoroutineScope.launch { is AdvancedSettingsEvents.SetTheme -> localCoroutineScope.launch {
appPreferencesStore.setTheme(event.theme.name) when (event.theme) {
showChangeThemeDialog = false ThemeOption.System -> appPreferencesStore.setTheme(Theme.System.name)
ThemeOption.Dark -> appPreferencesStore.setTheme(Theme.Dark.name)
ThemeOption.Light -> appPreferencesStore.setTheme(Theme.Light.name)
}
} }
is AdvancedSettingsEvents.SetHideInviteAvatars -> localCoroutineScope.launch { is AdvancedSettingsEvents.SetHideInviteAvatars -> localCoroutineScope.launch {
appPreferencesStore.setHideInviteAvatars(event.value) appPreferencesStore.setHideInviteAvatars(event.value)
@ -82,8 +90,7 @@ class AdvancedSettingsPresenter @Inject constructor(
isDeveloperModeEnabled = isDeveloperModeEnabled, isDeveloperModeEnabled = isDeveloperModeEnabled,
isSharePresenceEnabled = isSharePresenceEnabled, isSharePresenceEnabled = isSharePresenceEnabled,
doesCompressMedia = doesCompressMedia, doesCompressMedia = doesCompressMedia,
theme = theme, theme = themeOption,
showChangeThemeDialog = showChangeThemeDialog,
hideInviteAvatars = hideInviteAvatars, hideInviteAvatars = hideInviteAvatars,
timelineMediaPreviewValue = timelineMediaPreviewValue, timelineMediaPreviewValue = timelineMediaPreviewValue,
eventSink = { handleEvents(it) } eventSink = { handleEvents(it) }

View file

@ -7,16 +7,33 @@
package io.element.android.features.preferences.impl.advanced package io.element.android.features.preferences.impl.advanced
import io.element.android.compound.theme.Theme import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import io.element.android.libraries.designsystem.components.preferences.DropdownOption
import io.element.android.libraries.matrix.api.media.MediaPreviewValue import io.element.android.libraries.matrix.api.media.MediaPreviewValue
import io.element.android.libraries.ui.strings.CommonStrings
data class AdvancedSettingsState( data class AdvancedSettingsState(
val isDeveloperModeEnabled: Boolean, val isDeveloperModeEnabled: Boolean,
val isSharePresenceEnabled: Boolean, val isSharePresenceEnabled: Boolean,
val doesCompressMedia: Boolean, val doesCompressMedia: Boolean,
val theme: Theme, val theme: ThemeOption,
val showChangeThemeDialog: Boolean,
val hideInviteAvatars: Boolean, val hideInviteAvatars: Boolean,
val timelineMediaPreviewValue: MediaPreviewValue, val timelineMediaPreviewValue: MediaPreviewValue,
val eventSink: (AdvancedSettingsEvents) -> Unit val eventSink: (AdvancedSettingsEvents) -> Unit
) )
enum class ThemeOption : DropdownOption {
System {
@Composable
override fun getText(): String = stringResource(CommonStrings.common_system)
},
Dark {
@Composable
override fun getText(): String = stringResource(CommonStrings.common_dark)
},
Light {
@Composable
override fun getText(): String = stringResource(CommonStrings.common_light)
}
}

View file

@ -8,7 +8,6 @@
package io.element.android.features.preferences.impl.advanced package io.element.android.features.preferences.impl.advanced
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.compound.theme.Theme
import io.element.android.libraries.matrix.api.media.MediaPreviewValue import io.element.android.libraries.matrix.api.media.MediaPreviewValue
open class AdvancedSettingsStateProvider : PreviewParameterProvider<AdvancedSettingsState> { open class AdvancedSettingsStateProvider : PreviewParameterProvider<AdvancedSettingsState> {
@ -16,7 +15,6 @@ open class AdvancedSettingsStateProvider : PreviewParameterProvider<AdvancedSett
get() = sequenceOf( get() = sequenceOf(
aAdvancedSettingsState(), aAdvancedSettingsState(),
aAdvancedSettingsState(isDeveloperModeEnabled = true), aAdvancedSettingsState(isDeveloperModeEnabled = true),
aAdvancedSettingsState(showChangeThemeDialog = true),
aAdvancedSettingsState(isSharePresenceEnabled = true), aAdvancedSettingsState(isSharePresenceEnabled = true),
aAdvancedSettingsState(doesCompressMedia = true), aAdvancedSettingsState(doesCompressMedia = true),
aAdvancedSettingsState(hideInviteAvatars = true), aAdvancedSettingsState(hideInviteAvatars = true),
@ -28,16 +26,15 @@ fun aAdvancedSettingsState(
isDeveloperModeEnabled: Boolean = false, isDeveloperModeEnabled: Boolean = false,
isSharePresenceEnabled: Boolean = false, isSharePresenceEnabled: Boolean = false,
doesCompressMedia: Boolean = false, doesCompressMedia: Boolean = false,
showChangeThemeDialog: Boolean = false,
hideInviteAvatars: Boolean = false, hideInviteAvatars: Boolean = false,
theme: ThemeOption = ThemeOption.System,
timelineMediaPreviewValue: MediaPreviewValue = MediaPreviewValue.On, timelineMediaPreviewValue: MediaPreviewValue = MediaPreviewValue.On,
eventSink: (AdvancedSettingsEvents) -> Unit = {}, eventSink: (AdvancedSettingsEvents) -> Unit = {},
) = AdvancedSettingsState( ) = AdvancedSettingsState(
isDeveloperModeEnabled = isDeveloperModeEnabled, isDeveloperModeEnabled = isDeveloperModeEnabled,
isSharePresenceEnabled = isSharePresenceEnabled, isSharePresenceEnabled = isSharePresenceEnabled,
doesCompressMedia = doesCompressMedia, doesCompressMedia = doesCompressMedia,
theme = Theme.System, theme = theme,
showChangeThemeDialog = showChangeThemeDialog,
hideInviteAvatars = hideInviteAvatars, hideInviteAvatars = hideInviteAvatars,
timelineMediaPreviewValue = timelineMediaPreviewValue, timelineMediaPreviewValue = timelineMediaPreviewValue,
eventSink = eventSink eventSink = eventSink

View file

@ -12,14 +12,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import im.vector.app.features.analytics.plan.Interaction import im.vector.app.features.analytics.plan.Interaction
import io.element.android.compound.theme.Theme
import io.element.android.compound.theme.themes
import io.element.android.features.preferences.impl.R import io.element.android.features.preferences.impl.R
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
import io.element.android.libraries.designsystem.components.dialogs.ListOption
import io.element.android.libraries.designsystem.components.dialogs.SingleSelectionDialog
import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.list.ListItemContent
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
import io.element.android.libraries.designsystem.components.preferences.PreferenceDropdown
import io.element.android.libraries.designsystem.components.preferences.PreferencePage import io.element.android.libraries.designsystem.components.preferences.PreferencePage
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
@ -34,8 +31,7 @@ import io.element.android.libraries.matrix.api.media.MediaPreviewValue
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.analytics.compose.LocalAnalyticsService import io.element.android.services.analytics.compose.LocalAnalyticsService
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toPersistentList
import kotlinx.collections.immutable.toImmutableList
@Composable @Composable
fun AdvancedSettingsView( fun AdvancedSettingsView(
@ -49,15 +45,12 @@ fun AdvancedSettingsView(
onBackClick = onBackClick, onBackClick = onBackClick,
title = stringResource(id = CommonStrings.common_advanced_settings) title = stringResource(id = CommonStrings.common_advanced_settings)
) { ) {
ListItem( PreferenceDropdown(
headlineContent = { title = stringResource(id = CommonStrings.common_appearance),
Text(text = stringResource(id = CommonStrings.common_appearance)) selectedOption = state.theme,
}, options = ThemeOption.entries.toPersistentList(),
trailingContent = ListItemContent.Text( onSelectOption = { logLevel ->
state.theme.toHumanReadable() state.eventSink(AdvancedSettingsEvents.SetTheme(logLevel))
),
onClick = {
state.eventSink(AdvancedSettingsEvents.ChangeTheme)
} }
) )
ListItem( ListItem(
@ -108,21 +101,6 @@ fun AdvancedSettingsView(
) )
ModerationAndSafety(state) ModerationAndSafety(state)
} }
if (state.showChangeThemeDialog) {
SingleSelectionDialog(
options = getOptions(),
initialSelection = themes.indexOf(state.theme),
onSelectOption = {
state.eventSink(
AdvancedSettingsEvents.SetTheme(
themes[it]
)
)
},
onDismissRequest = { state.eventSink(AdvancedSettingsEvents.CancelChangeTheme) },
)
}
} }
@Composable @Composable
@ -176,24 +154,6 @@ private fun ModerationAndSafety(
} }
} }
@Composable
private fun getOptions(): ImmutableList<ListOption> {
return themes.map {
ListOption(title = it.toHumanReadable())
}.toImmutableList()
}
@Composable
private fun Theme.toHumanReadable(): String {
return stringResource(
id = when (this) {
Theme.System -> CommonStrings.common_system
Theme.Dark -> CommonStrings.common_dark
Theme.Light -> CommonStrings.common_light
}
)
}
@PreviewWithLargeHeight @PreviewWithLargeHeight
@Composable @Composable
internal fun AdvancedSettingsViewLightPreview(@PreviewParameter(AdvancedSettingsStateProvider::class) state: AdvancedSettingsState) = internal fun AdvancedSettingsViewLightPreview(@PreviewParameter(AdvancedSettingsStateProvider::class) state: AdvancedSettingsState) =

View file

@ -7,22 +7,28 @@
package io.element.android.features.preferences.impl.developer.tracing package io.element.android.features.preferences.impl.developer.tracing
import androidx.compose.runtime.Composable
import io.element.android.libraries.designsystem.components.preferences.DropdownOption import io.element.android.libraries.designsystem.components.preferences.DropdownOption
enum class LogLevelItem : DropdownOption { enum class LogLevelItem : DropdownOption {
ERROR { ERROR {
override val text: String = "Error" @Composable
override fun getText(): String = "Error"
}, },
WARN { WARN {
override val text: String = "Warn" @Composable
override fun getText(): String = "Warn"
}, },
INFO { INFO {
override val text: String = "Info" @Composable
override fun getText(): String = "Info"
}, },
DEBUG { DEBUG {
override val text: String = "Debug" @Composable
override fun getText(): String = "Debug"
}, },
TRACE { TRACE {
override val text: String = "Trace" @Composable
override fun getText(): String = "Trace"
} }
} }

View file

@ -11,11 +11,10 @@ import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.compound.theme.Theme import io.element.android.libraries.matrix.api.media.MediaPreviewValue
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.awaitLastSequentialItem
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -30,12 +29,12 @@ class AdvancedSettingsPresenterTest {
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitLastSequentialItem() with(awaitItem()) {
assertThat(initialState.isDeveloperModeEnabled).isFalse() assertThat(isDeveloperModeEnabled).isFalse()
assertThat(initialState.showChangeThemeDialog).isFalse() assertThat(isSharePresenceEnabled).isTrue()
assertThat(initialState.isSharePresenceEnabled).isTrue() assertThat(doesCompressMedia).isTrue()
assertThat(initialState.doesCompressMedia).isTrue() assertThat(theme).isEqualTo(ThemeOption.System)
assertThat(initialState.theme).isEqualTo(Theme.System) }
} }
} }
@ -45,12 +44,17 @@ class AdvancedSettingsPresenterTest {
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitLastSequentialItem() with(awaitItem()) {
assertThat(initialState.isDeveloperModeEnabled).isFalse() assertThat(isDeveloperModeEnabled).isFalse()
initialState.eventSink.invoke(AdvancedSettingsEvents.SetDeveloperModeEnabled(true)) eventSink(AdvancedSettingsEvents.SetDeveloperModeEnabled(true))
assertThat(awaitItem().isDeveloperModeEnabled).isTrue() }
initialState.eventSink.invoke(AdvancedSettingsEvents.SetDeveloperModeEnabled(false)) with(awaitItem()) {
assertThat(awaitItem().isDeveloperModeEnabled).isFalse() assertThat(isDeveloperModeEnabled).isTrue()
eventSink(AdvancedSettingsEvents.SetDeveloperModeEnabled(false))
}
with(awaitItem()) {
assertThat(isDeveloperModeEnabled).isFalse()
}
} }
} }
@ -60,12 +64,17 @@ class AdvancedSettingsPresenterTest {
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitLastSequentialItem() with(awaitItem()) {
assertThat(initialState.isSharePresenceEnabled).isTrue() assertThat(isSharePresenceEnabled).isTrue()
initialState.eventSink.invoke(AdvancedSettingsEvents.SetSharePresenceEnabled(false)) eventSink(AdvancedSettingsEvents.SetSharePresenceEnabled(false))
assertThat(awaitItem().isSharePresenceEnabled).isFalse() }
initialState.eventSink.invoke(AdvancedSettingsEvents.SetSharePresenceEnabled(true)) with(awaitItem()) {
assertThat(awaitItem().isSharePresenceEnabled).isTrue() assertThat(isSharePresenceEnabled).isFalse()
eventSink(AdvancedSettingsEvents.SetSharePresenceEnabled(true))
}
with(awaitItem()) {
assertThat(isSharePresenceEnabled).isTrue()
}
} }
} }
@ -75,12 +84,17 @@ class AdvancedSettingsPresenterTest {
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitLastSequentialItem() with(awaitItem()) {
assertThat(initialState.doesCompressMedia).isTrue() assertThat(doesCompressMedia).isTrue()
initialState.eventSink.invoke(AdvancedSettingsEvents.SetCompressMedia(false)) eventSink(AdvancedSettingsEvents.SetCompressMedia(false))
assertThat(awaitItem().doesCompressMedia).isFalse() }
initialState.eventSink.invoke(AdvancedSettingsEvents.SetCompressMedia(true)) with(awaitItem()) {
assertThat(awaitItem().doesCompressMedia).isTrue() assertThat(doesCompressMedia).isFalse()
eventSink(AdvancedSettingsEvents.SetCompressMedia(true))
}
with(awaitItem()) {
assertThat(doesCompressMedia).isTrue()
}
} }
} }
@ -90,20 +104,61 @@ class AdvancedSettingsPresenterTest {
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitLastSequentialItem() with(awaitItem()) {
initialState.eventSink.invoke(AdvancedSettingsEvents.ChangeTheme) assertThat(theme).isEqualTo(ThemeOption.System)
val withDialog = awaitItem() eventSink(AdvancedSettingsEvents.SetTheme(ThemeOption.Dark))
assertThat(withDialog.showChangeThemeDialog).isTrue() }
// Cancel with(awaitItem()) {
withDialog.eventSink(AdvancedSettingsEvents.CancelChangeTheme) assertThat(theme).isEqualTo(ThemeOption.Dark)
val withoutDialog = awaitItem() eventSink(AdvancedSettingsEvents.SetTheme(ThemeOption.Light))
assertThat(withoutDialog.showChangeThemeDialog).isFalse() }
withDialog.eventSink.invoke(AdvancedSettingsEvents.ChangeTheme) with(awaitItem()) {
assertThat(awaitItem().showChangeThemeDialog).isTrue() assertThat(theme).isEqualTo(ThemeOption.Light)
withDialog.eventSink(AdvancedSettingsEvents.SetTheme(Theme.Light)) eventSink(AdvancedSettingsEvents.SetTheme(ThemeOption.System))
val withNewTheme = awaitItem() }
assertThat(withNewTheme.showChangeThemeDialog).isFalse() with(awaitItem()) {
assertThat(withNewTheme.theme).isEqualTo(Theme.Light) assertThat(theme).isEqualTo(ThemeOption.System)
}
}
}
@Test
fun `present - hide invite avatars`() = runTest {
val presenter = createAdvancedSettingsPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
with(awaitItem()) {
assertThat(hideInviteAvatars).isFalse()
eventSink(AdvancedSettingsEvents.SetHideInviteAvatars(true))
}
with(awaitItem()) {
assertThat(hideInviteAvatars).isTrue()
eventSink(AdvancedSettingsEvents.SetHideInviteAvatars(false))
}
with(awaitItem()) {
assertThat(hideInviteAvatars).isFalse()
}
}
}
@Test
fun `present - timeline media preview value`() = runTest {
val presenter = createAdvancedSettingsPresenter()
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)
}
} }
} }

View file

@ -14,8 +14,8 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.Interaction import im.vector.app.features.analytics.plan.Interaction
import io.element.android.compound.theme.Theme
import io.element.android.features.preferences.impl.R import io.element.android.features.preferences.impl.R
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analytics.compose.LocalAnalyticsService import io.element.android.services.analytics.compose.LocalAnalyticsService
@ -29,6 +29,7 @@ import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TestRule import org.junit.rules.TestRule
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class AdvancedSettingsViewTest { class AdvancedSettingsViewTest {
@ -49,29 +50,17 @@ class AdvancedSettingsViewTest {
} }
} }
@Test
fun `clicking on Appearance emits the expected event`() {
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
rule.setAdvancedSettingsView(
state = aAdvancedSettingsState(
eventSink = eventsRecorder
),
)
rule.clickOn(CommonStrings.common_appearance)
eventsRecorder.assertSingle(AdvancedSettingsEvents.ChangeTheme)
}
@Test @Test
fun `clicking on other theme emits the expected event`() { fun `clicking on other theme emits the expected event`() {
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>() val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
rule.setAdvancedSettingsView( rule.setAdvancedSettingsView(
state = aAdvancedSettingsState( state = aAdvancedSettingsState(
eventSink = eventsRecorder, eventSink = eventsRecorder,
showChangeThemeDialog = true
), ),
) )
rule.clickOn(CommonStrings.common_appearance)
rule.clickOn(CommonStrings.common_dark) rule.clickOn(CommonStrings.common_dark)
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTheme(Theme.Dark)) eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTheme(ThemeOption.Dark))
} }
@Test @Test
@ -140,6 +129,34 @@ class AdvancedSettingsViewTest {
) )
) )
} }
@Test
@Config(qualifiers = "h640dp")
fun `clicking on hide invite avatars emits the expected event`() {
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
rule.setAdvancedSettingsView(
state = aAdvancedSettingsState(
eventSink = eventsRecorder,
hideInviteAvatars = false
),
)
rule.clickOn(R.string.screen_advanced_settings_hide_invite_avatars_toggle_title)
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetHideInviteAvatars(true))
}
@Test
@Config(qualifiers = "h640dp")
fun `clicking on timeline media preview 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_always_hide)
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Off))
}
} }
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setAdvancedSettingsView( private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setAdvancedSettingsView(

View file

@ -99,9 +99,10 @@ fun <T : DropdownOption> PreferenceDropdown(
*/ */
interface DropdownOption { interface DropdownOption {
/** /**
* The text to display for this option. * Returns the text to be displayed for this option.
*/ */
val text: String @Composable
fun getText(): String
} }
@Composable @Composable
@ -123,7 +124,7 @@ private fun <T : DropdownOption> DropdownTrailingContent(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Text( Text(
text = selectedOption?.text.orEmpty(), text = selectedOption?.getText().orEmpty(),
maxLines = 1, maxLines = 1,
style = ElementTheme.typography.fontBodyMdRegular, style = ElementTheme.typography.fontBodyMdRegular,
color = ElementTheme.colors.textSecondary, color = ElementTheme.colors.textSecondary,
@ -139,7 +140,7 @@ private fun <T : DropdownOption> DropdownTrailingContent(
DropdownMenuItem( DropdownMenuItem(
text = { text = {
Text( Text(
text = option.text, text = option.getText(),
style = ElementTheme.typography.fontBodyMdRegular style = ElementTheme.typography.fontBodyMdRegular
) )
}, },
@ -158,13 +159,16 @@ private fun <T : DropdownOption> DropdownTrailingContent(
internal fun PreferenceDropdownPreview() = ElementThemedPreview { internal fun PreferenceDropdownPreview() = ElementThemedPreview {
val options = listOf( val options = listOf(
object : DropdownOption { object : DropdownOption {
override val text = "Option 1" @Composable
override fun getText(): String = "Option 1"
}, },
object : DropdownOption { object : DropdownOption {
override val text = "Option 2" @Composable
override fun getText(): String = "Option 2"
}, },
object : DropdownOption { object : DropdownOption {
override val text = "Option 3" @Composable
override fun getText(): String = "Option 3"
}, },
).toImmutableList() ).toImmutableList()