From a3716d0e0dcf910b174f7802b162b612e104c01b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 5 Feb 2024 12:27:04 +0100 Subject: [PATCH 1/8] SessionPreferencesStore: add entries for `sharePresence`, `renderReadReceipts`, `sendTypingNotifications`, `renderTypingNotifications`. `sharePresence` should take existing value of `sendPublicReadReceipts`, which has been added first. --- .../api/store/SessionPreferencesStore.kt | 12 +++++++ .../store/DefaultSessionPreferencesStore.kt | 31 ++++++++++++++++ .../test/InMemorySessionPreferencesStore.kt | 35 +++++++++++++++++-- 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/SessionPreferencesStore.kt b/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/SessionPreferencesStore.kt index 0174d8d1eb..948e9acb75 100644 --- a/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/SessionPreferencesStore.kt +++ b/libraries/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/store/SessionPreferencesStore.kt @@ -19,8 +19,20 @@ package io.element.android.features.preferences.api.store import kotlinx.coroutines.flow.Flow interface SessionPreferencesStore { + suspend fun setSharePresence(enabled: Boolean) + fun isSharePresenceEnabled(): Flow + suspend fun setSendPublicReadReceipts(enabled: Boolean) fun isSendPublicReadReceiptsEnabled(): Flow + suspend fun setRenderReadReceipts(enabled: Boolean) + fun isRenderReadReceiptsEnabled(): Flow + + suspend fun setSendTypingNotifications(enabled: Boolean) + fun isSendTypingNotificationsEnabled(): Flow + + suspend fun setRenderTypingNotifications(enabled: Boolean) + fun isRenderTypingNotificationsEnabled(): Flow + suspend fun clear() } diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt index eb2fffb045..b6283d2c69 100644 --- a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt +++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt @@ -29,7 +29,9 @@ import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.matrix.api.core.SessionId import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map +import kotlinx.coroutines.runBlocking import java.io.File class DefaultSessionPreferencesStore( @@ -43,14 +45,43 @@ class DefaultSessionPreferencesStore( return context.preferencesDataStoreFile("session_${hashedUserId}_preferences") } } + + private val sharePresenceKey = booleanPreferencesKey("sharePresence") private val sendPublicReadReceiptsKey = booleanPreferencesKey("sendPublicReadReceipts") + private val renderReadReceiptsKey = booleanPreferencesKey("renderReadReceipts") + private val sendTypingNotificationsKey = booleanPreferencesKey("sendTypingNotifications") + private val renderTypingNotificationsKey = booleanPreferencesKey("renderTypingNotifications") private val dataStoreFile = storeFile(context, sessionId) private val store = PreferenceDataStoreFactory.create(scope = sessionCoroutineScope) { dataStoreFile } + override suspend fun setSharePresence(enabled: Boolean) { + update(sharePresenceKey, enabled) + // Also update all the other settings + setSendPublicReadReceipts(enabled) + setRenderReadReceipts(enabled) + setSendTypingNotifications(enabled) + setRenderTypingNotifications(enabled) + } + + override fun isSharePresenceEnabled(): Flow { + // Migration, if sendPublicReadReceiptsKey was false, consider that sharing presence is false. + val defaultValue = runBlocking { isSendPublicReadReceiptsEnabled().first() } + return get(sharePresenceKey, defaultValue) + } + override suspend fun setSendPublicReadReceipts(enabled: Boolean) = update(sendPublicReadReceiptsKey, enabled) override fun isSendPublicReadReceiptsEnabled(): Flow = get(sendPublicReadReceiptsKey, true) + override suspend fun setRenderReadReceipts(enabled: Boolean) = update(renderReadReceiptsKey, enabled) + override fun isRenderReadReceiptsEnabled(): Flow = get(renderReadReceiptsKey, true) + + override suspend fun setSendTypingNotifications(enabled: Boolean) = update(sendTypingNotificationsKey, enabled) + override fun isSendTypingNotificationsEnabled(): Flow = get(sendTypingNotificationsKey, true) + + override suspend fun setRenderTypingNotifications(enabled: Boolean) = update(renderTypingNotificationsKey, enabled) + override fun isRenderTypingNotificationsEnabled(): Flow = get(renderTypingNotificationsKey, true) + override suspend fun clear() { dataStoreFile.safeDelete() } diff --git a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemorySessionPreferencesStore.kt b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemorySessionPreferencesStore.kt index 1f6f7a6724..9e9fc7ba2a 100644 --- a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemorySessionPreferencesStore.kt +++ b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/featureflag/test/InMemorySessionPreferencesStore.kt @@ -21,19 +21,50 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow class InMemorySessionPreferencesStore( + isSharePresenceEnabled: Boolean = true, isSendPublicReadReceiptsEnabled: Boolean = true, + isRenderReadReceiptsEnabled: Boolean = true, + isSendTypingNotificationsEnabled: Boolean = true, + isRenderTypingNotificationsEnabled: Boolean = true, ) : SessionPreferencesStore { + private val isSharePresenceEnabled = MutableStateFlow(isSharePresenceEnabled) private val isSendPublicReadReceiptsEnabled = MutableStateFlow(isSendPublicReadReceiptsEnabled) + private val isRenderReadReceiptsEnabled = MutableStateFlow(isRenderReadReceiptsEnabled) + private val isSendTypingNotificationsEnabled = MutableStateFlow(isSendTypingNotificationsEnabled) + private val isRenderTypingNotificationsEnabled = MutableStateFlow(isRenderTypingNotificationsEnabled) var clearCallCount = 0 private set + override suspend fun setSharePresence(enabled: Boolean) { + isSharePresenceEnabled.tryEmit(enabled) + } + + override fun isSharePresenceEnabled(): Flow = isSharePresenceEnabled + override suspend fun setSendPublicReadReceipts(enabled: Boolean) { isSendPublicReadReceiptsEnabled.tryEmit(enabled) } - override fun isSendPublicReadReceiptsEnabled(): Flow { - return isSendPublicReadReceiptsEnabled + + override fun isSendPublicReadReceiptsEnabled(): Flow = isSendPublicReadReceiptsEnabled + + override suspend fun setRenderReadReceipts(enabled: Boolean) { + isRenderReadReceiptsEnabled.tryEmit(enabled) + } + + override fun isRenderReadReceiptsEnabled(): Flow = isRenderReadReceiptsEnabled + + override suspend fun setSendTypingNotifications(enabled: Boolean) { + isSendTypingNotificationsEnabled.tryEmit(enabled) + } + + override fun isSendTypingNotificationsEnabled(): Flow = isSendTypingNotificationsEnabled + + override suspend fun setRenderTypingNotifications(enabled: Boolean) { + isRenderTypingNotificationsEnabled.tryEmit(enabled) } + override fun isRenderTypingNotificationsEnabled(): Flow = isRenderTypingNotificationsEnabled + override suspend fun clear() { clearCallCount++ isSendPublicReadReceiptsEnabled.tryEmit(true) From 9d1bc5925c3d91b61e9c1b20d1358d3121f1702d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 5 Feb 2024 12:29:36 +0100 Subject: [PATCH 2/8] Avoid computing default value if it's not necessary. --- .../impl/store/DefaultSessionPreferencesStore.kt | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt index b6283d2c69..770ca699fe 100644 --- a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt +++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt @@ -66,21 +66,20 @@ class DefaultSessionPreferencesStore( override fun isSharePresenceEnabled(): Flow { // Migration, if sendPublicReadReceiptsKey was false, consider that sharing presence is false. - val defaultValue = runBlocking { isSendPublicReadReceiptsEnabled().first() } - return get(sharePresenceKey, defaultValue) + return get(sharePresenceKey) { runBlocking { isSendPublicReadReceiptsEnabled().first() } } } override suspend fun setSendPublicReadReceipts(enabled: Boolean) = update(sendPublicReadReceiptsKey, enabled) - override fun isSendPublicReadReceiptsEnabled(): Flow = get(sendPublicReadReceiptsKey, true) + override fun isSendPublicReadReceiptsEnabled(): Flow = get(sendPublicReadReceiptsKey) { true } override suspend fun setRenderReadReceipts(enabled: Boolean) = update(renderReadReceiptsKey, enabled) - override fun isRenderReadReceiptsEnabled(): Flow = get(renderReadReceiptsKey, true) + override fun isRenderReadReceiptsEnabled(): Flow = get(renderReadReceiptsKey) { true } override suspend fun setSendTypingNotifications(enabled: Boolean) = update(sendTypingNotificationsKey, enabled) - override fun isSendTypingNotificationsEnabled(): Flow = get(sendTypingNotificationsKey, true) + override fun isSendTypingNotificationsEnabled(): Flow = get(sendTypingNotificationsKey) { true } override suspend fun setRenderTypingNotifications(enabled: Boolean) = update(renderTypingNotificationsKey, enabled) - override fun isRenderTypingNotificationsEnabled(): Flow = get(renderTypingNotificationsKey, true) + override fun isRenderTypingNotificationsEnabled(): Flow = get(renderTypingNotificationsKey) { true } override suspend fun clear() { dataStoreFile.safeDelete() @@ -90,7 +89,7 @@ class DefaultSessionPreferencesStore( store.edit { prefs -> prefs[key] = value } } - private fun get(key: Preferences.Key, default: T): Flow { - return store.data.map { prefs -> prefs[key] ?: default } + private fun get(key: Preferences.Key, default: () -> T): Flow { + return store.data.map { prefs -> prefs[key] ?: default() } } } From 23fb7811f35aed90fb84bd2148cbbbaf9afd5ab9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 5 Feb 2024 13:06:50 +0100 Subject: [PATCH 3/8] Update advanced settings screen. Replace Read Receipt private mode by Share presence. --- .../impl/advanced/AdvancedSettingsEvents.kt | 2 +- .../impl/advanced/AdvancedSettingsPresenter.kt | 10 +++++----- .../impl/advanced/AdvancedSettingsState.kt | 2 +- .../impl/advanced/AdvancedSettingsStateProvider.kt | 2 +- .../impl/advanced/AdvancedSettingsView.kt | 8 ++++---- .../impl/src/main/res/values/localazy.xml | 2 ++ .../impl/advanced/AdvancedSettingsPresenterTest.kt | 14 +++++++------- 7 files changed, 21 insertions(+), 19 deletions(-) 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 d67a594dd2..8ee433f630 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 @@ -21,7 +21,7 @@ import io.element.android.compound.theme.Theme sealed interface AdvancedSettingsEvents { data class SetRichTextEditorEnabled(val enabled: Boolean) : AdvancedSettingsEvents data class SetDeveloperModeEnabled(val enabled: Boolean) : AdvancedSettingsEvents - data class SetSendPublicReadReceiptsEnabled(val enabled: Boolean) : AdvancedSettingsEvents + data class SetSharePresenceEnabled(val enabled: Boolean) : AdvancedSettingsEvents data object ChangeTheme : AdvancedSettingsEvents data object CancelChangeTheme : AdvancedSettingsEvents data class SetTheme(val theme: Theme) : 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 2ac5b664b5..2f0c2b7417 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 @@ -44,8 +44,8 @@ class AdvancedSettingsPresenter @Inject constructor( val isDeveloperModeEnabled by appPreferencesStore .isDeveloperModeEnabledFlow() .collectAsState(initial = false) - val isSendPublicReadReceiptsEnabled by sessionPreferencesStore - .isSendPublicReadReceiptsEnabled() + val isSharePresenceEnabled by sessionPreferencesStore + .isSharePresenceEnabled() .collectAsState(initial = true) val theme by remember { appPreferencesStore.getThemeFlow().mapToTheme() @@ -60,8 +60,8 @@ class AdvancedSettingsPresenter @Inject constructor( is AdvancedSettingsEvents.SetDeveloperModeEnabled -> localCoroutineScope.launch { appPreferencesStore.setDeveloperModeEnabled(event.enabled) } - is AdvancedSettingsEvents.SetSendPublicReadReceiptsEnabled -> localCoroutineScope.launch { - sessionPreferencesStore.setSendPublicReadReceipts(event.enabled) + is AdvancedSettingsEvents.SetSharePresenceEnabled -> localCoroutineScope.launch { + sessionPreferencesStore.setSharePresence(event.enabled) } AdvancedSettingsEvents.CancelChangeTheme -> showChangeThemeDialog = false AdvancedSettingsEvents.ChangeTheme -> showChangeThemeDialog = true @@ -75,7 +75,7 @@ class AdvancedSettingsPresenter @Inject constructor( return AdvancedSettingsState( isRichTextEditorEnabled = isRichTextEditorEnabled, isDeveloperModeEnabled = isDeveloperModeEnabled, - isSendPublicReadReceiptsEnabled = isSendPublicReadReceiptsEnabled, + isSharePresenceEnabled = isSharePresenceEnabled, theme = theme, showChangeThemeDialog = showChangeThemeDialog, 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 0ea04185f7..469f8a630c 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 @@ -21,7 +21,7 @@ import io.element.android.compound.theme.Theme data class AdvancedSettingsState( val isRichTextEditorEnabled: Boolean, val isDeveloperModeEnabled: Boolean, - val isSendPublicReadReceiptsEnabled: Boolean, + val isSharePresenceEnabled: Boolean, val theme: Theme, val showChangeThemeDialog: 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 index acfd9bb026..5e255fc091 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 @@ -38,7 +38,7 @@ fun aAdvancedSettingsState( ) = AdvancedSettingsState( isRichTextEditorEnabled = isRichTextEditorEnabled, isDeveloperModeEnabled = isDeveloperModeEnabled, - isSendPublicReadReceiptsEnabled = isSendPublicReadReceiptsEnabled, + isSharePresenceEnabled = isSendPublicReadReceiptsEnabled, theme = Theme.System, showChangeThemeDialog = showChangeThemeDialog, 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 7079a4daea..9e739c3a00 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 @@ -83,15 +83,15 @@ fun AdvancedSettingsView( ) ListItem( headlineContent = { - Text(text = stringResource(id = R.string.screen_advanced_settings_send_read_receipts)) + Text(text = stringResource(id = R.string.screen_advanced_settings_share_presence)) }, supportingContent = { - Text(text = stringResource(id = R.string.screen_advanced_settings_send_read_receipts_description)) + Text(text = stringResource(id = R.string.screen_advanced_settings_share_presence_description)) }, trailingContent = ListItemContent.Switch( - checked = state.isSendPublicReadReceiptsEnabled, + checked = state.isSharePresenceEnabled, ), - onClick = { state.eventSink(AdvancedSettingsEvents.SetSendPublicReadReceiptsEnabled(!state.isSendPublicReadReceiptsEnabled)) } + onClick = { state.eventSink(AdvancedSettingsEvents.SetSharePresenceEnabled(!state.isSharePresenceEnabled)) } ) } diff --git a/features/preferences/impl/src/main/res/values/localazy.xml b/features/preferences/impl/src/main/res/values/localazy.xml index 607329332f..70a1b032a9 100644 --- a/features/preferences/impl/src/main/res/values/localazy.xml +++ b/features/preferences/impl/src/main/res/values/localazy.xml @@ -8,6 +8,8 @@ "Disable the rich text editor to type Markdown manually." "Read receipts" "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" "Enable option to view message source in the timeline." "Display name" "Your display name" 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 index a042d792b8..1745d57396 100644 --- 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 @@ -43,7 +43,7 @@ class AdvancedSettingsPresenterTest { assertThat(initialState.isDeveloperModeEnabled).isFalse() assertThat(initialState.isRichTextEditorEnabled).isFalse() assertThat(initialState.showChangeThemeDialog).isFalse() - assertThat(initialState.isSendPublicReadReceiptsEnabled).isTrue() + assertThat(initialState.isSharePresenceEnabled).isTrue() assertThat(initialState.theme).isEqualTo(Theme.System) } } @@ -79,17 +79,17 @@ class AdvancedSettingsPresenterTest { } @Test - fun `present - send public read receipts off on`() = runTest { + fun `present - share presence off on`() = runTest { val presenter = createAdvancedSettingsPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitLastSequentialItem() - assertThat(initialState.isSendPublicReadReceiptsEnabled).isTrue() - initialState.eventSink.invoke(AdvancedSettingsEvents.SetSendPublicReadReceiptsEnabled(false)) - assertThat(awaitItem().isSendPublicReadReceiptsEnabled).isFalse() - initialState.eventSink.invoke(AdvancedSettingsEvents.SetSendPublicReadReceiptsEnabled(true)) - assertThat(awaitItem().isSendPublicReadReceiptsEnabled).isTrue() + assertThat(initialState.isSharePresenceEnabled).isTrue() + initialState.eventSink.invoke(AdvancedSettingsEvents.SetSharePresenceEnabled(false)) + assertThat(awaitItem().isSharePresenceEnabled).isFalse() + initialState.eventSink.invoke(AdvancedSettingsEvents.SetSharePresenceEnabled(true)) + assertThat(awaitItem().isSharePresenceEnabled).isTrue() } } From 5747453505b0a08258a1adedf972710ca2df495b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 5 Feb 2024 12:45:48 +0100 Subject: [PATCH 4/8] Take into account the setting `isSendTypingNotificationsEnabled` to not send typing notification when it's been disabled by the user. --- .../MessageComposerPresenter.kt | 15 ++++++++++--- .../messages/impl/MessagesPresenterTest.kt | 1 + .../MessageComposerPresenterTest.kt | 22 +++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 4fea2ee046..b98f5fcb39 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf @@ -37,6 +38,7 @@ import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.attachments.preview.error.sendAttachmentError import io.element.android.features.messages.impl.mentions.MentionSuggestion import io.element.android.features.messages.impl.mentions.MentionSuggestionsProcessor +import io.element.android.features.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage @@ -86,6 +88,7 @@ class MessageComposerPresenter @Inject constructor( private val room: MatrixRoom, private val mediaPickerProvider: PickerProvider, private val featureFlagService: FeatureFlagService, + private val sessionPreferencesStore: SessionPreferencesStore, private val localMediaFactory: LocalMediaFactory, private val mediaSender: MediaSender, private val snackbarDispatcher: SnackbarDispatcher, @@ -147,6 +150,8 @@ class MessageComposerPresenter @Inject constructor( var showAttachmentSourcePicker: Boolean by remember { mutableStateOf(false) } var showTextFormatting: Boolean by remember { mutableStateOf(false) } + val sendTypingNotifications by sessionPreferencesStore.isSendTypingNotificationsEnabled().collectAsState(initial = true) + LaunchedEffect(messageComposerContext.composerMode) { when (val modeValue = messageComposerContext.composerMode) { is MessageComposerMode.Edit -> @@ -212,7 +217,9 @@ class MessageComposerPresenter @Inject constructor( // Declare that the user is not typing anymore when the composer is disposed onDispose { appCoroutineScope.launch { - room.typingNotice(false) + if (sendTypingNotifications) { + room.typingNotice(false) + } } } } @@ -310,8 +317,10 @@ class MessageComposerPresenter @Inject constructor( analyticsService.trackError(event.error) } is MessageComposerEvents.TypingNotice -> { - localCoroutineScope.launch { - room.typingNotice(event.isTyping) + if (sendTypingNotifications) { + localCoroutineScope.launch { + room.typingNotice(event.isTyping) + } } } is MessageComposerEvents.SuggestionReceived -> { diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 1c10cb97e8..9dd9ff5d88 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -675,6 +675,7 @@ class MessagesPresenterTest { room = matrixRoom, mediaPickerProvider = FakePickerProvider(), featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.NotificationSettings.key to true)), + sessionPreferencesStore = InMemorySessionPreferencesStore(), localMediaFactory = FakeLocalMediaFactory(mockMediaUrl), mediaSender = mediaSender, snackbarDispatcher = SnackbarDispatcher(), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt index d4b3c33cb1..c189e28c13 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt @@ -32,11 +32,13 @@ import io.element.android.features.messages.impl.messagecomposer.MessageComposer import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter import io.element.android.features.messages.impl.messagecomposer.MessageComposerState +import io.element.android.features.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService +import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesStore import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.TransactionId import io.element.android.libraries.matrix.api.media.ImageInfo @@ -888,6 +890,24 @@ class MessageComposerPresenterTest { } } + @Test + fun `present - handle typing notice event when sending typing notice is disabled`() = runTest { + val room = FakeMatrixRoom() + val store = InMemorySessionPreferencesStore( + isSendTypingNotificationsEnabled = false + ) + val presenter = createPresenter(room = room, sessionPreferencesStore = store, coroutineScope = this) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitFirstItem() + assertThat(room.typingRecord).isEmpty() + initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(true)) + initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(false)) + assertThat(room.typingRecord).isEmpty() + } + } + private suspend fun ReceiveTurbine.backToNormalMode(state: MessageComposerState, skipCount: Int = 0): MessageComposerState { state.eventSink.invoke(MessageComposerEvents.CloseSpecialMode) skipItems(skipCount) @@ -901,6 +921,7 @@ class MessageComposerPresenterTest { room: MatrixRoom = FakeMatrixRoom(), pickerProvider: PickerProvider = this.pickerProvider, featureFlagService: FeatureFlagService = this.featureFlagService, + sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(), mediaPreProcessor: MediaPreProcessor = this.mediaPreProcessor, snackbarDispatcher: SnackbarDispatcher = this.snackbarDispatcher, permissionPresenter: PermissionsPresenter = FakePermissionsPresenter(), @@ -909,6 +930,7 @@ class MessageComposerPresenterTest { room, pickerProvider, featureFlagService, + sessionPreferencesStore, localMediaFactory, MediaSender(mediaPreProcessor, room), snackbarDispatcher, From cbf7e9cfcefd1406e3eee81cda83f0b73cb9fc73 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 5 Feb 2024 12:58:04 +0100 Subject: [PATCH 5/8] Hide Read Receipt if the user set Rended Read Receipt to false (actually disabled the "Share presence" toggle) --- .../impl/timeline/TimelinePresenter.kt | 2 ++ .../messages/impl/timeline/TimelineState.kt | 1 + .../impl/timeline/TimelineStateProvider.kt | 1 + .../messages/impl/timeline/TimelineView.kt | 1 + .../components/ATimelineItemEventRow.kt | 2 ++ .../components/TimelineItemEventRow.kt | 2 ++ .../TimelineItemEventRowWithRRPreview.kt | 3 +++ .../TimelineItemGroupedEventsRow.kt | 9 ++++++- .../timeline/components/TimelineItemRow.kt | 4 ++++ .../components/TimelineItemStateEventRow.kt | 3 +++ .../receipt/TimelineItemReadReceiptView.kt | 24 +++++++++++-------- 11 files changed, 41 insertions(+), 11 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index a0d7ebf771..cefc58d92f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -106,6 +106,7 @@ class TimelinePresenter @AssistedInject constructor( val keyBackupState by encryptionService.backupStateStateFlow.collectAsState() val isSendPublicReadReceiptsEnabled by sessionPreferencesStore.isSendPublicReadReceiptsEnabled().collectAsState(initial = true) + val renderReadReceipts by sessionPreferencesStore.isRenderReadReceiptsEnabled().collectAsState(initial = true) val sessionState by remember { derivedStateOf { @@ -183,6 +184,7 @@ class TimelinePresenter @AssistedInject constructor( highlightedEventId = highlightedEventId.value, paginationState = paginationState, timelineItems = timelineItems, + renderReadReceipts = renderReadReceipts, newEventState = newItemState.value, sessionState = sessionState, eventSink = { handleEvents(it) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt index ef25d94ebc..a709aa932f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt @@ -28,6 +28,7 @@ import kotlinx.collections.immutable.ImmutableList data class TimelineState( val timelineItems: ImmutableList, val timelineRoomInfo: TimelineRoomInfo, + val renderReadReceipts: Boolean, val highlightedEventId: EventId?, val paginationState: MatrixTimeline.PaginationState, val newEventState: NewEventState, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt index 3012a095e8..cb7ce3c938 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt @@ -48,6 +48,7 @@ import kotlin.random.Random fun aTimelineState(timelineItems: ImmutableList = persistentListOf()) = TimelineState( timelineItems = timelineItems, timelineRoomInfo = aTimelineRoomInfo(), + renderReadReceipts = false, paginationState = MatrixTimeline.PaginationState( isBackPaginating = false, hasMoreToLoadBackwards = true, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index 563eb78d78..72cf2be504 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -120,6 +120,7 @@ fun TimelineView( TimelineItemRow( timelineItem = timelineItem, timelineRoomInfo = state.timelineRoomInfo, + renderReadReceipts = state.renderReadReceipts, isLastOutgoingMessage = (timelineItem as? TimelineItem.Event)?.isMine == true && state.timelineItems.first().identifier() == timelineItem.identifier(), highlightedItem = state.highlightedEventId?.value, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt index a7c860f46e..a86095b3fc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt @@ -26,11 +26,13 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem internal fun ATimelineItemEventRow( event: TimelineItem.Event, timelineRoomInfo: TimelineRoomInfo = aTimelineRoomInfo(), + renderReadReceipts: Boolean = false, isLastOutgoingMessage: Boolean = false, isHighlighted: Boolean = false, ) = TimelineItemEventRow( event = event, timelineRoomInfo = timelineRoomInfo, + renderReadReceipts = renderReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, isHighlighted = isHighlighted, onClick = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index 3e23114729..bbc36b3d3f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -114,6 +114,7 @@ import kotlin.math.roundToInt fun TimelineItemEventRow( event: TimelineItem.Event, timelineRoomInfo: TimelineRoomInfo, + renderReadReceipts: Boolean, isLastOutgoingMessage: Boolean, isHighlighted: Boolean, onClick: () -> Unit, @@ -223,6 +224,7 @@ fun TimelineItemEventRow( isLastOutgoingMessage = isLastOutgoingMessage, receipts = event.readReceiptState.receipts, ), + renderReadReceipts = renderReadReceipts, onReadReceiptsClicked = { onReadReceiptClick(event) }, modifier = Modifier.padding(top = 4.dp), ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithRRPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithRRPreview.kt index 0e43e34670..85ff3a77bf 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithRRPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithRRPreview.kt @@ -47,6 +47,7 @@ internal fun TimelineItemEventRowWithRRPreview( timelineItemReactions = aTimelineItemReactions(count = 0), readReceiptState = TimelineItemReadReceipts(state.receipts), ), + renderReadReceipts = true, isLastOutgoingMessage = false, ) // A message from current user @@ -60,6 +61,7 @@ internal fun TimelineItemEventRowWithRRPreview( timelineItemReactions = aTimelineItemReactions(count = 0), readReceiptState = TimelineItemReadReceipts(state.receipts), ), + renderReadReceipts = true, isLastOutgoingMessage = false, ) // Another message from current user @@ -73,6 +75,7 @@ internal fun TimelineItemEventRowWithRRPreview( timelineItemReactions = aTimelineItemReactions(count = 0), readReceiptState = TimelineItemReadReceipts(state.receipts), ), + renderReadReceipts = true, isLastOutgoingMessage = true, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt index 5ca78adee6..c3d1f32ef2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt @@ -43,6 +43,7 @@ import io.element.android.libraries.matrix.api.core.UserId fun TimelineItemGroupedEventsRow( timelineItem: TimelineItem.GroupedEvents, timelineRoomInfo: TimelineRoomInfo, + renderReadReceipts: Boolean, isLastOutgoingMessage: Boolean, highlightedItem: String?, sessionState: SessionState, @@ -70,6 +71,7 @@ fun TimelineItemGroupedEventsRow( timelineItem = timelineItem, timelineRoomInfo = timelineRoomInfo, highlightedItem = highlightedItem, + renderReadReceipts = renderReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, sessionState = sessionState, onClick = onClick, @@ -93,6 +95,7 @@ private fun TimelineItemGroupedEventsRowContent( timelineItem: TimelineItem.GroupedEvents, timelineRoomInfo: TimelineRoomInfo, highlightedItem: String?, + renderReadReceipts: Boolean, isLastOutgoingMessage: Boolean, sessionState: SessionState, onClick: (TimelineItem.Event) -> Unit, @@ -124,6 +127,7 @@ private fun TimelineItemGroupedEventsRowContent( TimelineItemRow( timelineItem = subGroupEvent, timelineRoomInfo = timelineRoomInfo, + renderReadReceipts = renderReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, highlightedItem = highlightedItem, sessionState = sessionState, @@ -141,13 +145,14 @@ private fun TimelineItemGroupedEventsRowContent( ) } } - } else { + } else if (renderReadReceipts) { TimelineItemReadReceiptView( state = ReadReceiptViewState( sendState = null, isLastOutgoingMessage = false, receipts = timelineItem.aggregatedReadReceipts, ), + renderReadReceipts = true, onReadReceiptsClicked = onExpandGroupClick ) } @@ -163,6 +168,7 @@ internal fun TimelineItemGroupedEventsRowContentExpandedPreview() = ElementPrevi timelineItem = aGroupedEvents(withReadReceipts = true), timelineRoomInfo = aTimelineRoomInfo(), highlightedItem = null, + renderReadReceipts = true, isLastOutgoingMessage = false, sessionState = aSessionState(), onClick = {}, @@ -187,6 +193,7 @@ internal fun TimelineItemGroupedEventsRowContentCollapsePreview() = ElementPrevi timelineItem = aGroupedEvents(withReadReceipts = true), timelineRoomInfo = aTimelineRoomInfo(), highlightedItem = null, + renderReadReceipts = true, isLastOutgoingMessage = false, sessionState = aSessionState(), onClick = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt index 2fe1080743..899d38eb12 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt @@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.api.core.UserId internal fun TimelineItemRow( timelineItem: TimelineItem, timelineRoomInfo: TimelineRoomInfo, + renderReadReceipts: Boolean, isLastOutgoingMessage: Boolean, highlightedItem: String?, sessionState: SessionState, @@ -58,6 +59,7 @@ internal fun TimelineItemRow( if (timelineItem.content is TimelineItemStateContent) { TimelineItemStateEventRow( event = timelineItem, + renderReadReceipts = renderReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, isHighlighted = highlightedItem == timelineItem.identifier(), onClick = { onClick(timelineItem) }, @@ -70,6 +72,7 @@ internal fun TimelineItemRow( TimelineItemEventRow( event = timelineItem, timelineRoomInfo = timelineRoomInfo, + renderReadReceipts = renderReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, isHighlighted = highlightedItem == timelineItem.identifier(), onClick = { onClick(timelineItem) }, @@ -91,6 +94,7 @@ internal fun TimelineItemRow( TimelineItemGroupedEventsRow( timelineItem = timelineItem, timelineRoomInfo = timelineRoomInfo, + renderReadReceipts = renderReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, highlightedItem = highlightedItem, sessionState = sessionState, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt index 85eba29bc1..af8ed484bc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt @@ -47,6 +47,7 @@ import kotlinx.collections.immutable.toPersistentList @Composable fun TimelineItemStateEventRow( event: TimelineItem.Event, + renderReadReceipts: Boolean, isLastOutgoingMessage: Boolean, isHighlighted: Boolean, onClick: () -> Unit, @@ -90,6 +91,7 @@ fun TimelineItemStateEventRow( isLastOutgoingMessage = isLastOutgoingMessage, receipts = event.readReceiptState.receipts, ), + renderReadReceipts = renderReadReceipts, onReadReceiptsClicked = { onReadReceiptsClick(event) }, ) } @@ -107,6 +109,7 @@ internal fun TimelineItemStateEventRowPreview() = ElementPreview { receipts = listOf(aReadReceiptData(0)).toPersistentList(), ) ), + renderReadReceipts = true, isLastOutgoingMessage = false, isHighlighted = false, onClick = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt index b8f5dc3ca9..8f3d8f3710 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt @@ -58,20 +58,23 @@ import kotlinx.collections.immutable.ImmutableList @Composable fun TimelineItemReadReceiptView( state: ReadReceiptViewState, + renderReadReceipts: Boolean, onReadReceiptsClicked: () -> Unit, modifier: Modifier = Modifier, ) { if (state.receipts.isNotEmpty()) { - ReadReceiptsRow(modifier = modifier) { - ReadReceiptsAvatars( - receipts = state.receipts, - modifier = Modifier - .clip(RoundedCornerShape(4.dp)) - .clickable { - onReadReceiptsClicked() - } - .padding(2.dp) - ) + if (renderReadReceipts) { + ReadReceiptsRow(modifier = modifier) { + ReadReceiptsAvatars( + receipts = state.receipts, + modifier = Modifier + .clip(RoundedCornerShape(4.dp)) + .clickable { + onReadReceiptsClicked() + } + .padding(2.dp) + ) + } } } else { when (state.sendState) { @@ -206,6 +209,7 @@ internal fun TimelineItemReactionsViewPreview( ) = ElementPreview { TimelineItemReadReceiptView( state = state, + renderReadReceipts = true, onReadReceiptsClicked = {}, ) } From 95fba29669c648c70a275146581da191539d8ea7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 5 Feb 2024 15:16:13 +0100 Subject: [PATCH 6/8] Changelog --- changelog.d/2241.feature | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelog.d/2241.feature diff --git a/changelog.d/2241.feature b/changelog.d/2241.feature new file mode 100644 index 0000000000..4d97eccc21 --- /dev/null +++ b/changelog.d/2241.feature @@ -0,0 +1,2 @@ +Change "Read receipts" advanced setting used to send private Read Receipt to "Share presence" settings. +When disabled, private Read Receipts will be sent, and no typing notification will be sent. Also Read Receipts and typing notifications will not be rendered in the timeline. From cbb11d2550fd1ac98d9adfd3b595eb1df85b0503 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Mon, 5 Feb 2024 14:25:10 +0000 Subject: [PATCH 7/8] Update screenshots --- ...ll_AdvancedSettingsView-Day-1_2_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...ll_AdvancedSettingsView-Day-1_2_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...ll_AdvancedSettingsView-Day-1_2_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...ll_AdvancedSettingsView-Day-1_2_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...ll_AdvancedSettingsView-Day-1_2_null_4,NEXUS_5,1.0,en].png | 4 ++-- ..._AdvancedSettingsView-Night-1_3_null_0,NEXUS_5,1.0,en].png | 4 ++-- ..._AdvancedSettingsView-Night-1_3_null_1,NEXUS_5,1.0,en].png | 4 ++-- ..._AdvancedSettingsView-Night-1_3_null_2,NEXUS_5,1.0,en].png | 4 ++-- ..._AdvancedSettingsView-Night-1_3_null_3,NEXUS_5,1.0,en].png | 4 ++-- ..._AdvancedSettingsView-Night-1_3_null_4,NEXUS_5,1.0,en].png | 4 ++-- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_0,NEXUS_5,1.0,en].png index da1d8ddbfd..99f1da2d6f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da3f8614862ddacfd9b54afcd3bf091bec335e1489d7cbffc826059a89ae5478 -size 58453 +oid sha256:41d892f8a97bb9d89d6a6128c7acae4d5c509dadee0e1758c7fec9d76bc4216d +size 56386 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_1,NEXUS_5,1.0,en].png index c99349ac0c..0cee409bf8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1fd599f2e93432d5d5555f086b60059e84452a0da3c2a69d932042b7ee4d3bac -size 57922 +oid sha256:478e8c2d3dd29baf88f2641813760c623631d53446f31bca2f1372c05d4ca914 +size 55856 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_2,NEXUS_5,1.0,en].png index b228d561bd..7366bd20c2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9e9641228e0482c8e5500bb999b180e79cbfcb6a13e121f3be68aae5c58a6839 -size 57953 +oid sha256:6e174f5f0b15343f5f02cc716cb371aa89d87e298104ac8e5df83bdab2db573d +size 55887 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_3,NEXUS_5,1.0,en].png index 3df61a1ef4..7ba5c4b13b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c1c5b00098e5860d4c69ef9a652483ea1192d9fa10f5449fb817769ab054183 -size 36128 +oid sha256:d044628791909fdb8f621b3693cb63726e6a1dbc1fd6e12e1b3a4e3c9be1e868 +size 36288 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_4,NEXUS_5,1.0,en].png index 50da3b5d6c..bfc79dca12 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Day-1_2_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4fe6625641cf06c45a817e9d10bc0d7296aa59160fc5a3e9dc28304e5b794125 -size 57943 +oid sha256:62762b55b56ee307e15a2aa488e8426714771d6e9d76d68ee82185b3420a348f +size 55831 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_0,NEXUS_5,1.0,en].png index 47fbdb6c0f..7324db05b6 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e85b252d0d6c166f987974ebcf9a6b58b924316733b868ad0ed5a61413bda381 -size 54577 +oid sha256:923619c9af298ce1a250c5094429b6b820b560c62a55ec8baa40144478e42358 +size 53102 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_1,NEXUS_5,1.0,en].png index 10d7a6c5e1..b1a1b5e134 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f246ce9f60b25d9739d73e2aed98132dfb3a0a694acbf320ff796f35f805fdc -size 54273 +oid sha256:1125869fdf539f15103bef1a380e46af9b56c44262a1986009099e2700206038 +size 52799 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_2,NEXUS_5,1.0,en].png index 4b03ace56b..6b82d8a9ff 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0403a4bb8da3d36922cb5362977af9b3052624525def05e5236f4f922384c31 -size 54297 +oid sha256:ecee6cd60257a931f717c8ae23f256ded8dd6ac8fa7af7430082c6a3f837b274 +size 52821 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_3,NEXUS_5,1.0,en].png index 01e0e907a6..5f4b3fa5fc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:824774a1e19974090de4cb32e06a7f984b5db238c3dc8d04a35d3457e647ac84 -size 31999 +oid sha256:8449c20314175d6e829df65b2b1ba3cf36944a679b2e360ee5d2ed4d8333ad07 +size 32149 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_4,NEXUS_5,1.0,en].png index 449fb69524..fbe1510225 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.advanced_AdvancedSettingsView_null_AdvancedSettingsView-Night-1_3_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c0820b462a0dc103b66aef78528ae5ce13a55e2614a7a197e06713366c422893 -size 54313 +oid sha256:5c38a6f66d48174c6e2b3ac8525906cd371030f15ac3ca8727a8ca83f528916e +size 52823 From 706ce1d0cdd6fa099a384946c189ce690fe34379 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 6 Feb 2024 15:33:03 +0100 Subject: [PATCH 8/8] Do not use a key for the LazyColumn, or the scroll will not behave as expected if a room is moved to the top of the list. --- .../io/element/android/features/roomlist/impl/RoomListView.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 929d2550a4..ace8b9ce60 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 @@ -259,10 +259,11 @@ private fun RoomListContent( } val roomList = state.roomList.dataOrNull().orEmpty() + // Note: do not use a key for the LazyColumn, or the scroll will not behave as expected if a room + // is moved to the top of the list. itemsIndexed( items = roomList, contentType = { _, room -> room.contentType() }, - key = { _, room -> room.roomId.value } ) { index, room -> RoomSummaryRow( room = room,