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
This commit is contained in:
David Langley 2023-09-22 16:16:52 +01:00
parent ab33d064bb
commit a9d87da1ff
7 changed files with 147 additions and 0 deletions

View file

@ -26,19 +26,25 @@ 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.Presenter 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.notificationsettings.NotificationSettingsService
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.RoomListService
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.text.Collator
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
class EditDefaultNotificationSettingPresenter @AssistedInject constructor( class EditDefaultNotificationSettingPresenter @AssistedInject constructor(
private val notificationSettingsService: NotificationSettingsService, private val notificationSettingsService: NotificationSettingsService,
@Assisted private val isOneToOne: Boolean, @Assisted private val isOneToOne: Boolean,
private val roomListService: RoomListService,
private val matrixClient: MatrixClient,
) : Presenter<EditDefaultNotificationSettingState> { ) : Presenter<EditDefaultNotificationSettingState> {
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
@ -50,10 +56,16 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor(
val mode: MutableState<RoomNotificationMode?> = remember { val mode: MutableState<RoomNotificationMode?> = remember {
mutableStateOf(null) mutableStateOf(null)
} }
val roomsWithUserDefinedMode: MutableState<List<RoomSummary.Filled>> = remember {
mutableStateOf(listOf())
}
val localCoroutineScope = rememberCoroutineScope() val localCoroutineScope = rememberCoroutineScope()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
fetchSettings(mode) fetchSettings(mode)
observeNotificationSettings(mode) observeNotificationSettings(mode)
observeRoomSummaries(roomsWithUserDefinedMode)
} }
fun handleEvents(event: EditDefaultNotificationSettingStateEvents) { fun handleEvents(event: EditDefaultNotificationSettingStateEvents) {
@ -65,6 +77,7 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor(
return EditDefaultNotificationSettingState( return EditDefaultNotificationSettingState(
isOneToOne = isOneToOne, isOneToOne = isOneToOne,
mode = mode.value, mode = mode.value,
roomsWithUserDefinedMode = roomsWithUserDefinedMode.value,
eventSink = ::handleEvents eventSink = ::handleEvents
) )
} }
@ -83,6 +96,27 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor(
.launchIn(this) .launchIn(this)
} }
private fun CoroutineScope.observeRoomSummaries(roomsWithUserDefinedMode: MutableState<List<RoomSummary.Filled>>) {
roomListService.allRooms()
.summaries
.onEach {
updateRoomsWithUserDefinedMode(it, roomsWithUserDefinedMode)
}
.launchIn(this)
}
private fun CoroutineScope.updateRoomsWithUserDefinedMode(summaries: List<RoomSummary>, roomsWithUserDefinedMode: MutableState<List<RoomSummary.Filled>>) = launch {
val roomWithUserDefinedRules = notificationSettingsService.getRoomsWithUserDefinedRules().getOrThrow().toSet()
roomsWithUserDefinedMode.value = summaries
.filterIsInstance<RoomSummary.Filled>()
.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 { 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). // 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 = true, mode = mode, isOneToOne = isOneToOne)

View file

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

View file

@ -17,12 +17,26 @@
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 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.PreferenceCategory
import io.element.android.libraries.designsystem.components.preferences.PreferenceView 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.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.ui.strings.CommonStrings 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 = {},
)
}

View file

@ -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<EditDefaultNotificationSettingState> {
override val values: Sequence<EditDefaultNotificationSettingState>
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,
)
)

View file

@ -46,4 +46,6 @@ enum class AvatarSize(val dp: Dp) {
EditRoomDetails(70.dp), EditRoomDetails(70.dp),
NotificationsOptIn(32.dp), NotificationsOptIn(32.dp),
CustomRoomNotificationSetting(36.dp)
} }

View file

@ -38,4 +38,5 @@ interface NotificationSettingsService {
suspend fun setRoomMentionEnabled(enabled: Boolean): Result<Unit> suspend fun setRoomMentionEnabled(enabled: Boolean): Result<Unit>
suspend fun isCallEnabled(): Result<Boolean> suspend fun isCallEnabled(): Result<Boolean>
suspend fun setCallEnabled(enabled: Boolean): Result<Unit> suspend fun setCallEnabled(enabled: Boolean): Result<Unit>
suspend fun getRoomsWithUserDefinedRules(): Result<List<String>>
} }

View file

@ -110,4 +110,9 @@ class RustNotificationSettingsService(
notificationSettings.setCallEnabled(enabled) notificationSettings.setCallEnabled(enabled)
} }
} }
override suspend fun getRoomsWithUserDefinedRules(): Result<List<String>> =
runCatching {
notificationSettings.getRoomsWithUserDefinedRules(enabled = true)
}
} }