Add room notification settings (#807)

* Add room notification settings

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
Co-authored-by: Jorge Martín <jorgem@element.io>
Co-authored-by: Benoit Marty <benoit@matrix.org>
Co-authored-by: David Langley <langley.dave@gmail.com>
This commit is contained in:
Yoan Pintas 2023-09-07 08:24:34 +00:00 committed by GitHub
parent a40c9ef002
commit 4a5a01d710
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
68 changed files with 1381 additions and 70 deletions

View file

@ -39,6 +39,8 @@ import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.toEnabledColor
import io.element.android.libraries.designsystem.toSecondaryEnabledColor
import io.element.android.libraries.theme.ElementTheme
/**
@ -48,6 +50,7 @@ import io.element.android.libraries.theme.ElementTheme
fun PreferenceText(
title: String,
modifier: Modifier = Modifier,
enabled: Boolean = true,
subtitle: String? = null,
currentValue: String? = null,
loadingCurrentValue: Boolean = false,
@ -68,8 +71,9 @@ fun PreferenceText(
) {
PreferenceIcon(
icon = icon,
enabled = enabled,
isVisible = showIconAreaIfNoIcon,
tintColor = tintColor ?: ElementTheme.materialColors.secondary
tintColor = tintColor ?: enabled.toSecondaryEnabledColor(),
)
Column(
modifier = Modifier
@ -79,13 +83,13 @@ fun PreferenceText(
Text(
style = ElementTheme.typography.fontBodyLgRegular,
text = title,
color = tintColor ?: ElementTheme.materialColors.primary,
color = tintColor ?: enabled.toEnabledColor(),
)
if (subtitle != null) {
Text(
style = ElementTheme.typography.fontBodyMdRegular,
text = subtitle,
color = tintColor ?: ElementTheme.materialColors.secondary,
color = tintColor ?: enabled.toSecondaryEnabledColor(),
)
}
}
@ -96,7 +100,7 @@ fun PreferenceText(
.padding(start = 16.dp, end = 8.dp),
text = currentValue,
style = ElementTheme.typography.fontBodyXsMedium,
color = ElementTheme.materialColors.secondary,
color = enabled.toSecondaryEnabledColor(),
)
} else if (loadingCurrentValue) {
CircularProgressIndicator(
@ -135,6 +139,13 @@ private fun ContentToPreview() {
icon = Icons.Default.BugReport,
currentValue = "123",
)
PreferenceText(
title = "Title",
subtitle = "Some content",
icon = Icons.Default.BugReport,
currentValue = "123",
enabled = false,
)
PreferenceText(
title = "Title",
subtitle = "Some content",

View file

@ -30,5 +30,11 @@ enum class FeatureFlags(
key = "feature.polls",
title = "Polls",
description = "Create poll and render poll events in the timeline",
defaultValue = false,
),
NotificationSettings(
key = "feature.notificationsettings",
title = "Show notification settings",
defaultValue = false,
),
}

View file

@ -31,6 +31,7 @@ class BuildtimeFeatureFlagProvider @Inject constructor() :
when (feature) {
FeatureFlags.LocationSharing -> true
FeatureFlags.Polls -> false
FeatureFlags.NotificationSettings -> false
}
} else {
false

View file

@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.notification.NotificationService
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.pusher.PushersService
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
@ -49,6 +50,7 @@ interface MatrixClient : Closeable {
fun sessionVerificationService(): SessionVerificationService
fun pushersService(): PushersService
fun notificationService(): NotificationService
fun notificationSettingsService(): NotificationSettingsService
suspend fun getCacheSize(): Long
/**

View file

@ -0,0 +1,36 @@
/*
* 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.libraries.matrix.api.notificationsettings
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.MatrixRoomNotificationSettingsState
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
import kotlinx.coroutines.flow.SharedFlow
interface NotificationSettingsService {
/**
* State of the current room notification settings flow ([MatrixRoomNotificationSettingsState.Unknown] if not started).
*/
val notificationSettingsChangeFlow : SharedFlow<Unit>
suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, membersCount: Long): Result<RoomNotificationSettings>
suspend fun getDefaultRoomNotificationMode(isEncrypted: Boolean, membersCount: Long): Result<RoomNotificationMode>
suspend fun setRoomNotificationMode(roomId: RoomId, mode: RoomNotificationMode): Result<Unit>
suspend fun restoreDefaultRoomNotificationMode(roomId: RoomId): Result<Unit>
suspend fun muteRoom(roomId: RoomId): Result<Unit>
suspend fun unmuteRoom(roomId: RoomId, isEncrypted: Boolean, membersCount: Long): Result<Unit>
}

View file

@ -56,11 +56,15 @@ interface MatrixRoom : Closeable {
*/
val membersStateFlow: StateFlow<MatrixRoomMembersState>
val roomNotificationSettingsStateFlow: StateFlow<MatrixRoomNotificationSettingsState>
/**
* Try to load the room members and update the membersFlow.
*/
suspend fun updateMembers(): Result<Unit>
suspend fun updateRoomNotificationSettings(): Result<Unit>
val syncUpdateFlow: StateFlow<Long>
val timeline: MatrixTimeline

View file

@ -0,0 +1,33 @@
/*
* 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.libraries.matrix.api.room
sealed interface MatrixRoomNotificationSettingsState {
object Unknown : MatrixRoomNotificationSettingsState
data class Pending(val prevRoomNotificationSettings: RoomNotificationSettings? = null) : MatrixRoomNotificationSettingsState
data class Error(val failure: Throwable, val prevRoomNotificationSettings: RoomNotificationSettings? = null) : MatrixRoomNotificationSettingsState
data class Ready(val roomNotificationSettings: RoomNotificationSettings) : MatrixRoomNotificationSettingsState
}
fun MatrixRoomNotificationSettingsState.roomNotificationSettings(): RoomNotificationSettings? {
return when (this) {
is MatrixRoomNotificationSettingsState.Ready -> roomNotificationSettings
is MatrixRoomNotificationSettingsState.Pending -> prevRoomNotificationSettings
is MatrixRoomNotificationSettingsState.Error -> prevRoomNotificationSettings
else -> null
}
}

View file

@ -0,0 +1,26 @@
/*
* 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.libraries.matrix.api.room
data class RoomNotificationSettings(
val mode: RoomNotificationMode,
val isDefault: Boolean,
)
enum class RoomNotificationMode {
ALL_MESSAGES, MENTIONS_AND_KEYWORDS_ONLY, MUTE
}

View file

@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.api.createroom.RoomPreset
import io.element.android.libraries.matrix.api.createroom.RoomVisibility
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.notification.NotificationService
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.pusher.PushersService
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
@ -42,6 +43,7 @@ import io.element.android.libraries.matrix.impl.core.toProgressWatcher
import io.element.android.libraries.matrix.impl.mapper.toSessionData
import io.element.android.libraries.matrix.impl.media.RustMediaLoader
import io.element.android.libraries.matrix.impl.notification.RustNotificationService
import io.element.android.libraries.matrix.impl.notificationsettings.RustNotificationSettingsService
import io.element.android.libraries.matrix.impl.pushers.RustPushersService
import io.element.android.libraries.matrix.impl.room.RoomContentForwarder
import io.element.android.libraries.matrix.impl.room.RustMatrixRoom
@ -99,6 +101,7 @@ class RustMatrixClient constructor(
client = client,
dispatchers = dispatchers,
)
private val notificationProcessSetup = NotificationProcessSetup.SingleProcess(syncService)
private val notificationClient = client.notificationClient(notificationProcessSetup)
.use { builder ->
@ -106,8 +109,10 @@ class RustMatrixClient constructor(
.filterByPushRules()
.finish()
}
private val notificationSettings = client.getNotificationSettings()
private val notificationService = RustNotificationService(sessionId, notificationClient, dispatchers, clock)
private val notificationSettingsService = RustNotificationSettingsService(notificationSettings, dispatchers)
private val isLoggingOut = AtomicBoolean(false)
@ -173,6 +178,7 @@ class RustMatrixClient constructor(
sessionId = sessionId,
roomListItem = roomListItem,
innerRoom = fullRoom,
roomNotificationSettingsService = notificationSettingsService,
sessionCoroutineScope = sessionCoroutineScope,
coroutineDispatchers = dispatchers,
systemClock = clock,
@ -277,9 +283,13 @@ class RustMatrixClient constructor(
override fun notificationService(): NotificationService = notificationService
override fun notificationSettingsService(): NotificationSettingsService = notificationSettingsService
override fun close() {
sessionCoroutineScope.cancel()
client.setDelegate(null)
notificationSettings.setDelegate(null)
notificationSettings.destroy()
verificationService.destroy()
syncService.destroy()
innerRoomListService.destroy()

View file

@ -22,6 +22,7 @@ import dagger.Provides
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
@ -34,6 +35,11 @@ object SessionMatrixModule {
return matrixClient.sessionVerificationService()
}
@Provides
fun providesNotificationSettingsService(matrixClient: MatrixClient): NotificationSettingsService {
return matrixClient.notificationSettingsService()
}
@Provides
fun provideRoomMembershipObserver(matrixClient: MatrixClient): RoomMembershipObserver {
return matrixClient.roomMembershipObserver()

View file

@ -0,0 +1,44 @@
/*
* 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.libraries.matrix.impl.notificationsettings
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
import org.matrix.rustcomponents.sdk.RoomNotificationMode as RustRoomNotificationMode
import org.matrix.rustcomponents.sdk.RoomNotificationSettings as RustRoomNotificationSettings
object RoomNotificationSettingsMapper {
fun map(roomNotificationSettings: RustRoomNotificationSettings): RoomNotificationSettings =
RoomNotificationSettings(
mode = mapMode(roomNotificationSettings.mode),
isDefault = roomNotificationSettings.isDefault
)
fun mapMode(mode: RustRoomNotificationMode): RoomNotificationMode =
when (mode) {
RustRoomNotificationMode.ALL_MESSAGES -> RoomNotificationMode.ALL_MESSAGES
RustRoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
RustRoomNotificationMode.MUTE -> RoomNotificationMode.MUTE
}
fun mapMode(mode: RoomNotificationMode): RustRoomNotificationMode =
when (mode) {
RoomNotificationMode.ALL_MESSAGES -> RustRoomNotificationMode.ALL_MESSAGES
RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> RustRoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
RoomNotificationMode.MUTE -> RustRoomNotificationMode.MUTE
}
}

View file

@ -0,0 +1,86 @@
/*
* 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.libraries.matrix.impl.notificationsettings
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
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 kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.NotificationSettings
import org.matrix.rustcomponents.sdk.NotificationSettingsDelegate
class RustNotificationSettingsService(
private val notificationSettings: NotificationSettings,
private val dispatchers: CoroutineDispatchers,
) : NotificationSettingsService {
private val _notificationSettingsChangeFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override val notificationSettingsChangeFlow: SharedFlow<Unit> = _notificationSettingsChangeFlow.asSharedFlow()
private var notificationSettingsDelegate = object : NotificationSettingsDelegate {
override fun settingsDidChange() {
_notificationSettingsChangeFlow.tryEmit(Unit)
}
}
init {
notificationSettings.setDelegate(notificationSettingsDelegate)
}
override suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, membersCount: Long): Result<RoomNotificationSettings> =
runCatching {
notificationSettings.getRoomNotificationSettings(roomId.value, isEncrypted, isOneToOne(membersCount)).let(RoomNotificationSettingsMapper::map)
}
override suspend fun getDefaultRoomNotificationMode(isEncrypted: Boolean, membersCount: Long): Result<RoomNotificationMode> =
runCatching {
notificationSettings.getDefaultRoomNotificationMode(isEncrypted, isOneToOne(membersCount)).let(RoomNotificationSettingsMapper::mapMode)
}
override suspend fun setRoomNotificationMode(roomId: RoomId, mode: RoomNotificationMode): Result<Unit> = withContext(dispatchers.io) {
runCatching {
notificationSettings.setRoomNotificationMode(roomId.value, mode.let(RoomNotificationSettingsMapper::mapMode))
}
}
override suspend fun restoreDefaultRoomNotificationMode(roomId: RoomId): Result<Unit> = withContext(dispatchers.io) {
runCatching {
notificationSettings.restoreDefaultRoomNotificationMode(roomId.value)
}
}
override suspend fun muteRoom(roomId: RoomId): Result<Unit> = setRoomNotificationMode(roomId, RoomNotificationMode.MUTE)
override suspend fun unmuteRoom(roomId: RoomId, isEncrypted: Boolean, membersCount: Long) = withContext(dispatchers.io) {
runCatching {
notificationSettings.unmuteRoom(roomId.value, isEncrypted, isOneToOne(membersCount))
}
}
/**
* A one-to-one is a room with exactly 2 members.
* See [the Matrix spec](https://spec.matrix.org/latest/client-server-api/#default-underride-rules).
* @param membersCount The active members count in a room
*/
private fun isOneToOne(membersCount: Long) = membersCount == 2L
}

View file

@ -33,16 +33,19 @@ import io.element.android.libraries.matrix.api.media.VideoInfo
import io.element.android.libraries.matrix.api.poll.PollKind
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.room.MatrixRoomNotificationSettingsState
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.room.roomMembers
import io.element.android.libraries.matrix.api.room.roomNotificationSettings
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
import io.element.android.libraries.matrix.impl.core.toProgressWatcher
import io.element.android.libraries.matrix.impl.media.MediaUploadHandlerImpl
import io.element.android.libraries.matrix.impl.media.map
import io.element.android.libraries.matrix.impl.poll.toInner
import io.element.android.libraries.matrix.impl.notificationsettings.RustNotificationSettingsService
import io.element.android.libraries.matrix.impl.room.location.toInner
import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
import io.element.android.libraries.matrix.impl.util.destroyAll
@ -72,6 +75,7 @@ class RustMatrixRoom(
override val sessionId: SessionId,
private val roomListItem: RoomListItem,
private val innerRoom: Room,
private val roomNotificationSettingsService: RustNotificationSettingsService,
sessionCoroutineScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers,
private val systemClock: SystemClock,
@ -90,6 +94,10 @@ class RustMatrixRoom(
private val roomCoroutineScope = sessionCoroutineScope.childScope(coroutineDispatchers.main, "RoomScope-$roomId")
private val _membersStateFlow = MutableStateFlow<MatrixRoomMembersState>(MatrixRoomMembersState.Unknown)
private val _syncUpdateFlow = MutableStateFlow(0L)
private val _roomNotificationSettingsStateFlow = MutableStateFlow<MatrixRoomNotificationSettingsState>(MatrixRoomNotificationSettingsState.Unknown)
override val roomNotificationSettingsStateFlow: StateFlow<MatrixRoomNotificationSettingsState> = _roomNotificationSettingsStateFlow
private val _timeline by lazy {
RustMatrixTimeline(
matrixRoom = this,
@ -197,6 +205,22 @@ class RustMatrixRoom(
}
}
override suspend fun updateRoomNotificationSettings(): Result<Unit> = withContext(coroutineDispatchers.io) {
val currentState = _roomNotificationSettingsStateFlow.value
val currentRoomNotificationSettings = currentState.roomNotificationSettings()
_roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Pending(prevRoomNotificationSettings = currentRoomNotificationSettings)
runCatching {
roomNotificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, activeMemberCount).getOrThrow()
}.map {
_roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Ready(it)
}.onFailure {
_roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Error(
prevRoomNotificationSettings = currentRoomNotificationSettings,
failure = it
)
}
}
override suspend fun userAvatarUrl(userId: UserId): Result<String?> = withContext(roomDispatcher) {
runCatching {
innerRoom.memberAvatarUrl(userId.value)

View file

@ -24,6 +24,7 @@ import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.notification.NotificationService
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.pusher.PushersService
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
@ -33,6 +34,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.test.media.FakeMediaLoader
import io.element.android.libraries.matrix.test.notification.FakeNotificationService
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
import io.element.android.libraries.matrix.test.pushers.FakePushersService
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
@ -50,6 +52,7 @@ class FakeMatrixClient(
private val sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(),
private val pushersService: FakePushersService = FakePushersService(),
private val notificationService: FakeNotificationService = FakeNotificationService(),
private val notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
private val syncService: FakeSyncService = FakeSyncService(),
private val accountManagementUrlString: Result<String?> = Result.success(null),
) : MatrixClient {
@ -142,6 +145,7 @@ class FakeMatrixClient(
override fun pushersService(): PushersService = pushersService
override fun notificationService(): NotificationService = notificationService
override fun notificationSettingsService(): NotificationSettingsService = notificationSettingsService
override fun roomMembershipObserver(): RoomMembershipObserver {
return RoomMembershipObserver()

View file

@ -24,6 +24,8 @@ import io.element.android.libraries.matrix.api.core.SpaceId
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.core.TransactionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
const val A_USER_NAME = "alice"
const val A_PASSWORD = "password"
@ -59,6 +61,8 @@ const val A_HOMESERVER_URL_2 = "matrix-client.org"
val A_HOMESERVER = MatrixHomeServerDetails(A_HOMESERVER_URL, supportsPasswordLogin = true, supportsOidcLogin = false)
val A_HOMESERVER_OIDC = MatrixHomeServerDetails(A_HOMESERVER_URL, supportsPasswordLogin = false, supportsOidcLogin = true)
val A_ROOM_NOTIFICATION_MODE = RoomNotificationMode.MUTE
val A_ROOM_NOTIFICATION_SETTINGS = RoomNotificationSettings(mode = A_ROOM_NOTIFICATION_MODE, isDefault = false)
const val AN_AVATAR_URL = "mxc://data"

View file

@ -0,0 +1,64 @@
/*
* 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.libraries.matrix.test.notificationsettings
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_NOTIFICATION_MODE
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
class FakeNotificationSettingsService(
initialMode: RoomNotificationMode = A_ROOM_NOTIFICATION_MODE,
initialDefaultMode: RoomNotificationMode = A_ROOM_NOTIFICATION_MODE
) : NotificationSettingsService {
private var _roomNotificationSettingsStateFlow = MutableStateFlow(Unit)
private var defaultRoomNotificationMode: RoomNotificationMode = initialDefaultMode
private var roomNotificationMode: RoomNotificationMode = initialMode
override val notificationSettingsChangeFlow: SharedFlow<Unit>
get() = _roomNotificationSettingsStateFlow
override suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, membersCount: Long): Result<RoomNotificationSettings> {
return Result.success(RoomNotificationSettings(mode = roomNotificationMode, isDefault = roomNotificationMode == defaultRoomNotificationMode))
}
override suspend fun getDefaultRoomNotificationMode(isEncrypted: Boolean, membersCount: Long): Result<RoomNotificationMode> {
return Result.success(defaultRoomNotificationMode)
}
override suspend fun setRoomNotificationMode(roomId: RoomId, mode: RoomNotificationMode): Result<Unit> {
roomNotificationMode = mode
_roomNotificationSettingsStateFlow.emit(Unit)
return Result.success(Unit)
}
override suspend fun restoreDefaultRoomNotificationMode(roomId: RoomId): Result<Unit> {
roomNotificationMode = defaultRoomNotificationMode
_roomNotificationSettingsStateFlow.emit(Unit)
return Result.success(Unit)
}
override suspend fun muteRoom(roomId: RoomId): Result<Unit> {
return setRoomNotificationMode(roomId, RoomNotificationMode.MUTE)
}
override suspend fun unmuteRoom(roomId: RoomId, isEncrypted: Boolean, membersCount: Long): Result<Unit> {
return restoreDefaultRoomNotificationMode(roomId)
}
}

View file

@ -27,16 +27,19 @@ import io.element.android.libraries.matrix.api.media.FileInfo
import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.media.MediaUploadHandler
import io.element.android.libraries.matrix.api.media.VideoInfo
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.poll.PollKind
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.api.room.MatrixRoomNotificationSettingsState
import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.timeline.MatrixTimeline
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.coroutines.delay
@ -58,6 +61,7 @@ class FakeMatrixRoom(
override val isDirect: Boolean = false,
override val joinedMemberCount: Long = 123L,
override val activeMemberCount: Long = 234L,
val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(),
private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(),
canRedact: Boolean = false,
) : MatrixRoom {
@ -136,10 +140,19 @@ class FakeMatrixRoom(
override val membersStateFlow: MutableStateFlow<MatrixRoomMembersState> = MutableStateFlow(MatrixRoomMembersState.Unknown)
override val roomNotificationSettingsStateFlow: MutableStateFlow<MatrixRoomNotificationSettingsState> =
MutableStateFlow(MatrixRoomNotificationSettingsState.Unknown)
override suspend fun updateMembers(): Result<Unit> = simulateLongTask {
updateMembersResult
}
override suspend fun updateRoomNotificationSettings(): Result<Unit> = simulateLongTask {
val notificationSettings = notificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, activeMemberCount).getOrThrow()
roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Ready(notificationSettings)
return Result.success(Unit)
}
override val syncUpdateFlow: StateFlow<Long> = MutableStateFlow(0L)
override val timeline: MatrixTimeline = matrixTimeline

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="Use_an_identity_server_to_invite_by_email.__default_Use_the_default____defaultIdentityServerName_s___default__or_manage_in__settings_Settings__settings_._web">"Use an identity server to invite by email. "<default>"Use the default (%(defaultIdentityServerName)s)"</default>" or manage in "<settings>"Settings"</settings>"."</string>
<string name="a11y_hide_password">"Hide password"</string>
<string name="a11y_notifications_mentions_only">"Mentions only"</string>
<string name="a11y_notifications_muted">"Muted"</string>
@ -161,6 +162,10 @@
<string name="leave_room_alert_private_subtitle">"Are you sure that you want to leave this room? This room is not public and you won\'t be able to rejoin without an invite."</string>
<string name="leave_room_alert_subtitle">"Are you sure that you want to leave the room?"</string>
<string name="login_initial_device_name_android">"%1$s Android"</string>
<plurals name="__count_s_rooms_web">
<item quantity="one">"%(count)s room"</item>
<item quantity="other">"%(count)s rooms"</item>
</plurals>
<plurals name="common_member_count">
<item quantity="one">"%1$d member"</item>
<item quantity="other">"%1$d members"</item>