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
This commit is contained in:
David Langley 2023-10-17 16:08:08 +01:00
parent a9d87da1ff
commit b634db1772
25 changed files with 375 additions and 70 deletions

View file

@ -244,6 +244,10 @@ class LoggedInFlowNode @AssistedInject constructor(
override fun onVerifyClicked() { override fun onVerifyClicked() {
backstack.push(NavTarget.VerifySession) backstack.push(NavTarget.VerifySession)
} }
override fun onOpenRoomNotificationSettings(roomId: RoomId) {
backstack.push(NavTarget.Room(roomId, initialElement = RoomLoadedFlowNode.NavTarget.RoomNotificationSettings))
}
} }
preferencesEntryPoint.nodeBuilder(this, buildContext) preferencesEntryPoint.nodeBuilder(this, buildContext)
.callback(callback) .callback(callback)

View file

@ -142,6 +142,10 @@ class RoomLoadedFlowNode @AssistedInject constructor(
val inputs = RoomDetailsEntryPoint.Inputs(RoomDetailsEntryPoint.InitialTarget.RoomMemberDetails(navTarget.userId)) val inputs = RoomDetailsEntryPoint.Inputs(RoomDetailsEntryPoint.InitialTarget.RoomMemberDetails(navTarget.userId))
roomDetailsEntryPoint.createNode(this, buildContext, inputs, emptyList()) 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 @Parcelize
data class RoomMemberDetails(val userId: UserId) : NavTarget data class RoomMemberDetails(val userId: UserId) : NavTarget
@Parcelize
data object RoomNotificationSettings : NavTarget
} }
@Composable @Composable

View file

@ -20,6 +20,7 @@ import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.Plugin
import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.architecture.FeatureEntryPoint
import io.element.android.libraries.matrix.api.core.RoomId
interface PreferencesEntryPoint : FeatureEntryPoint { interface PreferencesEntryPoint : FeatureEntryPoint {
@ -33,5 +34,6 @@ interface PreferencesEntryPoint : FeatureEntryPoint {
interface Callback : Plugin { interface Callback : Plugin {
fun onOpenBugReport() fun onOpenBugReport()
fun onVerifyClicked() fun onVerifyClicked()
fun onOpenRoomNotificationSettings(roomId: RoomId)
} }
} }

View file

@ -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.animation.rememberDefaultTransitionHandler
import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.SessionScope 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 io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@ -152,8 +153,13 @@ class PreferencesFlowNode @AssistedInject constructor(
createNode<NotificationSettingsNode>(buildContext, listOf(notificationSettingsCallback)) createNode<NotificationSettingsNode>(buildContext, listOf(notificationSettingsCallback))
} }
is NavTarget.EditDefaultNotificationSetting -> { is NavTarget.EditDefaultNotificationSetting -> {
val callback = object : EditDefaultNotificationSettingNode.Callback {
override fun openRoomNotificationSettings(roomId: RoomId) {
plugins<PreferencesEntryPoint.Callback>().forEach { it.onOpenRoomNotificationSettings(roomId) }
}
}
val input = EditDefaultNotificationSettingNode.Inputs(navTarget.isOneToOne) val input = EditDefaultNotificationSettingNode.Inputs(navTarget.isOneToOne)
createNode<EditDefaultNotificationSettingNode>(buildContext, plugins = listOf(input)) createNode<EditDefaultNotificationSettingNode>(buildContext, plugins = listOf(input, callback))
} }
NavTarget.AdvancedSettings -> { NavTarget.AdvancedSettings -> {
createNode<AdvancedSettingsNode>(buildContext) createNode<AdvancedSettingsNode>(buildContext)

View file

@ -24,4 +24,5 @@ sealed interface NotificationSettingsEvents {
data class SetCallNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents data class SetCallNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents
data object FixConfigurationMismatch : NotificationSettingsEvents data object FixConfigurationMismatch : NotificationSettingsEvents
data object ClearConfigurationMismatchError : NotificationSettingsEvents data object ClearConfigurationMismatchError : NotificationSettingsEvents
data object ClearNotificationChangeError : NotificationSettingsEvents
} }

View file

@ -23,7 +23,9 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope 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.Presenter
import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService 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.RoomNotificationMode
@ -50,6 +52,7 @@ class NotificationSettingsPresenter @Inject constructor(
val systemNotificationsEnabled: MutableState<Boolean> = remember { val systemNotificationsEnabled: MutableState<Boolean> = remember {
mutableStateOf(systemNotificationsEnabledProvider.notificationsEnabled()) mutableStateOf(systemNotificationsEnabledProvider.notificationsEnabled())
} }
val changeNotificationSettingAction: MutableState<Async<Unit>> = remember { mutableStateOf(Async.Uninitialized) }
val localCoroutineScope = rememberCoroutineScope() val localCoroutineScope = rememberCoroutineScope()
val appNotificationsEnabled = userPushStore val appNotificationsEnabled = userPushStore
@ -67,8 +70,12 @@ class NotificationSettingsPresenter @Inject constructor(
fun handleEvents(event: NotificationSettingsEvents) { fun handleEvents(event: NotificationSettingsEvents) {
when (event) { when (event) {
is NotificationSettingsEvents.SetAtRoomNotificationsEnabled -> localCoroutineScope.setAtRoomNotificationsEnabled(event.enabled) is NotificationSettingsEvents.SetAtRoomNotificationsEnabled -> {
is NotificationSettingsEvents.SetCallNotificationsEnabled -> localCoroutineScope.setCallNotificationsEnabled(event.enabled) localCoroutineScope.setAtRoomNotificationsEnabled(event.enabled, changeNotificationSettingAction)
}
is NotificationSettingsEvents.SetCallNotificationsEnabled -> {
localCoroutineScope.setCallNotificationsEnabled(event.enabled, changeNotificationSettingAction)
}
is NotificationSettingsEvents.SetNotificationsEnabled -> localCoroutineScope.setNotificationsEnabled(userPushStore, event.enabled) is NotificationSettingsEvents.SetNotificationsEnabled -> localCoroutineScope.setNotificationsEnabled(userPushStore, event.enabled)
NotificationSettingsEvents.ClearConfigurationMismatchError -> { NotificationSettingsEvents.ClearConfigurationMismatchError -> {
matrixSettings.value = NotificationSettingsState.MatrixSettings.Invalid(fixFailed = false) matrixSettings.value = NotificationSettingsState.MatrixSettings.Invalid(fixFailed = false)
@ -77,6 +84,7 @@ class NotificationSettingsPresenter @Inject constructor(
NotificationSettingsEvents.RefreshSystemNotificationsEnabled -> { NotificationSettingsEvents.RefreshSystemNotificationsEnabled -> {
systemNotificationsEnabled.value = systemNotificationsEnabledProvider.notificationsEnabled() systemNotificationsEnabled.value = systemNotificationsEnabledProvider.notificationsEnabled()
} }
NotificationSettingsEvents.ClearNotificationChangeError -> changeNotificationSettingAction.value = Async.Uninitialized
} }
} }
@ -86,6 +94,7 @@ class NotificationSettingsPresenter @Inject constructor(
systemNotificationsEnabled = systemNotificationsEnabled.value, systemNotificationsEnabled = systemNotificationsEnabled.value,
appNotificationsEnabled = appNotificationsEnabled.value appNotificationsEnabled = appNotificationsEnabled.value
), ),
changeNotificationSettingAction = changeNotificationSettingAction.value,
eventSink = ::handleEvents eventSink = ::handleEvents
) )
} }
@ -154,12 +163,16 @@ class NotificationSettingsPresenter @Inject constructor(
) )
} }
private fun CoroutineScope.setAtRoomNotificationsEnabled(enabled: Boolean) = launch { private fun CoroutineScope.setAtRoomNotificationsEnabled(enabled: Boolean, action: MutableState<Async<Unit>>) = launch {
notificationSettingsService.setRoomMentionEnabled(enabled) suspend {
notificationSettingsService.setRoomMentionEnabled(enabled).getOrThrow()
}.runCatchingUpdatingState(action)
} }
private fun CoroutineScope.setCallNotificationsEnabled(enabled: Boolean) = launch { private fun CoroutineScope.setCallNotificationsEnabled(enabled: Boolean, action: MutableState<Async<Unit>>) = launch {
notificationSettingsService.setCallEnabled(enabled) suspend {
notificationSettingsService.setCallEnabled(enabled).getOrThrow()
}.runCatchingUpdatingState(action)
} }
private fun CoroutineScope.setNotificationsEnabled(userPushStore: UserPushStore, enabled: Boolean) = launch { private fun CoroutineScope.setNotificationsEnabled(userPushStore: UserPushStore, enabled: Boolean) = launch {

View file

@ -17,12 +17,14 @@
package io.element.android.features.preferences.impl.notifications package io.element.android.features.preferences.impl.notifications
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationMode
@Immutable @Immutable
data class NotificationSettingsState( data class NotificationSettingsState(
val matrixSettings: MatrixSettings, val matrixSettings: MatrixSettings,
val appSettings: AppSettings, val appSettings: AppSettings,
val changeNotificationSettingAction: Async<Unit>,
val eventSink: (NotificationSettingsEvents) -> Unit, val eventSink: (NotificationSettingsEvents) -> Unit,
) { ) {
sealed interface MatrixSettings { sealed interface MatrixSettings {

View file

@ -17,6 +17,7 @@
package io.element.android.features.preferences.impl.notifications package io.element.android.features.preferences.impl.notifications
import androidx.compose.ui.tooling.preview.PreviewParameterProvider 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.RoomNotificationMode
open class NotificationSettingsStateProvider : PreviewParameterProvider<NotificationSettingsState> { open class NotificationSettingsStateProvider : PreviewParameterProvider<NotificationSettingsState> {
@ -37,5 +38,6 @@ fun aNotificationSettingsState() = NotificationSettingsState(
systemNotificationsEnabled = false, systemNotificationsEnabled = false,
appNotificationsEnabled = true, appNotificationsEnabled = true,
), ),
changeNotificationSettingAction = Async.Uninitialized,
eventSink = {} eventSink = {}
) )

View file

@ -36,6 +36,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import io.element.android.libraries.androidutils.system.startNotificationSettingsIntent 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.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory 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.PreferenceSwitch
@ -91,6 +93,19 @@ fun NotificationSettingsView(
// onCallsNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetCallNotificationsEnabled(it)) }, // 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
}
} }
} }

View file

@ -21,12 +21,14 @@ import androidx.compose.ui.Modifier
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.core.plugin.plugins
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.inputs import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.core.RoomId
@ContributesNode(SessionScope::class) @ContributesNode(SessionScope::class)
class EditDefaultNotificationSettingNode @AssistedInject constructor( class EditDefaultNotificationSettingNode @AssistedInject constructor(
@ -35,20 +37,30 @@ class EditDefaultNotificationSettingNode @AssistedInject constructor(
presenterFactory: EditDefaultNotificationSettingPresenter.Factory presenterFactory: EditDefaultNotificationSettingPresenter.Factory
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
interface Callback : Plugin {
fun openRoomNotificationSettings(roomId: RoomId)
}
data class Inputs( data class Inputs(
val isOneToOne: Boolean val isOneToOne: Boolean
) : NodeInputs ) : NodeInputs
private val inputs = inputs<Inputs>() private val inputs = inputs<Inputs>()
private val callbacks = plugins<Callback>()
private val presenter = presenterFactory.create(inputs.isOneToOne) private val presenter = presenterFactory.create(inputs.isOneToOne)
private fun openRoomNotificationSettings(roomId: RoomId) {
callbacks.forEach { it.openRoomNotificationSettings(roomId) }
}
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state = presenter.present() val state = presenter.present()
EditDefaultNotificationSettingView( EditDefaultNotificationSettingView(
state = state, state = state,
openRoomNotificationSettings = { openRoomNotificationSettings(it) },
onBackPressed = ::navigateUp, onBackPressed = ::navigateUp,
modifier = modifier modifier = modifier,
) )
} }
} }

View file

@ -25,7 +25,9 @@ import androidx.compose.runtime.rememberCoroutineScope
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter 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.MatrixClient
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService 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.RoomNotificationMode
@ -57,6 +59,8 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor(
mutableStateOf(null) mutableStateOf(null)
} }
val changeNotificationSettingAction: MutableState<Async<Unit>> = remember { mutableStateOf(Async.Uninitialized) }
val roomsWithUserDefinedMode: MutableState<List<RoomSummary.Filled>> = remember { val roomsWithUserDefinedMode: MutableState<List<RoomSummary.Filled>> = remember {
mutableStateOf(listOf()) mutableStateOf(listOf())
} }
@ -70,7 +74,10 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor(
fun handleEvents(event: EditDefaultNotificationSettingStateEvents) { fun handleEvents(event: EditDefaultNotificationSettingStateEvents) {
when (event) { 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, isOneToOne = isOneToOne,
mode = mode.value, mode = mode.value,
roomsWithUserDefinedMode = roomsWithUserDefinedMode.value, roomsWithUserDefinedMode = roomsWithUserDefinedMode.value,
changeNotificationSettingAction = changeNotificationSettingAction.value,
eventSink = ::handleEvents eventSink = ::handleEvents
) )
} }
@ -105,9 +113,13 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor(
.launchIn(this) .launchIn(this)
} }
private fun CoroutineScope.updateRoomsWithUserDefinedMode(summaries: List<RoomSummary>, roomsWithUserDefinedMode: MutableState<List<RoomSummary.Filled>>) = launch { private fun CoroutineScope.updateRoomsWithUserDefinedMode(
val roomWithUserDefinedRules = notificationSettingsService.getRoomsWithUserDefinedRules().getOrThrow().toSet() summaries: List<RoomSummary>,
roomsWithUserDefinedMode.value = summaries roomsWithUserDefinedMode: MutableState<List<RoomSummary.Filled>>
) = launch {
val roomWithUserDefinedRules: Set<String> = notificationSettingsService.getRoomsWithUserDefinedRules().getOrThrow().toSet()
val sortedSummaries = summaries
.filterIsInstance<RoomSummary.Filled>() .filterIsInstance<RoomSummary.Filled>()
.filter { .filter {
val room = matrixClient.getRoom(it.details.roomId) ?: return@filter false val room = matrixClient.getRoom(it.details.roomId) ?: return@filter false
@ -115,12 +127,16 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor(
} }
// locale sensitive sorting // locale sensitive sorting
.sortedWith(compareBy(Collator.getInstance()){ it.details.name }) .sortedWith(compareBy(Collator.getInstance()){ it.details.name })
roomsWithUserDefinedMode.value = sortedSummaries
} }
private fun CoroutineScope.setDefaultNotificationMode(mode: RoomNotificationMode) = launch { private fun CoroutineScope.setDefaultNotificationMode(mode: RoomNotificationMode, action: MutableState<Async<Unit>>) = launch {
// On modern clients, we don't have different settings for encrypted and non-encrypted rooms (Legacy clients did). suspend {
notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = true, mode = mode, isOneToOne = isOneToOne) // On modern clients, we don't have different settings for encrypted and non-encrypted rooms (Legacy clients did).
notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = false, mode = mode, isOneToOne = isOneToOne) notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = true, mode = mode, isOneToOne = isOneToOne).getOrThrow()
notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = false, mode = mode, isOneToOne = isOneToOne).getOrThrow()
}.runCatchingUpdatingState(action)
} }
} }

View file

@ -16,6 +16,7 @@
package io.element.android.features.preferences.impl.notifications.edit 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.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.api.roomlist.RoomSummary
@ -23,5 +24,6 @@ data class EditDefaultNotificationSettingState(
val isOneToOne: Boolean, val isOneToOne: Boolean,
val mode: RoomNotificationMode?, val mode: RoomNotificationMode?,
val roomsWithUserDefinedMode: List<RoomSummary.Filled>, val roomsWithUserDefinedMode: List<RoomSummary.Filled>,
val changeNotificationSettingAction: Async<Unit>,
val eventSink: (EditDefaultNotificationSettingStateEvents) -> Unit, val eventSink: (EditDefaultNotificationSettingStateEvents) -> Unit,
) )

View file

@ -20,4 +20,5 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode
sealed interface EditDefaultNotificationSettingStateEvents { sealed interface EditDefaultNotificationSettingStateEvents {
data class SetNotificationMode(val mode: RoomNotificationMode): EditDefaultNotificationSettingStateEvents data class SetNotificationMode(val mode: RoomNotificationMode): EditDefaultNotificationSettingStateEvents
data object ClearError: EditDefaultNotificationSettingStateEvents
} }

View file

@ -17,19 +17,17 @@
package io.element.android.features.preferences.impl.notifications.edit package io.element.android.features.preferences.impl.notifications.edit
import androidx.compose.foundation.layout.Column 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.foundation.selection.selectableGroup
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.features.preferences.impl.notifications.NotificationSettingsState import io.element.android.libraries.architecture.Async
import io.element.android.features.preferences.impl.notifications.NotificationSettingsStateProvider import io.element.android.libraries.designsystem.components.ProgressDialog
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.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarData 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.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.list.ListItemContent
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
import io.element.android.libraries.designsystem.components.preferences.PreferenceView 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.preview.ElementPreview
import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.ListItem
import io.element.android.libraries.designsystem.theme.components.Text 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.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
@ -47,11 +46,12 @@ import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun EditDefaultNotificationSettingView( fun EditDefaultNotificationSettingView(
state: EditDefaultNotificationSettingState, state: EditDefaultNotificationSettingState,
openRoomNotificationSettings:(roomId: RoomId) -> Unit,
onBackPressed: () -> Unit, onBackPressed: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val title = if(state.isOneToOne) { val title = if (state.isOneToOne) {
CommonStrings.screen_notification_settings_direct_chats CommonStrings.screen_notification_settings_direct_chats
} else { } else {
CommonStrings.screen_notification_settings_group_chats CommonStrings.screen_notification_settings_group_chats
@ -65,7 +65,7 @@ fun EditDefaultNotificationSettingView(
// Only ALL_MESSAGES and MENTIONS_AND_KEYWORDS_ONLY are valid global defaults. // Only ALL_MESSAGES and MENTIONS_AND_KEYWORDS_ONLY are valid global defaults.
val validModes = listOf(RoomNotificationMode.ALL_MESSAGES, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) 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 CommonStrings.screen_notification_settings_edit_screen_direct_section_header
} else { } else {
CommonStrings.screen_notification_settings_edit_screen_group_section_header 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)) { PreferenceCategory(title = stringResource(id = CommonStrings.screen_notification_settings_edit_custom_settings_section_title)) {
LazyColumn { state.roomsWithUserDefinedMode.forEach { summary ->
items(state.roomsWithUserDefinedMode) { summary -> val subtitle = when (summary.details.notificationMode) {
val subtitle = when (summary.details.notificationMode) { RoomNotificationMode.ALL_MESSAGES -> stringResource(id = CommonStrings.screen_notification_settings_edit_mode_all_messages)
RoomNotificationMode.ALL_MESSAGES -> stringResource(id = CommonStrings.screen_notification_settings_edit_mode_all_messages) RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> {
RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> stringResource(id = CommonStrings.screen_notification_settings_edit_mode_mentions_and_keywords) stringResource(id = CommonStrings.screen_notification_settings_edit_mode_mentions_and_keywords)
RoomNotificationMode.MUTE -> stringResource(id = CommonStrings.common_mute)
null -> ""
} }
val avatarData = AvatarData( RoomNotificationMode.MUTE -> stringResource(id = CommonStrings.common_mute)
id = summary.identifier(), null -> ""
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)
}
)
} }
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 @DayNightPreviews
@Composable @Composable
internal fun EditDefaultNotificationSettingViewPreview(@PreviewParameter(EditDefaultNotificationSettingsStateProvider::class) state: EditDefaultNotificationSettingState) = ElementPreview { internal fun EditDefaultNotificationSettingViewPreview(
@PreviewParameter(EditDefaultNotificationSettingsStateProvider::class) state: EditDefaultNotificationSettingState
) = ElementPreview {
EditDefaultNotificationSettingView( EditDefaultNotificationSettingView(
state = state, state = state,
openRoomNotificationSettings = {},
onBackPressed = {}, onBackPressed = {},
) )
} }

View file

@ -17,6 +17,7 @@
package io.element.android.features.preferences.impl.notifications.edit package io.element.android.features.preferences.impl.notifications.edit
import androidx.compose.ui.tooling.preview.PreviewParameterProvider 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.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomNotificationMode 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.RoomSummary
@ -33,6 +34,7 @@ fun anEditDefaultNotificationSettingsState() = EditDefaultNotificationSettingSta
isOneToOne = false, isOneToOne = false,
mode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, mode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
roomsWithUserDefinedMode = listOf(aRoomSummary()), roomsWithUserDefinedMode = listOf(aRoomSummary()),
changeNotificationSettingAction = Async.Uninitialized,
eventSink = {} eventSink = {}
) )

View file

@ -33,6 +33,9 @@ interface RoomDetailsEntryPoint : FeatureEntryPoint {
@Parcelize @Parcelize
data class RoomMemberDetails(val roomMemberId: UserId) : InitialTarget data class RoomMemberDetails(val roomMemberId: UserId) : InitialTarget
@Parcelize
data object RoomNotificationSettings : InitialTarget
} }
data class Inputs(val initialElement: InitialTarget) : NodeInputs data class Inputs(val initialElement: InitialTarget) : NodeInputs

View file

@ -42,4 +42,5 @@ class DefaultRoomDetailsEntryPoint @Inject constructor() : RoomDetailsEntryPoint
internal fun InitialTarget.toNavTarget() = when (this) { internal fun InitialTarget.toNavTarget() = when (this) {
is InitialTarget.RoomDetails -> NavTarget.RoomDetails is InitialTarget.RoomDetails -> NavTarget.RoomDetails
is InitialTarget.RoomMemberDetails -> NavTarget.RoomMemberDetails(roomMemberId) is InitialTarget.RoomMemberDetails -> NavTarget.RoomMemberDetails(roomMemberId)
is InitialTarget.RoomNotificationSettings -> NavTarget.RoomNotificationSettings(showUserDefinedSettingStyle = true)
} }

View file

@ -68,7 +68,13 @@ class RoomDetailsFlowNode @AssistedInject constructor(
data object InviteMembers : NavTarget data object InviteMembers : NavTarget
@Parcelize @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 @Parcelize
data class RoomMemberDetails(val roomMemberId: UserId) : NavTarget data class RoomMemberDetails(val roomMemberId: UserId) : NavTarget
@ -91,7 +97,7 @@ class RoomDetailsFlowNode @AssistedInject constructor(
} }
override fun openRoomNotificationSettings() { override fun openRoomNotificationSettings() {
backstack.push(NavTarget.RoomNotificationSettings) backstack.push(NavTarget.RoomNotificationSettings(showUserDefinedSettingStyle = false))
} }
} }
createNode<RoomDetailsNode>(buildContext, listOf(roomDetailsCallback)) createNode<RoomDetailsNode>(buildContext, listOf(roomDetailsCallback))
@ -118,8 +124,9 @@ class RoomDetailsFlowNode @AssistedInject constructor(
createNode<RoomInviteMembersNode>(buildContext) createNode<RoomInviteMembersNode>(buildContext)
} }
NavTarget.RoomNotificationSettings -> { is NavTarget.RoomNotificationSettings -> {
createNode<RoomNotificationSettingsNode>(buildContext) val plugins = listOf(RoomNotificationSettingsNode.RoomNotificationSettingInput(navTarget.showUserDefinedSettingStyle))
createNode<RoomNotificationSettingsNode>(buildContext, plugins)
} }
is NavTarget.RoomMemberDetails -> { is NavTarget.RoomMemberDetails -> {

View file

@ -21,4 +21,6 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode
sealed interface RoomNotificationSettingsEvents { sealed interface RoomNotificationSettingsEvents {
data class RoomNotificationModeChanged(val mode: RoomNotificationMode) : RoomNotificationSettingsEvents data class RoomNotificationModeChanged(val mode: RoomNotificationMode) : RoomNotificationSettingsEvents
data class SetNotificationMode(val isDefault: Boolean): RoomNotificationSettingsEvents data class SetNotificationMode(val isDefault: Boolean): RoomNotificationSettingsEvents
data object DeleteCustomNotification: RoomNotificationSettingsEvents
data object ClearError: RoomNotificationSettingsEvents
} }

View file

@ -26,6 +26,8 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.analytics.plan.MobileScreen
import io.element.android.anvilannotations.ContributesNode 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.libraries.di.RoomScope
import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.AnalyticsService
@ -37,6 +39,12 @@ class RoomNotificationSettingsNode @AssistedInject constructor(
private val analyticsService: AnalyticsService, private val analyticsService: AnalyticsService,
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
data class RoomNotificationSettingInput(
val showUserDefinedSettingStyle: Boolean
) : NodeInputs
private val inputs = inputs<RoomNotificationSettingInput>()
init { init {
lifecycle.subscribe( lifecycle.subscribe(
onResume = { onResume = {
@ -48,10 +56,18 @@ class RoomNotificationSettingsNode @AssistedInject constructor(
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state = presenter.present() val state = presenter.present()
RoomNotificationSettingsView( if(inputs.showUserDefinedSettingStyle) {
state = state, UserDefinedRoomNotificationSettingsView(
modifier = modifier, state = state,
onBackPressed = this::navigateUp, modifier = modifier,
) onBackPressed = this::navigateUp,
)
} else {
RoomNotificationSettingsView(
state = state,
modifier = modifier,
onBackPressed = this::navigateUp,
)
}
} }
} }

View file

@ -22,9 +22,12 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable 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.Presenter
import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService 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.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationMode
@ -42,16 +45,18 @@ class RoomNotificationSettingsPresenter @Inject constructor(
private val room: MatrixRoom, private val room: MatrixRoom,
private val notificationSettingsService: NotificationSettingsService, private val notificationSettingsService: NotificationSettingsService,
) : Presenter<RoomNotificationSettingsState> { ) : Presenter<RoomNotificationSettingsState> {
@Composable @Composable
override fun present(): RoomNotificationSettingsState { override fun present(): RoomNotificationSettingsState {
val defaultRoomNotificationMode: MutableState<RoomNotificationMode?> = rememberSaveable { val defaultRoomNotificationMode: MutableState<RoomNotificationMode?> = rememberSaveable {
mutableStateOf(null) mutableStateOf(null)
} }
val localCoroutineScope = rememberCoroutineScope() val localCoroutineScope = rememberCoroutineScope()
val changeNotificationSettingAction: MutableState<Async<Unit>> = remember { mutableStateOf(Async.Uninitialized) }
val deleteCustomNotificationSettingAction: MutableState<Async<Unit>> = remember { mutableStateOf(Async.Uninitialized) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
getDefaultRoomNotificationMode(defaultRoomNotificationMode) getDefaultRoomNotificationMode(defaultRoomNotificationMode)
room.updateRoomNotificationSettings()
observeNotificationSettings() observeNotificationSettings()
} }
@ -60,23 +65,32 @@ class RoomNotificationSettingsPresenter @Inject constructor(
fun handleEvents(event: RoomNotificationSettingsEvents) { fun handleEvents(event: RoomNotificationSettingsEvents) {
when (event) { when (event) {
is RoomNotificationSettingsEvents.RoomNotificationModeChanged -> { is RoomNotificationSettingsEvents.RoomNotificationModeChanged -> {
localCoroutineScope.setRoomNotificationMode(event.mode) localCoroutineScope.setRoomNotificationMode(event.mode, changeNotificationSettingAction)
} }
is RoomNotificationSettingsEvents.SetNotificationMode -> { is RoomNotificationSettingsEvents.SetNotificationMode -> {
if (event.isDefault) { if (event.isDefault) {
localCoroutineScope.restoreDefaultRoomNotificationMode() localCoroutineScope.restoreDefaultRoomNotificationMode(changeNotificationSettingAction)
} else { } else {
defaultRoomNotificationMode.value?.let { defaultRoomNotificationMode.value?.let {
localCoroutineScope.setRoomNotificationMode(it) localCoroutineScope.setRoomNotificationMode(it, changeNotificationSettingAction)
} }
} }
} }
is RoomNotificationSettingsEvents.DeleteCustomNotification -> {
localCoroutineScope.restoreDefaultRoomNotificationMode(deleteCustomNotificationSettingAction)
}
RoomNotificationSettingsEvents.ClearError -> {
changeNotificationSettingAction.value = Async.Uninitialized
}
} }
} }
return RoomNotificationSettingsState( return RoomNotificationSettingsState(
roomName = room.displayName,
roomNotificationSettings = roomNotificationSettingsState.roomNotificationSettings(), roomNotificationSettings = roomNotificationSettingsState.roomNotificationSettings(),
defaultRoomNotificationMode = defaultRoomNotificationMode.value, defaultRoomNotificationMode = defaultRoomNotificationMode.value,
changeNotificationSettingAction = changeNotificationSettingAction.value,
deleteCustomNotificationSettingAction = deleteCustomNotificationSettingAction.value,
eventSink = ::handleEvents, eventSink = ::handleEvents,
) )
} }
@ -98,11 +112,15 @@ class RoomNotificationSettingsPresenter @Inject constructor(
).getOrThrow() ).getOrThrow()
} }
private fun CoroutineScope.setRoomNotificationMode(mode: RoomNotificationMode) = launch { private fun CoroutineScope.setRoomNotificationMode(mode: RoomNotificationMode, action: MutableState<Async<Unit>>) = launch {
notificationSettingsService.setRoomNotificationMode(room.roomId, mode) suspend {
notificationSettingsService.setRoomNotificationMode(room.roomId, mode).getOrThrow()
}.runCatchingUpdatingState(action)
} }
private fun CoroutineScope.restoreDefaultRoomNotificationMode() = launch { private fun CoroutineScope.restoreDefaultRoomNotificationMode(action: MutableState<Async<Unit>>) = launch {
notificationSettingsService.restoreDefaultRoomNotificationMode(room.roomId) suspend {
notificationSettingsService.restoreDefaultRoomNotificationMode(room.roomId).getOrThrow()
}.runCatchingUpdatingState(action)
} }
} }

View file

@ -16,11 +16,15 @@
package io.element.android.features.roomdetails.impl.notificationsettings 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.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
data class RoomNotificationSettingsState( data class RoomNotificationSettingsState(
val roomName: String,
val roomNotificationSettings: RoomNotificationSettings?, val roomNotificationSettings: RoomNotificationSettings?,
val defaultRoomNotificationMode: RoomNotificationMode?, val defaultRoomNotificationMode: RoomNotificationMode?,
val changeNotificationSettingAction: Async<Unit>,
val deleteCustomNotificationSettingAction: Async<Unit>,
val eventSink: (RoomNotificationSettingsEvents) -> Unit val eventSink: (RoomNotificationSettingsEvents) -> Unit
) )

View file

@ -17,6 +17,7 @@
package io.element.android.features.roomdetails.impl.notificationsettings package io.element.android.features.roomdetails.impl.notificationsettings
import androidx.compose.ui.tooling.preview.PreviewParameterProvider 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.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
@ -24,10 +25,13 @@ internal class RoomNotificationSettingsStateProvider : PreviewParameterProvider<
override val values: Sequence<RoomNotificationSettingsState> override val values: Sequence<RoomNotificationSettingsState>
get() = sequenceOf( get() = sequenceOf(
RoomNotificationSettingsState( RoomNotificationSettingsState(
roomName = "Room 1",
RoomNotificationSettings( RoomNotificationSettings(
mode = RoomNotificationMode.MUTE, mode = RoomNotificationMode.MUTE,
isDefault = true), isDefault = true),
RoomNotificationMode.ALL_MESSAGES, RoomNotificationMode.ALL_MESSAGES,
changeNotificationSettingAction = Async.Uninitialized,
deleteCustomNotificationSettingAction = Async.Uninitialized,
eventSink = { }, eventSink = { },
), ),
) )

View file

@ -18,7 +18,6 @@ package io.element.android.features.roomdetails.impl.notificationsettings
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding 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.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.features.roomdetails.impl.R 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.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.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.PreferenceCategory
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
import io.element.android.libraries.designsystem.components.preferences.PreferenceText 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.theme.ElementTheme
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
fun RoomNotificationSettingsView( fun RoomNotificationSettingsView(
state: RoomNotificationSettingsState, state: RoomNotificationSettingsState,
@ -74,7 +75,6 @@ fun RoomNotificationSettingsView(
null -> "" null -> ""
} }
PreferenceCategory(title = stringResource(id = R.string.screen_room_notification_settings_custom_settings_title)) { PreferenceCategory(title = stringResource(id = R.string.screen_room_notification_settings_custom_settings_title)) {
PreferenceSwitch( PreferenceSwitch(
isChecked = state.roomNotificationSettings?.isDefault.orTrue(), 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 @DayNightPreviews
@Composable @Composable
internal fun RoomNotificationSettingsPreview( internal fun RoomNotificationSettingsPreview(

View file

@ -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) },
)
}