Skip to content

Commit

Permalink
Merge pull request #1955 from vector-im/feature/bma/notificationForIn…
Browse files Browse the repository at this point in the history
…vites

Add setting to disable notification for invites
  • Loading branch information
bmarty authored Dec 5, 2023
2 parents b98351e + 2584d2f commit 8f5fed4
Show file tree
Hide file tree
Showing 19 changed files with 100 additions and 38 deletions.
1 change: 1 addition & 0 deletions changelog.d/1944.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add toggle in the notification settings to disable notifications for room invites.
2 changes: 1 addition & 1 deletion docs/_developer_onboarding.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ You can also have access to the aars through the [release](https://github.com/ma

#### Build the SDK locally

Easiest way: run the script [./tools/sdk/build_rust_sdk.sh](./tools/sdk/build_rust_sdk.sh) and just answer the questions.
Easiest way: run the script [../tools/sdk/build_rust_sdk.sh](../tools/sdk/build_rust_sdk.sh) and just answer the questions.

Legacy way:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
package io.element.android.features.preferences.impl.notifications

sealed interface NotificationSettingsEvents {

data object RefreshSystemNotificationsEnabled : NotificationSettingsEvents
data class SetNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents
data class SetAtRoomNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents
data class SetCallNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents
data class SetInviteForMeNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents
data object FixConfigurationMismatch : NotificationSettingsEvents
data object ClearConfigurationMismatchError : NotificationSettingsEvents
data object ClearNotificationChangeError : NotificationSettingsEvents
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ class NotificationSettingsPresenter @Inject constructor(
is NotificationSettingsEvents.SetCallNotificationsEnabled -> {
localCoroutineScope.setCallNotificationsEnabled(event.enabled, changeNotificationSettingAction)
}
is NotificationSettingsEvents.SetInviteForMeNotificationsEnabled -> {
localCoroutineScope.setInviteForMeNotificationsEnabled(event.enabled, changeNotificationSettingAction)
}
is NotificationSettingsEvents.SetNotificationsEnabled -> localCoroutineScope.setNotificationsEnabled(userPushStore, event.enabled)
NotificationSettingsEvents.ClearConfigurationMismatchError -> {
matrixSettings.value = NotificationSettingsState.MatrixSettings.Invalid(fixFailed = false)
Expand Down Expand Up @@ -123,10 +126,12 @@ class NotificationSettingsPresenter @Inject constructor(

val callNotificationsEnabled = notificationSettingsService.isCallEnabled().getOrThrow()
val atRoomNotificationsEnabled = notificationSettingsService.isRoomMentionEnabled().getOrThrow()
val inviteForMeNotificationsEnabled = notificationSettingsService.isInviteForMeEnabled().getOrThrow()

target.value = NotificationSettingsState.MatrixSettings.Valid(
atRoomNotificationsEnabled = atRoomNotificationsEnabled,
callNotificationsEnabled = callNotificationsEnabled,
inviteForMeNotificationsEnabled = inviteForMeNotificationsEnabled,
defaultGroupNotificationMode = encryptedGroupDefaultMode,
defaultOneToOneNotificationMode = encryptedOneToOneDefaultMode,
)
Expand Down Expand Up @@ -175,6 +180,12 @@ class NotificationSettingsPresenter @Inject constructor(
}.runCatchingUpdatingState(action)
}

private fun CoroutineScope.setInviteForMeNotificationsEnabled(enabled: Boolean, action: MutableState<Async<Unit>>) = launch {
suspend {
notificationSettingsService.setInviteForMeEnabled(enabled).getOrThrow()
}.runCatchingUpdatingState(action)
}

private fun CoroutineScope.setNotificationsEnabled(userPushStore: UserPushStore, enabled: Boolean) = launch {
userPushStore.setNotificationEnabledForDevice(enabled)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ data class NotificationSettingsState(
data class Valid(
val atRoomNotificationsEnabled: Boolean,
val callNotificationsEnabled: Boolean,
val inviteForMeNotificationsEnabled: Boolean,
val defaultGroupNotificationMode: RoomNotificationMode?,
val defaultOneToOneNotificationMode: RoomNotificationMode?,
) : MatrixSettings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ fun aNotificationSettingsState(
matrixSettings = NotificationSettingsState.MatrixSettings.Valid(
atRoomNotificationsEnabled = true,
callNotificationsEnabled = true,
inviteForMeNotificationsEnabled = true,
defaultGroupNotificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
defaultOneToOneNotificationMode = RoomNotificationMode.ALL_MESSAGES,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ fun NotificationSettingsView(
onMentionNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetAtRoomNotificationsEnabled(it)) },
// TODO We are removing the call notification toggle until support for call notifications has been added
// onCallsNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetCallNotificationsEnabled(it)) },
onInviteForMeNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetInviteForMeNotificationsEnabled(it)) },
)
}
AsyncView(
Expand All @@ -98,6 +99,7 @@ private fun NotificationSettingsContentView(
onMentionNotificationsChanged: (Boolean) -> Unit,
// TODO We are removing the call notification toggle until support for call notifications has been added
// onCallsNotificationsChanged: (Boolean) -> Unit,
onInviteForMeNotificationsChanged: (Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
Expand Down Expand Up @@ -147,16 +149,23 @@ private fun NotificationSettingsContentView(
onCheckedChange = onMentionNotificationsChanged
)
}
// TODO We are removing the call notification toggle until support for call notifications has been added
// PreferenceCategory(title = stringResource(id = CommonStrings.screen_notification_settings_additional_settings_section_title)) {
PreferenceCategory(title = stringResource(id = R.string.screen_notification_settings_additional_settings_section_title)) {
// TODO We are removing the call notification toggle until support for call notifications has been added
// PreferenceSwitch(
// modifier = Modifier,
// title = stringResource(id = CommonStrings.screen_notification_settings_calls_label),
// isChecked = matrixSettings.callNotificationsEnabled,
// switchAlignment = Alignment.Top,
// onCheckedChange = onCallsNotificationsChanged
// )
// }
PreferenceSwitch(
modifier = Modifier,
title = stringResource(id = R.string.screen_notification_settings_invite_for_me_label),
isChecked = matrixSettings.inviteForMeNotificationsEnabled,
switchAlignment = Alignment.Top,
onCheckedChange = onInviteForMeNotificationsChanged
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ class NotificationSettingsPresenterTests {
assertThat(initialState.appSettings.appNotificationsEnabled).isFalse()
assertThat(initialState.appSettings.systemNotificationsEnabled).isTrue()
assertThat(initialState.matrixSettings).isEqualTo(NotificationSettingsState.MatrixSettings.Uninitialized)

val loadedState = consumeItemsUntilPredicate {
it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid
}.last()
Expand All @@ -50,6 +49,7 @@ class NotificationSettingsPresenterTests {
val valid = loadedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
assertThat(valid?.atRoomNotificationsEnabled).isFalse()
assertThat(valid?.callNotificationsEnabled).isFalse()
assertThat(valid?.inviteForMeNotificationsEnabled).isFalse()
assertThat(valid?.defaultGroupNotificationMode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
assertThat(valid?.defaultOneToOneNotificationMode).isEqualTo(RoomNotificationMode.ALL_MESSAGES)
cancelAndIgnoreRemainingEvents()
Expand All @@ -63,7 +63,6 @@ class NotificationSettingsPresenterTests {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {

notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = true, isOneToOne = false, mode = RoomNotificationMode.ALL_MESSAGES)
notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = false, isOneToOne = false, mode = RoomNotificationMode.ALL_MESSAGES)
val updatedState = consumeItemsUntilPredicate {
Expand All @@ -82,7 +81,6 @@ class NotificationSettingsPresenterTests {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {

notificationSettingsService.setDefaultRoomNotificationMode(
isEncrypted = true,
isOneToOne = false,
Expand Down Expand Up @@ -118,7 +116,6 @@ class NotificationSettingsPresenterTests {
val fixedState = consumeItemsUntilPredicate(timeout = 2000.milliseconds) {
it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid
}.last()

val fixedMatrixState = fixedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
assertThat(fixedMatrixState?.defaultGroupNotificationMode).isEqualTo(RoomNotificationMode.ALL_MESSAGES)
}
Expand All @@ -134,7 +131,6 @@ class NotificationSettingsPresenterTests {
it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid
}.last()
assertThat(loadedState.appSettings.appNotificationsEnabled).isTrue()

loadedState.eventSink(NotificationSettingsEvents.SetNotificationsEnabled(false))
val updatedState = consumeItemsUntilPredicate {
!it.appSettings.appNotificationsEnabled
Expand All @@ -155,7 +151,6 @@ class NotificationSettingsPresenterTests {
}.last()
val validMatrixState = loadedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
assertThat(validMatrixState?.callNotificationsEnabled).isFalse()

loadedState.eventSink(NotificationSettingsEvents.SetCallNotificationsEnabled(true))
val updatedState = consumeItemsUntilPredicate {
(it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.callNotificationsEnabled == true
Expand All @@ -166,6 +161,27 @@ class NotificationSettingsPresenterTests {
}
}

@Test
fun `present - set invite for me notifications enabled`() = runTest {
val presenter = createNotificationSettingsPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val loadedState = consumeItemsUntilPredicate {
(it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.inviteForMeNotificationsEnabled == false
}.last()
val validMatrixState = loadedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
assertThat(validMatrixState?.inviteForMeNotificationsEnabled).isFalse()
loadedState.eventSink(NotificationSettingsEvents.SetInviteForMeNotificationsEnabled(true))
val updatedState = consumeItemsUntilPredicate {
(it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.inviteForMeNotificationsEnabled == true
}.last()
val updatedMatrixState = updatedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
assertThat(updatedMatrixState?.inviteForMeNotificationsEnabled).isTrue()
cancelAndIgnoreRemainingEvents()
}
}

@Test
fun `present - set atRoom notifications enabled`() = runTest {
val presenter = createNotificationSettingsPresenter()
Expand All @@ -177,7 +193,6 @@ class NotificationSettingsPresenterTests {
}.last()
val validMatrixState = loadedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
assertThat(validMatrixState?.atRoomNotificationsEnabled).isFalse()

loadedState.eventSink(NotificationSettingsEvents.SetAtRoomNotificationsEnabled(true))
val updatedState = consumeItemsUntilPredicate {
(it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.atRoomNotificationsEnabled == true
Expand All @@ -201,14 +216,12 @@ class NotificationSettingsPresenterTests {
}.last()
val validMatrixState = loadedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
assertThat(validMatrixState?.atRoomNotificationsEnabled).isFalse()

loadedState.eventSink(NotificationSettingsEvents.SetAtRoomNotificationsEnabled(true))
val errorState = consumeItemsUntilPredicate {
it.changeNotificationSettingAction.isFailure()
}.last()
assertThat(errorState.changeNotificationSettingAction.isFailure()).isTrue()
errorState.eventSink(NotificationSettingsEvents.ClearNotificationChangeError)

val clearErrorState = consumeItemsUntilPredicate {
it.changeNotificationSettingAction.isUninitialized()
}.last()
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ jsoup = "org.jsoup:jsoup:1.17.1"
appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
molecule-runtime = "app.cash.molecule:molecule-runtime:1.3.1"
timber = "com.jakewharton.timber:timber:5.0.1"
matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.73"
matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.74"
matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" }
matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" }
sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,7 @@ interface NotificationSettingsService {
suspend fun setRoomMentionEnabled(enabled: Boolean): Result<Unit>
suspend fun isCallEnabled(): Result<Boolean>
suspend fun setCallEnabled(enabled: Boolean): Result<Unit>
suspend fun isInviteForMeEnabled(): Result<Boolean>
suspend fun setInviteForMeEnabled(enabled: Boolean): Result<Unit>
suspend fun getRoomsWithUserDefinedRules(): Result<List<String>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,9 @@ 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 notificationSettingsService = RustNotificationSettingsService(client, dispatchers)
.apply { start() }
private val roomSyncSubscriber = RoomSyncSubscriber(innerRoomListService, dispatchers)
private val encryptionService = RustEncryptionService(
client = client,
Expand Down Expand Up @@ -346,8 +345,7 @@ class RustMatrixClient constructor(
override fun close() {
sessionCoroutineScope.cancel()
clientDelegateTaskHandle?.cancelAndDestroy()
notificationSettings.setDelegate(null)
notificationSettings.destroy()
notificationSettingsService.destroy()
verificationService.destroy()
syncService.destroy()
innerRoomListService.destroy()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,16 @@ 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.Client
import org.matrix.rustcomponents.sdk.NotificationSettingsDelegate
import org.matrix.rustcomponents.sdk.NotificationSettingsException
import timber.log.Timber

class RustNotificationSettingsService(
private val notificationSettings: NotificationSettings,
client: Client,
private val dispatchers: CoroutineDispatchers,
) : NotificationSettingsService {

private val notificationSettings = client.getNotificationSettings()
private val _notificationSettingsChangeFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override val notificationSettingsChangeFlow: SharedFlow<Unit> = _notificationSettingsChangeFlow.asSharedFlow()

Expand All @@ -45,10 +45,15 @@ class RustNotificationSettingsService(
}
}

init {
fun start() {
notificationSettings.setDelegate(notificationSettingsDelegate)
}

fun destroy() {
notificationSettings.setDelegate(null)
notificationSettings.destroy()
}

override suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result<RoomNotificationSettings> =
runCatching {
notificationSettings.getRoomNotificationSettings(roomId.value, isEncrypted, isOneToOne).let(RoomNotificationSettingsMapper::map)
Expand Down Expand Up @@ -119,6 +124,18 @@ class RustNotificationSettingsService(
}
}

override suspend fun isInviteForMeEnabled(): Result<Boolean> = withContext(dispatchers.io) {
runCatching {
notificationSettings.isInviteForMeEnabled()
}
}

override suspend fun setInviteForMeEnabled(enabled: Boolean): Result<Unit> = withContext(dispatchers.io) {
runCatching {
notificationSettings.setInviteForMeEnabled(enabled)
}
}

override suspend fun getRoomsWithUserDefinedRules(): Result<List<String>> =
runCatching {
notificationSettings.getRoomsWithUserDefinedRules(enabled = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class FakeNotificationSettingsService(
private var roomNotificationMode: RoomNotificationMode = initialRoomMode
private var roomNotificationModeIsDefault: Boolean = initialRoomModeIsDefault
private var callNotificationsEnabled = false
private var inviteNotificationsEnabled = false
private var atRoomNotificationsEnabled = false
private var setNotificationModeError: Throwable? = null
private var restoreDefaultNotificationModeError: Throwable? = null
Expand All @@ -52,7 +53,7 @@ class FakeNotificationSettingsService(
override suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result<RoomNotificationSettings> {
return Result.success(
RoomNotificationSettings(
mode = if(roomNotificationModeIsDefault) defaultEncryptedGroupRoomNotificationMode else roomNotificationMode,
mode = if (roomNotificationModeIsDefault) defaultEncryptedGroupRoomNotificationMode else roomNotificationMode,
isDefault = roomNotificationModeIsDefault
)
)
Expand Down Expand Up @@ -149,6 +150,15 @@ class FakeNotificationSettingsService(
return Result.success(Unit)
}

override suspend fun isInviteForMeEnabled(): Result<Boolean> {
return Result.success(inviteNotificationsEnabled)
}

override suspend fun setInviteForMeEnabled(enabled: Boolean): Result<Unit> {
inviteNotificationsEnabled = enabled
return Result.success(Unit)
}

override suspend fun getRoomsWithUserDefinedRules(): Result<List<String>> {
return Result.success(if (roomNotificationModeIsDefault) listOf() else listOf(A_ROOM_ID.value))
}
Expand All @@ -168,5 +178,4 @@ class FakeNotificationSettingsService(
fun givenSetDefaultNotificationModeError(throwable: Throwable?) {
setDefaultNotificationModeError = throwable
}

}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 8f5fed4

Please sign in to comment.