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) + } }