diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2fdaa2195e..f662e5352d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -81,6 +81,6 @@ jobs: # https://github.com/codecov/codecov-action - name: ☂️ Upload coverage reports to codecov if: always() - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v3 # with: # files: build/reports/kover/merged/xml/report.xml diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 00d65eba6a..cd0b6dda2d 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -48,6 +48,7 @@ dependencies { implementation(projects.libraries.mediapickers.api) implementation(projects.libraries.featureflag.api) implementation(projects.libraries.mediaupload.api) + implementation(projects.libraries.preferences.api) implementation(projects.features.networkmonitor.api) implementation(projects.services.analytics.api) implementation(libs.coil.compose) @@ -76,6 +77,7 @@ dependencies { testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.mediaupload.test) testImplementation(projects.libraries.mediapickers.test) + testImplementation(projects.libraries.preferences.test) testImplementation(projects.libraries.textcomposer.test) testImplementation(libs.test.mockk) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 8c985d45d9..1dc46aa2bd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -57,6 +57,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.utils.messagesummary.MessageSummaryFormatter import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkStatus +import io.element.android.features.preferences.api.store.PreferencesStore import io.element.android.libraries.androidutils.clipboard.ClipboardHelper import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter @@ -66,8 +67,6 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.utils.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.SnackbarMessage import io.element.android.libraries.designsystem.utils.collectSnackbarMessageAsState -import io.element.android.libraries.featureflag.api.FeatureFlagService -import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState @@ -97,7 +96,7 @@ class MessagesPresenter @AssistedInject constructor( private val dispatchers: CoroutineDispatchers, private val clipboardHelper: ClipboardHelper, private val analyticsService: AnalyticsService, - private val featureFlagService: FeatureFlagService, + private val preferencesStore: PreferencesStore, @Assisted private val navigator: MessagesNavigator, ) : Presenter { @@ -146,15 +145,17 @@ class MessagesPresenter @AssistedInject constructor( timelineState.eventSink(TimelineEvents.SetHighlightedEvent(composerState.mode.relatedEventId)) } - var enableTextFormatting by remember { mutableStateOf(true) } - LaunchedEffect(Unit) { - enableTextFormatting = featureFlagService.isFeatureEnabled(FeatureFlags.RichTextEditor) - } + val enableTextFormatting by preferencesStore.isRichTextEditorEnabledFlow().collectAsState(initial = true) fun handleEvents(event: MessagesEvents) { when (event) { is MessagesEvents.HandleAction -> { - localCoroutineScope.handleTimelineAction(event.action, event.event, composerState) + localCoroutineScope.handleTimelineAction( + action = event.action, + targetEvent = event.event, + composerState = composerState, + enableTextFormatting = enableTextFormatting, + ) } is MessagesEvents.ToggleReaction -> { localCoroutineScope.toggleReaction(event.emoji, event.eventId) @@ -204,14 +205,15 @@ class MessagesPresenter @AssistedInject constructor( action: TimelineItemAction, targetEvent: TimelineItem.Event, composerState: MessageComposerState, + enableTextFormatting: Boolean, ) = launch { when (action) { TimelineItemAction.Copy -> handleCopyContents(targetEvent) TimelineItemAction.Redact -> handleActionRedact(targetEvent) - TimelineItemAction.Edit -> handleActionEdit(targetEvent, composerState) + TimelineItemAction.Edit -> handleActionEdit(targetEvent, composerState, enableTextFormatting) TimelineItemAction.Reply, TimelineItemAction.ReplyInThread -> handleActionReply(targetEvent, composerState) - TimelineItemAction.Developer -> handleShowDebugInfoAction(targetEvent) + TimelineItemAction.ViewSource -> handleShowDebugInfoAction(targetEvent) TimelineItemAction.Forward -> handleForwardAction(targetEvent) TimelineItemAction.ReportContent -> handleReportAction(targetEvent) TimelineItemAction.EndPoll -> handleEndPollAction(targetEvent) @@ -260,11 +262,15 @@ class MessagesPresenter @AssistedInject constructor( } } - private suspend fun handleActionEdit(targetEvent: TimelineItem.Event, composerState: MessageComposerState) { + private suspend fun handleActionEdit( + targetEvent: TimelineItem.Event, + composerState: MessageComposerState, + enableTextFormatting: Boolean, + ) { val composerMode = MessageComposerMode.Edit( targetEvent.eventId, (targetEvent.content as? TimelineItemTextBasedContent)?.let { - if (featureFlagService.isFeatureEnabled(FeatureFlags.RichTextEditor)) { + if (enableTextFormatting) { it.htmlBody ?: it.body } else { it.body diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt index e87523d702..588adb1020 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt @@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.actionlist import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -30,15 +31,15 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent import io.element.android.features.messages.impl.timeline.model.event.canBeCopied import io.element.android.features.messages.impl.timeline.model.event.canReact +import io.element.android.features.preferences.api.store.PreferencesStore import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.core.meta.BuildMeta import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import javax.inject.Inject class ActionListPresenter @Inject constructor( - private val buildMeta: BuildMeta, + private val preferencesStore: PreferencesStore, ) : Presenter { @Composable @@ -49,6 +50,8 @@ class ActionListPresenter @Inject constructor( mutableStateOf(ActionListState.Target.None) } + val isDeveloperModeEnabled by preferencesStore.isDeveloperModeEnabledFlow().collectAsState(initial = false) + val displayEmojiReactions by remember { derivedStateOf { val event = (target.value as? ActionListState.Target.Success)?.event @@ -63,6 +66,7 @@ class ActionListPresenter @Inject constructor( timelineItem = event.event, userCanRedact = event.canRedact, userCanSendMessage = event.canSendMessage, + isDeveloperModeEnabled = isDeveloperModeEnabled, target = target, ) } @@ -79,14 +83,15 @@ class ActionListPresenter @Inject constructor( timelineItem: TimelineItem.Event, userCanRedact: Boolean, userCanSendMessage: Boolean, + isDeveloperModeEnabled: Boolean, target: MutableState ) = launch { target.value = ActionListState.Target.Loading(timelineItem) val actions = when (timelineItem.content) { is TimelineItemRedactedContent -> { - if (buildMeta.isDebuggable) { - listOf(TimelineItemAction.Developer) + if (isDeveloperModeEnabled) { + listOf(TimelineItemAction.ViewSource) } else { emptyList() } @@ -94,8 +99,8 @@ class ActionListPresenter @Inject constructor( is TimelineItemStateContent -> { buildList { add(TimelineItemAction.Copy) - if (buildMeta.isDebuggable) { - add(TimelineItemAction.Developer) + if (isDeveloperModeEnabled) { + add(TimelineItemAction.ViewSource) } } } @@ -115,8 +120,8 @@ class ActionListPresenter @Inject constructor( if (timelineItem.content.canBeCopied()) { add(TimelineItemAction.Copy) } - if (buildMeta.isDebuggable) { - add(TimelineItemAction.Developer) + if (isDeveloperModeEnabled) { + add(TimelineItemAction.ViewSource) } if (!timelineItem.isMine) { add(TimelineItemAction.ReportContent) @@ -144,8 +149,8 @@ class ActionListPresenter @Inject constructor( if (timelineItem.content.canBeCopied()) { add(TimelineItemAction.Copy) } - if (buildMeta.isDebuggable) { - add(TimelineItemAction.Developer) + if (isDeveloperModeEnabled) { + add(TimelineItemAction.ViewSource) } if (!timelineItem.isMine) { add(TimelineItemAction.ReportContent) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt index bb9c851288..44736bc076 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt @@ -111,7 +111,7 @@ fun aTimelineItemActionList(): ImmutableList { TimelineItemAction.Edit, TimelineItemAction.Redact, TimelineItemAction.ReportContent, - TimelineItemAction.Developer, + TimelineItemAction.ViewSource, ) } fun aTimelineItemPollActionList(): ImmutableList { @@ -119,7 +119,7 @@ fun aTimelineItemPollActionList(): ImmutableList { TimelineItemAction.EndPoll, TimelineItemAction.Reply, TimelineItemAction.Copy, - TimelineItemAction.Developer, + TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, TimelineItemAction.Redact, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt index 331edfa1a5..948c0b0cb1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt @@ -34,7 +34,7 @@ sealed class TimelineItemAction( data object Reply : TimelineItemAction(CommonStrings.action_reply, VectorIcons.Reply) data object ReplyInThread : TimelineItemAction(CommonStrings.action_reply_in_thread, VectorIcons.Reply) data object Edit : TimelineItemAction(CommonStrings.action_edit, VectorIcons.Edit) - data object Developer : TimelineItemAction(CommonStrings.action_view_source, VectorIcons.DeveloperMode) + data object ViewSource : TimelineItemAction(CommonStrings.action_view_source, VectorIcons.DeveloperMode) data object ReportContent : TimelineItemAction(CommonStrings.action_report_content, VectorIcons.ReportContent, destructive = true) data object EndPoll : TimelineItemAction(CommonStrings.action_end_poll, VectorIcons.PollEnd) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt index c9ef88f18c..db2abcc1dd 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt @@ -54,6 +54,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.utils.SnackbarDispatcher import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService +import io.element.android.libraries.featureflag.test.InMemoryPreferencesStore import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState @@ -64,7 +65,6 @@ import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_SESSION_ID_2 -import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.libraries.mediapickers.test.FakePickerProvider @@ -364,7 +364,7 @@ class MessagesPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Developer, aMessageEvent())) + initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.ViewSource, aMessageEvent())) assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None) assertThat(navigator.onShowEventDebugInfoClickedCount).isEqualTo(1) } @@ -614,7 +614,7 @@ class MessagesPresenterTest { messageComposerContext = MessageComposerContextImpl(), richTextEditorStateFactory = TestRichTextEditorStateFactory(), - ) + ) val timelinePresenter = TimelinePresenter( timelineItemsFactory = aTimelineItemsFactory(), room = matrixRoom, @@ -622,12 +622,11 @@ class MessagesPresenterTest { appScope = this, analyticsService = analyticsService, ) - val buildMeta = aBuildMeta() - val actionListPresenter = ActionListPresenter(buildMeta = buildMeta) + val preferencesStore = InMemoryPreferencesStore(isRichTextEditorEnabled = true) + val actionListPresenter = ActionListPresenter(preferencesStore = preferencesStore) val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider()) val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom) val retrySendMenuPresenter = RetrySendMenuPresenter(room = matrixRoom) - val featureFlagsService = FakeFeatureFlagService(mapOf(FeatureFlags.RichTextEditor.key to true)) return MessagesPresenter( room = matrixRoom, composerPresenter = messageComposerPresenter, @@ -642,7 +641,7 @@ class MessagesPresenterTest { navigator = navigator, clipboardHelper = clipboardHelper, analyticsService = analyticsService, - featureFlagService = featureFlagsService, + preferencesStore = preferencesStore, dispatchers = coroutineDispatchers, ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt index 2682a71207..316e39317e 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt @@ -31,8 +31,8 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent +import io.element.android.libraries.featureflag.test.InMemoryPreferencesStore import io.element.android.libraries.matrix.test.A_MESSAGE -import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.tests.testutils.WarmUpRule import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.runTest @@ -46,7 +46,7 @@ class ActionListPresenterTest { @Test fun `present - initial state`() = runTest { - val presenter = anActionListPresenter(isBuildDebuggable = true) + val presenter = anActionListPresenter(isDeveloperModeEnabled = true) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -57,7 +57,7 @@ class ActionListPresenterTest { @Test fun `present - compute for message from me redacted`() = runTest { - val presenter = anActionListPresenter(isBuildDebuggable = true) + val presenter = anActionListPresenter(isDeveloperModeEnabled = true) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -71,7 +71,7 @@ class ActionListPresenterTest { ActionListState.Target.Success( messageEvent, persistentListOf( - TimelineItemAction.Developer, + TimelineItemAction.ViewSource, ) ) ) @@ -82,7 +82,7 @@ class ActionListPresenterTest { @Test fun `present - compute for message from others redacted`() = runTest { - val presenter = anActionListPresenter(isBuildDebuggable = true) + val presenter = anActionListPresenter(isDeveloperModeEnabled = true) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -96,7 +96,7 @@ class ActionListPresenterTest { ActionListState.Target.Success( messageEvent, persistentListOf( - TimelineItemAction.Developer, + TimelineItemAction.ViewSource, ) ) ) @@ -107,7 +107,7 @@ class ActionListPresenterTest { @Test fun `present - compute for others message`() = runTest { - val presenter = anActionListPresenter(isBuildDebuggable = true) + val presenter = anActionListPresenter(isDeveloperModeEnabled = true) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -127,7 +127,7 @@ class ActionListPresenterTest { TimelineItemAction.Reply, TimelineItemAction.Forward, TimelineItemAction.Copy, - TimelineItemAction.Developer, + TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, ) ) @@ -139,7 +139,7 @@ class ActionListPresenterTest { @Test fun `present - compute for others message cannot sent message`() = runTest { - val presenter = anActionListPresenter(isBuildDebuggable = true) + val presenter = anActionListPresenter(isDeveloperModeEnabled = true) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -158,7 +158,7 @@ class ActionListPresenterTest { persistentListOf( TimelineItemAction.Forward, TimelineItemAction.Copy, - TimelineItemAction.Developer, + TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, ) ) @@ -170,7 +170,7 @@ class ActionListPresenterTest { @Test fun `present - compute for others message and can redact`() = runTest { - val presenter = anActionListPresenter(isBuildDebuggable = true) + val presenter = anActionListPresenter(isDeveloperModeEnabled = true) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -188,7 +188,7 @@ class ActionListPresenterTest { TimelineItemAction.Reply, TimelineItemAction.Forward, TimelineItemAction.Copy, - TimelineItemAction.Developer, + TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, TimelineItemAction.Redact, ) @@ -201,7 +201,7 @@ class ActionListPresenterTest { @Test fun `present - compute for my message`() = runTest { - val presenter = anActionListPresenter(isBuildDebuggable = true) + val presenter = anActionListPresenter(isDeveloperModeEnabled = true) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -222,7 +222,7 @@ class ActionListPresenterTest { TimelineItemAction.Forward, TimelineItemAction.Edit, TimelineItemAction.Copy, - TimelineItemAction.Developer, + TimelineItemAction.ViewSource, TimelineItemAction.Redact, ) ) @@ -234,7 +234,7 @@ class ActionListPresenterTest { @Test fun `present - compute for a media item`() = runTest { - val presenter = anActionListPresenter(isBuildDebuggable = true) + val presenter = anActionListPresenter(isDeveloperModeEnabled = true) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -253,7 +253,7 @@ class ActionListPresenterTest { persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, - TimelineItemAction.Developer, + TimelineItemAction.ViewSource, TimelineItemAction.Redact, ) ) @@ -265,7 +265,7 @@ class ActionListPresenterTest { @Test fun `present - compute for a state item in debug build`() = runTest { - val presenter = anActionListPresenter(isBuildDebuggable = true) + val presenter = anActionListPresenter(isDeveloperModeEnabled = true) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -283,7 +283,7 @@ class ActionListPresenterTest { stateEvent, persistentListOf( TimelineItemAction.Copy, - TimelineItemAction.Developer, + TimelineItemAction.ViewSource, ) ) ) @@ -294,7 +294,7 @@ class ActionListPresenterTest { @Test fun `present - compute for a state item in non-debuggable build`() = runTest { - val presenter = anActionListPresenter(isBuildDebuggable = false) + val presenter = anActionListPresenter(isDeveloperModeEnabled = false) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -322,7 +322,7 @@ class ActionListPresenterTest { @Test fun `present - compute message in non-debuggable build`() = runTest { - val presenter = anActionListPresenter(isBuildDebuggable = false) + val presenter = anActionListPresenter(isDeveloperModeEnabled = false) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -354,7 +354,7 @@ class ActionListPresenterTest { @Test fun `present - compute message with no actions`() = runTest { - val presenter = anActionListPresenter(isBuildDebuggable = false) + val presenter = anActionListPresenter(isDeveloperModeEnabled = false) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -381,7 +381,7 @@ class ActionListPresenterTest { @Test fun `present - compute not sent message`() = runTest { - val presenter = anActionListPresenter(isBuildDebuggable = false) + val presenter = anActionListPresenter(isDeveloperModeEnabled = false) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -410,7 +410,7 @@ class ActionListPresenterTest { @Test fun `present - compute for poll message`() = runTest { - val presenter = anActionListPresenter(isBuildDebuggable = false) + val presenter = anActionListPresenter(isDeveloperModeEnabled = false) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -436,7 +436,7 @@ class ActionListPresenterTest { @Test fun `present - compute for ended poll message`() = runTest { - val presenter = anActionListPresenter(isBuildDebuggable = false) + val presenter = anActionListPresenter(isDeveloperModeEnabled = false) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -460,5 +460,8 @@ class ActionListPresenterTest { } } -private fun anActionListPresenter(isBuildDebuggable: Boolean) = ActionListPresenter(buildMeta = aBuildMeta(isDebuggable = isBuildDebuggable)) +private fun anActionListPresenter(isDeveloperModeEnabled: Boolean): ActionListPresenter { + val preferencesStore = InMemoryPreferencesStore(isDeveloperModeEnabled = isDeveloperModeEnabled) + return ActionListPresenter(preferencesStore = preferencesStore) +} diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts index 336388772c..34a33ff80b 100644 --- a/features/preferences/impl/build.gradle.kts +++ b/features/preferences/impl/build.gradle.kts @@ -41,6 +41,7 @@ dependencies { implementation(projects.libraries.featureflag.ui) implementation(projects.libraries.network) implementation(projects.libraries.pushstore.api) + implementation(projects.libraries.preferences.api) implementation(projects.libraries.testtags) implementation(projects.libraries.uiStrings) implementation(projects.features.rageshake.api) @@ -54,6 +55,7 @@ dependencies { implementation(libs.accompanist.placeholder) implementation(libs.coil.compose) implementation(libs.androidx.browser) + implementation(libs.androidx.datastore.preferences) api(projects.features.preferences.api) ksp(libs.showkase.processor) @@ -64,6 +66,7 @@ dependencies { testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.featureflag.test) + testImplementation(projects.libraries.preferences.test) testImplementation(projects.libraries.pushstore.test) testImplementation(projects.features.rageshake.test) testImplementation(projects.features.rageshake.impl) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt index 85ae3ac55f..238b4006e7 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt @@ -31,11 +31,12 @@ import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.features.preferences.api.PreferencesEntryPoint import io.element.android.features.preferences.impl.about.AboutNode +import io.element.android.features.preferences.impl.advanced.AdvancedSettingsNode import io.element.android.features.preferences.impl.analytics.AnalyticsSettingsNode import io.element.android.features.preferences.impl.developer.DeveloperSettingsNode +import io.element.android.features.preferences.impl.developer.tracing.ConfigureTracingNode import io.element.android.features.preferences.impl.notifications.NotificationSettingsNode import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingNode -import io.element.android.features.preferences.impl.developer.tracing.ConfigureTracingNode import io.element.android.features.preferences.impl.root.PreferencesRootNode import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler @@ -63,6 +64,9 @@ class PreferencesFlowNode @AssistedInject constructor( @Parcelize data object DeveloperSettings : NavTarget + @Parcelize + data object AdvancedSettings : NavTarget + @Parcelize data object ConfigureTracing : NavTarget @@ -106,6 +110,10 @@ class PreferencesFlowNode @AssistedInject constructor( override fun onOpenNotificationSettings() { backstack.push(NavTarget.NotificationSettings) } + + override fun onOpenAdvancedSettings() { + backstack.push(NavTarget.AdvancedSettings) + } } createNode(buildContext, plugins = listOf(callback)) } @@ -138,6 +146,9 @@ class PreferencesFlowNode @AssistedInject constructor( val input = EditDefaultNotificationSettingNode.Inputs(navTarget.isOneToOne) createNode(buildContext, plugins = listOf(input)) } + NavTarget.AdvancedSettings -> { + createNode(buildContext) + } } } 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 new file mode 100644 index 0000000000..37641d684c --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.impl.advanced + +sealed interface AdvancedSettingsEvents { + data class SetRichTextEditorEnabled(val enabled: Boolean) : AdvancedSettingsEvents + data class SetDeveloperModeEnabled(val enabled: Boolean) : AdvancedSettingsEvents +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsNode.kt new file mode 100644 index 0000000000..f7f0fd2eb8 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsNode.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.impl.advanced + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.SessionScope + +@ContributesNode(SessionScope::class) +class AdvancedSettingsNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: AdvancedSettingsPresenter, +) : Node(buildContext, plugins = plugins) { + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + AdvancedSettingsView( + state = state, + modifier = modifier, + onBackPressed = ::navigateUp + ) + } +} 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 new file mode 100644 index 0000000000..5738fe43c8 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.impl.advanced + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import io.element.android.features.preferences.api.store.PreferencesStore +import io.element.android.libraries.architecture.Presenter +import kotlinx.coroutines.launch +import javax.inject.Inject + +class AdvancedSettingsPresenter @Inject constructor( + private val preferencesStore: PreferencesStore, +) : Presenter { + + @Composable + override fun present(): AdvancedSettingsState { + val localCoroutineScope = rememberCoroutineScope() + val isRichTextEditorEnabled by preferencesStore + .isRichTextEditorEnabledFlow() + .collectAsState(initial = false) + val isDeveloperModeEnabled by preferencesStore + .isDeveloperModeEnabledFlow() + .collectAsState(initial = false) + + fun handleEvents(event: AdvancedSettingsEvents) { + when (event) { + is AdvancedSettingsEvents.SetRichTextEditorEnabled -> localCoroutineScope.launch { + preferencesStore.setRichTextEditorEnabled(event.enabled) + } + is AdvancedSettingsEvents.SetDeveloperModeEnabled -> localCoroutineScope.launch { + preferencesStore.setDeveloperModeEnabled(event.enabled) + } + } + } + + return AdvancedSettingsState( + isRichTextEditorEnabled = isRichTextEditorEnabled, + isDeveloperModeEnabled = isDeveloperModeEnabled, + eventSink = ::handleEvents + ) + } +} 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 new file mode 100644 index 0000000000..19625b9ebc --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.impl.advanced + +data class AdvancedSettingsState constructor( + val isRichTextEditorEnabled: Boolean, + val isDeveloperModeEnabled: Boolean, + 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 new file mode 100644 index 0000000000..5ab50c8a16 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.impl.advanced + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider + +open class AdvancedSettingsStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aAdvancedSettingsState(), + aAdvancedSettingsState(isRichTextEditorEnabled = true), + aAdvancedSettingsState(isDeveloperModeEnabled = true), + ) +} + +fun aAdvancedSettingsState( + isRichTextEditorEnabled: Boolean = false, + isDeveloperModeEnabled: Boolean = false, +) = AdvancedSettingsState( + isRichTextEditorEnabled = isRichTextEditorEnabled, + isDeveloperModeEnabled = isDeveloperModeEnabled, + 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 new file mode 100644 index 0000000000..cff1454594 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.impl.advanced + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch +import io.element.android.libraries.designsystem.components.preferences.PreferenceView +import io.element.android.libraries.designsystem.preview.DayNightPreviews +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun AdvancedSettingsView( + state: AdvancedSettingsState, + onBackPressed: () -> Unit, + modifier: Modifier = Modifier, +) { + PreferenceView( + modifier = modifier, + onBackPressed = onBackPressed, + title = stringResource(id = CommonStrings.common_advanced_settings) + ) { + PreferenceSwitch( + title = stringResource(id = CommonStrings.common_rich_text_editor), + // TODO i18n + subtitle = "Disable the rich text editor to type Markdown manually", + isChecked = state.isRichTextEditorEnabled, + onCheckedChange = { state.eventSink(AdvancedSettingsEvents.SetRichTextEditorEnabled(it)) }, + ) + PreferenceSwitch( + // TODO i18n + title = "Developer mode", + // TODO i18n + subtitle = "The developer mode activates hidden features. For developers only!", + isChecked = state.isDeveloperModeEnabled, + onCheckedChange = { state.eventSink(AdvancedSettingsEvents.SetDeveloperModeEnabled(it)) }, + ) + } +} + +@DayNightPreviews +@Composable +internal fun AdvancedSettingsViewPreview(@PreviewParameter(AdvancedSettingsStateProvider::class) state: AdvancedSettingsState) = + ElementPreview { + AdvancedSettingsView(state = state, onBackPressed = { }) + } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt index 5226d55b6b..b4495d8899 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt @@ -46,6 +46,7 @@ class PreferencesRootNode @AssistedInject constructor( fun onOpenAbout() fun onOpenDeveloperSettings() fun onOpenNotificationSettings() + fun onOpenAdvancedSettings() } private fun onOpenBugReport() { @@ -60,6 +61,10 @@ class PreferencesRootNode @AssistedInject constructor( plugins().forEach { it.onOpenDeveloperSettings() } } + private fun onOpenAdvancedSettings() { + plugins().forEach { it.onOpenAdvancedSettings() } + } + private fun onOpenAnalytics() { plugins().forEach { it.onOpenAnalytics() } } @@ -100,6 +105,7 @@ class PreferencesRootNode @AssistedInject constructor( onOpenAbout = this::onOpenAbout, onVerifyClicked = this::onVerifyClicked, onOpenDeveloperSettings = this::onOpenDeveloperSettings, + onOpenAdvancedSettings = this::onOpenAdvancedSettings, onSuccessLogout = { onSuccessLogout(activity, it) }, onManageAccountClicked = { onManageAccountClicked(activity, it, isDark) }, onOpenNotificationSettings = this::onOpenNotificationSettings diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt index 3d901c4617..16c7df6b51 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt @@ -25,6 +25,7 @@ import androidx.compose.material.icons.outlined.Help import androidx.compose.material.icons.outlined.InsertChart import androidx.compose.material.icons.outlined.Notifications import androidx.compose.material.icons.outlined.OpenInNew +import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.VerifiedUser import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -58,6 +59,7 @@ fun PreferencesRootView( onOpenRageShake: () -> Unit, onOpenAbout: () -> Unit, onOpenDeveloperSettings: () -> Unit, + onOpenAdvancedSettings: () -> Unit, onSuccessLogout: (logoutUrlResult: String?) -> Unit, onOpenNotificationSettings: () -> Unit, modifier: Modifier = Modifier, @@ -121,10 +123,15 @@ fun PreferencesRootView( ) HorizontalDivider() } + PreferenceText( + title = stringResource(id = CommonStrings.common_advanced_settings), + icon = Icons.Outlined.Settings, + onClick = onOpenAdvancedSettings, + ) if (state.showDeveloperSettings) { DeveloperPreferencesView(onOpenDeveloperSettings) - HorizontalDivider() } + HorizontalDivider() LogoutPreferenceView( state = state.logoutState, onSuccessLogout = onSuccessLogout, @@ -168,6 +175,7 @@ private fun ContentToPreview(matrixUser: MatrixUser) { onOpenAnalytics = {}, onOpenRageShake = {}, onOpenDeveloperSettings = {}, + onOpenAdvancedSettings = {}, onOpenAbout = {}, onVerifyClicked = {}, onSuccessLogout = {}, diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt new file mode 100644 index 0000000000..76808ee5f9 --- /dev/null +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.impl.advanced + +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.featureflag.test.InMemoryPreferencesStore +import io.element.android.tests.testutils.WarmUpRule +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class AdvancedSettingsPresenterTest { + + @get:Rule + val warmUpRule = WarmUpRule() + + @Test + fun `present - initial state`() = runTest { + val store = InMemoryPreferencesStore() + val presenter = AdvancedSettingsPresenter(store) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.isDeveloperModeEnabled).isFalse() + assertThat(initialState.isRichTextEditorEnabled).isFalse() + } + } + + @Test + fun `present - developer mode on off`() = runTest { + val store = InMemoryPreferencesStore() + val presenter = AdvancedSettingsPresenter(store) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.isDeveloperModeEnabled).isFalse() + initialState.eventSink.invoke(AdvancedSettingsEvents.SetDeveloperModeEnabled(true)) + assertThat(awaitItem().isDeveloperModeEnabled).isTrue() + initialState.eventSink.invoke(AdvancedSettingsEvents.SetDeveloperModeEnabled(false)) + assertThat(awaitItem().isDeveloperModeEnabled).isFalse() + } + } + + @Test + fun `present - rich text editor on off`() = runTest { + val store = InMemoryPreferencesStore() + val presenter = AdvancedSettingsPresenter(store) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.isRichTextEditorEnabled).isFalse() + initialState.eventSink.invoke(AdvancedSettingsEvents.SetRichTextEditorEnabled(true)) + assertThat(awaitItem().isRichTextEditorEnabled).isTrue() + initialState.eventSink.invoke(AdvancedSettingsEvents.SetRichTextEditorEnabled(false)) + assertThat(awaitItem().isRichTextEditorEnabled).isFalse() + } + } +} diff --git a/gradle.properties b/gradle.properties index 6f311e45ef..9847be0949 100644 --- a/gradle.properties +++ b/gradle.properties @@ -56,6 +56,3 @@ android.experimental.enableTestFixtures=true # Create BuildConfig files as bytecode to avoid Java compilation phase android.enableBuildConfigAsBytecode=true - -# This should be removed after upgrading to AGP 8.1.0 -android.suppressUnsupportedCompileSdk=34 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cdc80fbd1b..ebd4f4c888 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ android_gradle_plugin = "8.1.1" kotlin = "1.9.10" ksp = "1.9.10-1.0.13" -molecule = "1.2.0" +molecule = "1.2.1" # AndroidX material = "1.9.0" @@ -66,7 +66,7 @@ android_gradle_plugin = { module = "com.android.tools.build:gradle", version.ref android_desugar = "com.android.tools:desugar_jdk_libs:2.0.3" kotlin_gradle_plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } # https://firebase.google.com/docs/android/setup#available-libraries -google_firebase_bom = "com.google.firebase:firebase-bom:32.2.3" +google_firebase_bom = "com.google.firebase:firebase-bom:32.3.0" # AndroidX androidx_material = { module = "com.google.android.material:material", version.ref = "material" } @@ -149,7 +149,7 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "molecule" } timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.52" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.53" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt index 52e0a69430..77ca4bd010 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt @@ -100,6 +100,7 @@ private fun ContentToPreview() { "placeholderBackground" to ElementTheme.colors.placeholderBackground, "messageFromMeBackground" to ElementTheme.colors.messageFromMeBackground, "messageFromOtherBackground" to ElementTheme.colors.messageFromOtherBackground, + "progressIndicatorTrackColor" to ElementTheme.colors.progressIndicatorTrackColor, "temporaryColorBgSpecial" to ElementTheme.colors.temporaryColorBgSpecial, ) ) diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index ca68b0cbd1..990aee3a93 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -43,9 +43,4 @@ enum class FeatureFlags( title = "Show notification settings", defaultValue = true, ), - RichTextEditor( - key = "feature.richtexteditor", - title = "Enable rich text editor", - defaultValue = true, - ), } diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt index d7759ac474..82184d510c 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt @@ -35,7 +35,6 @@ class StaticFeatureFlagProvider @Inject constructor() : FeatureFlags.LocationSharing -> true FeatureFlags.Polls -> true FeatureFlags.NotificationSettings -> true - FeatureFlags.RichTextEditor -> true } } else { false diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index 6014644733..f2acb0b1be 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -57,6 +57,8 @@ class RustMatrixAuthenticationService @Inject constructor( userAgent = userAgentProvider.provide(), oidcConfiguration = oidcConfiguration, customSlidingSyncProxy = null, + sessionDelegate = null, + crossProcessRefreshLockId = null, ) private var currentHomeserver = MutableStateFlow(null) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt index 9ae10db12d..e2bfd85f95 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt @@ -71,7 +71,7 @@ class RoomSummaryListProcessor( } } - private suspend fun MutableList.applyUpdate(update: RoomListEntriesUpdate) { + private fun MutableList.applyUpdate(update: RoomListEntriesUpdate) { when (update) { is RoomListEntriesUpdate.Append -> { val roomSummaries = update.values.map { @@ -114,7 +114,7 @@ class RoomSummaryListProcessor( } } - private suspend fun buildSummaryForRoomListEntry(entry: RoomListEntry): RoomSummary { + private fun buildSummaryForRoomListEntry(entry: RoomListEntry): RoomSummary { return when (entry) { RoomListEntry.Empty -> buildEmptyRoomSummary() is RoomListEntry.Filled -> buildAndCacheRoomSummaryForIdentifier(entry.roomId) @@ -128,9 +128,9 @@ class RoomSummaryListProcessor( return RoomSummary.Empty(UUID.randomUUID().toString()) } - private suspend fun buildAndCacheRoomSummaryForIdentifier(identifier: String): RoomSummary { + private fun buildAndCacheRoomSummaryForIdentifier(identifier: String): RoomSummary { val builtRoomSummary = roomListService.roomOrNull(identifier)?.use { roomListItem -> - roomListItem.roomInfo().use { roomInfo -> + roomListItem.roomInfoBlocking().use { roomInfo -> RoomSummary.Filled( details = roomSummaryDetailsFactory.create(roomInfo) ) diff --git a/libraries/preferences/api/build.gradle.kts b/libraries/preferences/api/build.gradle.kts new file mode 100644 index 0000000000..f782dd328b --- /dev/null +++ b/libraries/preferences/api/build.gradle.kts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.preferences.api" +} + +dependencies { + implementation(libs.coroutines.core) +} diff --git a/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/PreferencesStore.kt b/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/PreferencesStore.kt new file mode 100644 index 0000000000..8ad2c098f6 --- /dev/null +++ b/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/PreferencesStore.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.api.store + +import kotlinx.coroutines.flow.Flow + +interface PreferencesStore { + suspend fun setRichTextEditorEnabled(enabled: Boolean) + fun isRichTextEditorEnabledFlow(): Flow + + suspend fun setDeveloperModeEnabled(enabled: Boolean) + fun isDeveloperModeEnabledFlow(): Flow + + suspend fun reset() +} diff --git a/libraries/preferences/impl/build.gradle.kts b/libraries/preferences/impl/build.gradle.kts new file mode 100644 index 0000000000..9c31d83481 --- /dev/null +++ b/libraries/preferences/impl/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id("io.element.android-library") + alias(libs.plugins.anvil) +} + +android { + namespace = "io.element.android.libraries.preferences.impl" +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + api(projects.libraries.preferences.api) + implementation(libs.dagger) + implementation(libs.androidx.datastore.preferences) + implementation(projects.libraries.di) + implementation(projects.libraries.core) +} diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultPreferencesStore.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultPreferencesStore.kt new file mode 100644 index 0000000000..337301f23e --- /dev/null +++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultPreferencesStore.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.preferences.impl.store + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.preferencesDataStore +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.preferences.api.store.PreferencesStore +import io.element.android.libraries.core.bool.orTrue +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 kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +private val Context.dataStore: DataStore by preferencesDataStore(name = "elementx_preferences") + +private val richTextEditorKey = booleanPreferencesKey("richTextEditor") +private val developerModeKey = booleanPreferencesKey("developerMode") + +@ContributesBinding(AppScope::class) +class DefaultPreferencesStore @Inject constructor( + @ApplicationContext context: Context, + private val buildMeta: BuildMeta, +) : PreferencesStore { + private val store = context.dataStore + + override suspend fun setRichTextEditorEnabled(enabled: Boolean) { + store.edit { prefs -> + prefs[richTextEditorKey] = enabled + } + } + + override fun isRichTextEditorEnabledFlow(): Flow { + return store.data.map { prefs -> + // enabled by default + prefs[richTextEditorKey].orTrue() + } + } + + override suspend fun setDeveloperModeEnabled(enabled: Boolean) { + store.edit { prefs -> + prefs[developerModeKey] = enabled + } + } + + override fun isDeveloperModeEnabledFlow(): Flow { + return store.data.map { prefs -> + // disabled by default on release and nightly, enabled by default on debug + prefs[developerModeKey] ?: (buildMeta.buildType == BuildType.DEBUG) + } + } + + override suspend fun reset() { + store.edit { it.clear() } + } +} diff --git a/libraries/preferences/test/build.gradle.kts b/libraries/preferences/test/build.gradle.kts new file mode 100644 index 0000000000..86b891b21e --- /dev/null +++ b/libraries/preferences/test/build.gradle.kts @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.preferences.test" + + dependencies { + api(projects.libraries.preferences.api) + implementation(libs.coroutines.core) + } +} diff --git a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryPreferencesStore.kt b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryPreferencesStore.kt new file mode 100644 index 0000000000..a2a9fdaa3f --- /dev/null +++ b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemoryPreferencesStore.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.featureflag.test + +import io.element.android.features.preferences.api.store.PreferencesStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow + +class InMemoryPreferencesStore( + isRichTextEditorEnabled: Boolean = false, + isDeveloperModeEnabled: Boolean = false, +) : PreferencesStore { + private var _isRichTextEditorEnabled = MutableStateFlow(isRichTextEditorEnabled) + private var _isDeveloperModeEnabled = MutableStateFlow(isDeveloperModeEnabled) + + override suspend fun setRichTextEditorEnabled(enabled: Boolean) { + _isRichTextEditorEnabled.value = enabled + } + + override fun isRichTextEditorEnabledFlow(): Flow { + return _isRichTextEditorEnabled + } + + override suspend fun setDeveloperModeEnabled(enabled: Boolean) { + _isDeveloperModeEnabled.value = enabled + } + + override fun isDeveloperModeEnabledFlow(): Flow { + return _isDeveloperModeEnabled + } + + override suspend fun reset() { + // No op + } +} diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index 592cf3c52a..1029100823 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -92,6 +92,7 @@ fun DependencyHandlerScope.allLibrariesImpl() { implementation(project(":libraries:pushproviders:unifiedpush")) implementation(project(":libraries:featureflag:impl")) implementation(project(":libraries:pushstore:impl")) + implementation(project(":libraries:preferences:impl")) implementation(project(":libraries:architecture")) implementation(project(":libraries:dateformatter:impl")) implementation(project(":libraries:di")) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_null_AdvancedSettingsView-D-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_null_AdvancedSettingsView-D-0_1_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..aa5e7b202a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_null_AdvancedSettingsView-D-0_1_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a11d23d53b983a6cb5448f6083e448902b50889d6393f48454ef1b19c0ad0f07 +size 38028 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_null_AdvancedSettingsView-D-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_null_AdvancedSettingsView-D-0_1_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..99efbef109 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_null_AdvancedSettingsView-D-0_1_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18063647932eef08f66236825751456801e2a172a72ff09691a1d4b93897466d +size 37617 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_null_AdvancedSettingsView-D-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_null_AdvancedSettingsView-D-0_1_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..9cfecc1b15 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_null_AdvancedSettingsView-D-0_1_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:333e3cb2b49f6d2794e732b7b9fc4204a7fc8564d0518b8cf60a9e7f14ea9a39 +size 37688 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_null_AdvancedSettingsView-N-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_null_AdvancedSettingsView-N-0_2_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..ca2c271e89 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_null_AdvancedSettingsView-N-0_2_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47332a39d0c1a95a8396548b45bc3822b734111b268c06d917fa2ffb2d277cc1 +size 35649 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_null_AdvancedSettingsView-N-0_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_null_AdvancedSettingsView-N-0_2_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..e31840c360 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_null_AdvancedSettingsView-N-0_2_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9deacb3ff9bdd2dbee7dd97686941cee9b1f19855f6b87c7e6860e9d47c671e4 +size 35278 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_null_AdvancedSettingsView-N-0_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_null_AdvancedSettingsView-N-0_2_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..44081fe3ab --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_null_AdvancedSettingsView-N-0_2_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:16b226707d1e862aa004c426221b89dbc9c7b2cedee19b11ff8843fa524a76ad +size 35378 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_null_ConfigureTracingView-D-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_null_ConfigureTracingView-D-1_2_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_null_ConfigureTracingView-D-0_1_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_null_ConfigureTracingView-D-1_2_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_null_ConfigureTracingView-N-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_null_ConfigureTracingView-N-1_3_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_null_ConfigureTracingView-N-0_2_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.developer.tracing_null_ConfigureTracingView-N-1_3_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_DefaultNotificationSettingOption-D-1_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_DefaultNotificationSettingOption-D-2_3_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_DefaultNotificationSettingOption-D-1_2_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_DefaultNotificationSettingOption-D-2_3_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_DefaultNotificationSettingOption-N-1_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_DefaultNotificationSettingOption-N-2_4_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_DefaultNotificationSettingOption-N-1_3_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_DefaultNotificationSettingOption-N-2_4_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png index 05ac81ce49..72fbec227e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aed6ab34c685d32aa77e926c416621716acedd586eea2e76e51f1c51867be6cf -size 43419 +oid sha256:56f2a7a7aca8336fb669030a98af4fd851ab1bf52c68d33be36fe8df922c01ee +size 46097 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png index 66bf07e1ce..a2d529d3a0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4dce8dec2408bcf8e1e8a547689cdac25145d2fef603b21ff20ba2a40e181b1 -size 42741 +oid sha256:6936337fd9365fc5d38d5570798ceaa409099240593e32a468e7688398926e4d +size 45415 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png index 793b31326f..29b2a82cea 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b01d7dcb16041285c2915b7572c16e39d199ac930ed0f6a69b1219bdf5d0ccd5 -size 46362 +oid sha256:c9bc0aa48e94e2a4fb61ebc52b5d62d3fd960a3a3c52300906c76ad52f92b662 +size 49333 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png index 399417d8ac..97fc36e283 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a9667115ed339726a09e7917737a6aa5698f4f03249f2704f162461775ddc20b -size 46229 +oid sha256:bc40ec0fb6fa758768fc697496f54b9381a0fd0a67cce1ee36b6dd703e11ea18 +size 49229 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme_null_ColorAliasesDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme_null_ColorAliasesDark_0_null,NEXUS_5,1.0,en].png index 5c56f5f39b..b1c6c996d8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme_null_ColorAliasesDark_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme_null_ColorAliasesDark_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:38642daa43d72b6b8e0626ed5fcd034df1e79bb844af8ade06112225dfc0f5b7 -size 40707 +oid sha256:d57924194a017902912825d4f43fcd290db11430d4d64abd89abe39b9e2ffc27 +size 48726 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme_null_ColorAliasesLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme_null_ColorAliasesLight_0_null,NEXUS_5,1.0,en].png index 0b39e21087..eaf130c209 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme_null_ColorAliasesLight_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme_null_ColorAliasesLight_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4aa0a56393799bcc582f8c384b4bf782fff63bc8402bbef5733c938ce564a75f -size 40000 +oid sha256:0eaa20c7c00c18cf08135aca70407d55ea9fc33a7f262aee3fa6ced272f7ab16 +size 48465