From 752da3738308196a9d9c1c9013a7addcc5d19d48 Mon Sep 17 00:00:00 2001 From: David Langley Date: Fri, 22 Sep 2023 16:16:52 +0100 Subject: [PATCH 01/19] Add roomsWithUserDefinedRules data and render list - get roomsWithUserDefinedRules from rust - add to state in the presenter - render in the edit defaults view as a list --- ...EditDefaultNotificationSettingPresenter.kt | 34 ++++++++++++ .../EditDefaultNotificationSettingState.kt | 2 + .../EditDefaultNotificationSettingView.kt | 54 +++++++++++++++++++ ...efaultNotificationSettingsStateProvider.kt | 49 +++++++++++++++++ .../components/avatar/AvatarSize.kt | 2 + .../NotificationSettingsService.kt | 1 + .../RustNotificationSettingsService.kt | 5 ++ 7 files changed, 147 insertions(+) create mode 100644 features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingsStateProvider.kt diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt index 764b37c52d..58140cb35f 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt @@ -26,19 +26,25 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.room.RoomNotificationMode +import io.element.android.libraries.matrix.api.roomlist.RoomListService +import io.element.android.libraries.matrix.api.roomlist.RoomSummary import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import java.text.Collator import kotlin.time.Duration.Companion.seconds class EditDefaultNotificationSettingPresenter @AssistedInject constructor( private val notificationSettingsService: NotificationSettingsService, @Assisted private val isOneToOne: Boolean, + private val roomListService: RoomListService, + private val matrixClient: MatrixClient, ) : Presenter { @AssistedFactory interface Factory { @@ -50,10 +56,16 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( val mode: MutableState = remember { mutableStateOf(null) } + + val roomsWithUserDefinedMode: MutableState> = remember { + mutableStateOf(listOf()) + } + val localCoroutineScope = rememberCoroutineScope() LaunchedEffect(Unit) { fetchSettings(mode) observeNotificationSettings(mode) + observeRoomSummaries(roomsWithUserDefinedMode) } fun handleEvents(event: EditDefaultNotificationSettingStateEvents) { @@ -65,6 +77,7 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( return EditDefaultNotificationSettingState( isOneToOne = isOneToOne, mode = mode.value, + roomsWithUserDefinedMode = roomsWithUserDefinedMode.value, eventSink = ::handleEvents ) } @@ -83,6 +96,27 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( .launchIn(this) } + private fun CoroutineScope.observeRoomSummaries(roomsWithUserDefinedMode: MutableState>) { + roomListService.allRooms() + .summaries + .onEach { + updateRoomsWithUserDefinedMode(it, roomsWithUserDefinedMode) + } + .launchIn(this) + } + + private fun CoroutineScope.updateRoomsWithUserDefinedMode(summaries: List, roomsWithUserDefinedMode: MutableState>) = launch { + val roomWithUserDefinedRules = notificationSettingsService.getRoomsWithUserDefinedRules().getOrThrow().toSet() + roomsWithUserDefinedMode.value = summaries + .filterIsInstance() + .filter { + val room = matrixClient.getRoom(it.details.roomId) ?: return@filter false + roomWithUserDefinedRules.contains(it.identifier()) && isOneToOne == room.isOneToOne + } + // locale sensitive sorting + .sortedWith(compareBy(Collator.getInstance()){ it.details.name }) + } + private fun CoroutineScope.setDefaultNotificationMode(mode: RoomNotificationMode) = launch { // On modern clients, we don't have different settings for encrypted and non-encrypted rooms (Legacy clients did). notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = true, mode = mode, isOneToOne = isOneToOne) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt index 62c708d988..e4d18239cd 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt @@ -17,9 +17,11 @@ package io.element.android.features.preferences.impl.notifications.edit import io.element.android.libraries.matrix.api.room.RoomNotificationMode +import io.element.android.libraries.matrix.api.roomlist.RoomSummary data class EditDefaultNotificationSettingState( val isOneToOne: Boolean, val mode: RoomNotificationMode?, + val roomsWithUserDefinedMode: List, val eventSink: (EditDefaultNotificationSettingStateEvents) -> Unit, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt index 4cc95af71f..56cf3acf9a 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt @@ -17,12 +17,26 @@ package io.element.android.features.preferences.impl.notifications.edit import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.selection.selectableGroup 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.features.preferences.impl.notifications.NotificationSettingsState +import io.element.android.features.preferences.impl.notifications.NotificationSettingsStateProvider +import io.element.android.features.preferences.impl.notifications.NotificationSettingsView +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferenceView +import io.element.android.libraries.designsystem.preview.DayNightPreviews +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.ui.strings.CommonStrings @@ -70,6 +84,46 @@ fun EditDefaultNotificationSettingView( } } } + if(state.roomsWithUserDefinedMode.isNotEmpty()) { + PreferenceCategory(title = stringResource(id = CommonStrings.screen_notification_settings_edit_custom_settings_section_title)) { + LazyColumn { + items(state.roomsWithUserDefinedMode) { summary -> + val subtitle = when (summary.details.notificationMode) { + RoomNotificationMode.ALL_MESSAGES -> stringResource(id = CommonStrings.screen_notification_settings_edit_mode_all_messages) + RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> stringResource(id = CommonStrings.screen_notification_settings_edit_mode_mentions_and_keywords) + RoomNotificationMode.MUTE -> stringResource(id = CommonStrings.common_mute) + null -> "" + } + val avatarData = AvatarData( + id = summary.identifier(), + name = summary.details.name, + url = summary.details.avatarURLString, + size = AvatarSize.CustomRoomNotificationSetting, + ) + ListItem( + headlineContent = { + Text(text = summary.details.name) + }, + supportingContent = { + Text(text = subtitle) + }, + leadingContent = ListItemContent.Custom { + Avatar(avatarData = avatarData) + } + ) + } + } + } + } + } } +@DayNightPreviews +@Composable +internal fun EditDefaultNotificationSettingViewPreview(@PreviewParameter(EditDefaultNotificationSettingsStateProvider::class) state: EditDefaultNotificationSettingState) = ElementPreview { + EditDefaultNotificationSettingView( + state = state, + onBackPressed = {}, + ) +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingsStateProvider.kt new file mode 100644 index 0000000000..738074e9e6 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingsStateProvider.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.features.preferences.impl.notifications.edit + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.RoomNotificationMode +import io.element.android.libraries.matrix.api.roomlist.RoomSummary +import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails + +open class EditDefaultNotificationSettingsStateProvider: PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + anEditDefaultNotificationSettingsState(), + ) +} + +fun anEditDefaultNotificationSettingsState() = EditDefaultNotificationSettingState( + isOneToOne = false, + mode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, + roomsWithUserDefinedMode = listOf(aRoomSummary()), + eventSink = {} +) + +private fun aRoomSummary() = RoomSummary.Filled( + RoomSummaryDetails( + roomId = RoomId("!roomId:domain"), + name = "Room", + avatarURLString = null, + isDirect = false, + lastMessage = null, + lastMessageTimestamp = null, + unreadNotificationCount = 0, + ) +) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt index 45d7780393..b2004ed204 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt @@ -46,4 +46,6 @@ enum class AvatarSize(val dp: Dp) { EditRoomDetails(70.dp), NotificationsOptIn(32.dp), + + CustomRoomNotificationSetting(36.dp) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notificationsettings/NotificationSettingsService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notificationsettings/NotificationSettingsService.kt index 5a81edb052..71d46a2b8e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notificationsettings/NotificationSettingsService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notificationsettings/NotificationSettingsService.kt @@ -38,4 +38,5 @@ interface NotificationSettingsService { suspend fun setRoomMentionEnabled(enabled: Boolean): Result suspend fun isCallEnabled(): Result suspend fun setCallEnabled(enabled: Boolean): Result + suspend fun getRoomsWithUserDefinedRules(): Result> } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt index a2fffdbdfb..df5c4d577f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt @@ -110,4 +110,9 @@ class RustNotificationSettingsService( notificationSettings.setCallEnabled(enabled) } } + + override suspend fun getRoomsWithUserDefinedRules(): Result> = + runCatching { + notificationSettings.getRoomsWithUserDefinedRules(enabled = true) + } } From eadaa2f65cd1d69330e081b40ac39c1d56618839 Mon Sep 17 00:00:00 2001 From: David Langley Date: Tue, 17 Oct 2023 16:08:08 +0100 Subject: [PATCH 02/19] List user define room notification settings - List user define room notification settings - Add new user defined style of the room notification settings view - Add navigation to expose room notification settings ui to the global settings - Add Progress indicators - Improve error handing --- .../android/appnav/LoggedInFlowNode.kt | 4 + .../android/appnav/room/RoomLoadedFlowNode.kt | 7 + .../preferences/api/PreferencesEntryPoint.kt | 2 + .../preferences/impl/PreferencesFlowNode.kt | 8 +- .../NotificationSettingsEvents.kt | 1 + .../NotificationSettingsPresenter.kt | 25 +++- .../NotificationSettingsState.kt | 2 + .../NotificationSettingsStateProvider.kt | 2 + .../notifications/NotificationSettingsView.kt | 15 ++ .../EditDefaultNotificationSettingNode.kt | 14 +- ...EditDefaultNotificationSettingPresenter.kt | 32 +++-- .../EditDefaultNotificationSettingState.kt | 2 + ...itDefaultNotificationSettingStateEvents.kt | 1 + .../EditDefaultNotificationSettingView.kt | 86 +++++++----- ...efaultNotificationSettingsStateProvider.kt | 2 + .../roomdetails/api/RoomDetailsEntryPoint.kt | 3 + .../impl/DefaultRoomDetailsEntryPoint.kt | 1 + .../roomdetails/impl/RoomDetailsFlowNode.kt | 15 +- .../RoomNotificationSettingsEvents.kt | 2 + .../RoomNotificationSettingsNode.kt | 26 +++- .../RoomNotificationSettingsPresenter.kt | 34 +++-- .../RoomNotificationSettingsState.kt | 4 + .../RoomNotificationSettingsStateProvider.kt | 4 + .../RoomNotificationSettingsView.kt | 25 +++- ...UserDefinedRoomNotificationSettingsView.kt | 128 ++++++++++++++++++ 25 files changed, 375 insertions(+), 70 deletions(-) create mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 164c2ae2e4..065cfaafc2 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -244,6 +244,10 @@ class LoggedInFlowNode @AssistedInject constructor( override fun onVerifyClicked() { backstack.push(NavTarget.VerifySession) } + + override fun onOpenRoomNotificationSettings(roomId: RoomId) { + backstack.push(NavTarget.Room(roomId, initialElement = RoomLoadedFlowNode.NavTarget.RoomNotificationSettings)) + } } preferencesEntryPoint.nodeBuilder(this, buildContext) .callback(callback) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt index 34c459f979..2b063a2cbf 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt @@ -142,6 +142,10 @@ class RoomLoadedFlowNode @AssistedInject constructor( val inputs = RoomDetailsEntryPoint.Inputs(RoomDetailsEntryPoint.InitialTarget.RoomMemberDetails(navTarget.userId)) roomDetailsEntryPoint.createNode(this, buildContext, inputs, emptyList()) } + NavTarget.RoomNotificationSettings -> { + val inputs = RoomDetailsEntryPoint.Inputs(RoomDetailsEntryPoint.InitialTarget.RoomNotificationSettings) + roomDetailsEntryPoint.createNode(this, buildContext, inputs, emptyList()) + } } } @@ -154,6 +158,9 @@ class RoomLoadedFlowNode @AssistedInject constructor( @Parcelize data class RoomMemberDetails(val userId: UserId) : NavTarget + + @Parcelize + data object RoomNotificationSettings : NavTarget } @Composable diff --git a/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt b/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt index 3d1a516593..50a605efe4 100644 --- a/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt +++ b/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt @@ -20,6 +20,7 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint +import io.element.android.libraries.matrix.api.core.RoomId interface PreferencesEntryPoint : FeatureEntryPoint { @@ -33,5 +34,6 @@ interface PreferencesEntryPoint : FeatureEntryPoint { interface Callback : Plugin { fun onOpenBugReport() fun onVerifyClicked() + fun onOpenRoomNotificationSettings(roomId: RoomId) } } 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 6cf0390db2..8d77757527 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 @@ -43,6 +43,7 @@ import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.parcelize.Parcelize @@ -152,8 +153,13 @@ class PreferencesFlowNode @AssistedInject constructor( createNode(buildContext, listOf(notificationSettingsCallback)) } is NavTarget.EditDefaultNotificationSetting -> { + val callback = object : EditDefaultNotificationSettingNode.Callback { + override fun openRoomNotificationSettings(roomId: RoomId) { + plugins().forEach { it.onOpenRoomNotificationSettings(roomId) } + } + } val input = EditDefaultNotificationSettingNode.Inputs(navTarget.isOneToOne) - createNode(buildContext, plugins = listOf(input)) + createNode(buildContext, plugins = listOf(input, callback)) } NavTarget.AdvancedSettings -> { createNode(buildContext) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsEvents.kt index 374b8078ca..9e87675b3a 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsEvents.kt @@ -24,4 +24,5 @@ sealed interface NotificationSettingsEvents { data class SetCallNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents data object FixConfigurationMismatch : NotificationSettingsEvents data object ClearConfigurationMismatchError : NotificationSettingsEvents + data object ClearNotificationChangeError : NotificationSettingsEvents } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt index 697d5887f0..689cca8f66 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt @@ -23,7 +23,9 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.room.RoomNotificationMode @@ -50,6 +52,7 @@ class NotificationSettingsPresenter @Inject constructor( val systemNotificationsEnabled: MutableState = remember { mutableStateOf(systemNotificationsEnabledProvider.notificationsEnabled()) } + val changeNotificationSettingAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } val localCoroutineScope = rememberCoroutineScope() val appNotificationsEnabled = userPushStore @@ -67,8 +70,12 @@ class NotificationSettingsPresenter @Inject constructor( fun handleEvents(event: NotificationSettingsEvents) { when (event) { - is NotificationSettingsEvents.SetAtRoomNotificationsEnabled -> localCoroutineScope.setAtRoomNotificationsEnabled(event.enabled) - is NotificationSettingsEvents.SetCallNotificationsEnabled -> localCoroutineScope.setCallNotificationsEnabled(event.enabled) + is NotificationSettingsEvents.SetAtRoomNotificationsEnabled -> { + localCoroutineScope.setAtRoomNotificationsEnabled(event.enabled, changeNotificationSettingAction) + } + is NotificationSettingsEvents.SetCallNotificationsEnabled -> { + localCoroutineScope.setCallNotificationsEnabled(event.enabled, changeNotificationSettingAction) + } is NotificationSettingsEvents.SetNotificationsEnabled -> localCoroutineScope.setNotificationsEnabled(userPushStore, event.enabled) NotificationSettingsEvents.ClearConfigurationMismatchError -> { matrixSettings.value = NotificationSettingsState.MatrixSettings.Invalid(fixFailed = false) @@ -77,6 +84,7 @@ class NotificationSettingsPresenter @Inject constructor( NotificationSettingsEvents.RefreshSystemNotificationsEnabled -> { systemNotificationsEnabled.value = systemNotificationsEnabledProvider.notificationsEnabled() } + NotificationSettingsEvents.ClearNotificationChangeError -> changeNotificationSettingAction.value = Async.Uninitialized } } @@ -86,6 +94,7 @@ class NotificationSettingsPresenter @Inject constructor( systemNotificationsEnabled = systemNotificationsEnabled.value, appNotificationsEnabled = appNotificationsEnabled.value ), + changeNotificationSettingAction = changeNotificationSettingAction.value, eventSink = ::handleEvents ) } @@ -154,12 +163,16 @@ class NotificationSettingsPresenter @Inject constructor( ) } - private fun CoroutineScope.setAtRoomNotificationsEnabled(enabled: Boolean) = launch { - notificationSettingsService.setRoomMentionEnabled(enabled) + private fun CoroutineScope.setAtRoomNotificationsEnabled(enabled: Boolean, action: MutableState>) = launch { + suspend { + notificationSettingsService.setRoomMentionEnabled(enabled).getOrThrow() + }.runCatchingUpdatingState(action) } - private fun CoroutineScope.setCallNotificationsEnabled(enabled: Boolean) = launch { - notificationSettingsService.setCallEnabled(enabled) + private fun CoroutineScope.setCallNotificationsEnabled(enabled: Boolean, action: MutableState>) = launch { + suspend { + notificationSettingsService.setCallEnabled(enabled).getOrThrow() + }.runCatchingUpdatingState(action) } private fun CoroutineScope.setNotificationsEnabled(userPushStore: UserPushStore, enabled: Boolean) = launch { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsState.kt index cf3cf6e3d0..2b0faa110c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsState.kt @@ -17,12 +17,14 @@ package io.element.android.features.preferences.impl.notifications import androidx.compose.runtime.Immutable +import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.room.RoomNotificationMode @Immutable data class NotificationSettingsState( val matrixSettings: MatrixSettings, val appSettings: AppSettings, + val changeNotificationSettingAction: Async, val eventSink: (NotificationSettingsEvents) -> Unit, ) { sealed interface MatrixSettings { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt index 1e653c47e0..cfff59e905 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt @@ -17,6 +17,7 @@ package io.element.android.features.preferences.impl.notifications import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.room.RoomNotificationMode open class NotificationSettingsStateProvider : PreviewParameterProvider { @@ -37,5 +38,6 @@ fun aNotificationSettingsState() = NotificationSettingsState( systemNotificationsEnabled = false, appNotificationsEnabled = true, ), + changeNotificationSettingAction = Async.Uninitialized, eventSink = {} ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt index dd3aba842d..c7bcbb573c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt @@ -36,6 +36,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import io.element.android.libraries.androidutils.system.startNotificationSettingsIntent +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch @@ -91,6 +93,19 @@ fun NotificationSettingsView( // onCallsNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetCallNotificationsEnabled(it)) }, ) } + when (state.changeNotificationSettingAction) { + is Async.Loading -> { + ProgressDialog() + } + is Async.Failure -> { + ErrorDialog( + title = stringResource(CommonStrings.dialog_title_error), + content = stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode), + onDismiss = { state.eventSink(NotificationSettingsEvents.ClearNotificationChangeError) }, + ) + } + else -> Unit + } } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingNode.kt index 6c4fd646f4..535203e35e 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingNode.kt @@ -21,12 +21,14 @@ 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 com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.core.RoomId @ContributesNode(SessionScope::class) class EditDefaultNotificationSettingNode @AssistedInject constructor( @@ -35,20 +37,30 @@ class EditDefaultNotificationSettingNode @AssistedInject constructor( presenterFactory: EditDefaultNotificationSettingPresenter.Factory ) : Node(buildContext, plugins = plugins) { + interface Callback : Plugin { + fun openRoomNotificationSettings(roomId: RoomId) + } + data class Inputs( val isOneToOne: Boolean ) : NodeInputs private val inputs = inputs() + private val callbacks = plugins() private val presenter = presenterFactory.create(inputs.isOneToOne) + private fun openRoomNotificationSettings(roomId: RoomId) { + callbacks.forEach { it.openRoomNotificationSettings(roomId) } + } + @Composable override fun View(modifier: Modifier) { val state = presenter.present() EditDefaultNotificationSettingView( state = state, + openRoomNotificationSettings = { openRoomNotificationSettings(it) }, onBackPressed = ::navigateUp, - modifier = modifier + modifier = modifier, ) } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt index 58140cb35f..79201e27d3 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt @@ -25,7 +25,9 @@ import androidx.compose.runtime.rememberCoroutineScope import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.room.RoomNotificationMode @@ -57,6 +59,8 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( mutableStateOf(null) } + val changeNotificationSettingAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } + val roomsWithUserDefinedMode: MutableState> = remember { mutableStateOf(listOf()) } @@ -70,7 +74,10 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( fun handleEvents(event: EditDefaultNotificationSettingStateEvents) { when (event) { - is EditDefaultNotificationSettingStateEvents.SetNotificationMode -> localCoroutineScope.setDefaultNotificationMode(event.mode) + is EditDefaultNotificationSettingStateEvents.SetNotificationMode -> { + localCoroutineScope.setDefaultNotificationMode(event.mode, changeNotificationSettingAction) + } + EditDefaultNotificationSettingStateEvents.ClearError -> changeNotificationSettingAction.value = Async.Uninitialized } } @@ -78,6 +85,7 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( isOneToOne = isOneToOne, mode = mode.value, roomsWithUserDefinedMode = roomsWithUserDefinedMode.value, + changeNotificationSettingAction = changeNotificationSettingAction.value, eventSink = ::handleEvents ) } @@ -105,9 +113,13 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( .launchIn(this) } - private fun CoroutineScope.updateRoomsWithUserDefinedMode(summaries: List, roomsWithUserDefinedMode: MutableState>) = launch { - val roomWithUserDefinedRules = notificationSettingsService.getRoomsWithUserDefinedRules().getOrThrow().toSet() - roomsWithUserDefinedMode.value = summaries + private fun CoroutineScope.updateRoomsWithUserDefinedMode( + summaries: List, + roomsWithUserDefinedMode: MutableState> + ) = launch { + val roomWithUserDefinedRules: Set = notificationSettingsService.getRoomsWithUserDefinedRules().getOrThrow().toSet() + + val sortedSummaries = summaries .filterIsInstance() .filter { val room = matrixClient.getRoom(it.details.roomId) ?: return@filter false @@ -115,12 +127,16 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( } // locale sensitive sorting .sortedWith(compareBy(Collator.getInstance()){ it.details.name }) + + roomsWithUserDefinedMode.value = sortedSummaries } - private fun CoroutineScope.setDefaultNotificationMode(mode: RoomNotificationMode) = launch { - // On modern clients, we don't have different settings for encrypted and non-encrypted rooms (Legacy clients did). - notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = true, mode = mode, isOneToOne = isOneToOne) - notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = false, mode = mode, isOneToOne = isOneToOne) + private fun CoroutineScope.setDefaultNotificationMode(mode: RoomNotificationMode, action: MutableState>) = launch { + suspend { + // On modern clients, we don't have different settings for encrypted and non-encrypted rooms (Legacy clients did). + notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = true, mode = mode, isOneToOne = isOneToOne).getOrThrow() + notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = false, mode = mode, isOneToOne = isOneToOne).getOrThrow() + }.runCatchingUpdatingState(action) } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt index e4d18239cd..e8590ec27f 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt @@ -16,6 +16,7 @@ package io.element.android.features.preferences.impl.notifications.edit +import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.roomlist.RoomSummary @@ -23,5 +24,6 @@ data class EditDefaultNotificationSettingState( val isOneToOne: Boolean, val mode: RoomNotificationMode?, val roomsWithUserDefinedMode: List, + val changeNotificationSettingAction: Async, val eventSink: (EditDefaultNotificationSettingStateEvents) -> Unit, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateEvents.kt index 75c9b6c1a4..f5774f1d78 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateEvents.kt @@ -20,4 +20,5 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode sealed interface EditDefaultNotificationSettingStateEvents { data class SetNotificationMode(val mode: RoomNotificationMode): EditDefaultNotificationSettingStateEvents + data object ClearError: EditDefaultNotificationSettingStateEvents } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt index 56cf3acf9a..cda19e15bb 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt @@ -17,19 +17,17 @@ package io.element.android.features.preferences.impl.notifications.edit import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.selection.selectableGroup 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.features.preferences.impl.notifications.NotificationSettingsState -import io.element.android.features.preferences.impl.notifications.NotificationSettingsStateProvider -import io.element.android.features.preferences.impl.notifications.NotificationSettingsView +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferenceView @@ -37,6 +35,7 @@ import io.element.android.libraries.designsystem.preview.DayNightPreviews import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.ui.strings.CommonStrings @@ -47,11 +46,12 @@ import io.element.android.libraries.ui.strings.CommonStrings @Composable fun EditDefaultNotificationSettingView( state: EditDefaultNotificationSettingState, + openRoomNotificationSettings:(roomId: RoomId) -> Unit, onBackPressed: () -> Unit, modifier: Modifier = Modifier, ) { - val title = if(state.isOneToOne) { + val title = if (state.isOneToOne) { CommonStrings.screen_notification_settings_direct_chats } else { CommonStrings.screen_notification_settings_group_chats @@ -65,7 +65,7 @@ fun EditDefaultNotificationSettingView( // Only ALL_MESSAGES and MENTIONS_AND_KEYWORDS_ONLY are valid global defaults. val validModes = listOf(RoomNotificationMode.ALL_MESSAGES, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) - val categoryTitle = if(state.isOneToOne) { + val categoryTitle = if (state.isOneToOne) { CommonStrings.screen_notification_settings_edit_screen_direct_section_header } else { CommonStrings.screen_notification_settings_edit_screen_group_section_header @@ -84,46 +84,64 @@ fun EditDefaultNotificationSettingView( } } } - if(state.roomsWithUserDefinedMode.isNotEmpty()) { + if (state.roomsWithUserDefinedMode.isNotEmpty()) { PreferenceCategory(title = stringResource(id = CommonStrings.screen_notification_settings_edit_custom_settings_section_title)) { - LazyColumn { - items(state.roomsWithUserDefinedMode) { summary -> - val subtitle = when (summary.details.notificationMode) { - RoomNotificationMode.ALL_MESSAGES -> stringResource(id = CommonStrings.screen_notification_settings_edit_mode_all_messages) - RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> stringResource(id = CommonStrings.screen_notification_settings_edit_mode_mentions_and_keywords) - RoomNotificationMode.MUTE -> stringResource(id = CommonStrings.common_mute) - null -> "" + state.roomsWithUserDefinedMode.forEach { summary -> + val subtitle = when (summary.details.notificationMode) { + RoomNotificationMode.ALL_MESSAGES -> stringResource(id = CommonStrings.screen_notification_settings_edit_mode_all_messages) + RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> { + stringResource(id = CommonStrings.screen_notification_settings_edit_mode_mentions_and_keywords) } - val avatarData = AvatarData( - id = summary.identifier(), - name = summary.details.name, - url = summary.details.avatarURLString, - size = AvatarSize.CustomRoomNotificationSetting, - ) - ListItem( - headlineContent = { - Text(text = summary.details.name) - }, - supportingContent = { - Text(text = subtitle) - }, - leadingContent = ListItemContent.Custom { - Avatar(avatarData = avatarData) - } - ) + RoomNotificationMode.MUTE -> stringResource(id = CommonStrings.common_mute) + null -> "" } + val avatarData = AvatarData( + id = summary.identifier(), + name = summary.details.name, + url = summary.details.avatarURLString, + size = AvatarSize.CustomRoomNotificationSetting, + ) + ListItem( + headlineContent = { + Text(text = summary.details.name) + }, + supportingContent = { + Text(text = subtitle) + }, + leadingContent = ListItemContent.Custom { + Avatar(avatarData = avatarData) + }, + onClick = { + openRoomNotificationSettings(summary.details.roomId) + } + ) } } } - + when (state.changeNotificationSettingAction) { + is Async.Loading -> { + ProgressDialog() + } + is Async.Failure -> { + ErrorDialog( + title = stringResource(CommonStrings.dialog_title_error), + content = stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode), + onDismiss = { state.eventSink(EditDefaultNotificationSettingStateEvents.ClearError) }, + ) + } + else -> Unit + } } } @DayNightPreviews @Composable -internal fun EditDefaultNotificationSettingViewPreview(@PreviewParameter(EditDefaultNotificationSettingsStateProvider::class) state: EditDefaultNotificationSettingState) = ElementPreview { +internal fun EditDefaultNotificationSettingViewPreview( + @PreviewParameter(EditDefaultNotificationSettingsStateProvider::class) state: EditDefaultNotificationSettingState +) = ElementPreview { EditDefaultNotificationSettingView( state = state, + openRoomNotificationSettings = {}, onBackPressed = {}, ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingsStateProvider.kt index 738074e9e6..6910b581bb 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingsStateProvider.kt @@ -17,6 +17,7 @@ package io.element.android.features.preferences.impl.notifications.edit import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.roomlist.RoomSummary @@ -33,6 +34,7 @@ fun anEditDefaultNotificationSettingsState() = EditDefaultNotificationSettingSta isOneToOne = false, mode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, roomsWithUserDefinedMode = listOf(aRoomSummary()), + changeNotificationSettingAction = Async.Uninitialized, eventSink = {} ) diff --git a/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt b/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt index 4fa5c18b2e..ed1e6f5c5a 100644 --- a/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt +++ b/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt @@ -33,6 +33,9 @@ interface RoomDetailsEntryPoint : FeatureEntryPoint { @Parcelize data class RoomMemberDetails(val roomMemberId: UserId) : InitialTarget + + @Parcelize + data object RoomNotificationSettings : InitialTarget } data class Inputs(val initialElement: InitialTarget) : NodeInputs diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt index be6b915212..8cd6cb54d6 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt @@ -42,4 +42,5 @@ class DefaultRoomDetailsEntryPoint @Inject constructor() : RoomDetailsEntryPoint internal fun InitialTarget.toNavTarget() = when (this) { is InitialTarget.RoomDetails -> NavTarget.RoomDetails is InitialTarget.RoomMemberDetails -> NavTarget.RoomMemberDetails(roomMemberId) + is InitialTarget.RoomNotificationSettings -> NavTarget.RoomNotificationSettings(showUserDefinedSettingStyle = true) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index 675ef7de60..5d7539e626 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -68,7 +68,13 @@ class RoomDetailsFlowNode @AssistedInject constructor( data object InviteMembers : NavTarget @Parcelize - object RoomNotificationSettings : NavTarget + data class RoomNotificationSettings( + /** + * When presented from oursite the context of the room, the rooms settings UI is different. + * Figma designs: https://www.figma.com/file/0MMNu7cTOzLOlWb7ctTkv3/Element-X?type=design&node-id=5199-198932&mode=design&t=fTTvpuxYFjewYQOe-0 + */ + val showUserDefinedSettingStyle: Boolean + ) : NavTarget @Parcelize data class RoomMemberDetails(val roomMemberId: UserId) : NavTarget @@ -91,7 +97,7 @@ class RoomDetailsFlowNode @AssistedInject constructor( } override fun openRoomNotificationSettings() { - backstack.push(NavTarget.RoomNotificationSettings) + backstack.push(NavTarget.RoomNotificationSettings(showUserDefinedSettingStyle = false)) } } createNode(buildContext, listOf(roomDetailsCallback)) @@ -118,8 +124,9 @@ class RoomDetailsFlowNode @AssistedInject constructor( createNode(buildContext) } - NavTarget.RoomNotificationSettings -> { - createNode(buildContext) + is NavTarget.RoomNotificationSettings -> { + val plugins = listOf(RoomNotificationSettingsNode.RoomNotificationSettingInput(navTarget.showUserDefinedSettingStyle)) + createNode(buildContext, plugins) } is NavTarget.RoomMemberDetails -> { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsEvents.kt index bbe756b154..c69896a98b 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsEvents.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsEvents.kt @@ -21,4 +21,6 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode sealed interface RoomNotificationSettingsEvents { data class RoomNotificationModeChanged(val mode: RoomNotificationMode) : RoomNotificationSettingsEvents data class SetNotificationMode(val isDefault: Boolean): RoomNotificationSettingsEvents + data object DeleteCustomNotification: RoomNotificationSettingsEvents + data object ClearError: RoomNotificationSettingsEvents } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt index 224f850e28..cb0168a42b 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt @@ -26,6 +26,8 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.RoomScope import io.element.android.services.analytics.api.AnalyticsService @@ -37,6 +39,12 @@ class RoomNotificationSettingsNode @AssistedInject constructor( private val analyticsService: AnalyticsService, ) : Node(buildContext, plugins = plugins) { + data class RoomNotificationSettingInput( + val showUserDefinedSettingStyle: Boolean + ) : NodeInputs + + private val inputs = inputs() + init { lifecycle.subscribe( onResume = { @@ -48,10 +56,18 @@ class RoomNotificationSettingsNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { val state = presenter.present() - RoomNotificationSettingsView( - state = state, - modifier = modifier, - onBackPressed = this::navigateUp, - ) + if(inputs.showUserDefinedSettingStyle) { + UserDefinedRoomNotificationSettingsView( + state = state, + modifier = modifier, + onBackPressed = this::navigateUp, + ) + } else { + RoomNotificationSettingsView( + state = state, + modifier = modifier, + onBackPressed = this::navigateUp, + ) + } } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt index a6e2477bcc..9fb0c8b1c8 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt @@ -22,9 +22,12 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable +import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomNotificationMode @@ -42,16 +45,18 @@ class RoomNotificationSettingsPresenter @Inject constructor( private val room: MatrixRoom, private val notificationSettingsService: NotificationSettingsService, ) : Presenter { - @Composable override fun present(): RoomNotificationSettingsState { val defaultRoomNotificationMode: MutableState = rememberSaveable { mutableStateOf(null) } val localCoroutineScope = rememberCoroutineScope() + val changeNotificationSettingAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } + val deleteCustomNotificationSettingAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } LaunchedEffect(Unit) { getDefaultRoomNotificationMode(defaultRoomNotificationMode) + room.updateRoomNotificationSettings() observeNotificationSettings() } @@ -60,23 +65,32 @@ class RoomNotificationSettingsPresenter @Inject constructor( fun handleEvents(event: RoomNotificationSettingsEvents) { when (event) { is RoomNotificationSettingsEvents.RoomNotificationModeChanged -> { - localCoroutineScope.setRoomNotificationMode(event.mode) + localCoroutineScope.setRoomNotificationMode(event.mode, changeNotificationSettingAction) } is RoomNotificationSettingsEvents.SetNotificationMode -> { if (event.isDefault) { - localCoroutineScope.restoreDefaultRoomNotificationMode() + localCoroutineScope.restoreDefaultRoomNotificationMode(changeNotificationSettingAction) } else { defaultRoomNotificationMode.value?.let { - localCoroutineScope.setRoomNotificationMode(it) + localCoroutineScope.setRoomNotificationMode(it, changeNotificationSettingAction) } } } + is RoomNotificationSettingsEvents.DeleteCustomNotification -> { + localCoroutineScope.restoreDefaultRoomNotificationMode(deleteCustomNotificationSettingAction) + } + RoomNotificationSettingsEvents.ClearError -> { + changeNotificationSettingAction.value = Async.Uninitialized + } } } return RoomNotificationSettingsState( + roomName = room.displayName, roomNotificationSettings = roomNotificationSettingsState.roomNotificationSettings(), defaultRoomNotificationMode = defaultRoomNotificationMode.value, + changeNotificationSettingAction = changeNotificationSettingAction.value, + deleteCustomNotificationSettingAction = deleteCustomNotificationSettingAction.value, eventSink = ::handleEvents, ) } @@ -98,11 +112,15 @@ class RoomNotificationSettingsPresenter @Inject constructor( ).getOrThrow() } - private fun CoroutineScope.setRoomNotificationMode(mode: RoomNotificationMode) = launch { - notificationSettingsService.setRoomNotificationMode(room.roomId, mode) + private fun CoroutineScope.setRoomNotificationMode(mode: RoomNotificationMode, action: MutableState>) = launch { + suspend { + notificationSettingsService.setRoomNotificationMode(room.roomId, mode).getOrThrow() + }.runCatchingUpdatingState(action) } - private fun CoroutineScope.restoreDefaultRoomNotificationMode() = launch { - notificationSettingsService.restoreDefaultRoomNotificationMode(room.roomId) + private fun CoroutineScope.restoreDefaultRoomNotificationMode(action: MutableState>) = launch { + suspend { + notificationSettingsService.restoreDefaultRoomNotificationMode(room.roomId).getOrThrow() + }.runCatchingUpdatingState(action) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt index 04742781b5..a7c5c3b883 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt @@ -16,11 +16,15 @@ package io.element.android.features.roomdetails.impl.notificationsettings +import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationSettings data class RoomNotificationSettingsState( + val roomName: String, val roomNotificationSettings: RoomNotificationSettings?, val defaultRoomNotificationMode: RoomNotificationMode?, + val changeNotificationSettingAction: Async, + val deleteCustomNotificationSettingAction: Async, val eventSink: (RoomNotificationSettingsEvents) -> Unit ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt index df1dd7977b..220d82f6b5 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt @@ -17,6 +17,7 @@ package io.element.android.features.roomdetails.impl.notificationsettings import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationSettings @@ -24,10 +25,13 @@ internal class RoomNotificationSettingsStateProvider : PreviewParameterProvider< override val values: Sequence get() = sequenceOf( RoomNotificationSettingsState( + roomName = "Room 1", RoomNotificationSettings( mode = RoomNotificationMode.MUTE, isDefault = true), RoomNotificationMode.ALL_MESSAGES, + changeNotificationSettingAction = Async.Uninitialized, + deleteCustomNotificationSettingAction = Async.Uninitialized, eventSink = { }, ), ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt index 5cf4adb84f..92bfe0c084 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt @@ -18,7 +18,6 @@ package io.element.android.features.roomdetails.impl.notificationsettings import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -30,8 +29,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.features.roomdetails.impl.R +import io.element.android.libraries.architecture.Async import io.element.android.libraries.core.bool.orTrue +import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch import io.element.android.libraries.designsystem.components.preferences.PreferenceText @@ -45,7 +47,6 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings -@OptIn(ExperimentalLayoutApi::class) @Composable fun RoomNotificationSettingsView( state: RoomNotificationSettingsState, @@ -74,7 +75,6 @@ fun RoomNotificationSettingsView( null -> "" } - PreferenceCategory(title = stringResource(id = R.string.screen_room_notification_settings_custom_settings_title)) { PreferenceSwitch( isChecked = state.roomNotificationSettings?.isDefault.orTrue(), @@ -102,6 +102,16 @@ fun RoomNotificationSettingsView( ) } } + + when (state.changeNotificationSettingAction) { + is Async.Loading -> { + ProgressDialog() + } + is Async.Failure -> { + ShowChangeNotificationSettingError(state) + } + else -> Unit + } } } } @@ -144,6 +154,15 @@ fun RoomNotificationSettingsOptions( } } +@Composable +fun ShowChangeNotificationSettingError(state: RoomNotificationSettingsState) { + ErrorDialog( + title = stringResource(CommonStrings.dialog_title_error), + content = stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode), + onDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearError) }, + ) +} + @DayNightPreviews @Composable internal fun RoomNotificationSettingsPreview( diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt new file mode 100644 index 0000000000..6435e64497 --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt @@ -0,0 +1,128 @@ +/* + * 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.roomdetails.impl.notificationsettings + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import io.element.android.features.roomdetails.impl.R +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.designsystem.VectorIcons +import io.element.android.libraries.designsystem.components.ProgressDialog +import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.preferences.PreferenceText +import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TopAppBar + +@Composable +fun UserDefinedRoomNotificationSettingsView( + state: RoomNotificationSettingsState, + modifier: Modifier = Modifier, + onBackPressed: () -> Unit = {}, +) { + Scaffold( + modifier = modifier, + topBar = { + UserDefinedRoomNotificationSettingsTopBar( + roomName = state.roomName, + onBackPressed = { onBackPressed() } + ) + } + ) { padding -> + Column( + modifier = Modifier + .fillMaxWidth() + .padding(padding) + .consumeWindowInsets(padding), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + if (state.roomNotificationSettings != null) { + RoomNotificationSettingsOptions( + selected = state.roomNotificationSettings.mode, + enabled = !state.roomNotificationSettings.isDefault, + onOptionSelected = { + state.eventSink(RoomNotificationSettingsEvents.RoomNotificationModeChanged(it.mode)) + }, + ) + } + + PreferenceText( + title = stringResource(R.string.screen_room_notification_settings_edit_remove_setting), + icon = ImageVector.vectorResource(VectorIcons.Delete), + tintColor = MaterialTheme.colorScheme.error, + onClick = { + state.eventSink(RoomNotificationSettingsEvents.DeleteCustomNotification) + } + ) + + when (state.changeNotificationSettingAction) { + is Async.Loading -> { + ProgressDialog() + } + is Async.Failure -> { + ShowChangeNotificationSettingError(state) + } + else -> Unit + } + + when (state.deleteCustomNotificationSettingAction) { + is Async.Loading -> { + ProgressDialog() + } + is Async.Failure -> { + ShowChangeNotificationSettingError(state) + } + is Async.Success -> { + LaunchedEffect(state.deleteCustomNotificationSettingAction) { + onBackPressed() + } + } + else -> Unit + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun UserDefinedRoomNotificationSettingsTopBar( + roomName: String, + modifier: Modifier = Modifier, + onBackPressed: () -> Unit = {}, +) { + TopAppBar( + modifier = modifier, + title = { + Text( + text = roomName, + ) + }, + navigationIcon = { BackButton(onClick = onBackPressed) }, + ) +} From 895a5332f26115d6635954cb1ba81fb00917959e Mon Sep 17 00:00:00 2001 From: David Langley Date: Tue, 17 Oct 2023 16:08:35 +0100 Subject: [PATCH 03/19] Add tests --- ...faultNotificationSettingsPresenterTests.kt | 48 +++++++++++++++-- .../RoomNotificationSettingsPresenterTests.kt | 51 ++++++++++++++++--- .../FakeNotificationSettingsService.kt | 13 ++++- 3 files changed, 100 insertions(+), 12 deletions(-) diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt index e8c9ff4fe5..8376aa95c3 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt @@ -23,7 +23,13 @@ import com.google.common.truth.Truth import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingPresenter import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingStateEvents import io.element.android.libraries.matrix.api.room.RoomNotificationMode +import io.element.android.libraries.matrix.api.roomlist.RoomSummary +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService +import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.aRoomSummaryDetail +import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.tests.testutils.consumeItemsUntilPredicate import kotlinx.coroutines.test.runTest import org.junit.Test @@ -32,7 +38,7 @@ class EditDefaultNotificationSettingsPresenterTests { @Test fun `present - ensures initial state is correct`() = runTest { val notificationSettingsService = FakeNotificationSettingsService() - val presenter = EditDefaultNotificationSettingPresenter(notificationSettingsService = notificationSettingsService, isOneToOne = false) + val presenter = createPresenter(notificationSettingsService) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -47,10 +53,32 @@ class EditDefaultNotificationSettingsPresenterTests { } } + @Test + fun `present - ensure list of rooms with user defined mode`() = runTest { + val room = FakeMatrixRoom() + val notificationSettingsService = FakeNotificationSettingsService( + initialRoomMode = RoomNotificationMode.ALL_MESSAGES, + initialRoomModeIsDefault = false + ) + val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService).apply { + givenGetRoomResult(A_ROOM_ID, room) + } + val roomListService = FakeRoomListService() + val presenter = createPresenter(notificationSettingsService, roomListService, matrixClient) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + roomListService.postAllRooms(listOf(RoomSummary.Filled(aRoomSummaryDetail(notificationMode = RoomNotificationMode.ALL_MESSAGES)))) + val loadedState = consumeItemsUntilPredicate { state -> + state.roomsWithUserDefinedMode.any { it.details.notificationMode == RoomNotificationMode.ALL_MESSAGES } + }.last() + Truth.assertThat(loadedState.roomsWithUserDefinedMode.any { it.details.notificationMode == RoomNotificationMode.ALL_MESSAGES }).isTrue() + } + } + @Test fun `present - edit default notification setting`() = runTest { - val notificationSettingsService = FakeNotificationSettingsService() - val presenter = EditDefaultNotificationSettingPresenter(notificationSettingsService = notificationSettingsService, isOneToOne = false) + val presenter = createPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -61,4 +89,18 @@ class EditDefaultNotificationSettingsPresenterTests { Truth.assertThat(loadedState.mode).isEqualTo(RoomNotificationMode.ALL_MESSAGES) } } + + private fun createPresenter( + notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(), + roomListService: FakeRoomListService = FakeRoomListService(), + matrixClient: FakeMatrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService) + ): EditDefaultNotificationSettingPresenter { + return EditDefaultNotificationSettingPresenter( + notificationSettingsService = notificationSettingsService, + isOneToOne = false, + roomListService = roomListService, + matrixClient = matrixClient + ) + } + } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt index a1b7831ce2..7d4c83d518 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt @@ -24,14 +24,17 @@ import io.element.android.features.roomdetails.aMatrixRoom import io.element.android.features.roomdetails.impl.notificationsettings.RoomNotificationSettingsEvents import io.element.android.features.roomdetails.impl.notificationsettings.RoomNotificationSettingsPresenter import io.element.android.libraries.matrix.api.room.RoomNotificationMode +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.tests.testutils.consumeItemsUntilPredicate import kotlinx.coroutines.test.runTest import org.junit.Test +import kotlin.time.Duration.Companion.milliseconds class RoomNotificationSettingsPresenterTests { @Test fun `present - initial state is created from room info`() = runTest { - val presenter = aNotificationPresenter + val presenter = aNotificationPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -44,7 +47,7 @@ class RoomNotificationSettingsPresenterTests { @Test fun `present - notification mode changed`() = runTest { - val presenter = aNotificationPresenter + val presenter = aNotificationPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -56,27 +59,61 @@ class RoomNotificationSettingsPresenterTests { } } + @Test + fun `present - observe notification mode changed`() = runTest { + val notificationSettingsService = FakeNotificationSettingsService() + val presenter = aNotificationPresenter(notificationSettingsService) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + notificationSettingsService.setRoomNotificationMode(A_ROOM_ID, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) + val updatedState = consumeItemsUntilPredicate() { + it.roomNotificationSettings?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY + }.last() + Truth.assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) + } + } + + + @Test + fun `present - notification settings set custom`() = runTest { + val notificationSettingsService = FakeNotificationSettingsService() + val presenter = aNotificationPresenter(notificationSettingsService) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(false)) + val defaultState = consumeItemsUntilPredicate(timeout = 8000.milliseconds) { + it.roomNotificationSettings?.isDefault == false + }.last() + Truth.assertThat(defaultState.roomNotificationSettings?.isDefault).isFalse() + } + } + @Test fun `present - notification settings restore default`() = runTest { - val presenter = aNotificationPresenter + val presenter = aNotificationPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() initialState.eventSink(RoomNotificationSettingsEvents.RoomNotificationModeChanged(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)) initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(true)) - val defaultState = consumeItemsUntilPredicate { + val defaultState = consumeItemsUntilPredicate(timeout = 2000.milliseconds) { it.roomNotificationSettings?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY }.last() Truth.assertThat(defaultState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) } } - private val aNotificationPresenter: RoomNotificationSettingsPresenter get() { - val room = aMatrixRoom() + private fun aNotificationPresenter( + notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService() + ): RoomNotificationSettingsPresenter{ + val room = aMatrixRoom(notificationSettingsService = notificationSettingsService) return RoomNotificationSettingsPresenter( room = room, - notificationSettingsService = room.notificationSettingsService + notificationSettingsService = notificationSettingsService ) } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt index 77592d6d1f..2b65857053 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt @@ -20,12 +20,14 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationSettings +import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_NOTIFICATION_MODE import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow class FakeNotificationSettingsService( initialRoomMode: RoomNotificationMode = A_ROOM_NOTIFICATION_MODE, + initialRoomModeIsDefault: Boolean = true, initialGroupDefaultMode: RoomNotificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, initialEncryptedGroupDefaultMode: RoomNotificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, initialOneToOneDefaultMode: RoomNotificationMode = RoomNotificationMode.ALL_MESSAGES, @@ -37,6 +39,7 @@ class FakeNotificationSettingsService( private var defaultOneToOneRoomNotificationMode: RoomNotificationMode = initialOneToOneDefaultMode private var defaultEncryptedOneToOneRoomNotificationMode: RoomNotificationMode = initialEncryptedOneToOneDefaultMode private var roomNotificationMode: RoomNotificationMode = initialRoomMode + private var roomNotificationModeIsDefault: Boolean = initialRoomModeIsDefault private var callNotificationsEnabled = false private var atRoomNotificationsEnabled = false override val notificationSettingsChangeFlow: SharedFlow @@ -45,8 +48,8 @@ class FakeNotificationSettingsService( override suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result { return Result.success( RoomNotificationSettings( - mode = roomNotificationMode, - isDefault = roomNotificationMode == defaultEncryptedGroupRoomNotificationMode + mode = if(roomNotificationModeIsDefault) defaultEncryptedGroupRoomNotificationMode else roomNotificationMode, + isDefault = roomNotificationModeIsDefault ) ) } @@ -86,12 +89,14 @@ class FakeNotificationSettingsService( } override suspend fun setRoomNotificationMode(roomId: RoomId, mode: RoomNotificationMode): Result { + roomNotificationModeIsDefault = false roomNotificationMode = mode _notificationSettingsStateFlow.emit(Unit) return Result.success(Unit) } override suspend fun restoreDefaultRoomNotificationMode(roomId: RoomId): Result { + roomNotificationModeIsDefault = true roomNotificationMode = defaultEncryptedGroupRoomNotificationMode _notificationSettingsStateFlow.emit(Unit) return Result.success(Unit) @@ -122,4 +127,8 @@ class FakeNotificationSettingsService( callNotificationsEnabled = enabled return Result.success(Unit) } + + override suspend fun getRoomsWithUserDefinedRules(): Result> { + return Result.success(if (roomNotificationModeIsDefault) listOf() else listOf(A_ROOM_ID.value)) + } } From 8d6ef153d90b4f938f1aa2769ebc33291067109c Mon Sep 17 00:00:00 2001 From: David Langley Date: Wed, 18 Oct 2023 21:44:37 +0100 Subject: [PATCH 04/19] Fix switch and radio buttons toggling to invalid intermediate states. --- .../RoomNotificationSettingsEvents.kt | 3 +- .../RoomNotificationSettingsPresenter.kt | 108 ++++++++++++++---- .../RoomNotificationSettingsState.kt | 16 ++- .../RoomNotificationSettingsStateProvider.kt | 12 +- .../RoomNotificationSettingsView.kt | 32 ++++-- ...UserDefinedRoomNotificationSettingsView.kt | 13 ++- 6 files changed, 134 insertions(+), 50 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsEvents.kt index c69896a98b..8b3c25d267 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsEvents.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsEvents.kt @@ -22,5 +22,6 @@ sealed interface RoomNotificationSettingsEvents { data class RoomNotificationModeChanged(val mode: RoomNotificationMode) : RoomNotificationSettingsEvents data class SetNotificationMode(val isDefault: Boolean): RoomNotificationSettingsEvents data object DeleteCustomNotification: RoomNotificationSettingsEvents - data object ClearError: RoomNotificationSettingsEvents + data object ClearSetNotificationError: RoomNotificationSettingsEvents + data object ClearRestoreDefaultError: RoomNotificationSettingsEvents } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt index 9fb0c8b1c8..3086d878c4 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt @@ -19,8 +19,6 @@ package io.element.android.features.roomdetails.impl.notificationsettings import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -31,7 +29,7 @@ import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomNotificationMode -import io.element.android.libraries.matrix.api.room.roomNotificationSettings +import io.element.android.libraries.matrix.api.room.RoomNotificationSettings import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.debounce @@ -51,76 +49,136 @@ class RoomNotificationSettingsPresenter @Inject constructor( mutableStateOf(null) } val localCoroutineScope = rememberCoroutineScope() - val changeNotificationSettingAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } - val deleteCustomNotificationSettingAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } + val setNotificationSettingAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } + val restoreDefaultAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } + + val roomNotificationSettings: MutableState> = remember { + mutableStateOf(Async.Uninitialized) + } + + // We store state of which mode the user has set via the notification service before the new push settings have been updated. + // We show this state immediately to the user and debounce updates to notification settings to hide some invalid states returned + // by the rust sdk during these two events that cause the radio buttons ot toggle quickly back and forth. + // This is a client side work-around until bulk push rule updates are supported. + // ref: https://github.com/matrix-org/matrix-spec-proposals/pull/3934 + val pendingRoomNotificationMode: MutableState = remember { + mutableStateOf(null) + } + + // We store state of whether the user has set the notifications settings to default or custom via the notification service. + // We show this state immediately to the user and debounce updates to notification settings to hide some invalid states returned + // by the rust sdk during these two events that cause the switch ot toggle quickly back and forth. + // This is a client side work-around until bulk push rule updates are supported. + // ref: https://github.com/matrix-org/matrix-spec-proposals/pull/3934 + val pendingSetDefault: MutableState = remember { + mutableStateOf(null) + } LaunchedEffect(Unit) { getDefaultRoomNotificationMode(defaultRoomNotificationMode) - room.updateRoomNotificationSettings() - observeNotificationSettings() + fetchNotificationSettings(pendingRoomNotificationMode, roomNotificationSettings) + observeNotificationSettings(pendingRoomNotificationMode, roomNotificationSettings) } - val roomNotificationSettingsState by room.roomNotificationSettingsStateFlow.collectAsState() - fun handleEvents(event: RoomNotificationSettingsEvents) { when (event) { is RoomNotificationSettingsEvents.RoomNotificationModeChanged -> { - localCoroutineScope.setRoomNotificationMode(event.mode, changeNotificationSettingAction) + localCoroutineScope.setRoomNotificationMode(event.mode, pendingRoomNotificationMode, pendingSetDefault, setNotificationSettingAction) } is RoomNotificationSettingsEvents.SetNotificationMode -> { if (event.isDefault) { - localCoroutineScope.restoreDefaultRoomNotificationMode(changeNotificationSettingAction) + localCoroutineScope.restoreDefaultRoomNotificationMode(restoreDefaultAction, pendingSetDefault) } else { defaultRoomNotificationMode.value?.let { - localCoroutineScope.setRoomNotificationMode(it, changeNotificationSettingAction) + localCoroutineScope.setRoomNotificationMode(it, pendingRoomNotificationMode, pendingSetDefault, setNotificationSettingAction) } } } is RoomNotificationSettingsEvents.DeleteCustomNotification -> { - localCoroutineScope.restoreDefaultRoomNotificationMode(deleteCustomNotificationSettingAction) + localCoroutineScope.restoreDefaultRoomNotificationMode(restoreDefaultAction, pendingSetDefault) } - RoomNotificationSettingsEvents.ClearError -> { - changeNotificationSettingAction.value = Async.Uninitialized + RoomNotificationSettingsEvents.ClearSetNotificationError -> { + setNotificationSettingAction.value = Async.Uninitialized + } + RoomNotificationSettingsEvents.ClearRestoreDefaultError -> { + restoreDefaultAction.value = Async.Uninitialized } } } return RoomNotificationSettingsState( roomName = room.displayName, - roomNotificationSettings = roomNotificationSettingsState.roomNotificationSettings(), + roomNotificationSettings = roomNotificationSettings.value, + pendingRoomNotificationMode = pendingRoomNotificationMode.value, + pendingSetDefault = pendingSetDefault.value, defaultRoomNotificationMode = defaultRoomNotificationMode.value, - changeNotificationSettingAction = changeNotificationSettingAction.value, - deleteCustomNotificationSettingAction = deleteCustomNotificationSettingAction.value, + setNotificationSettingAction = setNotificationSettingAction.value, + restoreDefaultAction = restoreDefaultAction.value, eventSink = ::handleEvents, ) } @OptIn(FlowPreview::class) - private fun CoroutineScope.observeNotificationSettings() { + private fun CoroutineScope.observeNotificationSettings( + pendingModeState: MutableState, + roomNotificationSettings: MutableState> + ) { notificationSettingsService.notificationSettingsChangeFlow .debounce(0.5.seconds) .onEach { - room.updateRoomNotificationSettings() + fetchNotificationSettings(pendingModeState, roomNotificationSettings) } .launchIn(this) } - private fun CoroutineScope.getDefaultRoomNotificationMode(defaultRoomNotificationMode: MutableState) = launch { + private fun CoroutineScope.fetchNotificationSettings( + pendingModeState: MutableState, + roomNotificationSettings: MutableState> + ) = launch { + suspend { + pendingModeState.value = null + notificationSettingsService.getRoomNotificationSettings(room.roomId, room.isEncrypted, room.isOneToOne).getOrThrow() + }.runCatchingUpdatingState(roomNotificationSettings) + } + + private fun CoroutineScope.getDefaultRoomNotificationMode( + defaultRoomNotificationMode: MutableState + ) = launch { defaultRoomNotificationMode.value = notificationSettingsService.getDefaultRoomNotificationMode( room.isEncrypted, room.isOneToOne ).getOrThrow() } - private fun CoroutineScope.setRoomNotificationMode(mode: RoomNotificationMode, action: MutableState>) = launch { + private fun CoroutineScope.setRoomNotificationMode( + mode: RoomNotificationMode, + pendingModeState: MutableState, + pendingDefaultState: MutableState, + action: MutableState> + ) = launch { suspend { - notificationSettingsService.setRoomNotificationMode(room.roomId, mode).getOrThrow() + pendingModeState.value = mode + pendingDefaultState.value = false + val result = notificationSettingsService.setRoomNotificationMode(room.roomId, mode) + if (result.isFailure) { + pendingModeState.value = null + pendingDefaultState.value = null + } + result.getOrThrow() }.runCatchingUpdatingState(action) } - private fun CoroutineScope.restoreDefaultRoomNotificationMode(action: MutableState>) = launch { + private fun CoroutineScope.restoreDefaultRoomNotificationMode( + action: MutableState>, + pendingDefaultState: MutableState + ) = launch { suspend { - notificationSettingsService.restoreDefaultRoomNotificationMode(room.roomId).getOrThrow() + pendingDefaultState.value = true + val result = notificationSettingsService.restoreDefaultRoomNotificationMode(room.roomId) + if (result.isFailure) { + pendingDefaultState.value = null + } + result.getOrThrow() }.runCatchingUpdatingState(action) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt index a7c5c3b883..1f8c7e4ce8 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt @@ -22,9 +22,19 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationSettings data class RoomNotificationSettingsState( val roomName: String, - val roomNotificationSettings: RoomNotificationSettings?, + val roomNotificationSettings: Async, + val pendingRoomNotificationMode: RoomNotificationMode?, + val pendingSetDefault: Boolean?, val defaultRoomNotificationMode: RoomNotificationMode?, - val changeNotificationSettingAction: Async, - val deleteCustomNotificationSettingAction: Async, + val setNotificationSettingAction: Async, + val restoreDefaultAction: Async, val eventSink: (RoomNotificationSettingsEvents) -> Unit ) + +val RoomNotificationSettingsState.displayNotificationMode: RoomNotificationMode? get() { + return pendingRoomNotificationMode ?: roomNotificationSettings.dataOrNull()?.mode +} + +val RoomNotificationSettingsState.displayIsDefault: Boolean? get() { + return pendingSetDefault ?: roomNotificationSettings.dataOrNull()?.isDefault +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt index 220d82f6b5..961909f933 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt @@ -26,12 +26,14 @@ internal class RoomNotificationSettingsStateProvider : PreviewParameterProvider< get() = sequenceOf( RoomNotificationSettingsState( roomName = "Room 1", - RoomNotificationSettings( + Async.Success(RoomNotificationSettings( mode = RoomNotificationMode.MUTE, - isDefault = true), - RoomNotificationMode.ALL_MESSAGES, - changeNotificationSettingAction = Async.Uninitialized, - deleteCustomNotificationSettingAction = Async.Uninitialized, + isDefault = true)), + pendingRoomNotificationMode = null, + pendingSetDefault = null, + defaultRoomNotificationMode = RoomNotificationMode.ALL_MESSAGES, + setNotificationSettingAction = Async.Uninitialized, + restoreDefaultAction = Async.Uninitialized, eventSink = { }, ), ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt index 92bfe0c084..6f440958b2 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt @@ -75,27 +75,29 @@ fun RoomNotificationSettingsView( null -> "" } + val roomNotificationSettings = state.roomNotificationSettings.dataOrNull() + PreferenceCategory(title = stringResource(id = R.string.screen_room_notification_settings_custom_settings_title)) { PreferenceSwitch( - isChecked = state.roomNotificationSettings?.isDefault.orTrue(), + isChecked = state.displayIsDefault.orTrue(), onCheckedChange = { state.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(it)) }, title = "Match default setting", subtitle = subtitle, - enabled = state.roomNotificationSettings != null + enabled = roomNotificationSettings != null ) PreferenceText( title = stringResource(id = R.string.screen_room_notification_settings_allow_custom), subtitle = stringResource(id = R.string.screen_room_notification_settings_allow_custom_footnote), - enabled = state.roomNotificationSettings != null && !state.roomNotificationSettings.isDefault, + enabled = !state.displayIsDefault.orTrue(), ) - if (state.roomNotificationSettings != null) { + if (roomNotificationSettings != null && state.displayNotificationMode != null) { RoomNotificationSettingsOptions( - selected = state.roomNotificationSettings.mode, - enabled = !state.roomNotificationSettings.isDefault, + selected = state.displayNotificationMode, + enabled = !state.displayIsDefault.orTrue(), onOptionSelected = { state.eventSink(RoomNotificationSettingsEvents.RoomNotificationModeChanged(it.mode)) }, @@ -103,12 +105,22 @@ fun RoomNotificationSettingsView( } } - when (state.changeNotificationSettingAction) { + when (state.setNotificationSettingAction) { is Async.Loading -> { ProgressDialog() } is Async.Failure -> { - ShowChangeNotificationSettingError(state) + ShowChangeNotificationSettingError(state, RoomNotificationSettingsEvents.ClearSetNotificationError) + } + else -> Unit + } + + when (state.restoreDefaultAction) { + is Async.Loading -> { + ProgressDialog() + } + is Async.Failure -> { + ShowChangeNotificationSettingError(state, RoomNotificationSettingsEvents.ClearRestoreDefaultError) } else -> Unit } @@ -155,11 +167,11 @@ fun RoomNotificationSettingsOptions( } @Composable -fun ShowChangeNotificationSettingError(state: RoomNotificationSettingsState) { +fun ShowChangeNotificationSettingError(state: RoomNotificationSettingsState, event: RoomNotificationSettingsEvents) { ErrorDialog( title = stringResource(CommonStrings.dialog_title_error), content = stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode), - onDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearError) }, + onDismiss = { state.eventSink(event) }, ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt index 6435e64497..aa8a2a739d 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt @@ -62,10 +62,11 @@ fun UserDefinedRoomNotificationSettingsView( .consumeWindowInsets(padding), verticalArrangement = Arrangement.spacedBy(16.dp), ) { - if (state.roomNotificationSettings != null) { + val roomNotificationSettings = state.roomNotificationSettings.dataOrNull() + if (roomNotificationSettings != null && state.displayNotificationMode != null) { RoomNotificationSettingsOptions( - selected = state.roomNotificationSettings.mode, - enabled = !state.roomNotificationSettings.isDefault, + selected = state.displayNotificationMode, + enabled = roomNotificationSettings.isDefault, onOptionSelected = { state.eventSink(RoomNotificationSettingsEvents.RoomNotificationModeChanged(it.mode)) }, @@ -81,7 +82,7 @@ fun UserDefinedRoomNotificationSettingsView( } ) - when (state.changeNotificationSettingAction) { + when (state.setNotificationSettingAction) { is Async.Loading -> { ProgressDialog() } @@ -91,7 +92,7 @@ fun UserDefinedRoomNotificationSettingsView( else -> Unit } - when (state.deleteCustomNotificationSettingAction) { + when (state.restoreDefaultAction) { is Async.Loading -> { ProgressDialog() } @@ -99,7 +100,7 @@ fun UserDefinedRoomNotificationSettingsView( ShowChangeNotificationSettingError(state) } is Async.Success -> { - LaunchedEffect(state.deleteCustomNotificationSettingAction) { + LaunchedEffect(state.restoreDefaultAction) { onBackPressed() } } From b5ca65ed0f5d60a839d70a9ba45c08287b48f040 Mon Sep 17 00:00:00 2001 From: David Langley Date: Wed, 18 Oct 2023 22:30:29 +0100 Subject: [PATCH 05/19] Fix enabled state and ClearError events. --- .../UserDefinedRoomNotificationSettingsView.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt index aa8a2a739d..75642f4e15 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt @@ -32,13 +32,14 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import io.element.android.features.roomdetails.impl.R import io.element.android.libraries.architecture.Async -import io.element.android.libraries.designsystem.VectorIcons +import io.element.android.libraries.core.bool.orTrue import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.preferences.PreferenceText import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.designsystem.utils.CommonDrawables @Composable fun UserDefinedRoomNotificationSettingsView( @@ -66,7 +67,7 @@ fun UserDefinedRoomNotificationSettingsView( if (roomNotificationSettings != null && state.displayNotificationMode != null) { RoomNotificationSettingsOptions( selected = state.displayNotificationMode, - enabled = roomNotificationSettings.isDefault, + enabled = !state.displayIsDefault.orTrue(), onOptionSelected = { state.eventSink(RoomNotificationSettingsEvents.RoomNotificationModeChanged(it.mode)) }, @@ -75,7 +76,7 @@ fun UserDefinedRoomNotificationSettingsView( PreferenceText( title = stringResource(R.string.screen_room_notification_settings_edit_remove_setting), - icon = ImageVector.vectorResource(VectorIcons.Delete), + icon = ImageVector.vectorResource(CommonDrawables.ic_compound_delete), tintColor = MaterialTheme.colorScheme.error, onClick = { state.eventSink(RoomNotificationSettingsEvents.DeleteCustomNotification) @@ -87,7 +88,7 @@ fun UserDefinedRoomNotificationSettingsView( ProgressDialog() } is Async.Failure -> { - ShowChangeNotificationSettingError(state) + ShowChangeNotificationSettingError(state, RoomNotificationSettingsEvents.ClearSetNotificationError) } else -> Unit } @@ -97,7 +98,7 @@ fun UserDefinedRoomNotificationSettingsView( ProgressDialog() } is Async.Failure -> { - ShowChangeNotificationSettingError(state) + ShowChangeNotificationSettingError(state, RoomNotificationSettingsEvents.ClearRestoreDefaultError) } is Async.Success -> { LaunchedEffect(state.restoreDefaultAction) { From 7505ac8edac1c8452efdce70747621f610dd2189 Mon Sep 17 00:00:00 2001 From: David Langley Date: Thu, 19 Oct 2023 16:17:57 +0100 Subject: [PATCH 06/19] Fix tests and lint --- ...efaultNotificationSettingStateProvider.kt} | 2 +- .../EditDefaultNotificationSettingView.kt | 2 +- ...faultNotificationSettingsPresenterTests.kt | 8 +-- .../RoomNotificationSettingsOptions.kt | 43 ++++++++++++++ .../RoomNotificationSettingsView.kt | 33 +---------- .../ShowChangeNotificationSettingError.kt | 31 ++++++++++ ...edRoomNotificationSettingsStateProvider.kt | 42 +++++++++++++ ...UserDefinedRoomNotificationSettingsView.kt | 13 +++- .../RoomNotificationSettingsPresenterTests.kt | 59 +++++++++++++------ .../FakeNotificationSettingsService.kt | 18 ++++-- 10 files changed, 190 insertions(+), 61 deletions(-) rename features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/{EditDefaultNotificationSettingsStateProvider.kt => EditDefaultNotificationSettingStateProvider.kt} (93%) create mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsOptions.kt create mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/ShowChangeNotificationSettingError.kt create mode 100644 features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsStateProvider.kt diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt similarity index 93% rename from features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingsStateProvider.kt rename to features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt index 6910b581bb..3446c24efe 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt @@ -23,7 +23,7 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails -open class EditDefaultNotificationSettingsStateProvider: PreviewParameterProvider { +open class EditDefaultNotificationSettingStateProvider: PreviewParameterProvider { override val values: Sequence get() = sequenceOf( anEditDefaultNotificationSettingsState(), diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt index bf82de7251..94f5a6b053 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt @@ -136,7 +136,7 @@ fun EditDefaultNotificationSettingView( @PreviewsDayNight @Composable internal fun EditDefaultNotificationSettingViewPreview( - @PreviewParameter(EditDefaultNotificationSettingsStateProvider::class) state: EditDefaultNotificationSettingState + @PreviewParameter(EditDefaultNotificationSettingStateProvider::class) state: EditDefaultNotificationSettingState ) = ElementPreview { EditDefaultNotificationSettingView( state = state, diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt index 8376aa95c3..2269df46e1 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt @@ -38,7 +38,7 @@ class EditDefaultNotificationSettingsPresenterTests { @Test fun `present - ensures initial state is correct`() = runTest { val notificationSettingsService = FakeNotificationSettingsService() - val presenter = createPresenter(notificationSettingsService) + val presenter = createEditDefaultNotificationSettingPresenter(notificationSettingsService) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -64,7 +64,7 @@ class EditDefaultNotificationSettingsPresenterTests { givenGetRoomResult(A_ROOM_ID, room) } val roomListService = FakeRoomListService() - val presenter = createPresenter(notificationSettingsService, roomListService, matrixClient) + val presenter = createEditDefaultNotificationSettingPresenter(notificationSettingsService, roomListService, matrixClient) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -78,7 +78,7 @@ class EditDefaultNotificationSettingsPresenterTests { @Test fun `present - edit default notification setting`() = runTest { - val presenter = createPresenter() + val presenter = createEditDefaultNotificationSettingPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -90,7 +90,7 @@ class EditDefaultNotificationSettingsPresenterTests { } } - private fun createPresenter( + private fun createEditDefaultNotificationSettingPresenter( notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(), roomListService: FakeRoomListService = FakeRoomListService(), matrixClient: FakeMatrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsOptions.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsOptions.kt new file mode 100644 index 0000000000..878632db9c --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsOptions.kt @@ -0,0 +1,43 @@ +/* + * 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.roomdetails.impl.notificationsettings + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.selection.selectableGroup +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import io.element.android.libraries.matrix.api.room.RoomNotificationMode + +@Composable +fun RoomNotificationSettingsOptions( + selected: RoomNotificationMode?, + enabled: Boolean, + modifier: Modifier = Modifier, + onOptionSelected: (RoomNotificationSettingsItem) -> Unit = {}, +) { + val items = roomNotificationSettingsItems() + Column(modifier = modifier.selectableGroup()) { + items.forEach { item -> + RoomNotificationSettingsOption( + roomNotificationSettingsItem = item, + isSelected = selected == item.mode, + onOptionSelected = onOptionSelected, + enabled = enabled + ) + } + } +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt index b0c82d291c..3d95e4ec20 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt @@ -21,7 +21,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.selection.selectableGroup import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -33,12 +32,11 @@ import io.element.android.libraries.architecture.Async import io.element.android.libraries.core.bool.orTrue import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.button.BackButton -import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch import io.element.android.libraries.designsystem.components.preferences.PreferenceText -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.aliasScreenTitle import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text @@ -146,35 +144,6 @@ private fun RoomNotificationSettingsTopBar( ) } -@Composable -fun RoomNotificationSettingsOptions( - selected: RoomNotificationMode?, - enabled: Boolean, - modifier: Modifier = Modifier, - onOptionSelected: (RoomNotificationSettingsItem) -> Unit = {}, -) { - val items = roomNotificationSettingsItems() - Column(modifier = modifier.selectableGroup()) { - items.forEach { item -> - RoomNotificationSettingsOption( - roomNotificationSettingsItem = item, - isSelected = selected == item.mode, - onOptionSelected = onOptionSelected, - enabled = enabled - ) - } - } -} - -@Composable -fun ShowChangeNotificationSettingError(state: RoomNotificationSettingsState, event: RoomNotificationSettingsEvents) { - ErrorDialog( - title = stringResource(CommonStrings.dialog_title_error), - content = stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode), - onDismiss = { state.eventSink(event) }, - ) -} - @PreviewsDayNight @Composable internal fun RoomNotificationSettingsPreview( diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/ShowChangeNotificationSettingError.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/ShowChangeNotificationSettingError.kt new file mode 100644 index 0000000000..4b99976988 --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/ShowChangeNotificationSettingError.kt @@ -0,0 +1,31 @@ +/* + * 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.roomdetails.impl.notificationsettings + +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun ShowChangeNotificationSettingError(state: RoomNotificationSettingsState, event: RoomNotificationSettingsEvents) { + ErrorDialog( + title = stringResource(CommonStrings.dialog_title_error), + content = stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode), + onDismiss = { state.eventSink(event) }, + ) +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsStateProvider.kt new file mode 100644 index 0000000000..76714a82d0 --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsStateProvider.kt @@ -0,0 +1,42 @@ +/* + * 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.roomdetails.impl.notificationsettings + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.room.RoomNotificationMode +import io.element.android.libraries.matrix.api.room.RoomNotificationSettings + +internal class UserDefinedRoomNotificationSettingsStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + RoomNotificationSettingsState( + roomName = "Room 1", + Async.Success( + RoomNotificationSettings( + mode = RoomNotificationMode.MUTE, + isDefault = false) + ), + pendingRoomNotificationMode = null, + pendingSetDefault = null, + defaultRoomNotificationMode = RoomNotificationMode.ALL_MESSAGES, + setNotificationSettingAction = Async.Uninitialized, + restoreDefaultAction = Async.Uninitialized, + eventSink = { }, + ), + ) +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt index 75642f4e15..6afde5bbf9 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.features.roomdetails.impl.R import io.element.android.libraries.architecture.Async @@ -36,6 +37,8 @@ import io.element.android.libraries.core.bool.orTrue import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.preferences.PreferenceText +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar @@ -113,7 +116,7 @@ fun UserDefinedRoomNotificationSettingsView( @OptIn(ExperimentalMaterial3Api::class) @Composable -fun UserDefinedRoomNotificationSettingsTopBar( +private fun UserDefinedRoomNotificationSettingsTopBar( roomName: String, modifier: Modifier = Modifier, onBackPressed: () -> Unit = {}, @@ -128,3 +131,11 @@ fun UserDefinedRoomNotificationSettingsTopBar( navigationIcon = { BackButton(onClick = onBackPressed) }, ) } + +@PreviewsDayNight +@Composable +internal fun UserDefinedRoomNotificationSettingsPreview( + @PreviewParameter(UserDefinedRoomNotificationSettingsStateProvider::class) state: RoomNotificationSettingsState +) = ElementPreview { + UserDefinedRoomNotificationSettingsView(state) +} diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt index 7d4c83d518..7f4ea57cf1 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt @@ -25,6 +25,7 @@ import io.element.android.features.roomdetails.impl.notificationsettings.RoomNot import io.element.android.features.roomdetails.impl.notificationsettings.RoomNotificationSettingsPresenter import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.tests.testutils.consumeItemsUntilPredicate import kotlinx.coroutines.test.runTest @@ -34,12 +35,12 @@ import kotlin.time.Duration.Companion.milliseconds class RoomNotificationSettingsPresenterTests { @Test fun `present - initial state is created from room info`() = runTest { - val presenter = aNotificationPresenter() + val presenter = createRoomNotificationSettingsPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() - Truth.assertThat(initialState.roomNotificationSettings).isNull() + Truth.assertThat(initialState.roomNotificationSettings.dataOrNull()).isNull() Truth.assertThat(initialState.defaultRoomNotificationMode).isNull() cancelAndIgnoreRemainingEvents() } @@ -47,53 +48,74 @@ class RoomNotificationSettingsPresenterTests { @Test fun `present - notification mode changed`() = runTest { - val presenter = aNotificationPresenter() + val presenter = createRoomNotificationSettingsPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { awaitItem().eventSink(RoomNotificationSettingsEvents.RoomNotificationModeChanged(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)) val updatedState = consumeItemsUntilPredicate { - it.roomNotificationSettings?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY + it.roomNotificationSettings.dataOrNull()?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY }.last() - Truth.assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) + Truth.assertThat(updatedState.roomNotificationSettings.dataOrNull()?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) + cancelAndIgnoreRemainingEvents() } } @Test fun `present - observe notification mode changed`() = runTest { val notificationSettingsService = FakeNotificationSettingsService() - val presenter = aNotificationPresenter(notificationSettingsService) + val presenter = createRoomNotificationSettingsPresenter(notificationSettingsService) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { notificationSettingsService.setRoomNotificationMode(A_ROOM_ID, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) - val updatedState = consumeItemsUntilPredicate() { - it.roomNotificationSettings?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY + val updatedState = consumeItemsUntilPredicate { + it.roomNotificationSettings.dataOrNull()?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY }.last() - Truth.assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) + Truth.assertThat(updatedState.roomNotificationSettings.dataOrNull()?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) } } @Test - fun `present - notification settings set custom`() = runTest { + fun `present - notification settings set custom failed`() = runTest { val notificationSettingsService = FakeNotificationSettingsService() - val presenter = aNotificationPresenter(notificationSettingsService) + notificationSettingsService.givenSetNotificationModeError(A_THROWABLE) + val presenter = createRoomNotificationSettingsPresenter(notificationSettingsService) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(false)) - val defaultState = consumeItemsUntilPredicate(timeout = 8000.milliseconds) { - it.roomNotificationSettings?.isDefault == false + val states = consumeItemsUntilPredicate { + it.roomNotificationSettings.dataOrNull()?.isDefault == false + } + states.forEach { + Truth.assertThat(it.roomNotificationSettings.dataOrNull()?.isDefault).isTrue() + Truth.assertThat(it.pendingSetDefault).isNull() + } + } + } + + @Test + fun `present - notification settings set custom`() = runTest { + val notificationSettingsService = FakeNotificationSettingsService() + val presenter = createRoomNotificationSettingsPresenter(notificationSettingsService) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(false)) + val defaultState = consumeItemsUntilPredicate { + it.roomNotificationSettings.dataOrNull()?.isDefault == false }.last() - Truth.assertThat(defaultState.roomNotificationSettings?.isDefault).isFalse() + Truth.assertThat(defaultState.roomNotificationSettings.dataOrNull()?.isDefault).isFalse() } } @Test fun `present - notification settings restore default`() = runTest { - val presenter = aNotificationPresenter() + val presenter = createRoomNotificationSettingsPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -101,13 +123,14 @@ class RoomNotificationSettingsPresenterTests { initialState.eventSink(RoomNotificationSettingsEvents.RoomNotificationModeChanged(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)) initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(true)) val defaultState = consumeItemsUntilPredicate(timeout = 2000.milliseconds) { - it.roomNotificationSettings?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY + it.roomNotificationSettings.dataOrNull()?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY }.last() - Truth.assertThat(defaultState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) + Truth.assertThat(defaultState.roomNotificationSettings.dataOrNull()?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) + cancelAndIgnoreRemainingEvents() } } - private fun aNotificationPresenter( + private fun createRoomNotificationSettingsPresenter( notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService() ): RoomNotificationSettingsPresenter{ val room = aMatrixRoom(notificationSettingsService = notificationSettingsService) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt index 2b65857053..7d7fb1a36a 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt @@ -42,6 +42,7 @@ class FakeNotificationSettingsService( private var roomNotificationModeIsDefault: Boolean = initialRoomModeIsDefault private var callNotificationsEnabled = false private var atRoomNotificationsEnabled = false + private var setNotificationModeError: Throwable? = null override val notificationSettingsChangeFlow: SharedFlow get() = _notificationSettingsStateFlow @@ -89,10 +90,15 @@ class FakeNotificationSettingsService( } override suspend fun setRoomNotificationMode(roomId: RoomId, mode: RoomNotificationMode): Result { - roomNotificationModeIsDefault = false - roomNotificationMode = mode - _notificationSettingsStateFlow.emit(Unit) - return Result.success(Unit) + val error = setNotificationModeError + return if (error != null) { + Result.failure(error) + } else { + roomNotificationModeIsDefault = false + roomNotificationMode = mode + _notificationSettingsStateFlow.emit(Unit) + Result.success(Unit) + } } override suspend fun restoreDefaultRoomNotificationMode(roomId: RoomId): Result { @@ -131,4 +137,8 @@ class FakeNotificationSettingsService( override suspend fun getRoomsWithUserDefinedRules(): Result> { return Result.success(if (roomNotificationModeIsDefault) listOf() else listOf(A_ROOM_ID.value)) } + + fun givenSetNotificationModeError(throwable: Throwable?) { + setNotificationModeError = throwable + } } From aa78255b4bfde9d0045d8acef2a491f9ca0533ef Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 19 Oct 2023 15:47:25 +0000 Subject: [PATCH 07/19] Update screenshots --- ...ultNotificationSettingView-D-8_8_null_0,NEXUS_5,1.0,en].png | 3 +++ ...ultNotificationSettingView-N-8_9_null_0,NEXUS_5,1.0,en].png | 3 +++ ...ull_EditUserProfileView-D-10_10_null_0,NEXUS_5,1.0,en].png} | 0 ...ull_EditUserProfileView-N-10_11_null_0,NEXUS_5,1.0,en].png} | 0 ...user_null_UserPreferences-D-9_9_null_0,NEXUS_5,1.0,en].png} | 0 ...user_null_UserPreferences-D-9_9_null_1,NEXUS_5,1.0,en].png} | 0 ...user_null_UserPreferences-D-9_9_null_2,NEXUS_5,1.0,en].png} | 0 ...ser_null_UserPreferences-N-9_10_null_0,NEXUS_5,1.0,en].png} | 0 ...ser_null_UserPreferences-N-9_10_null_1,NEXUS_5,1.0,en].png} | 0 ...ser_null_UserPreferences-N-9_10_null_2,NEXUS_5,1.0,en].png} | 0 ...edRoomNotificationSettings-D-5_5_null_0,NEXUS_5,1.0,en].png | 3 +++ ...edRoomNotificationSettings-N-5_6_null_0,NEXUS_5,1.0,en].png | 3 +++ ...ts.avatar_null_Avatars_Avatar_0_null_48,NEXUS_5,1.0,en].png | 3 +++ ...ts.avatar_null_Avatars_Avatar_0_null_49,NEXUS_5,1.0,en].png | 3 +++ ...ts.avatar_null_Avatars_Avatar_0_null_50,NEXUS_5,1.0,en].png | 3 +++ 15 files changed, 21 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_0,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.user.editprofile_null_EditUserProfileView-D-9_9_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.user.editprofile_null_EditUserProfileView-D-10_10_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.user.editprofile_null_EditUserProfileView-N-9_10_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.user.editprofile_null_EditUserProfileView-N-10_11_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.user_null_UserPreferences-D-8_8_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.user_null_UserPreferences-D-9_9_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.user_null_UserPreferences-D-8_8_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.user_null_UserPreferences-D-9_9_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.user_null_UserPreferences-D-8_8_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.user_null_UserPreferences-D-9_9_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.user_null_UserPreferences-N-8_9_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.user_null_UserPreferences-N-9_10_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.user_null_UserPreferences-N-8_9_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.user_null_UserPreferences-N-9_10_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.user_null_UserPreferences-N-8_9_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.user_null_UserPreferences-N-9_10_null_2,NEXUS_5,1.0,en].png} (100%) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_UserDefinedRoomNotificationSettings-D-5_5_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_UserDefinedRoomNotificationSettings-N-5_6_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_48,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_49,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_50,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..1d4750eecb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1172d81259b7e7b694f428c5144d96c8a7748134f5b4f515a5c25c58ff8e5e1b +size 31318 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c423fe4456 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e835ab12c0a77c4a4dcaf8bac0d78ed5ec9a0ab8eef669c7d814317ab06abd27 +size 28443 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_null_EditUserProfileView-D-9_9_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_null_EditUserProfileView-D-10_10_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.user.editprofile_null_EditUserProfileView-D-9_9_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_null_EditUserProfileView-D-10_10_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_null_EditUserProfileView-N-9_10_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_null_EditUserProfileView-N-10_11_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.user.editprofile_null_EditUserProfileView-N-9_10_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_null_EditUserProfileView-N-10_11_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-D-8_8_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-D-9_9_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.user_null_UserPreferences-D-8_8_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-D-9_9_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-D-8_8_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-D-9_9_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-D-8_8_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-D-9_9_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-D-8_8_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-D-9_9_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-D-8_8_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-D-9_9_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-N-8_9_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-N-9_10_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.user_null_UserPreferences-N-8_9_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-N-9_10_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-N-8_9_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-N-9_10_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-N-8_9_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-N-9_10_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-N-8_9_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-N-9_10_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-N-8_9_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_null_UserPreferences-N-9_10_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_UserDefinedRoomNotificationSettings-D-5_5_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_UserDefinedRoomNotificationSettings-D-5_5_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..d26299f528 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_UserDefinedRoomNotificationSettings-D-5_5_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:372ff5eca3c8c9de6bb15d5ae9a55f1d8ec124c7e454d411be6bd28f78d3aed0 +size 24324 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_UserDefinedRoomNotificationSettings-N-5_6_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_UserDefinedRoomNotificationSettings-N-5_6_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f8beb00ae9 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_UserDefinedRoomNotificationSettings-N-5_6_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3795832f75ba48402f228dbd5be5dc05b10dab00242922fb861c0f77fcd391e4 +size 22853 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_48,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_48,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a3d0076294 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_48,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2619a9a70eca12d5931ebc21adce48a1aed8383f35908bb09546f44b40f04543 +size 23094 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_49,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_49,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..81b9668b0f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_49,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d7d71d58b250bdec2d9b6e1ed46c5e3ffd98ffeefcaf4267b9979970a89750b +size 22226 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_50,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_50,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..5cb8c0a0bf --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_null_Avatars_Avatar_0_null_50,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7bdea3caef3f1be9fac1fbb5511fe2d76b7985576c038f8c1d920615c3d49cd +size 25005 From f1cf9b6e310f923effa8138f3d659dd7459d1cc7 Mon Sep 17 00:00:00 2001 From: David Langley Date: Thu, 19 Oct 2023 16:57:35 +0100 Subject: [PATCH 08/19] Fix typo and preview --- .../edit/EditDefaultNotificationSettingStateProvider.kt | 1 + .../android/features/roomdetails/impl/RoomDetailsFlowNode.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt index 3446c24efe..2b1236f4af 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt @@ -47,5 +47,6 @@ private fun aRoomSummary() = RoomSummary.Filled( lastMessage = null, lastMessageTimestamp = null, unreadNotificationCount = 0, + notificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, ) ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index 5d7539e626..6945ebb0ca 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -70,7 +70,7 @@ class RoomDetailsFlowNode @AssistedInject constructor( @Parcelize data class RoomNotificationSettings( /** - * When presented from oursite the context of the room, the rooms settings UI is different. + * When presented from outsite the context of the room, the rooms settings UI is different. * Figma designs: https://www.figma.com/file/0MMNu7cTOzLOlWb7ctTkv3/Element-X?type=design&node-id=5199-198932&mode=design&t=fTTvpuxYFjewYQOe-0 */ val showUserDefinedSettingStyle: Boolean From 85ae33a0fde209271a73cb478a731158e5903c84 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 19 Oct 2023 16:08:50 +0000 Subject: [PATCH 09/19] Update screenshots --- ...ltNotificationSettingView-D-8_8_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...ltNotificationSettingView-N-8_9_null_0,NEXUS_5,1.0,en].png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_0,NEXUS_5,1.0,en].png index 1d4750eecb..7e1e8e88a0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1172d81259b7e7b694f428c5144d96c8a7748134f5b4f515a5c25c58ff8e5e1b -size 31318 +oid sha256:11aa649bb8e25975c79ebc8c20aa82153acac75b4087f99485c58bd604ba3f33 +size 35947 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_0,NEXUS_5,1.0,en].png index c423fe4456..6e3219983b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e835ab12c0a77c4a4dcaf8bac0d78ed5ec9a0ab8eef669c7d814317ab06abd27 -size 28443 +oid sha256:1df0f8620db5a3f751e37543f34785517703e4731af9e624dc50b91de79f46e9 +size 33107 From 5adcaef5694c725355f3228b68ec4dfb456b344b Mon Sep 17 00:00:00 2001 From: David Langley Date: Fri, 20 Oct 2023 21:08:44 +0100 Subject: [PATCH 10/19] Add new ui for room notifications screen - Add new ui for room notifications screen - Fix error when changing default notification setting. --- .../android/appnav/LoggedInFlowNode.kt | 16 +++- .../android/appnav/room/RoomLoadedFlowNode.kt | 22 +++-- .../android/appnav/RoomFlowNodeTest.kt | 25 ++++-- features/preferences/api/build.gradle.kts | 1 + .../preferences/api/PreferencesEntryPoint.kt | 13 +++ .../impl/DefaultPreferencesEntryPoint.kt | 10 +++ .../preferences/impl/PreferencesFlowNode.kt | 2 +- ...EditDefaultNotificationSettingPresenter.kt | 8 +- .../roomdetails/api/RoomDetailsEntryPoint.kt | 14 ++- features/roomdetails/impl/build.gradle.kts | 1 + .../impl/DefaultRoomDetailsEntryPoint.kt | 26 ++++-- .../roomdetails/impl/RoomDetailsFlowNode.kt | 12 ++- .../RoomNotificationSettingsNode.kt | 11 ++- .../RoomNotificationSettingsStateProvider.kt | 12 +++ .../RoomNotificationSettingsView.kt | 85 ++++++++++++------- .../RustNotificationSettingsService.kt | 10 ++- 16 files changed, 205 insertions(+), 63 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 891301532c..8d2edd0843 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -197,7 +197,9 @@ class LoggedInFlowNode @AssistedInject constructor( ) : NavTarget @Parcelize - data object Settings : NavTarget + data class Settings( + val initialElement: PreferencesEntryPoint.InitialTarget = PreferencesEntryPoint.InitialTarget.Root + ) : NavTarget @Parcelize data object CreateRoom : NavTarget @@ -227,7 +229,7 @@ class LoggedInFlowNode @AssistedInject constructor( } override fun onSettingsClicked() { - backstack.push(NavTarget.Settings) + backstack.push(NavTarget.Settings()) } override fun onCreateRoomClicked() { @@ -260,11 +262,15 @@ class LoggedInFlowNode @AssistedInject constructor( override fun onForwardedToSingleRoom(roomId: RoomId) { coroutineScope.launch { attachRoom(roomId) } } + + override fun onOpenGlobalNotificationSettings() { + backstack.push(NavTarget.Settings(PreferencesEntryPoint.InitialTarget.NotificationSettings)) + } } val inputs = RoomFlowNode.Inputs(roomId = navTarget.roomId, initialElement = navTarget.initialElement) createNode(buildContext, plugins = listOf(inputs, callback)) } - NavTarget.Settings -> { + is NavTarget.Settings -> { val callback = object : PreferencesEntryPoint.Callback { override fun onOpenBugReport() { plugins().forEach { it.onOpenBugReport() } @@ -278,7 +284,9 @@ class LoggedInFlowNode @AssistedInject constructor( backstack.push(NavTarget.Room(roomId, initialElement = RoomLoadedFlowNode.NavTarget.RoomNotificationSettings)) } } - preferencesEntryPoint.nodeBuilder(this, buildContext) + val inputs = PreferencesEntryPoint.Params(navTarget.initialElement) + return preferencesEntryPoint.nodeBuilder(this, buildContext) + .params(inputs) .callback(callback) .build() } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt index fa583645b2..613ed650c8 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt @@ -75,6 +75,7 @@ class RoomLoadedFlowNode @AssistedInject constructor( interface Callback : Plugin { fun onForwardedToSingleRoom(roomId: RoomId) + fun onOpenGlobalNotificationSettings() } data class Inputs( @@ -128,6 +129,18 @@ class RoomLoadedFlowNode @AssistedInject constructor( } } + private fun createRoomDetailsNode(buildContext: BuildContext, initialTarget: RoomDetailsEntryPoint.InitialTarget): Node { + val callback = object : RoomDetailsEntryPoint.Callback { + override fun onOpenGlobalNotificationSettings() { + callbacks.forEach { it.onOpenGlobalNotificationSettings() } + } + } + return roomDetailsEntryPoint.nodeBuilder(this, buildContext) + .params(RoomDetailsEntryPoint.Params(initialTarget)) + .callback(callback) + .build() + } + override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Messages -> { @@ -147,16 +160,13 @@ class RoomLoadedFlowNode @AssistedInject constructor( messagesEntryPoint.createNode(this, buildContext, callback) } NavTarget.RoomDetails -> { - val inputs = RoomDetailsEntryPoint.Inputs(RoomDetailsEntryPoint.InitialTarget.RoomDetails) - roomDetailsEntryPoint.createNode(this, buildContext, inputs, emptyList()) + createRoomDetailsNode(buildContext, RoomDetailsEntryPoint.InitialTarget.RoomDetails) } is NavTarget.RoomMemberDetails -> { - val inputs = RoomDetailsEntryPoint.Inputs(RoomDetailsEntryPoint.InitialTarget.RoomMemberDetails(navTarget.userId)) - roomDetailsEntryPoint.createNode(this, buildContext, inputs, emptyList()) + createRoomDetailsNode(buildContext, RoomDetailsEntryPoint.InitialTarget.RoomMemberDetails(navTarget.userId)) } NavTarget.RoomNotificationSettings -> { - val inputs = RoomDetailsEntryPoint.Inputs(RoomDetailsEntryPoint.InitialTarget.RoomNotificationSettings) - roomDetailsEntryPoint.createNode(this, buildContext, inputs, emptyList()) + createRoomDetailsNode(buildContext, RoomDetailsEntryPoint.InitialTarget.RoomNotificationSettings) } } } diff --git a/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt index a25e4d8134..c05ddb6c73 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt @@ -31,6 +31,7 @@ import io.element.android.appnav.room.RoomLoadedFlowNode import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint import io.element.android.libraries.architecture.childNode +import io.element.android.libraries.architecture.createNode import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.test.room.FakeMatrixRoom @@ -71,14 +72,22 @@ class RoomFlowNodeTest { var nodeId: String? = null - override fun createNode( - parentNode: Node, - buildContext: BuildContext, - inputs: RoomDetailsEntryPoint.Inputs, - plugins: List - ): Node { - return node(buildContext) {}.also { - nodeId = it.id + override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): RoomDetailsEntryPoint.NodeBuilder { + return object : RoomDetailsEntryPoint.NodeBuilder { + + override fun params(params: RoomDetailsEntryPoint.Params): RoomDetailsEntryPoint.NodeBuilder { + return this + } + + override fun callback(callback: RoomDetailsEntryPoint.Callback): RoomDetailsEntryPoint.NodeBuilder { + return this + } + + override fun build(): Node { + return node(buildContext) {}.also { + nodeId = it.id + } + } } } } diff --git a/features/preferences/api/build.gradle.kts b/features/preferences/api/build.gradle.kts index c20fe9aabb..0278385ab3 100644 --- a/features/preferences/api/build.gradle.kts +++ b/features/preferences/api/build.gradle.kts @@ -15,6 +15,7 @@ */ plugins { id("io.element.android-library") + id("kotlin-parcelize") } android { diff --git a/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt b/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt index 50a605efe4..a0d2b8e057 100644 --- a/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt +++ b/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt @@ -16,17 +16,30 @@ package io.element.android.features.preferences.api +import android.os.Parcelable import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint +import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.matrix.api.core.RoomId +import kotlinx.parcelize.Parcelize interface PreferencesEntryPoint : FeatureEntryPoint { + sealed interface InitialTarget : Parcelable { + @Parcelize + data object Root : InitialTarget + @Parcelize + data object NotificationSettings : InitialTarget + } + + data class Params(val initialElement: InitialTarget) : NodeInputs fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder interface NodeBuilder { + + fun params(params: Params): NodeBuilder fun callback(callback: Callback): NodeBuilder fun build(): Node } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultPreferencesEntryPoint.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultPreferencesEntryPoint.kt index aa286394dc..e551d9d8dc 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultPreferencesEntryPoint.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultPreferencesEntryPoint.kt @@ -31,6 +31,11 @@ class DefaultPreferencesEntryPoint @Inject constructor() : PreferencesEntryPoint return object : PreferencesEntryPoint.NodeBuilder { val plugins = ArrayList() + override fun params(params: PreferencesEntryPoint.Params): PreferencesEntryPoint.NodeBuilder { + plugins += params + return this + } + override fun callback(callback: PreferencesEntryPoint.Callback): PreferencesEntryPoint.NodeBuilder { plugins += callback return this @@ -42,3 +47,8 @@ class DefaultPreferencesEntryPoint @Inject constructor() : PreferencesEntryPoint } } } + +internal fun PreferencesEntryPoint.InitialTarget.toNavTarget() = when (this) { + is PreferencesEntryPoint.InitialTarget.Root -> PreferencesFlowNode.NavTarget.Root + is PreferencesEntryPoint.InitialTarget.NotificationSettings -> PreferencesFlowNode.NavTarget.NotificationSettings +} 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 8d77757527..0fcf04d1df 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 @@ -53,7 +53,7 @@ class PreferencesFlowNode @AssistedInject constructor( @Assisted plugins: List, ) : BackstackNode( backstack = BackStack( - initialElement = NavTarget.Root, + initialElement = plugins.filterIsInstance().first().initialElement.toNavTarget(), savedStateMap = buildContext.savedStateMap, ), buildContext = buildContext, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt index 79201e27d3..5fb34687d8 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt @@ -135,7 +135,13 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( suspend { // On modern clients, we don't have different settings for encrypted and non-encrypted rooms (Legacy clients did). notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = true, mode = mode, isOneToOne = isOneToOne).getOrThrow() - notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = false, mode = mode, isOneToOne = isOneToOne).getOrThrow() + val result = notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = false, mode = mode, isOneToOne = isOneToOne) + + if (result.isFailure) { + result.exceptionOrNull()?.printStackTrace() + } + + result.getOrThrow() }.runCatchingUpdatingState(action) } diff --git a/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt b/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt index ed1e6f5c5a..0aaac324da 100644 --- a/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt +++ b/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt @@ -38,7 +38,17 @@ interface RoomDetailsEntryPoint : FeatureEntryPoint { data object RoomNotificationSettings : InitialTarget } - data class Inputs(val initialElement: InitialTarget) : NodeInputs + data class Params(val initialElement: InitialTarget) : NodeInputs - fun createNode(parentNode: Node, buildContext: BuildContext, inputs: Inputs, plugins: List): Node + interface Callback : Plugin { + fun onOpenGlobalNotificationSettings() + } + + interface NodeBuilder { + fun params(params: Params): NodeBuilder + fun callback(callback: Callback): NodeBuilder + fun build(): Node + } + + fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder } diff --git a/features/roomdetails/impl/build.gradle.kts b/features/roomdetails/impl/build.gradle.kts index 3bf38d4fa7..a2fdfa18c1 100644 --- a/features/roomdetails/impl/build.gradle.kts +++ b/features/roomdetails/impl/build.gradle.kts @@ -44,6 +44,7 @@ dependencies { implementation(projects.libraries.mediaupload.api) implementation(projects.libraries.featureflag.api) implementation(projects.libraries.permissions.api) + implementation(projects.libraries.preferences.api) api(projects.features.roomdetails.api) api(projects.libraries.usersearch.api) api(projects.services.apperror.api) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt index 8cd6cb54d6..108ef088f0 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt @@ -29,13 +29,25 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class DefaultRoomDetailsEntryPoint @Inject constructor() : RoomDetailsEntryPoint { - override fun createNode( - parentNode: Node, - buildContext: BuildContext, - inputs: RoomDetailsEntryPoint.Inputs, - plugins: List - ): Node { - return parentNode.createNode(buildContext, plugins + inputs) + + override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): RoomDetailsEntryPoint.NodeBuilder { + return object : RoomDetailsEntryPoint.NodeBuilder { + val plugins = ArrayList() + + override fun params(params: RoomDetailsEntryPoint.Params): RoomDetailsEntryPoint.NodeBuilder { + plugins += params + return this + } + + override fun callback(callback: RoomDetailsEntryPoint.Callback): RoomDetailsEntryPoint.NodeBuilder { + plugins += callback + return this + } + + override fun build(): Node { + return parentNode.createNode(buildContext, plugins) + } + } } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index 6945ebb0ca..dba31ab223 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -23,6 +23,7 @@ import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.push import dagger.assisted.Assisted @@ -47,7 +48,7 @@ class RoomDetailsFlowNode @AssistedInject constructor( @Assisted plugins: List, ) : BackstackNode( backstack = BackStack( - initialElement = plugins.filterIsInstance().first().initialElement.toNavTarget(), + initialElement = plugins.filterIsInstance().first().initialElement.toNavTarget(), savedStateMap = buildContext.savedStateMap, ), buildContext = buildContext, @@ -125,8 +126,13 @@ class RoomDetailsFlowNode @AssistedInject constructor( } is NavTarget.RoomNotificationSettings -> { - val plugins = listOf(RoomNotificationSettingsNode.RoomNotificationSettingInput(navTarget.showUserDefinedSettingStyle)) - createNode(buildContext, plugins) + val input = RoomNotificationSettingsNode.RoomNotificationSettingInput(navTarget.showUserDefinedSettingStyle) + val callback = object : RoomNotificationSettingsNode.Callback { + override fun openGlobalNotificationSettings() { + plugins().forEach { it.onOpenGlobalNotificationSettings() } + } + } + createNode(buildContext, listOf(input, callback)) } is NavTarget.RoomMemberDetails -> { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt index cb0168a42b..173e3b6af1 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt @@ -22,6 +22,7 @@ import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import im.vector.app.features.analytics.plan.MobileScreen @@ -42,8 +43,15 @@ class RoomNotificationSettingsNode @AssistedInject constructor( data class RoomNotificationSettingInput( val showUserDefinedSettingStyle: Boolean ) : NodeInputs - + interface Callback : Plugin { + fun openGlobalNotificationSettings() + } private val inputs = inputs() + private val callbacks = plugins() + + private fun openGlobalNotificationSettings() { + callbacks.forEach { it.openGlobalNotificationSettings() } + } init { lifecycle.subscribe( @@ -66,6 +74,7 @@ class RoomNotificationSettingsNode @AssistedInject constructor( RoomNotificationSettingsView( state = state, modifier = modifier, + onShowGlobalNotifications = this::openGlobalNotificationSettings, onBackPressed = this::navigateUp, ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt index 961909f933..1a95530d29 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt @@ -36,5 +36,17 @@ internal class RoomNotificationSettingsStateProvider : PreviewParameterProvider< restoreDefaultAction = Async.Uninitialized, eventSink = { }, ), + RoomNotificationSettingsState( + roomName = "Room 1", + Async.Success(RoomNotificationSettings( + mode = RoomNotificationMode.MUTE, + isDefault = false)), + pendingRoomNotificationMode = null, + pendingSetDefault = null, + defaultRoomNotificationMode = RoomNotificationMode.ALL_MESSAGES, + setNotificationSettingAction = Async.Uninitialized, + restoreDefaultAction = Async.Uninitialized, + eventSink = { }, + ), ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt index 3d95e4ec20..21bc11781d 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt @@ -21,10 +21,14 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.ClickableText import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.features.roomdetails.impl.R @@ -34,9 +38,9 @@ import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch -import io.element.android.libraries.designsystem.components.preferences.PreferenceText import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart import io.element.android.libraries.designsystem.theme.aliasScreenTitle import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text @@ -49,6 +53,7 @@ import io.element.android.libraries.ui.strings.CommonStrings fun RoomNotificationSettingsView( state: RoomNotificationSettingsState, modifier: Modifier = Modifier, + onShowGlobalNotifications: () -> Unit = {}, onBackPressed: () -> Unit = {}, ) { Scaffold( @@ -66,40 +71,62 @@ fun RoomNotificationSettingsView( .consumeWindowInsets(padding), verticalArrangement = Arrangement.spacedBy(16.dp), ) { - val subtitle = when (state.defaultRoomNotificationMode) { - RoomNotificationMode.ALL_MESSAGES -> stringResource(id = R.string.screen_room_notification_settings_mode_all_messages) - RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> stringResource(id = R.string.screen_room_notification_settings_mode_mentions_and_keywords) - RoomNotificationMode.MUTE -> stringResource(id = CommonStrings.common_mute) - null -> "" - } - val roomNotificationSettings = state.roomNotificationSettings.dataOrNull() - - PreferenceCategory(title = stringResource(id = R.string.screen_room_notification_settings_custom_settings_title)) { - PreferenceSwitch( - isChecked = state.displayIsDefault.orTrue(), - onCheckedChange = { - state.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(it)) - }, - title = "Match default setting", - subtitle = subtitle, - enabled = roomNotificationSettings != null - ) - - PreferenceText( - title = stringResource(id = R.string.screen_room_notification_settings_allow_custom), - subtitle = stringResource(id = R.string.screen_room_notification_settings_allow_custom_footnote), - enabled = !state.displayIsDefault.orTrue(), - ) - - if (roomNotificationSettings != null && state.displayNotificationMode != null) { + PreferenceSwitch( + isChecked = !state.displayIsDefault.orTrue(), + onCheckedChange = { + state.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(!it)) + }, + title = stringResource(id = R.string.screen_room_notification_settings_allow_custom), + subtitle = stringResource(id = R.string.screen_room_notification_settings_allow_custom_footnote), + enabled = roomNotificationSettings != null + ) + if (state.displayIsDefault.orTrue()) { + PreferenceCategory(title = stringResource(id = R.string.screen_room_notification_settings_default_setting_title)) { + val text = buildAnnotatedStringWithStyledPart( + R.string.screen_room_notification_settings_default_setting_footnote, + R.string.screen_room_notification_settings_default_setting_footnote_content_link, + color = Color.Unspecified, + underline = false, + bold = true, + ) + ClickableText( + text = text, + onClick = { + onShowGlobalNotifications() + }, + modifier = Modifier + .padding(start = 16.dp, bottom = 16.dp, end = 16.dp), + style = ElementTheme.typography.fontBodyMdRegular + .copy( + color = MaterialTheme.colorScheme.secondary, + textAlign = TextAlign.Center, + ) + ) + if(state.defaultRoomNotificationMode != null){ + val defaultModeTitle = when (state.defaultRoomNotificationMode) { + RoomNotificationMode.ALL_MESSAGES -> stringResource(id = R.string.screen_room_notification_settings_mode_all_messages) + RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> { + stringResource(id = R.string.screen_room_notification_settings_mode_mentions_and_keywords) + } + RoomNotificationMode.MUTE -> stringResource(id = CommonStrings.common_mute) + } + RoomNotificationSettingsOption( + roomNotificationSettingsItem = RoomNotificationSettingsItem(state.defaultRoomNotificationMode, defaultModeTitle), + isSelected = true, + onOptionSelected = { }, + enabled = true + ) + } + } + } else { + PreferenceCategory(title = stringResource(id = R.string.screen_room_notification_settings_custom_settings_title)) { RoomNotificationSettingsOptions( selected = state.displayNotificationMode, enabled = !state.displayIsDefault.orTrue(), onOptionSelected = { state.eventSink(RoomNotificationSettingsEvents.RoomNotificationModeChanged(it.mode)) - }, - ) + },) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt index df5c4d577f..42b2662a6d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt @@ -28,6 +28,8 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.NotificationSettings import org.matrix.rustcomponents.sdk.NotificationSettingsDelegate +import org.matrix.rustcomponents.sdk.NotificationSettingsException +import timber.log.Timber class RustNotificationSettingsService( private val notificationSettings: NotificationSettings, @@ -63,7 +65,13 @@ class RustNotificationSettingsService( isOneToOne: Boolean ): Result = withContext(dispatchers.io) { runCatching { - notificationSettings.setDefaultRoomNotificationMode(isEncrypted, isOneToOne, mode.let(RoomNotificationSettingsMapper::mapMode)) + try { + notificationSettings.setDefaultRoomNotificationMode(isEncrypted, isOneToOne, mode.let(RoomNotificationSettingsMapper::mapMode)) + } catch (exception: NotificationSettingsException.RuleNotFound) { + // `setDefaultRoomNotificationMode` updates multiple rules including unstable rules (e.g. the polls push rules defined in the MSC3930) + // since production home servers may not have these rules yet, we drop the RuleNotFound error + Timber.w("Unable to find the rule: ${exception.ruleId}") + } } } From ea2c332e52bceb91314c43bbb79287f19c7d1c7f Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 20 Oct 2023 20:30:14 +0000 Subject: [PATCH 11/19] Update screenshots --- ...ltNotificationSettingView-D-8_8_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...ltNotificationSettingView-N-8_9_null_0,NEXUS_5,1.0,en].png | 4 ++-- ..._RoomNotificationSettings-D-4_4_null_0,NEXUS_5,1.0,en].png | 4 ++-- ..._RoomNotificationSettings-D-4_4_null_1,NEXUS_5,1.0,en].png | 3 +++ ..._RoomNotificationSettings-N-4_5_null_0,NEXUS_5,1.0,en].png | 4 ++-- ..._RoomNotificationSettings-N-4_5_null_1,NEXUS_5,1.0,en].png | 3 +++ 6 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_0,NEXUS_5,1.0,en].png index 1d4750eecb..7e1e8e88a0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1172d81259b7e7b694f428c5144d96c8a7748134f5b4f515a5c25c58ff8e5e1b -size 31318 +oid sha256:11aa649bb8e25975c79ebc8c20aa82153acac75b4087f99485c58bd604ba3f33 +size 35947 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_0,NEXUS_5,1.0,en].png index c423fe4456..6e3219983b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e835ab12c0a77c4a4dcaf8bac0d78ed5ec9a0ab8eef669c7d814317ab06abd27 -size 28443 +oid sha256:1df0f8620db5a3f751e37543f34785517703e4731af9e624dc50b91de79f46e9 +size 33107 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_0,NEXUS_5,1.0,en].png index 88554d0d72..7a08009659 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f34dde086bbd14cc5cfeb0003d6bfd0d3868ecbc497956dc17794e69f6773279 -size 40070 +oid sha256:3f2fc33febab98860da9b6591c4ae33dc9ba4fa28365520e4fcb08f8bfaf6ff8 +size 33828 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..383adaff52 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d99786d84445bc42f7e7f7e414f973a04ba6684780f69eb1cfe60a09ff3ec6e +size 38017 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_0,NEXUS_5,1.0,en].png index 9adff83203..d59805dfbd 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef0545d55a8a78af5d8286fc590998c973cee986a3f6aff91850cf277d47a5e5 -size 36415 +oid sha256:0cf8d93229fd5d8034dc7e2f68f9868fc42515c538098c89d4dddca814827bc3 +size 31448 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..9b7a1f3917 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13c5007d3a97734dca40a88f2312e59d09a8906deb71d16c0611df7dd25a6391 +size 35124 From 58f9c35ad8ccb75ab500b96979e7280941fa9f1a Mon Sep 17 00:00:00 2001 From: David Langley Date: Mon, 23 Oct 2023 10:28:04 +0100 Subject: [PATCH 12/19] fix unused import --- .../test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt index c05ddb6c73..fd5de85b1d 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt @@ -31,7 +31,6 @@ import io.element.android.appnav.room.RoomLoadedFlowNode import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint import io.element.android.libraries.architecture.childNode -import io.element.android.libraries.architecture.createNode import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.test.room.FakeMatrixRoom From 5324eb0f1adfe1a5bcf09a6962184a1a81c73198 Mon Sep 17 00:00:00 2001 From: David Langley Date: Tue, 24 Oct 2023 16:27:26 +0100 Subject: [PATCH 13/19] Address comments - Add additional states to preview. - Add TODO description for commented code - Move showUserDefinedSettingStyle from the node to the view for testability. --- .../notifications/NotificationSettingsView.kt | 4 +++- ...DefaultNotificationSettingStateProvider.kt | 5 +++-- .../RoomNotificationSettingsNode.kt | 11 ++-------- .../RoomNotificationSettingsPresenter.kt | 14 ++++++++++-- .../RoomNotificationSettingsState.kt | 1 + .../RoomNotificationSettingsStateProvider.kt | 1 + .../RoomNotificationSettingsView.kt | 22 +++++++++++++++++++ ...edRoomNotificationSettingsStateProvider.kt | 1 + .../RoomNotificationSettingsPresenterTests.kt | 3 ++- 9 files changed, 47 insertions(+), 15 deletions(-) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt index c6680ed819..223107226e 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt @@ -89,6 +89,7 @@ fun NotificationSettingsView( onGroupChatsClicked = { onOpenEditDefault(false) }, onDirectChatsClicked = { onOpenEditDefault(true) }, onMentionNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetAtRoomNotificationsEnabled(it)) }, + // TODO We are removing the call notification toggle until support for call notifications has been added // onCallsNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetCallNotificationsEnabled(it)) }, ) } @@ -116,6 +117,7 @@ private fun NotificationSettingsContentView( onGroupChatsClicked: () -> Unit, onDirectChatsClicked: () -> Unit, onMentionNotificationsChanged: (Boolean) -> Unit, + // TODO We are removing the call notification toggle until support for call notifications has been added // onCallsNotificationsChanged: (Boolean) -> Unit, modifier: Modifier = Modifier, ) { @@ -166,7 +168,7 @@ private fun NotificationSettingsContentView( onCheckedChange = onMentionNotificationsChanged ) } - // We are removing the call notification toggle until call support has been added + // TODO We are removing the call notification toggle until support for call notifications has been added // PreferenceCategory(title = stringResource(id = CommonStrings.screen_notification_settings_additional_settings_section_title)) { // PreferenceSwitch( // modifier = Modifier, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt index 2b1236f4af..2ff02c008c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt @@ -27,11 +27,12 @@ open class EditDefaultNotificationSettingStateProvider: PreviewParameterProvider override val values: Sequence get() = sequenceOf( anEditDefaultNotificationSettingsState(), + anEditDefaultNotificationSettingsState(isOneToOne = true) ) } -fun anEditDefaultNotificationSettingsState() = EditDefaultNotificationSettingState( - isOneToOne = false, +fun anEditDefaultNotificationSettingsState(isOneToOne: Boolean = false) = EditDefaultNotificationSettingState( + isOneToOne = isOneToOne, mode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, roomsWithUserDefinedMode = listOf(aRoomSummary()), changeNotificationSettingAction = Async.Uninitialized, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt index cb0168a42b..5972314ea4 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt @@ -35,7 +35,7 @@ import io.element.android.services.analytics.api.AnalyticsService class RoomNotificationSettingsNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, - private val presenter: RoomNotificationSettingsPresenter, + presenterFactory: RoomNotificationSettingsPresenter.Factory, private val analyticsService: AnalyticsService, ) : Node(buildContext, plugins = plugins) { @@ -45,6 +45,7 @@ class RoomNotificationSettingsNode @AssistedInject constructor( private val inputs = inputs() + private val presenter = presenterFactory.create(inputs.showUserDefinedSettingStyle) init { lifecycle.subscribe( onResume = { @@ -56,18 +57,10 @@ class RoomNotificationSettingsNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { val state = presenter.present() - if(inputs.showUserDefinedSettingStyle) { - UserDefinedRoomNotificationSettingsView( - state = state, - modifier = modifier, - onBackPressed = this::navigateUp, - ) - } else { RoomNotificationSettingsView( state = state, modifier = modifier, onBackPressed = this::navigateUp, ) - } } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt index 3086d878c4..a75ea94e39 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt @@ -23,6 +23,9 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState @@ -36,13 +39,19 @@ import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import javax.inject.Inject import kotlin.time.Duration.Companion.seconds -class RoomNotificationSettingsPresenter @Inject constructor( +class RoomNotificationSettingsPresenter @AssistedInject constructor( private val room: MatrixRoom, private val notificationSettingsService: NotificationSettingsService, + @Assisted private val showUserDefinedSettingStyle: Boolean, ) : Presenter { + + @AssistedFactory + interface Factory { + fun create(showUserDefinedSettingStyle: Boolean): RoomNotificationSettingsPresenter + } + @Composable override fun present(): RoomNotificationSettingsState { val defaultRoomNotificationMode: MutableState = rememberSaveable { @@ -107,6 +116,7 @@ class RoomNotificationSettingsPresenter @Inject constructor( } return RoomNotificationSettingsState( + showUserDefinedSettingStyle = showUserDefinedSettingStyle, roomName = room.displayName, roomNotificationSettings = roomNotificationSettings.value, pendingRoomNotificationMode = pendingRoomNotificationMode.value, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt index 1f8c7e4ce8..ea51636eb3 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt @@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationSettings data class RoomNotificationSettingsState( + val showUserDefinedSettingStyle: Boolean, val roomName: String, val roomNotificationSettings: Async, val pendingRoomNotificationMode: RoomNotificationMode?, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt index 961909f933..a7faa52210 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt @@ -25,6 +25,7 @@ internal class RoomNotificationSettingsStateProvider : PreviewParameterProvider< override val values: Sequence get() = sequenceOf( RoomNotificationSettingsState( + showUserDefinedSettingStyle = false, roomName = "Room 1", Async.Success(RoomNotificationSettings( mode = RoomNotificationMode.MUTE, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt index 3d95e4ec20..43e736137e 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt @@ -45,11 +45,33 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings + @Composable fun RoomNotificationSettingsView( state: RoomNotificationSettingsState, modifier: Modifier = Modifier, onBackPressed: () -> Unit = {}, +) { + if(state.showUserDefinedSettingStyle) { + UserDefinedRoomNotificationSettingsView( + state = state, + modifier = modifier, + onBackPressed = onBackPressed, + ) + } else { + RoomSpecificNotificationSettingsView( + state = state, + modifier = modifier, + onBackPressed = onBackPressed, + ) + } +} + +@Composable +private fun RoomSpecificNotificationSettingsView( + state: RoomNotificationSettingsState, + modifier: Modifier = Modifier, + onBackPressed: () -> Unit = {}, ) { Scaffold( modifier = modifier, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsStateProvider.kt index 76714a82d0..59181f4d9c 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsStateProvider.kt @@ -25,6 +25,7 @@ internal class UserDefinedRoomNotificationSettingsStateProvider : PreviewParamet override val values: Sequence get() = sequenceOf( RoomNotificationSettingsState( + showUserDefinedSettingStyle = false, roomName = "Room 1", Async.Success( RoomNotificationSettings( diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt index 7f4ea57cf1..31d7973109 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt @@ -136,7 +136,8 @@ class RoomNotificationSettingsPresenterTests { val room = aMatrixRoom(notificationSettingsService = notificationSettingsService) return RoomNotificationSettingsPresenter( room = room, - notificationSettingsService = notificationSettingsService + notificationSettingsService = notificationSettingsService, + showUserDefinedSettingStyle = false, ) } } From d7386856a1f603f94e006b6bda77884b809c17f5 Mon Sep 17 00:00:00 2001 From: David Langley Date: Tue, 24 Oct 2023 16:34:47 +0100 Subject: [PATCH 14/19] Remove debugging code. --- .../edit/EditDefaultNotificationSettingPresenter.kt | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt index 5fb34687d8..79201e27d3 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt @@ -135,13 +135,7 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( suspend { // On modern clients, we don't have different settings for encrypted and non-encrypted rooms (Legacy clients did). notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = true, mode = mode, isOneToOne = isOneToOne).getOrThrow() - val result = notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = false, mode = mode, isOneToOne = isOneToOne) - - if (result.isFailure) { - result.exceptionOrNull()?.printStackTrace() - } - - result.getOrThrow() + notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = false, mode = mode, isOneToOne = isOneToOne).getOrThrow() }.runCatchingUpdatingState(action) } From 117614d59da68347ae4e50aa256f45672f1b6240 Mon Sep 17 00:00:00 2001 From: David Langley Date: Tue, 24 Oct 2023 17:14:48 +0100 Subject: [PATCH 15/19] Fix compile error. --- .../RoomNotificationSettingsStateProvider.kt | 1 + .../impl/notificationsettings/RoomNotificationSettingsView.kt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt index 0024abbb35..cfc82585f0 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt @@ -38,6 +38,7 @@ internal class RoomNotificationSettingsStateProvider : PreviewParameterProvider< eventSink = { }, ), RoomNotificationSettingsState( + showUserDefinedSettingStyle = false, roomName = "Room 1", Async.Success(RoomNotificationSettings( mode = RoomNotificationMode.MUTE, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt index 898268359c..0b9a1fd5f9 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt @@ -67,6 +67,7 @@ fun RoomNotificationSettingsView( RoomSpecificNotificationSettingsView( state = state, modifier = modifier, + onShowGlobalNotifications = onShowGlobalNotifications, onBackPressed = onBackPressed, ) } @@ -76,6 +77,7 @@ fun RoomNotificationSettingsView( private fun RoomSpecificNotificationSettingsView( state: RoomNotificationSettingsState, modifier: Modifier = Modifier, + onShowGlobalNotifications: () -> Unit = {}, onBackPressed: () -> Unit = {}, ) { Scaffold( From 21f325686fa4238b10347b3fc7289d64266a3a3e Mon Sep 17 00:00:00 2001 From: ElementBot Date: Tue, 24 Oct 2023 16:35:20 +0000 Subject: [PATCH 16/19] Update screenshots --- ...ultNotificationSettingView-D-8_8_null_1,NEXUS_5,1.0,en].png | 3 +++ ...ultNotificationSettingView-N-8_9_null_1,NEXUS_5,1.0,en].png | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..16d4767fba --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9bfce546aa824f56a1d89d638fc1a2ed6fc02da326edc5360d3618795c58545a +size 35875 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7290142a52 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a58daa364819cef83467a57974517118d683f6b47ae0ec19164dc0678cfa63c6 +size 33064 From 02de87235a13143627a5d99bd422e2fe5ea2b47f Mon Sep 17 00:00:00 2001 From: David Langley Date: Tue, 24 Oct 2023 19:15:14 +0100 Subject: [PATCH 17/19] Fix indentation --- .../RoomNotificationSettingsNode.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt index 4f3265ea1f..8ccdd3b158 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt @@ -65,11 +65,11 @@ class RoomNotificationSettingsNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { val state = presenter.present() - RoomNotificationSettingsView( - state = state, - modifier = modifier, - onShowGlobalNotifications = this::openGlobalNotificationSettings, - onBackPressed = this::navigateUp, - ) + RoomNotificationSettingsView( + state = state, + modifier = modifier, + onShowGlobalNotifications = this::openGlobalNotificationSettings, + onBackPressed = this::navigateUp, + ) } } From 75e95333a803c93afc7c62f24eaa124ed14f08d5 Mon Sep 17 00:00:00 2001 From: David Langley Date: Tue, 24 Oct 2023 21:39:53 +0100 Subject: [PATCH 18/19] Unit and Snapshot tests for error and loading states. --- .../NotificationSettingsStateProvider.kt | 8 ++- ...DefaultNotificationSettingStateProvider.kt | 11 ++-- ...faultNotificationSettingsPresenterTests.kt | 22 ++++++++ .../NotificationSettingsPresenterTests.kt | 30 +++++++++++ .../RoomNotificationSettingsStateProvider.kt | 52 +++++++++---------- .../RoomNotificationSettingsPresenterTests.kt | 49 +++++++++++++---- .../FakeNotificationSettingsService.kt | 28 ++++++++++ 7 files changed, 159 insertions(+), 41 deletions(-) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt index cfff59e905..9336857585 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt @@ -24,10 +24,14 @@ open class NotificationSettingsStateProvider : PreviewParameterProvider get() = sequenceOf( aNotificationSettingsState(), + aNotificationSettingsState(changeNotificationSettingAction = Async.Loading(Unit)), + aNotificationSettingsState(changeNotificationSettingAction = Async.Failure(Throwable("error"))), ) } -fun aNotificationSettingsState() = NotificationSettingsState( +fun aNotificationSettingsState( + changeNotificationSettingAction: Async = Async.Uninitialized, +) = NotificationSettingsState( matrixSettings = NotificationSettingsState.MatrixSettings.Valid( atRoomNotificationsEnabled = true, callNotificationsEnabled = true, @@ -38,6 +42,6 @@ fun aNotificationSettingsState() = NotificationSettingsState( systemNotificationsEnabled = false, appNotificationsEnabled = true, ), - changeNotificationSettingAction = Async.Uninitialized, + changeNotificationSettingAction = changeNotificationSettingAction, eventSink = {} ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt index 2ff02c008c..71fbfb1e1b 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt @@ -27,15 +27,20 @@ open class EditDefaultNotificationSettingStateProvider: PreviewParameterProvider override val values: Sequence get() = sequenceOf( anEditDefaultNotificationSettingsState(), - anEditDefaultNotificationSettingsState(isOneToOne = true) + anEditDefaultNotificationSettingsState(isOneToOne = true), + anEditDefaultNotificationSettingsState(changeNotificationSettingAction = Async.Loading(Unit)), + anEditDefaultNotificationSettingsState(changeNotificationSettingAction = Async.Failure(Throwable("error"))), ) } -fun anEditDefaultNotificationSettingsState(isOneToOne: Boolean = false) = EditDefaultNotificationSettingState( +private fun anEditDefaultNotificationSettingsState( + isOneToOne: Boolean = false, + changeNotificationSettingAction: Async = Async.Uninitialized +) = EditDefaultNotificationSettingState( isOneToOne = isOneToOne, mode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, roomsWithUserDefinedMode = listOf(aRoomSummary()), - changeNotificationSettingAction = Async.Uninitialized, + changeNotificationSettingAction = changeNotificationSettingAction, eventSink = {} ) diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt index 2269df46e1..f4cc7c80e9 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTests.kt @@ -25,6 +25,7 @@ import io.element.android.features.preferences.impl.notifications.edit.EditDefau import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.room.FakeMatrixRoom @@ -90,6 +91,27 @@ class EditDefaultNotificationSettingsPresenterTests { } } + @Test + fun `present - edit default notification setting failed`() = runTest { + val notificationSettingsService = FakeNotificationSettingsService() + val presenter = createEditDefaultNotificationSettingPresenter(notificationSettingsService) + notificationSettingsService.givenSetDefaultNotificationModeError(A_THROWABLE) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + awaitItem().eventSink(EditDefaultNotificationSettingStateEvents.SetNotificationMode(RoomNotificationMode.ALL_MESSAGES)) + val errorState = consumeItemsUntilPredicate { + it.changeNotificationSettingAction.isFailure() + }.last() + Truth.assertThat(errorState.changeNotificationSettingAction.isFailure()).isTrue() + errorState.eventSink(EditDefaultNotificationSettingStateEvents.ClearError) + val clearErrorState = consumeItemsUntilPredicate { + it.changeNotificationSettingAction.isUninitialized() + }.last() + Truth.assertThat(clearErrorState.changeNotificationSettingAction.isUninitialized()).isTrue() + } + } + private fun createEditDefaultNotificationSettingPresenter( notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(), roomListService: FakeRoomListService = FakeRoomListService(), diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTests.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTests.kt index cc2c0a7035..c3d6f51256 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTests.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTests.kt @@ -22,6 +22,7 @@ import app.cash.turbine.test import com.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory import com.google.common.truth.Truth import io.element.android.libraries.matrix.api.room.RoomNotificationMode +import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.tests.testutils.consumeItemsUntilPredicate @@ -187,6 +188,35 @@ class NotificationSettingsPresenterTests { } } + @Test + fun `present - clear notification settings change error`() = runTest { + val notificationSettingsService = FakeNotificationSettingsService() + val presenter = createNotificationSettingsPresenter(notificationSettingsService) + notificationSettingsService.givenSetAtRoomError(A_THROWABLE) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val loadedState = consumeItemsUntilPredicate { + (it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.atRoomNotificationsEnabled == false + }.last() + val validMatrixState = loadedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid + Truth.assertThat(validMatrixState?.atRoomNotificationsEnabled).isFalse() + + loadedState.eventSink(NotificationSettingsEvents.SetAtRoomNotificationsEnabled(true)) + val errorState = consumeItemsUntilPredicate { + it.changeNotificationSettingAction.isFailure() + }.last() + Truth.assertThat(errorState.changeNotificationSettingAction.isFailure()).isTrue() + errorState.eventSink(NotificationSettingsEvents.ClearNotificationChangeError) + + val clearErrorState = consumeItemsUntilPredicate { + it.changeNotificationSettingAction.isUninitialized() + }.last() + Truth.assertThat(clearErrorState.changeNotificationSettingAction.isUninitialized()).isTrue() + cancelAndIgnoreRemainingEvents() + } + } + private fun createNotificationSettingsPresenter( notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService() ) : NotificationSettingsPresenter { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt index cfc82585f0..2df0edede7 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt @@ -24,31 +24,31 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationSettings internal class RoomNotificationSettingsStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - RoomNotificationSettingsState( - showUserDefinedSettingStyle = false, - roomName = "Room 1", - Async.Success(RoomNotificationSettings( - mode = RoomNotificationMode.MUTE, - isDefault = true)), - pendingRoomNotificationMode = null, - pendingSetDefault = null, - defaultRoomNotificationMode = RoomNotificationMode.ALL_MESSAGES, - setNotificationSettingAction = Async.Uninitialized, - restoreDefaultAction = Async.Uninitialized, - eventSink = { }, - ), - RoomNotificationSettingsState( - showUserDefinedSettingStyle = false, - roomName = "Room 1", - Async.Success(RoomNotificationSettings( - mode = RoomNotificationMode.MUTE, - isDefault = false)), - pendingRoomNotificationMode = null, - pendingSetDefault = null, - defaultRoomNotificationMode = RoomNotificationMode.ALL_MESSAGES, - setNotificationSettingAction = Async.Uninitialized, - restoreDefaultAction = Async.Uninitialized, - eventSink = { }, - ), + aRoomNotificationSettingsState(), + aRoomNotificationSettingsState(isDefault = false), + aRoomNotificationSettingsState(setNotificationSettingAction = Async.Loading(Unit)), + aRoomNotificationSettingsState(setNotificationSettingAction = Async.Failure(Throwable("error"))), + aRoomNotificationSettingsState(restoreDefaultAction = Async.Loading(Unit)), + aRoomNotificationSettingsState(restoreDefaultAction = Async.Failure(Throwable("error"))), ) + + private fun aRoomNotificationSettingsState( + isDefault: Boolean = true, + setNotificationSettingAction: Async = Async.Uninitialized, + restoreDefaultAction: Async = Async.Uninitialized, + ): RoomNotificationSettingsState { + return RoomNotificationSettingsState( + showUserDefinedSettingStyle = false, + roomName = "Room 1", + Async.Success(RoomNotificationSettings( + mode = RoomNotificationMode.MUTE, + isDefault = isDefault)), + pendingRoomNotificationMode = null, + pendingSetDefault = null, + defaultRoomNotificationMode = RoomNotificationMode.ALL_MESSAGES, + setNotificationSettingAction = setNotificationSettingAction, + restoreDefaultAction = restoreDefaultAction, + eventSink = { }, + ) + } } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt index 31d7973109..c4134462f7 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/notificationsettings/RoomNotificationSettingsPresenterTests.kt @@ -30,7 +30,6 @@ import io.element.android.libraries.matrix.test.notificationsettings.FakeNotific import io.element.android.tests.testutils.consumeItemsUntilPredicate import kotlinx.coroutines.test.runTest import org.junit.Test -import kotlin.time.Duration.Companion.milliseconds class RoomNotificationSettingsPresenterTests { @Test @@ -76,7 +75,6 @@ class RoomNotificationSettingsPresenterTests { } } - @Test fun `present - notification settings set custom failed`() = runTest { val notificationSettingsService = FakeNotificationSettingsService() @@ -87,13 +85,20 @@ class RoomNotificationSettingsPresenterTests { }.test { val initialState = awaitItem() initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(false)) - val states = consumeItemsUntilPredicate { - it.roomNotificationSettings.dataOrNull()?.isDefault == false - } - states.forEach { - Truth.assertThat(it.roomNotificationSettings.dataOrNull()?.isDefault).isTrue() - Truth.assertThat(it.pendingSetDefault).isNull() - } + val failedState = consumeItemsUntilPredicate { + it.setNotificationSettingAction.isFailure() + }.last() + + Truth.assertThat(failedState.roomNotificationSettings.dataOrNull()?.isDefault).isTrue() + Truth.assertThat(failedState.pendingSetDefault).isNull() + Truth.assertThat(failedState.setNotificationSettingAction.isFailure()).isTrue() + + failedState.eventSink(RoomNotificationSettingsEvents.ClearSetNotificationError) + + val errorClearedState = consumeItemsUntilPredicate { + it.setNotificationSettingAction.isUninitialized() + }.last() + Truth.assertThat(errorClearedState.setNotificationSettingAction.isUninitialized()).isTrue() } } @@ -122,7 +127,7 @@ class RoomNotificationSettingsPresenterTests { val initialState = awaitItem() initialState.eventSink(RoomNotificationSettingsEvents.RoomNotificationModeChanged(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)) initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(true)) - val defaultState = consumeItemsUntilPredicate(timeout = 2000.milliseconds) { + val defaultState = consumeItemsUntilPredicate { it.roomNotificationSettings.dataOrNull()?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY }.last() Truth.assertThat(defaultState.roomNotificationSettings.dataOrNull()?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) @@ -130,6 +135,30 @@ class RoomNotificationSettingsPresenterTests { } } + @Test + fun `present - notification settings restore default failed`() = runTest { + val notificationSettingsService = FakeNotificationSettingsService() + notificationSettingsService.givenRestoreDefaultNotificationModeError(A_THROWABLE) + val presenter = createRoomNotificationSettingsPresenter(notificationSettingsService) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(RoomNotificationSettingsEvents.RoomNotificationModeChanged(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)) + initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(true)) + val failedState = consumeItemsUntilPredicate { + it.restoreDefaultAction.isFailure() + }.last() + Truth.assertThat(failedState.restoreDefaultAction.isFailure()).isTrue() + failedState.eventSink(RoomNotificationSettingsEvents.ClearRestoreDefaultError) + + val errorClearedState = consumeItemsUntilPredicate { + it.restoreDefaultAction.isUninitialized() + }.last() + Truth.assertThat(errorClearedState.restoreDefaultAction.isUninitialized()).isTrue() + cancelAndIgnoreRemainingEvents() + } + } private fun createRoomNotificationSettingsPresenter( notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService() ): RoomNotificationSettingsPresenter{ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt index 7d7fb1a36a..039b855e7e 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt @@ -43,6 +43,9 @@ class FakeNotificationSettingsService( private var callNotificationsEnabled = false private var atRoomNotificationsEnabled = false private var setNotificationModeError: Throwable? = null + private var restoreDefaultNotificationModeError: Throwable? = null + private var setDefaultNotificationModeError: Throwable? = null + private var setAtRoomError: Throwable? = null override val notificationSettingsChangeFlow: SharedFlow get() = _notificationSettingsStateFlow @@ -72,6 +75,10 @@ class FakeNotificationSettingsService( } override suspend fun setDefaultRoomNotificationMode(isEncrypted: Boolean, mode: RoomNotificationMode, isOneToOne: Boolean): Result { + val error = setDefaultNotificationModeError + if (error != null) { + return Result.failure(error) + } if (isOneToOne) { if (isEncrypted) { defaultEncryptedOneToOneRoomNotificationMode = mode @@ -102,6 +109,10 @@ class FakeNotificationSettingsService( } override suspend fun restoreDefaultRoomNotificationMode(roomId: RoomId): Result { + val error = restoreDefaultNotificationModeError + if (error != null) { + return Result.failure(error) + } roomNotificationModeIsDefault = true roomNotificationMode = defaultEncryptedGroupRoomNotificationMode _notificationSettingsStateFlow.emit(Unit) @@ -121,6 +132,10 @@ class FakeNotificationSettingsService( } override suspend fun setRoomMentionEnabled(enabled: Boolean): Result { + val error = setAtRoomError + if (error != null) { + return Result.failure(error) + } atRoomNotificationsEnabled = enabled return Result.success(Unit) } @@ -141,4 +156,17 @@ class FakeNotificationSettingsService( fun givenSetNotificationModeError(throwable: Throwable?) { setNotificationModeError = throwable } + + fun givenRestoreDefaultNotificationModeError(throwable: Throwable?) { + restoreDefaultNotificationModeError = throwable + } + + fun givenSetAtRoomError(throwable: Throwable?) { + setAtRoomError = throwable + } + + fun givenSetDefaultNotificationModeError(throwable: Throwable?) { + setDefaultNotificationModeError = throwable + } + } From f5999a5a35c0b6f21e0275e63ef495adde17e7f8 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Tue, 24 Oct 2023 21:31:36 +0000 Subject: [PATCH 19/19] Update screenshots --- ...ultNotificationSettingView-D-8_8_null_2,NEXUS_5,1.0,en].png | 3 +++ ...ultNotificationSettingView-D-8_8_null_3,NEXUS_5,1.0,en].png | 3 +++ ...ultNotificationSettingView-N-8_9_null_2,NEXUS_5,1.0,en].png | 3 +++ ...ultNotificationSettingView-N-8_9_null_3,NEXUS_5,1.0,en].png | 3 +++ ...l_NotificationSettingsView-D-5_5_null_1,NEXUS_5,1.0,en].png | 3 +++ ...l_NotificationSettingsView-D-5_5_null_2,NEXUS_5,1.0,en].png | 3 +++ ...l_NotificationSettingsView-N-5_6_null_1,NEXUS_5,1.0,en].png | 3 +++ ...l_NotificationSettingsView-N-5_6_null_2,NEXUS_5,1.0,en].png | 3 +++ ...l_RoomNotificationSettings-D-4_4_null_2,NEXUS_5,1.0,en].png | 3 +++ ...l_RoomNotificationSettings-D-4_4_null_3,NEXUS_5,1.0,en].png | 3 +++ ...l_RoomNotificationSettings-D-4_4_null_4,NEXUS_5,1.0,en].png | 3 +++ ...l_RoomNotificationSettings-D-4_4_null_5,NEXUS_5,1.0,en].png | 3 +++ ...l_RoomNotificationSettings-N-4_5_null_2,NEXUS_5,1.0,en].png | 3 +++ ...l_RoomNotificationSettings-N-4_5_null_3,NEXUS_5,1.0,en].png | 3 +++ ...l_RoomNotificationSettings-N-4_5_null_4,NEXUS_5,1.0,en].png | 3 +++ ...l_RoomNotificationSettings-N-4_5_null_5,NEXUS_5,1.0,en].png | 3 +++ 16 files changed, 48 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_null_NotificationSettingsView-D-5_5_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_null_NotificationSettingsView-D-5_5_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_null_NotificationSettingsView-N-5_6_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_null_NotificationSettingsView-N-5_6_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_4,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_5,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_4,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_5,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4830f2db94 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:addc7ec085394342bdd8f072f8d65ff25208bc1352b0fe1d3b9109bb4998424e +size 32286 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..fdfa653f1f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-D-8_8_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca186fcad4d0a39f4f6108cd4c050e5ee692fca17168a1a44393b7aded6126dc +size 36827 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..58d70849e6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b551c64c9d99fbdf315662967f0374668e456738e1b2cab44bfc99726f22a60e +size 29033 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4b8fd2030c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_null_EditDefaultNotificationSettingView-N-8_9_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0b0df0468cc575ff14b9dc07ca6d4a61bb19ad709ead861a412489499e1bfd5 +size 32201 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_null_NotificationSettingsView-D-5_5_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_null_NotificationSettingsView-D-5_5_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..84201a2891 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_null_NotificationSettingsView-D-5_5_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5524d336f303c995801049bb32c2ae4ff4511cfe20ddc3709d1cd7a0632eeb8c +size 48900 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_null_NotificationSettingsView-D-5_5_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_null_NotificationSettingsView-D-5_5_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..d5919c0743 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_null_NotificationSettingsView-D-5_5_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80c8d43a01b2d39d5305635341329f863edea75d5adc0bf3989a63d9d38d46a0 +size 48814 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_null_NotificationSettingsView-N-5_6_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_null_NotificationSettingsView-N-5_6_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..40351f203f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_null_NotificationSettingsView-N-5_6_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c14d79d561656b6ac874d6cf54a731626cc221b302c99ca002cd25e9dd8c55ed +size 45047 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_null_NotificationSettingsView-N-5_6_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_null_NotificationSettingsView-N-5_6_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..62424895b4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_null_NotificationSettingsView-N-5_6_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d3a3796c755d202361d4eacf2be151a9b579b588242d55706dbc35e26b13b081 +size 44306 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..2f2e6b821f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4993368d2f9e605674efdf3027314a312428ddbda78e3168612fd03de220a581 +size 33874 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..14bce95be5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:315b8d8a5081cf2aeb3dcf50992863c1c0e3300d3825954e1a12908567843d58 +size 41165 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..2f2e6b821f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4993368d2f9e605674efdf3027314a312428ddbda78e3168612fd03de220a581 +size 33874 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..14bce95be5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-D-4_4_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:315b8d8a5081cf2aeb3dcf50992863c1c0e3300d3825954e1a12908567843d58 +size 41165 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f5ee587b5f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2351abc0cbecc9ff503ba50a1d9c01eda5e771181335d5d1b0fc72f00b337c3 +size 30586 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4c2b14932b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bf1fc0c9605c5e8e18fb7a45ec3a7ed0ff2f9920b85582800098929f0521fbd +size 36512 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f5ee587b5f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2351abc0cbecc9ff503ba50a1d9c01eda5e771181335d5d1b0fc72f00b337c3 +size 30586 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4c2b14932b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.notificationsettings_null_RoomNotificationSettings-N-4_5_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bf1fc0c9605c5e8e18fb7a45ec3a7ed0ff2f9920b85582800098929f0521fbd +size 36512