Merge pull request #4581 from element-hq/feature/fga/advanced_settings_theme_rework

change (preferences) : use PreferenceDropdown for appearance
This commit is contained in:
ganfra 2025-04-14 15:19:48 +02:00 committed by GitHub
commit 5552c85b2a
23 changed files with 222 additions and 168 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()

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:926ce46cacac7beaac403e1c4e8034398d91dc48ab5f651bc7f0639ac65fe33a oid sha256:733b71c4199d1a5908b5e76d8e44f5781cee39e97d57a049be99f9e66c3d30e1
size 46759 size 46936

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:84c2474bc4d10e1aa3184c41b12b1bcf51d7e2b7ec801944608613aa46f50eba oid sha256:ef41e0df758991e44e9abcf3d7d511c7cc932711db58e94ad1b4617fe45c60c3
size 46636 size 46826

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:611d435a2b3e6e0f8c7721905f635c1c93aa217df3fe4f4b6bb38bf5700e3629 oid sha256:de45164ae8112206c1cf1a67c284cdea1509f4fa070f963fddf6a87eb9183ee1
size 34586 size 46813

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:4d9b0a865db8f87d6c1c95b8fb6d916f261d44cecec52fdebd56288f2df35f0f oid sha256:7c3c1d2b2fc9e5a16a1bc57551238c5190f25a873575eaf3731c36aaf19d7f00
size 46627 size 46802

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:0505bdd3cfccf156249ab27ed0f087bd4d01d11121cb1f5e0c8450c6b7b58059 oid sha256:1759e7063862ec1de7ce2b894067478599afb7c87b183e904c8d0199c93ed546
size 46615 size 46646

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:a004afcec0cb40f82172c7abdc4404d1a0d879d0e23c63d0dcd1d7325cddff43 oid sha256:420e10e0eb2c90d38e28262152dd2dee54cfb73f7b031b7a34acea17a8f784fa
size 46471 size 46943

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cb09af4f9c96afb60b917d9484e28bacb73c494f9dfc2f42abb482896027b7b7
size 46767

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:4dfb5f83a129332a84f0184ba6a37ba9a95ded6fd9692a6e5710aaeb6b424c7c oid sha256:7faf10e196f9275fdafabb53cb354532b2c5aa73869d1fe48ac67b53bbb34531
size 48610 size 48681

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:903d0aa3faa5b4feae990139c467a5a6662808ffb768c7a2a5cd314f9ea09ba2 oid sha256:15b0cf42d99a96ef4b52349301e4e90a5478150396a6890a66df1d33c3b6b89d
size 48482 size 48558

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:4a2538700b5dc993568575e7017fc26c3cba7af7b7567fac069af5aa0c6f6b5f oid sha256:f7debb36ffa6a60d7d30f9d59e247e6091c455860bdaae67502a52a5cec5e9c9
size 36484 size 48572

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:9177ba1065f5cfe3137a16c024445332b2fe326969f5f6d65ccf6b1707deabe9 oid sha256:106251ed2fb0a330fa98d4dabd8aebcb946fdbd098b18372c8a9c3b8df39086f
size 48502 size 48554

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:1f07064f41aafea6a91e3314d7d3bb099918e348ef9f9154a057d90758b9fb71 oid sha256:a9b8cb284cc78c03f4bd4f4364d898e656a07ebe228243fdded35c283a1205e0
size 48480 size 48492

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:92c1c93890214c88ed0e4d1d9faee27cc671db94fe928633ccc82471c137b64a oid sha256:ddb21e08e8047aada11330634ad9c137cdb8a3a349f457c066243394fcc23924
size 48422 size 48682

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7f21d860514e97b48c3fab7fef4fbf6d710854d21ea2eecc3b44a5ba340c92eb
size 48608