diff --git a/features/joinroom/impl/build.gradle.kts b/features/joinroom/impl/build.gradle.kts index ecf3843674..983ae33ce3 100644 --- a/features/joinroom/impl/build.gradle.kts +++ b/features/joinroom/impl/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { implementation(projects.features.invite.api) implementation(projects.features.roomdirectory.api) implementation(projects.services.analytics.api) + implementation(projects.libraries.preferences.api) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) @@ -46,5 +47,6 @@ dependencies { testImplementation(projects.libraries.matrix.test) testImplementation(projects.tests.testutils) testImplementation(libs.androidx.compose.ui.test.junit) + testImplementation(projects.libraries.preferences.test) testReleaseImplementation(libs.androidx.compose.ui.test.manifest) } diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt index 9914afbded..53f33be16a 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt @@ -52,6 +52,7 @@ 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 @@ -69,6 +70,7 @@ class JoinRoomPresenter @AssistedInject constructor( private val forgetRoom: ForgetRoom, private val acceptDeclineInvitePresenter: Presenter, private val buildMeta: BuildMeta, + private val appPreferencesStore: AppPreferencesStore, private val seenInvitesStore: SeenInvitesStore, ) : Presenter { interface Factory { @@ -94,6 +96,9 @@ class JoinRoomPresenter @AssistedInject constructor( val forgetRoomAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } var knockMessage by rememberSaveable { mutableStateOf("") } var isDismissingContent by remember { mutableStateOf(false) } + val hideInviteAvatars by remember { + appPreferencesStore.getHideInviteAvatarsFlow() + }.collectAsState(initial = false) val contentState by produceState( initialValue = ContentState.Loading, key1 = roomInfo, @@ -202,6 +207,7 @@ class JoinRoomPresenter @AssistedInject constructor( cancelKnockAction = cancelKnockAction.value, applicationName = buildMeta.applicationName, knockMessage = knockMessage, + hideInviteAvatars = hideInviteAvatars, eventSink = ::handleEvents ) } diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt index 7162aea414..df2a5fcf8f 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt @@ -31,6 +31,7 @@ data class JoinRoomState( val cancelKnockAction: AsyncAction, private val applicationName: String, val knockMessage: String, + val hideInviteAvatars: Boolean, val eventSink: (JoinRoomEvents) -> Unit ) { val isJoinActionUnauthorized = joinAction is AsyncAction.Failure && joinAction.error is JoinRoomFailures.UnauthorizedJoin @@ -57,6 +58,8 @@ data class JoinRoomState( } else -> JoinAuthorisationStatus.None } + + val hideAvatarsImages = hideInviteAvatars && joinAuthorisationStatus is JoinAuthorisationStatus.IsInvited } @Immutable diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt index 3ebf021cc1..e21794b46d 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt @@ -171,6 +171,7 @@ fun aJoinRoomState( forgetAction: AsyncAction = AsyncAction.Uninitialized, cancelKnockAction: AsyncAction = AsyncAction.Uninitialized, knockMessage: String = "", + hideInviteAvatars: Boolean = false, eventSink: (JoinRoomEvents) -> Unit = {} ) = JoinRoomState( roomIdOrAlias = roomIdOrAlias, @@ -182,6 +183,7 @@ fun aJoinRoomState( forgetAction = forgetAction, applicationName = "AppName", knockMessage = knockMessage, + hideInviteAvatars = hideInviteAvatars, eventSink = eventSink ) diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt index 0e8c3e3581..8aae4a029b 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt @@ -97,6 +97,7 @@ fun JoinRoomView( roomIdOrAlias = state.roomIdOrAlias, contentState = state.contentState, knockMessage = state.knockMessage, + hideAvatarsImages = state.hideAvatarsImages, onKnockMessageUpdate = { state.eventSink(JoinRoomEvents.UpdateKnockMessage(it)) }, ) }, @@ -371,6 +372,7 @@ private fun JoinRoomContent( roomIdOrAlias: RoomIdOrAlias, contentState: ContentState, knockMessage: String, + hideAvatarsImages: Boolean, onKnockMessageUpdate: (String) -> Unit, modifier: Modifier = Modifier, ) { @@ -385,13 +387,14 @@ private fun JoinRoomContent( Column(horizontalAlignment = Alignment.CenterHorizontally) { val inviteSender = (contentState.joinAuthorisationStatus as? JoinAuthorisationStatus.IsInvited)?.inviteSender if (inviteSender != null) { - InviteSenderView(inviteSender = inviteSender) + InviteSenderView(inviteSender = inviteSender, hideAvatarImage = hideAvatarsImages) Spacer(modifier = Modifier.height(32.dp)) } DefaultLoadedContent( modifier = Modifier.verticalScroll(rememberScrollState()), contentState = contentState, knockMessage = knockMessage, + hideAvatarImage = hideAvatarsImages, onKnockMessageUpdate = onKnockMessageUpdate ) } @@ -474,13 +477,14 @@ private fun IsKnockedLoadedContent(modifier: Modifier = Modifier) { private fun DefaultLoadedContent( contentState: ContentState.Loaded, knockMessage: String, + hideAvatarImage: Boolean, onKnockMessageUpdate: (String) -> Unit, modifier: Modifier = Modifier, ) { RoomPreviewOrganism( modifier = modifier, avatar = { - Avatar(contentState.avatarData(AvatarSize.RoomHeader)) + Avatar(contentState.avatarData(AvatarSize.RoomHeader), hideImage = hideAvatarImage) }, title = { if (contentState.name != null) { diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt index 6a9bd559af..90e22b70bc 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt @@ -22,6 +22,7 @@ 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 @@ -36,6 +37,7 @@ object JoinRoomModule { forgetRoom: ForgetRoom, acceptDeclineInvitePresenter: Presenter, buildMeta: BuildMeta, + appPreferencesStore: AppPreferencesStore, seenInvitesStore: SeenInvitesStore, ): JoinRoomPresenter.Factory { return object : JoinRoomPresenter.Factory { @@ -59,6 +61,7 @@ object JoinRoomModule { cancelKnockRoom = cancelKnockRoom, acceptDeclineInvitePresenter = acceptDeclineInvitePresenter, buildMeta = buildMeta, + appPreferencesStore = appPreferencesStore, seenInvitesStore = seenInvitesStore, ) } diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt index b226d53706..22f7796a5b 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt @@ -47,6 +47,8 @@ import io.element.android.libraries.matrix.test.room.aRoomPreviewInfo import io.element.android.libraries.matrix.test.room.aRoomSummary import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom 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 @@ -768,6 +770,7 @@ class JoinRoomPresenterTest { forgetRoom: ForgetRoom = FakeForgetRoom(), buildMeta: BuildMeta = aBuildMeta(applicationName = "AppName"), acceptDeclineInvitePresenter: Presenter = Presenter { anAcceptDeclineInviteState() }, + appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(), seenInvitesStore: SeenInvitesStore = InMemorySeenInvitesStore(), ): JoinRoomPresenter { return JoinRoomPresenter( @@ -783,6 +786,7 @@ class JoinRoomPresenterTest { forgetRoom = forgetRoom, buildMeta = buildMeta, acceptDeclineInvitePresenter = acceptDeclineInvitePresenter, + appPreferencesStore = appPreferencesStore, seenInvitesStore = seenInvitesStore, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenter.kt index e4283b1a02..b04ee1f176 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenter.kt @@ -16,25 +16,32 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import io.element.android.libraries.architecture.Presenter 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.isPreviewEnabled +import io.element.android.libraries.matrix.api.room.MatrixRoom 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 room: MatrixRoom, ) : Presenter { + private val allowedEvents = mutableStateOf>(setOf()) + @Composable override fun present(): TimelineProtectionState { - val hideMediaContent by remember { - appPreferencesStore.doesHideImagesAndVideosFlow() - }.collectAsState(initial = false) - var allowedEvents by remember { mutableStateOf>(setOf()) } - val protectionState by remember(hideMediaContent) { + val mediaPreviewValue = remember { + appPreferencesStore.getTimelineMediaPreviewValueFlow() + }.collectAsState(initial = MediaPreviewValue.On) + val roomInfo = room.roomInfoFlow.collectAsState() + val protectionState by remember { derivedStateOf { - if (hideMediaContent) { - ProtectionState.RenderOnly(eventIds = allowedEvents.toImmutableSet()) - } else { + val isPreviewEnabled = mediaPreviewValue.value.isPreviewEnabled(roomInfo.value.joinRule) + if (isPreviewEnabled) { ProtectionState.RenderAll + } else { + ProtectionState.RenderOnly(eventIds = allowedEvents.value.toImmutableSet()) } } } @@ -42,7 +49,7 @@ class TimelineProtectionPresenter @Inject constructor( fun handleEvent(event: TimelineProtectionEvent) { when (event) { is TimelineProtectionEvent.ShowContent -> { - allowedEvents = allowedEvents + setOfNotNull(event.eventId) + allowedEvents.value = allowedEvents.value + setOfNotNull(event.eventId) } } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenterTest.kt index b55d9313af..6da5b38185 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenterTest.kt @@ -8,7 +8,12 @@ package io.element.android.features.messages.impl.timeline.protection import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.media.MediaPreviewValue +import io.element.android.libraries.matrix.api.room.MatrixRoom +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.room.FakeMatrixRoom +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 @@ -32,8 +37,8 @@ class TimelineProtectionPresenterTest { } @Test - fun `present - protected`() = runTest { - val appPreferencesStore = InMemoryAppPreferencesStore(hideImagesAndVideos = true) + fun `present - media preview value off`() = runTest { + val appPreferencesStore = InMemoryAppPreferencesStore(timelineMediaPreviewValue = MediaPreviewValue.Off) val presenter = createPresenter(appPreferencesStore) presenter.test { skipItems(1) @@ -47,9 +52,42 @@ class TimelineProtectionPresenterTest { } } + @Test + fun `present - media preview value private in public room`() = runTest { + val appPreferencesStore = InMemoryAppPreferencesStore(timelineMediaPreviewValue = MediaPreviewValue.Private) + val room = FakeMatrixRoom(initialRoomInfo = aRoomInfo(joinRule = JoinRule.Public)) + val presenter = createPresenter(appPreferencesStore, room) + presenter.test { + skipItems(1) + val initialState = awaitItem() + assertThat(initialState.protectionState).isEqualTo(ProtectionState.RenderOnly(persistentSetOf())) + // ShowContent with null should have no effect. + initialState.eventSink(TimelineProtectionEvent.ShowContent(eventId = null)) + initialState.eventSink(TimelineProtectionEvent.ShowContent(eventId = AN_EVENT_ID)) + val finalState = awaitItem() + assertThat(finalState.protectionState).isEqualTo(ProtectionState.RenderOnly(persistentSetOf(AN_EVENT_ID))) + } + } + + @Test + fun `present - media preview value private in non public room`() = runTest { + val appPreferencesStore = InMemoryAppPreferencesStore(timelineMediaPreviewValue = MediaPreviewValue.Private) + val room = FakeMatrixRoom(initialRoomInfo = aRoomInfo(joinRule = JoinRule.Invite)) + val presenter = createPresenter(appPreferencesStore, room) + presenter.test { + val initialState = awaitItem() + assertThat(initialState.protectionState).isEqualTo(ProtectionState.RenderAll) + // ShowContent with null should have no effect. + initialState.eventSink(TimelineProtectionEvent.ShowContent(eventId = null)) + initialState.eventSink(TimelineProtectionEvent.ShowContent(eventId = AN_EVENT_ID)) + } + } + private fun createPresenter( appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(), + room: MatrixRoom = FakeMatrixRoom(), ) = TimelineProtectionPresenter( appPreferencesStore = appPreferencesStore, + room = room, ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt index 45d7ae615b..fabaf7afc5 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt @@ -8,6 +8,7 @@ package io.element.android.features.preferences.impl.advanced import io.element.android.compound.theme.Theme +import io.element.android.libraries.matrix.api.media.MediaPreviewValue sealed interface AdvancedSettingsEvents { data class SetDeveloperModeEnabled(val enabled: Boolean) : AdvancedSettingsEvents @@ -16,4 +17,6 @@ sealed interface AdvancedSettingsEvents { data object ChangeTheme : AdvancedSettingsEvents data object CancelChangeTheme : AdvancedSettingsEvents data class SetTheme(val theme: Theme) : AdvancedSettingsEvents + data class SetTimelineMediaPreviewValue(val value: MediaPreviewValue) : AdvancedSettingsEvents + data class SetHideInviteAvatars(val value: Boolean) : AdvancedSettingsEvents } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt index 80f9a98b0c..065c6fc553 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt @@ -17,6 +17,7 @@ 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.Presenter +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.launch @@ -43,6 +44,14 @@ class AdvancedSettingsPresenter @Inject constructor( }.collectAsState(initial = Theme.System) var showChangeThemeDialog by remember { mutableStateOf(false) } + val hideInviteAvatars by remember { + appPreferencesStore.getHideInviteAvatarsFlow() + }.collectAsState(false) + + val timelineMediaPreviewValue by remember { + appPreferencesStore.getTimelineMediaPreviewValueFlow() + }.collectAsState(initial = MediaPreviewValue.On) + fun handleEvents(event: AdvancedSettingsEvents) { when (event) { is AdvancedSettingsEvents.SetDeveloperModeEnabled -> localCoroutineScope.launch { @@ -60,6 +69,12 @@ class AdvancedSettingsPresenter @Inject constructor( appPreferencesStore.setTheme(event.theme.name) showChangeThemeDialog = false } + is AdvancedSettingsEvents.SetHideInviteAvatars -> localCoroutineScope.launch { + appPreferencesStore.setHideInviteAvatars(event.value) + } + is AdvancedSettingsEvents.SetTimelineMediaPreviewValue -> localCoroutineScope.launch { + appPreferencesStore.setTimelineMediaPreviewValue(event.value) + } } } @@ -69,6 +84,8 @@ class AdvancedSettingsPresenter @Inject constructor( doesCompressMedia = doesCompressMedia, theme = theme, showChangeThemeDialog = showChangeThemeDialog, + hideInviteAvatars = hideInviteAvatars, + timelineMediaPreviewValue = timelineMediaPreviewValue, eventSink = { handleEvents(it) } ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt index a202ccc95f..9f55036154 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt @@ -8,6 +8,7 @@ package io.element.android.features.preferences.impl.advanced import io.element.android.compound.theme.Theme +import io.element.android.libraries.matrix.api.media.MediaPreviewValue data class AdvancedSettingsState( val isDeveloperModeEnabled: Boolean, @@ -15,5 +16,7 @@ data class AdvancedSettingsState( val doesCompressMedia: Boolean, val theme: Theme, val showChangeThemeDialog: Boolean, + val hideInviteAvatars: Boolean, + val timelineMediaPreviewValue: MediaPreviewValue, val eventSink: (AdvancedSettingsEvents) -> Unit ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt index d8e0730bf0..19e4b8b26a 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt @@ -9,6 +9,7 @@ package io.element.android.features.preferences.impl.advanced import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.compound.theme.Theme +import io.element.android.libraries.matrix.api.media.MediaPreviewValue open class AdvancedSettingsStateProvider : PreviewParameterProvider { override val values: Sequence @@ -18,6 +19,8 @@ open class AdvancedSettingsStateProvider : PreviewParameterProvider Unit = {}, ) = AdvancedSettingsState( isDeveloperModeEnabled = isDeveloperModeEnabled, @@ -33,5 +38,7 @@ fun aAdvancedSettingsState( doesCompressMedia = doesCompressMedia, theme = Theme.System, showChangeThemeDialog = showChangeThemeDialog, + hideInviteAvatars = hideInviteAvatars, + timelineMediaPreviewValue = timelineMediaPreviewValue, eventSink = eventSink ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt index 0332e98a95..dc26487f91 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt @@ -15,14 +15,22 @@ 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.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.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferencePage -import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListSectionHeader +import io.element.android.libraries.designsystem.theme.components.ListSupportingText +import io.element.android.libraries.designsystem.theme.components.ListSupportingTextDefaults import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.matrix.api.media.MediaPreviewValue import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.analytics.compose.LocalAnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction @@ -98,6 +106,7 @@ fun AdvancedSettingsView( state.eventSink(AdvancedSettingsEvents.SetCompressMedia(newValue)) } ) + ModerationAndSafety(state) } if (state.showChangeThemeDialog) { @@ -116,6 +125,57 @@ fun AdvancedSettingsView( } } +@Composable +private fun ModerationAndSafety( + state: AdvancedSettingsState, + modifier: Modifier = Modifier, +) { + PreferenceCategory( + modifier = modifier, + title = stringResource(R.string.screen_advanced_settings_moderation_and_safety_section_title), + showTopDivider = true + ) { + PreferenceSwitch( + title = stringResource(R.string.screen_advanced_settings_hide_invite_avatars_toggle_title), + isChecked = state.hideInviteAvatars, + onCheckedChange = { + state.eventSink(AdvancedSettingsEvents.SetHideInviteAvatars(it)) + }, + ) + ListSectionHeader( + title = stringResource(R.string.screen_advanced_settings_show_media_timeline_title), + hasDivider = false, + description = { + ListSupportingText( + text = stringResource(R.string.screen_advanced_settings_show_media_timeline_subtitle), + contentPadding = ListSupportingTextDefaults.Padding.None, + ) + } + ) + 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), + onClick = { + state.eventSink(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Off)) + }, + ) + 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), + onClick = { + state.eventSink(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Private)) + }, + ) + 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), + onClick = { + state.eventSink(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.On)) + }, + ) + } +} + @Composable private fun getOptions(): ImmutableList { return themes.map { @@ -134,9 +194,21 @@ private fun Theme.toHumanReadable(): String { ) } -@PreviewsDayNight +@PreviewWithLargeHeight @Composable -internal fun AdvancedSettingsViewPreview(@PreviewParameter(AdvancedSettingsStateProvider::class) state: AdvancedSettingsState) = - ElementPreview { - AdvancedSettingsView(state = state, onBackClick = { }) - } +internal fun AdvancedSettingsViewLightPreview(@PreviewParameter(AdvancedSettingsStateProvider::class) state: AdvancedSettingsState) = + ElementPreviewLight { ContentToPreview(state) } + +@PreviewWithLargeHeight +@Composable +internal fun AdvancedSettingsViewDarkPreview(@PreviewParameter(AdvancedSettingsStateProvider::class) state: AdvancedSettingsState) = + ElementPreviewDark { ContentToPreview(state) } + +@ExcludeFromCoverage +@Composable +private fun ContentToPreview(state: AdvancedSettingsState) { + AdvancedSettingsView( + state = state, + onBackClick = { } + ) +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt index 74c673bc8a..ced7b8d2b4 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt @@ -14,7 +14,6 @@ import io.element.android.libraries.matrix.api.tracing.TraceLogPack sealed interface DeveloperSettingsEvents { data class UpdateEnabledFeature(val feature: FeatureUiModel, val isEnabled: Boolean) : DeveloperSettingsEvents data class SetCustomElementCallBaseUrl(val baseUrl: String?) : DeveloperSettingsEvents - data class SetHideImagesAndVideos(val value: Boolean) : DeveloperSettingsEvents data class SetTracingLogLevel(val logLevel: LogLevelItem) : DeveloperSettingsEvents data class ToggleTracingLogPack(val logPack: TraceLogPack, val enabled: Boolean) : DeveloperSettingsEvents data object ClearCache : DeveloperSettingsEvents diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt index 5159983a6e..cc4ae25eba 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt @@ -75,10 +75,6 @@ class DeveloperSettingsPresenter @Inject constructor( appPreferencesStore .getCustomElementCallBaseUrlFlow() }.collectAsState(initial = null) - val hideImagesAndVideos by remember { - appPreferencesStore - .doesHideImagesAndVideosFlow() - }.collectAsState(initial = false) val tracingLogLevelFlow = remember { appPreferencesStore.getTracingLogLevelFlow().map { AsyncData.Success(it.toLogLevelItem()) } @@ -128,9 +124,6 @@ class DeveloperSettingsPresenter @Inject constructor( appPreferencesStore.setCustomElementCallBaseUrl(urlToSave) } DeveloperSettingsEvents.ClearCache -> coroutineScope.clearCache(clearCacheAction) - is DeveloperSettingsEvents.SetHideImagesAndVideos -> coroutineScope.launch { - appPreferencesStore.setHideImagesAndVideos(event.value) - } is DeveloperSettingsEvents.SetTracingLogLevel -> coroutineScope.launch { appPreferencesStore.setTracingLogLevel(event.logLevel.toLogLevel()) } @@ -155,7 +148,6 @@ class DeveloperSettingsPresenter @Inject constructor( baseUrl = customElementCallBaseUrl, validator = ::customElementCallUrlValidator, ), - hideImagesAndVideos = hideImagesAndVideos, tracingLogLevel = tracingLogLevel, tracingLogPacks = tracingLogPacks, eventSink = ::handleEvents diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt index efcfcd01d4..93e7b9ae7b 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt @@ -21,7 +21,6 @@ data class DeveloperSettingsState( val rageshakeState: RageshakePreferencesState, val clearCacheAction: AsyncAction, val customElementCallBaseUrlState: CustomElementCallBaseUrlState, - val hideImagesAndVideos: Boolean, val tracingLogLevel: AsyncData, val tracingLogPacks: ImmutableList, val eventSink: (DeveloperSettingsEvents) -> Unit diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt index 18151d32c7..1585a3b8dd 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt @@ -34,7 +34,6 @@ open class DeveloperSettingsStateProvider : PreviewParameterProvider = AsyncAction.Uninitialized, customElementCallBaseUrlState: CustomElementCallBaseUrlState = aCustomElementCallBaseUrlState(), - hideImagesAndVideos: Boolean = false, traceLogPacks: List = emptyList(), eventSink: (DeveloperSettingsEvents) -> Unit = {}, ) = DeveloperSettingsState( @@ -43,7 +42,6 @@ fun aDeveloperSettingsState( cacheSize = AsyncData.Success("1.2 MB"), clearCacheAction = clearCacheAction, customElementCallBaseUrlState = customElementCallBaseUrlState, - hideImagesAndVideos = hideImagesAndVideos, tracingLogLevel = AsyncData.Success(LogLevelItem.INFO), tracingLogPacks = traceLogPacks.toPersistentList(), eventSink = eventSink, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt index e335c0a6cd..9a9cc61d68 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt @@ -51,7 +51,6 @@ fun DeveloperSettingsView( title = stringResource(id = CommonStrings.common_developer_options) ) { // Note: this is OK to hardcode strings in this debug screen. - SettingsCategory(state) PreferenceCategory( title = "Feature flags", showTopDivider = true, @@ -134,22 +133,6 @@ fun DeveloperSettingsView( } } -@Composable -private fun SettingsCategory( - state: DeveloperSettingsState, -) { - PreferenceCategory(title = "Preferences", showTopDivider = false) { - PreferenceSwitch( - title = "Hide image & video previews", - subtitle = "When toggled image & video will not render in the timeline by default.", - isChecked = state.hideImagesAndVideos, - onCheckedChange = { - state.eventSink(DeveloperSettingsEvents.SetHideImagesAndVideos(it)) - } - ) - } -} - @Composable private fun ElementCallCategory( state: DeveloperSettingsState, diff --git a/features/preferences/impl/src/main/res/values/localazy.xml b/features/preferences/impl/src/main/res/values/localazy.xml index 3b54fef5eb..520a2a9e18 100644 --- a/features/preferences/impl/src/main/res/values/localazy.xml +++ b/features/preferences/impl/src/main/res/values/localazy.xml @@ -19,6 +19,11 @@ "If turned off, your read receipts won\'t be sent to anyone. You will still receive read receipts from other users." "Share presence" "If turned off, you won’t be able to send or receive read receipts or typing notifications." + "Always hide" + "Always show" + "In private rooms" + "A hidden media can always be shown by tapping on it" + "Show media in timeline" "Enable option to view message source in the timeline." "You have no blocked users" "Unblock" diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt index c2b4672878..cf372fdd9d 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt @@ -44,7 +44,6 @@ class DeveloperSettingsPresenterTest { assertThat(state.cacheSize).isEqualTo(AsyncData.Uninitialized) assertThat(state.customElementCallBaseUrlState).isNotNull() assertThat(state.customElementCallBaseUrlState.baseUrl).isNull() - assertThat(state.hideImagesAndVideos).isFalse() assertThat(state.rageshakeState.isEnabled).isFalse() assertThat(state.rageshakeState.isSupported).isTrue() assertThat(state.rageshakeState.sensitivity).isEqualTo(0.3f) @@ -147,28 +146,6 @@ class DeveloperSettingsPresenterTest { } } - @Test - fun `present - toggling hide image and video`() = runTest { - val preferences = InMemoryAppPreferencesStore() - val presenter = createDeveloperSettingsPresenter(preferencesStore = preferences) - presenter.test { - skipItems(2) - awaitItem().also { state -> - assertThat(state.hideImagesAndVideos).isFalse() - state.eventSink(DeveloperSettingsEvents.SetHideImagesAndVideos(true)) - } - awaitItem().also { state -> - assertThat(state.hideImagesAndVideos).isTrue() - assertThat(preferences.doesHideImagesAndVideosFlow().first()).isTrue() - state.eventSink(DeveloperSettingsEvents.SetHideImagesAndVideos(false)) - } - awaitItem().also { state -> - assertThat(state.hideImagesAndVideos).isFalse() - assertThat(preferences.doesHideImagesAndVideosFlow().first()).isFalse() - } - } - } - @Test fun `present - changing tracing log level`() = runTest { val preferences = InMemoryAppPreferencesStore() diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt index 255c6442d9..5d0030af8e 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt @@ -109,18 +109,6 @@ class DeveloperSettingsViewTest { rule.onNodeWithText("Clear cache").performClick() eventsRecorder.assertSingle(DeveloperSettingsEvents.ClearCache) } - - @Test - fun `clicking on the hide images and videos switch emits the expected event`() { - val eventsRecorder = EventsRecorder() - rule.setDeveloperSettingsView( - state = aDeveloperSettingsState( - eventSink = eventsRecorder - ), - ) - rule.onNodeWithText("Hide image & video previews").performClick() - eventsRecorder.assertSingle(DeveloperSettingsEvents.SetHideImagesAndVideos(true)) - } } private fun AndroidComposeTestRule.setDeveloperSettingsView( diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index 461204b2a2..082a662c30 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -119,6 +119,9 @@ class RoomListPresenter @Inject constructor( // Avatar indicator val showAvatarIndicator by indicatorService.showRoomListTopBarIndicator() + val hideInvitesAvatar by remember { + appPreferencesStore.getHideInviteAvatarsFlow() + }.collectAsState(initial = false) val contextMenu = remember { mutableStateOf(RoomListState.ContextMenu.Hidden) } @@ -173,6 +176,7 @@ class RoomListPresenter @Inject constructor( contentState = contentState, acceptDeclineInviteState = acceptDeclineInviteState, directLogoutState = directLogoutState, + hideInvitesAvatars = hideInvitesAvatar, eventSink = ::handleEvents, ) } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt index ae62b88deb..307a2d1313 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt @@ -35,6 +35,7 @@ data class RoomListState( val contentState: RoomListContentState, val acceptDeclineInviteState: AcceptDeclineInviteState, val directLogoutState: DirectLogoutState, + val hideInvitesAvatars: Boolean, val eventSink: (RoomListEvents) -> Unit, ) { val displayFilters = contentState is RoomListContentState.Rooms diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt index 6b075db9df..1f93e81d67 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt @@ -61,6 +61,7 @@ internal fun aRoomListState( contentState: RoomListContentState = aRoomsContentState(), acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(), directLogoutState: DirectLogoutState = aDirectLogoutState(), + hideInvitesAvatars: Boolean = false, eventSink: (RoomListEvents) -> Unit = {} ) = RoomListState( matrixUser = matrixUser, @@ -75,6 +76,7 @@ internal fun aRoomListState( contentState = contentState, acceptDeclineInviteState = acceptDeclineInviteState, directLogoutState = directLogoutState, + hideInvitesAvatars = hideInvitesAvatars, eventSink = eventSink, ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt index 64ec1f8406..090180a7b8 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt @@ -81,6 +81,7 @@ fun RoomListView( RoomListSearchView( state = state.searchState, eventSink = state.eventSink, + hideInvitesAvatars = state.hideInvitesAvatars, onRoomClick = onRoomClick, modifier = Modifier .statusBarsPadding() @@ -134,6 +135,7 @@ private fun RoomListScaffold( RoomListContentView( contentState = state.contentState, filtersState = state.filtersState, + hideInvitesAvatars = state.hideInvitesAvatars, eventSink = state.eventSink, onSetUpRecoveryClick = onSetUpRecoveryClick, onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick, diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt index acdb762fa7..0118a4fb81 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt @@ -60,6 +60,7 @@ import kotlinx.collections.immutable.ImmutableList fun RoomListContentView( contentState: RoomListContentState, filtersState: RoomListFiltersState, + hideInvitesAvatars: Boolean, eventSink: (RoomListEvents) -> Unit, onSetUpRecoveryClick: () -> Unit, onConfirmRecoveryKeyClick: () -> Unit, @@ -86,6 +87,7 @@ fun RoomListContentView( is RoomListContentState.Rooms -> { RoomsView( state = contentState, + hideInvitesAvatars = hideInvitesAvatars, filtersState = filtersState, eventSink = eventSink, onSetUpRecoveryClick = onSetUpRecoveryClick, @@ -156,6 +158,7 @@ private fun EmptyView( @Composable private fun RoomsView( state: RoomListContentState.Rooms, + hideInvitesAvatars: Boolean, filtersState: RoomListFiltersState, eventSink: (RoomListEvents) -> Unit, onSetUpRecoveryClick: () -> Unit, @@ -171,6 +174,7 @@ private fun RoomsView( } else { RoomsViewList( state = state, + hideInvitesAvatars = hideInvitesAvatars, eventSink = eventSink, onSetUpRecoveryClick = onSetUpRecoveryClick, onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick, @@ -183,6 +187,7 @@ private fun RoomsView( @Composable private fun RoomsViewList( state: RoomListContentState.Rooms, + hideInvitesAvatars: Boolean, eventSink: (RoomListEvents) -> Unit, onSetUpRecoveryClick: () -> Unit, onConfirmRecoveryKeyClick: () -> Unit, @@ -240,6 +245,7 @@ private fun RoomsViewList( ) { index, room -> RoomSummaryRow( room = room, + hideInviteAvatars = hideInvitesAvatars, isInviteSeen = room.displayType == RoomSummaryDisplayType.INVITE && state.seenRoomInvites.contains(room.roomId), onClick = onRoomClick, @@ -303,6 +309,7 @@ internal fun RoomListContentViewPreview(@PreviewParameter(RoomListContentStatePr filtersState = aRoomListFiltersState( filterSelectionStates = RoomListFilter.entries.map { FilterSelectionState(it, isSelected = true) } ), + hideInvitesAvatars = false, eventSink = {}, onSetUpRecoveryClick = {}, onConfirmRecoveryKeyClick = {}, diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt index bfa255af13..9f7a67bd00 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt @@ -68,6 +68,7 @@ internal val minHeight = 84.dp @Composable internal fun RoomSummaryRow( room: RoomListRoomSummary, + hideInviteAvatars: Boolean, isInviteSeen: Boolean, onClick: (RoomListRoomSummary) -> Unit, eventSink: (RoomListEvents) -> Unit, @@ -81,6 +82,7 @@ internal fun RoomSummaryRow( RoomSummaryDisplayType.INVITE -> { RoomSummaryScaffoldRow( room = room, + hideAvatarImage = hideInviteAvatars, onClick = onClick, onLongClick = { Timber.d("Long click on invite room") @@ -93,6 +95,7 @@ internal fun RoomSummaryRow( InviteSenderView( modifier = Modifier.fillMaxWidth(), inviteSender = room.inviteSender, + hideAvatarImage = hideInviteAvatars ) } Spacer(modifier = Modifier.height(12.dp)) @@ -165,6 +168,7 @@ private fun RoomSummaryScaffoldRow( onClick: (RoomListRoomSummary) -> Unit, onLongClick: (RoomListRoomSummary) -> Unit, modifier: Modifier = Modifier, + hideAvatarImage: Boolean = false, content: @Composable ColumnScope.() -> Unit ) { val clickModifier = Modifier.combinedClickable( @@ -185,6 +189,7 @@ private fun RoomSummaryScaffoldRow( CompositeAvatar( avatarData = room.avatarData, heroes = room.heroes, + hideAvatarImages = hideAvatarImage, ) Spacer(modifier = Modifier.width(16.dp)) Column( @@ -388,6 +393,7 @@ private fun MentionIndicatorAtom() { internal fun RoomSummaryRowPreview(@PreviewParameter(RoomListRoomSummaryProvider::class) data: RoomListRoomSummary) = ElementPreview { RoomSummaryRow( room = data, + hideInviteAvatars = false, // Set isInviteSeen to true for the preview when the room has name "Bob" isInviteSeen = data.name == "Bob", onClick = {}, diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt index 4f6e783704..bfb2011dd5 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt @@ -54,6 +54,7 @@ import io.element.android.libraries.ui.strings.CommonStrings @Composable internal fun RoomListSearchView( state: RoomListSearchState, + hideInvitesAvatars: Boolean, eventSink: (RoomListEvents) -> Unit, onRoomClick: (RoomId) -> Unit, modifier: Modifier = Modifier, @@ -80,6 +81,7 @@ internal fun RoomListSearchView( if (state.isSearchActive) { RoomListSearchContent( state = state, + hideInvitesAvatars = hideInvitesAvatars, onRoomClick = onRoomClick, eventSink = eventSink, ) @@ -92,6 +94,7 @@ internal fun RoomListSearchView( @Composable private fun RoomListSearchContent( state: RoomListSearchState, + hideInvitesAvatars: Boolean, eventSink: (RoomListEvents) -> Unit, onRoomClick: (RoomId) -> Unit, ) { @@ -173,6 +176,7 @@ private fun RoomListSearchContent( ) { room -> RoomSummaryRow( room = room, + hideInviteAvatars = hideInvitesAvatars, // TODO isInviteSeen = false, onClick = ::onRoomClick, @@ -189,6 +193,7 @@ private fun RoomListSearchContent( internal fun RoomListSearchContentPreview(@PreviewParameter(RoomListSearchStateProvider::class) state: RoomListSearchState) = ElementPreview { RoomListSearchContent( state = state, + hideInvitesAvatars = false, onRoomClick = {}, eventSink = {}, ) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt index 31dd89b87a..1086fec9d0 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt @@ -48,11 +48,13 @@ fun Avatar( contentDescription: String? = null, // If not null, will be used instead of the size from avatarData forcedAvatarSize: Dp? = null, + // If true, will show initials even if avatarData.url is not null + hideImage: Boolean = false, ) { val commonModifier = modifier .size(forcedAvatarSize ?: avatarData.size.dp) .clip(CircleShape) - if (avatarData.url.isNullOrBlank()) { + if (avatarData.url.isNullOrBlank() || hideImage) { InitialsAvatar( avatarData = avatarData, forcedAvatarSize = forcedAvatarSize, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/CompositeAvatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/CompositeAvatar.kt index 9e40e08e2a..c696050f23 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/CompositeAvatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/CompositeAvatar.kt @@ -33,10 +33,16 @@ fun CompositeAvatar( avatarData: AvatarData, heroes: ImmutableList, modifier: Modifier = Modifier, + hideAvatarImages: Boolean = false, contentDescription: String? = null, ) { if (avatarData.url != null || heroes.isEmpty()) { - Avatar(avatarData, modifier, contentDescription) + Avatar( + avatarData = avatarData, + modifier = modifier, + contentDescription = contentDescription, + hideImage = hideAvatarImages + ) } else { val limitedHeroes = heroes.take(4) val numberOfHeroes = limitedHeroes.size @@ -49,7 +55,12 @@ fun CompositeAvatar( error("Unsupported number of heroes: 0") } 1 -> { - Avatar(heroes[0], modifier, contentDescription) + Avatar( + avatarData = heroes[0], + modifier = modifier, + contentDescription = contentDescription, + hideImage = hideAvatarImages + ) } else -> { val angle = 2 * Math.PI / numberOfHeroes @@ -91,8 +102,9 @@ fun CompositeAvatar( ) ) { Avatar( - heroAvatar, + avatarData = heroAvatar, forcedAvatarSize = heroAvatarSize, + hideImage = hideAvatarImages, ) } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaPreviewValue.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaPreviewValue.kt new file mode 100644 index 0000000000..83d6d464d5 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaPreviewValue.kt @@ -0,0 +1,39 @@ +/* + * 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.libraries.matrix.api.media + +import io.element.android.libraries.matrix.api.media.MediaPreviewValue.Off +import io.element.android.libraries.matrix.api.media.MediaPreviewValue.On +import io.element.android.libraries.matrix.api.media.MediaPreviewValue.Private +import io.element.android.libraries.matrix.api.room.join.JoinRule + +/** + * Represents the values for media preview settings. + * - [On] means that media preview are enabled + * - [Off] means that media preview are disabled + * - [Private] means that media preview are enabled only for private chats. + */ +enum class MediaPreviewValue { + On, + Off, + Private +} + +fun MediaPreviewValue.isPreviewEnabled(joinRule: JoinRule?): Boolean { + return when (this) { + On -> true + Off -> false + Private -> when (joinRule) { + is JoinRule.Knock, + is JoinRule.Invite, + is JoinRule.Restricted, + is JoinRule.KnockRestricted -> true + else -> false + } + } +} diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt index 452f6ed5a2..946c0d51e9 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt @@ -27,14 +27,15 @@ import io.element.android.libraries.matrix.ui.model.InviteSender @Composable fun InviteSenderView( inviteSender: InviteSender, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + hideAvatarImage: Boolean = false, ) { Row( horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = modifier, ) { Box(modifier = Modifier.padding(vertical = 2.dp)) { - Avatar(avatarData = inviteSender.avatarData) + Avatar(avatarData = inviteSender.avatarData, hideImage = hideAvatarImage) } Text( text = inviteSender.annotatedString(), diff --git a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt index c072229626..dfc0e38b89 100644 --- a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt +++ b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.preferences.api.store +import io.element.android.libraries.matrix.api.media.MediaPreviewValue import io.element.android.libraries.matrix.api.tracing.LogLevel import io.element.android.libraries.matrix.api.tracing.TraceLogPack import kotlinx.coroutines.flow.Flow @@ -21,8 +22,11 @@ interface AppPreferencesStore { suspend fun setTheme(theme: String) fun getThemeFlow(): Flow - suspend fun setHideImagesAndVideos(value: Boolean) - fun doesHideImagesAndVideosFlow(): Flow + suspend fun setHideInviteAvatars(value: Boolean) + fun getHideInviteAvatarsFlow(): Flow + + suspend fun setTimelineMediaPreviewValue(value: MediaPreviewValue) + fun getTimelineMediaPreviewValueFlow(): Flow suspend fun setTracingLogLevel(logLevel: LogLevel) fun getTracingLogLevelFlow(): Flow diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt index a05e9c48da..599c73fc9d 100644 --- a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt +++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt @@ -19,6 +19,7 @@ import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext +import io.element.android.libraries.matrix.api.media.MediaPreviewValue import io.element.android.libraries.matrix.api.tracing.LogLevel import io.element.android.libraries.matrix.api.tracing.TraceLogPack import io.element.android.libraries.preferences.api.store.AppPreferencesStore @@ -31,7 +32,8 @@ private val Context.dataStore: DataStore by preferencesDataStore(na private val developerModeKey = booleanPreferencesKey("developerMode") private val customElementCallBaseUrlKey = stringPreferencesKey("elementCallBaseUrl") private val themeKey = stringPreferencesKey("theme") -private val hideImagesAndVideosKey = booleanPreferencesKey("hideImagesAndVideos") +private val hideInviteAvatarsKey = booleanPreferencesKey("hideInviteAvatars") +private val timelineMediaPreviewValueKey = stringPreferencesKey("timelineMediaPreviewValue") private val logLevelKey = stringPreferencesKey("logLevel") private val traceLogPacksKey = stringPreferencesKey("traceLogPacks") @@ -83,15 +85,27 @@ class DefaultAppPreferencesStore @Inject constructor( } } - override suspend fun setHideImagesAndVideos(value: Boolean) { + override suspend fun setHideInviteAvatars(value: Boolean) { store.edit { prefs -> - prefs[hideImagesAndVideosKey] = value + prefs[hideInviteAvatarsKey] = value } } - override fun doesHideImagesAndVideosFlow(): Flow { + override fun getHideInviteAvatarsFlow(): Flow { return store.data.map { prefs -> - prefs[hideImagesAndVideosKey] ?: false + prefs[hideInviteAvatarsKey] == true + } + } + + override suspend fun setTimelineMediaPreviewValue(value: MediaPreviewValue) { + store.edit { prefs -> + prefs[timelineMediaPreviewValueKey] = value.name + } + } + + override fun getTimelineMediaPreviewValueFlow(): Flow { + return store.data.map { prefs -> + prefs[timelineMediaPreviewValueKey]?.let { MediaPreviewValue.valueOf(it) } ?: MediaPreviewValue.On } } diff --git a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt index ab3913cd08..c5440a6be9 100644 --- a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt +++ b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.preferences.test +import io.element.android.libraries.matrix.api.media.MediaPreviewValue import io.element.android.libraries.matrix.api.tracing.LogLevel import io.element.android.libraries.matrix.api.tracing.TraceLogPack import io.element.android.libraries.preferences.api.store.AppPreferencesStore @@ -15,18 +16,20 @@ import kotlinx.coroutines.flow.MutableStateFlow class InMemoryAppPreferencesStore( isDeveloperModeEnabled: Boolean = false, - hideImagesAndVideos: Boolean = false, customElementCallBaseUrl: String? = null, + hideInviteAvatars: Boolean = false, + timelineMediaPreviewValue: MediaPreviewValue = MediaPreviewValue.On, theme: String? = null, logLevel: LogLevel = LogLevel.INFO, traceLockPacks: Set = emptySet(), ) : AppPreferencesStore { private val isDeveloperModeEnabled = MutableStateFlow(isDeveloperModeEnabled) - private val hideImagesAndVideos = MutableStateFlow(hideImagesAndVideos) private val customElementCallBaseUrl = MutableStateFlow(customElementCallBaseUrl) private val theme = MutableStateFlow(theme) private val logLevel = MutableStateFlow(logLevel) private val tracingLogPacks = MutableStateFlow(traceLockPacks) + private val hideInviteAvatars = MutableStateFlow(hideInviteAvatars) + private val timelineMediaPreviewValue = MutableStateFlow(timelineMediaPreviewValue) override suspend fun setDeveloperModeEnabled(enabled: Boolean) { isDeveloperModeEnabled.value = enabled @@ -52,12 +55,20 @@ class InMemoryAppPreferencesStore( return theme } - override suspend fun setHideImagesAndVideos(value: Boolean) { - hideImagesAndVideos.value = value + override suspend fun setHideInviteAvatars(value: Boolean) { + hideInviteAvatars.value = value } - override fun doesHideImagesAndVideosFlow(): Flow { - return hideImagesAndVideos + override fun getHideInviteAvatarsFlow(): Flow { + return hideInviteAvatars + } + + override suspend fun setTimelineMediaPreviewValue(value: MediaPreviewValue) { + timelineMediaPreviewValue.value = value + } + + override fun getTimelineMediaPreviewValueFlow(): Flow { + return timelineMediaPreviewValue } override suspend fun setTracingLogLevel(logLevel: LogLevel) { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt index c8c90fe9bd..d7557f41df 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt @@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.media.MediaPreviewValue import io.element.android.libraries.matrix.api.notification.NotificationContent import io.element.android.libraries.matrix.api.notification.NotificationData import io.element.android.libraries.matrix.api.permalink.PermalinkParser @@ -305,7 +306,7 @@ class DefaultNotifiableEventResolver @Inject constructor( } private suspend fun NotificationContent.MessageLike.RoomMessage.fetchImageIfPresent(client: MatrixClient): Uri? { - if (appPreferencesStore.doesHideImagesAndVideosFlow().first()) { + if (appPreferencesStore.getTimelineMediaPreviewValueFlow().first() != MediaPreviewValue.On) { return null } val fileResult = when (val messageType = messageType) { @@ -332,7 +333,7 @@ class DefaultNotifiableEventResolver @Inject constructor( } private suspend fun NotificationContent.MessageLike.RoomMessage.getImageMimetype(): String? { - if (appPreferencesStore.doesHideImagesAndVideosFlow().first()) { + if (appPreferencesStore.getTimelineMediaPreviewValueFlow().first() != MediaPreviewValue.On) { return null } return when (val messageType = messageType) { diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_0_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_0_en.png new file mode 100644 index 0000000000..c6ea7ea86b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:926ce46cacac7beaac403e1c4e8034398d91dc48ab5f651bc7f0639ac65fe33a +size 46759 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_1_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_1_en.png new file mode 100644 index 0000000000..edccccc989 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:84c2474bc4d10e1aa3184c41b12b1bcf51d7e2b7ec801944608613aa46f50eba +size 46636 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_2_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_2_en.png new file mode 100644 index 0000000000..01cf719299 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:611d435a2b3e6e0f8c7721905f635c1c93aa217df3fe4f4b6bb38bf5700e3629 +size 34586 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_3_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_3_en.png new file mode 100644 index 0000000000..f844203887 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d9b0a865db8f87d6c1c95b8fb6d916f261d44cecec52fdebd56288f2df35f0f +size 46627 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_4_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_4_en.png new file mode 100644 index 0000000000..69f5a502d5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0505bdd3cfccf156249ab27ed0f087bd4d01d11121cb1f5e0c8450c6b7b58059 +size 46615 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_5_en.png new file mode 100644 index 0000000000..ef2041611a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a004afcec0cb40f82172c7abdc4404d1a0d879d0e23c63d0dcd1d7325cddff43 +size 46471 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_6_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_6_en.png new file mode 100644 index 0000000000..a3a7ee69cf --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewDark_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb09af4f9c96afb60b917d9484e28bacb73c494f9dfc2f42abb482896027b7b7 +size 46767 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_0_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_0_en.png new file mode 100644 index 0000000000..a61c1185f1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4dfb5f83a129332a84f0184ba6a37ba9a95ded6fd9692a6e5710aaeb6b424c7c +size 48610 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_1_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_1_en.png new file mode 100644 index 0000000000..6c498e613b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:903d0aa3faa5b4feae990139c467a5a6662808ffb768c7a2a5cd314f9ea09ba2 +size 48482 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_2_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_2_en.png new file mode 100644 index 0000000000..992a95c8f1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a2538700b5dc993568575e7017fc26c3cba7af7b7567fac069af5aa0c6f6b5f +size 36484 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_3_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_3_en.png new file mode 100644 index 0000000000..babb6a6546 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9177ba1065f5cfe3137a16c024445332b2fe326969f5f6d65ccf6b1707deabe9 +size 48502 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_4_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_4_en.png new file mode 100644 index 0000000000..909a027708 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f07064f41aafea6a91e3314d7d3bb099918e348ef9f9154a057d90758b9fb71 +size 48480 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_5_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_5_en.png new file mode 100644 index 0000000000..e2d2f592e0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92c1c93890214c88ed0e4d1d9faee27cc671db94fe928633ccc82471c137b64a +size 48422 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_6_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_6_en.png new file mode 100644 index 0000000000..feba53f652 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsViewLight_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f21d860514e97b48c3fab7fef4fbf6d710854d21ea2eecc3b44a5ba340c92eb +size 48608 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_0_en.png deleted file mode 100644 index d1bd764c2a..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_0_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c8f664f4a07b88fef329728a9f1ce3151a3b36b0255bcb62d4bc653a6d62b5a4 -size 54746 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_1_en.png deleted file mode 100644 index d46ecb62d4..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9471766f40c07f65aba37fb166f99c8821995e192d9fa2fe633a84dd8dc4f4ac -size 54511 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_2_en.png deleted file mode 100644 index d263193b4d..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_2_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d36aea49da81fc526b91bdd5b30fdaa974bf6ffc498fde32f68182954f3d6b44 -size 31893 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_3_en.png deleted file mode 100644 index 1f21a81383..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_3_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a76df447d4e5108fc8412eebfcab8037e356f7ed410ad247379399b3d547da11 -size 54570 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_4_en.png deleted file mode 100644 index 91f497a0ed..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_4_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7560757da0a4a8c9ffe70c4a81b6dc4d4e379db4c664d16fe4d958a27d77026d -size 54541 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_0_en.png deleted file mode 100644 index 8c9189d33a..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_0_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8f73399ea5f3126559521931424428af6b6d6de578ba8799a577f59ec628aded -size 53554 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_1_en.png deleted file mode 100644 index 5398e5646b..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ba80841973fa1447940382975d5b56eff806bcf819441bc8cf25fc9b60c50eb9 -size 53265 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_2_en.png deleted file mode 100644 index 8b6f452cce..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_2_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a3907566d471c26da9d8c4f1f10fb2cc5d69db584a32f83b662e195f696df320 -size 29568 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_3_en.png deleted file mode 100644 index b1435afb3e..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_3_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e7ae58bd327e19f6c7d10c5be512fbd6ec4eeaa716b995dcc0dc081f2ff3c9c3 -size 53306 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_4_en.png deleted file mode 100644 index e64f9b5cfd..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_4_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bb6e24a7ae84d488a7b9a1574ca86d08b134ff889c2f885c7b7e2b1b5044868e -size 53270 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Day_0_en.png index 5a6c498300..9381505a00 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1cb2f64719f8abda45e50b9cca1a2985630736c35e9cdb32b207696ec067edf -size 57683 +oid sha256:7836dc63d1181d2b057df6baf5fe3b8a05298aaa4e23a75a79790cafff1fd451 +size 54048 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Day_1_en.png index 5a6c498300..9381505a00 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1cb2f64719f8abda45e50b9cca1a2985630736c35e9cdb32b207696ec067edf -size 57683 +oid sha256:7836dc63d1181d2b057df6baf5fe3b8a05298aaa4e23a75a79790cafff1fd451 +size 54048 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Day_2_en.png index c9c81afac2..48046221dc 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40cdf166be2b920b5e6282835fa80654a734ad19bbefdc2c37d287447cb3b49a -size 56274 +oid sha256:cad88329f2179428e72e96499d91ebbfba0647c74fb271bbfffaea0f695d28fb +size 52595 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Night_0_en.png index 50fb79702e..c49b1ace09 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8ff541c779c046143e109f80fe45c5742868b1b4b9d4d5a96371b336e2f1c56 -size 55837 +oid sha256:5861efb5c4453365fa215ec675e8c1289d86fa7756b42c57dcd9c7f138cd62bb +size 52067 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Night_1_en.png index 50fb79702e..c49b1ace09 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8ff541c779c046143e109f80fe45c5742868b1b4b9d4d5a96371b336e2f1c56 -size 55837 +oid sha256:5861efb5c4453365fa215ec675e8c1289d86fa7756b42c57dcd9c7f138cd62bb +size 52067 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Night_2_en.png index 9f66038e62..67d26e583e 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.developer_DeveloperSettingsView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d18ec536792e9e3cea6a40e8a2901e131631be7795f92f64726b8b91e8c52e34 -size 54433 +oid sha256:af268c15499c557a3c6502f6daf732ede68f7fca1539a10a420809ca0fb212a3 +size 50664