From 9fbfe82044ec6792a114856a0bf408d40123f290 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Thu, 24 Nov 2022 16:56:06 +0100 Subject: [PATCH] Fix observation of the notification status for the current session --- ...oggleNotificationsViaAccountDataUseCase.kt | 32 +++++ ...icationSettingsAccountDataAsFlowUseCase.kt | 37 ++++++ .../GetNotificationsStatusUseCase.kt | 34 +++--- ...eNotificationsViaAccountDataUseCaseTest.kt | 88 ++++++++++++++ ...ionSettingsAccountDataAsFlowUseCaseTest.kt | 109 ++++++++++++++++++ .../GetNotificationsStatusUseCaseTest.kt | 25 ++-- .../fakes/FakeSessionAccountDataService.kt | 11 ++ 7 files changed, 314 insertions(+), 22 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/notification/CanToggleNotificationsViaAccountDataUseCase.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/notification/GetNotificationSettingsAccountDataAsFlowUseCase.kt create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/notification/CanToggleNotificationsViaAccountDataUseCaseTest.kt create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/notification/GetNotificationSettingsAccountDataAsFlowUseCaseTest.kt diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/notification/CanToggleNotificationsViaAccountDataUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/notification/CanToggleNotificationsViaAccountDataUseCase.kt new file mode 100644 index 0000000000..ac466852eb --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/notification/CanToggleNotificationsViaAccountDataUseCase.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2.notification + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import org.matrix.android.sdk.api.session.Session +import javax.inject.Inject + +class CanToggleNotificationsViaAccountDataUseCase @Inject constructor( + private val getNotificationSettingsAccountDataAsFlowUseCase: GetNotificationSettingsAccountDataAsFlowUseCase, +) { + + fun execute(session: Session, deviceId: String): Flow { + return getNotificationSettingsAccountDataAsFlowUseCase.execute(session, deviceId) + .map { it?.isSilenced != null } + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/notification/GetNotificationSettingsAccountDataAsFlowUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/notification/GetNotificationSettingsAccountDataAsFlowUseCase.kt new file mode 100644 index 0000000000..ea4bd40f1f --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/notification/GetNotificationSettingsAccountDataAsFlowUseCase.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2.notification + +import androidx.lifecycle.asFlow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import org.matrix.android.sdk.api.account.LocalNotificationSettingsContent +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes +import org.matrix.android.sdk.api.session.events.model.toModel +import javax.inject.Inject + +class GetNotificationSettingsAccountDataAsFlowUseCase @Inject constructor() { + + fun execute(session: Session, deviceId: String): Flow { + return session + .accountDataService() + .getLiveUserAccountDataEvent(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId) + .asFlow() + .map { it.getOrNull()?.content?.toModel() } + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/notification/GetNotificationsStatusUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/notification/GetNotificationsStatusUseCase.kt index ae7e859573..8cf684975e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/notification/GetNotificationsStatusUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/notification/GetNotificationsStatusUseCase.kt @@ -31,20 +31,30 @@ import javax.inject.Inject class GetNotificationsStatusUseCase @Inject constructor( private val canToggleNotificationsViaPusherUseCase: CanToggleNotificationsViaPusherUseCase, - private val checkIfCanToggleNotificationsViaAccountDataUseCase: CheckIfCanToggleNotificationsViaAccountDataUseCase, + private val canToggleNotificationsViaAccountDataUseCase: CanToggleNotificationsViaAccountDataUseCase, ) { fun execute(session: Session, deviceId: String): Flow { - return when { - checkIfCanToggleNotificationsViaAccountDataUseCase.execute(session, deviceId) -> { - session.flow() - .liveUserAccountData(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId) - .unwrap() - .map { it.content.toModel()?.isSilenced?.not() } - .map { if (it == true) NotificationsStatus.ENABLED else NotificationsStatus.DISABLED } - .distinctUntilChanged() - } - else -> canToggleNotificationsViaPusherUseCase.execute(session) + return canToggleNotificationsViaAccountDataUseCase.execute(session, deviceId) + .flatMapLatest { canToggle -> + if (canToggle) { + notificationStatusFromAccountData(session, deviceId) + } else { + notificationStatusFromPusher(session, deviceId) + } + } + .distinctUntilChanged() + } + + private fun notificationStatusFromAccountData(session: Session, deviceId: String) = + session.flow() + .liveUserAccountData(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId) + .unwrap() + .map { it.content.toModel()?.isSilenced?.not() } + .map { if (it == true) NotificationsStatus.ENABLED else NotificationsStatus.DISABLED } + + private fun notificationStatusFromPusher(session: Session, deviceId: String) = + canToggleNotificationsViaPusherUseCase.execute(session) .flatMapLatest { canToggle -> if (canToggle) { session.flow() @@ -63,6 +73,4 @@ class GetNotificationsStatusUseCase @Inject constructor( flowOf(NotificationsStatus.NOT_SUPPORTED) } } - } - } } diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/notification/CanToggleNotificationsViaAccountDataUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/notification/CanToggleNotificationsViaAccountDataUseCaseTest.kt new file mode 100644 index 0000000000..a1dfed6902 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/notification/CanToggleNotificationsViaAccountDataUseCaseTest.kt @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2.notification + +import im.vector.app.test.fakes.FakeSession +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBe +import org.junit.Test +import org.matrix.android.sdk.api.account.LocalNotificationSettingsContent + +class CanToggleNotificationsViaAccountDataUseCaseTest { + + private val fakeGetNotificationSettingsAccountDataAsFlowUseCase = mockk() + + private val canToggleNotificationsViaAccountDataUseCase = CanToggleNotificationsViaAccountDataUseCase( + getNotificationSettingsAccountDataAsFlowUseCase = fakeGetNotificationSettingsAccountDataAsFlowUseCase, + ) + + @Test + fun `given current session and content for account data when execute then true is returned`() = runTest { + // Given + val aSession = FakeSession() + val aDeviceId = "aDeviceId" + val localNotificationSettingsContent = LocalNotificationSettingsContent( + isSilenced = true, + ) + every { fakeGetNotificationSettingsAccountDataAsFlowUseCase.execute(any(), any()) } returns flowOf(localNotificationSettingsContent) + + // When + val result = canToggleNotificationsViaAccountDataUseCase.execute(aSession, aDeviceId).firstOrNull() + + // Then + result shouldBe true + verify { fakeGetNotificationSettingsAccountDataAsFlowUseCase.execute(aSession, aDeviceId) } + } + + @Test + fun `given current session and empty content for account data when execute then false is returned`() = runTest { + // Given + val aSession = FakeSession() + val aDeviceId = "aDeviceId" + val localNotificationSettingsContent = LocalNotificationSettingsContent( + isSilenced = null, + ) + every { fakeGetNotificationSettingsAccountDataAsFlowUseCase.execute(any(), any()) } returns flowOf(localNotificationSettingsContent) + + // When + val result = canToggleNotificationsViaAccountDataUseCase.execute(aSession, aDeviceId).firstOrNull() + + // Then + result shouldBe false + verify { fakeGetNotificationSettingsAccountDataAsFlowUseCase.execute(aSession, aDeviceId) } + } + + @Test + fun `given current session and no related account data when execute then false is returned`() = runTest { + // Given + val aSession = FakeSession() + val aDeviceId = "aDeviceId" + every { fakeGetNotificationSettingsAccountDataAsFlowUseCase.execute(any(), any()) } returns flowOf(null) + + // When + val result = canToggleNotificationsViaAccountDataUseCase.execute(aSession, aDeviceId).firstOrNull() + + // Then + result shouldBe false + verify { fakeGetNotificationSettingsAccountDataAsFlowUseCase.execute(aSession, aDeviceId) } + } +} diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/notification/GetNotificationSettingsAccountDataAsFlowUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/notification/GetNotificationSettingsAccountDataAsFlowUseCaseTest.kt new file mode 100644 index 0000000000..6280d4c48b --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/notification/GetNotificationSettingsAccountDataAsFlowUseCaseTest.kt @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.settings.devices.v2.notification + +import im.vector.app.test.fakes.FakeFlowLiveDataConversions +import im.vector.app.test.fakes.FakeSession +import im.vector.app.test.fakes.givenAsFlow +import io.mockk.unmockkAll +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.matrix.android.sdk.api.account.LocalNotificationSettingsContent +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes +import org.matrix.android.sdk.api.session.events.model.toContent + +class GetNotificationSettingsAccountDataAsFlowUseCaseTest { + + private val fakeFlowLiveDataConversions = FakeFlowLiveDataConversions() + private val getNotificationSettingsAccountDataAsFlowUseCase = GetNotificationSettingsAccountDataAsFlowUseCase() + + @Before + fun setUp() { + fakeFlowLiveDataConversions.setup() + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given a device id when execute then retrieve the account data event corresponding to this id if any`() = runTest { + // Given + val aDeviceId = "device-id" + val aSession = FakeSession() + val expectedContent = LocalNotificationSettingsContent(isSilenced = true) + aSession + .accountDataService() + .givenGetLiveUserAccountDataEventReturns( + type = UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + aDeviceId, + content = expectedContent.toContent(), + ) + .givenAsFlow() + + // When + val result = getNotificationSettingsAccountDataAsFlowUseCase.execute(aSession, aDeviceId).firstOrNull() + + // Then + result shouldBeEqualTo expectedContent + } + + @Test + fun `given a device id and no content for account data when execute then retrieve the account data event corresponding to this id if any`() = runTest { + // Given + val aDeviceId = "device-id" + val aSession = FakeSession() + aSession + .accountDataService() + .givenGetLiveUserAccountDataEventReturns( + type = UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + aDeviceId, + content = null, + ) + .givenAsFlow() + + // When + val result = getNotificationSettingsAccountDataAsFlowUseCase.execute(aSession, aDeviceId).firstOrNull() + + // Then + result shouldBeEqualTo null + } + + @Test + fun `given a device id and empty content for account data when execute then retrieve the account data event corresponding to this id if any`() = runTest { + // Given + val aDeviceId = "device-id" + val aSession = FakeSession() + val expectedContent = LocalNotificationSettingsContent(isSilenced = null) + aSession + .accountDataService() + .givenGetLiveUserAccountDataEventReturns( + type = UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + aDeviceId, + content = expectedContent.toContent(), + ) + .givenAsFlow() + + // When + val result = getNotificationSettingsAccountDataAsFlowUseCase.execute(aSession, aDeviceId).firstOrNull() + + // Then + result shouldBeEqualTo expectedContent + } +} diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/notification/GetNotificationsStatusUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/notification/GetNotificationsStatusUseCaseTest.kt index 3c454f7965..e4b681c5ec 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/notification/GetNotificationsStatusUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/notification/GetNotificationsStatusUseCaseTest.kt @@ -22,6 +22,7 @@ import im.vector.app.test.fixtures.PusherFixture import im.vector.app.test.testDispatcher import io.mockk.every import io.mockk.mockk +import io.mockk.verify import io.mockk.verifyOrder import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.firstOrNull @@ -46,14 +47,14 @@ class GetNotificationsStatusUseCaseTest { val instantTaskExecutorRule = InstantTaskExecutorRule() private val fakeSession = FakeSession() - private val fakeCheckIfCanToggleNotificationsViaAccountDataUseCase = - mockk() + private val fakeCanToggleNotificationsViaAccountDataUseCase = + mockk() private val fakeCanToggleNotificationsViaPusherUseCase = mockk() private val getNotificationsStatusUseCase = GetNotificationsStatusUseCase( - checkIfCanToggleNotificationsViaAccountDataUseCase = fakeCheckIfCanToggleNotificationsViaAccountDataUseCase, + canToggleNotificationsViaAccountDataUseCase = fakeCanToggleNotificationsViaAccountDataUseCase, canToggleNotificationsViaPusherUseCase = fakeCanToggleNotificationsViaPusherUseCase, ) @@ -70,7 +71,7 @@ class GetNotificationsStatusUseCaseTest { @Test fun `given current session and toggle is not supported when execute then resulting flow contains NOT_SUPPORTED value`() = runTest { // Given - every { fakeCheckIfCanToggleNotificationsViaAccountDataUseCase.execute(fakeSession, A_DEVICE_ID) } returns false + every { fakeCanToggleNotificationsViaAccountDataUseCase.execute(fakeSession, A_DEVICE_ID) } returns flowOf(false) every { fakeCanToggleNotificationsViaPusherUseCase.execute(fakeSession) } returns flowOf(false) // When @@ -80,7 +81,7 @@ class GetNotificationsStatusUseCaseTest { result.firstOrNull() shouldBeEqualTo NotificationsStatus.NOT_SUPPORTED verifyOrder { // we should first check account data - fakeCheckIfCanToggleNotificationsViaAccountDataUseCase.execute(fakeSession, A_DEVICE_ID) + fakeCanToggleNotificationsViaAccountDataUseCase.execute(fakeSession, A_DEVICE_ID) fakeCanToggleNotificationsViaPusherUseCase.execute(fakeSession) } } @@ -95,7 +96,7 @@ class GetNotificationsStatusUseCaseTest { ) ) fakeSession.pushersService().givenPushersLive(pushers) - every { fakeCheckIfCanToggleNotificationsViaAccountDataUseCase.execute(fakeSession, A_DEVICE_ID) } returns false + every { fakeCanToggleNotificationsViaAccountDataUseCase.execute(fakeSession, A_DEVICE_ID) } returns flowOf(false) every { fakeCanToggleNotificationsViaPusherUseCase.execute(fakeSession) } returns flowOf(true) // When @@ -109,7 +110,7 @@ class GetNotificationsStatusUseCaseTest { fun `given toggle via pusher is supported and no registered pusher when execute then resulting flow contains NOT_SUPPORTED value`() = runTest { // Given fakeSession.pushersService().givenPushersLive(emptyList()) - every { fakeCheckIfCanToggleNotificationsViaAccountDataUseCase.execute(fakeSession, A_DEVICE_ID) } returns false + every { fakeCanToggleNotificationsViaAccountDataUseCase.execute(fakeSession, A_DEVICE_ID) } returns flowOf(false) every { fakeCanToggleNotificationsViaPusherUseCase.execute(fakeSession) } returns flowOf(true) // When @@ -120,7 +121,7 @@ class GetNotificationsStatusUseCaseTest { } @Test - fun `given current session and toggle via account data is supported when execute then resulting flow contains status based on settings value`() = runTest { + fun `given current session and toggle via account data is supported when execute then resulting flow contains status based on account data`() = runTest { // Given fakeSession .accountDataService() @@ -130,7 +131,7 @@ class GetNotificationsStatusUseCaseTest { isSilenced = false ).toContent(), ) - every { fakeCheckIfCanToggleNotificationsViaAccountDataUseCase.execute(fakeSession, A_DEVICE_ID) } returns true + every { fakeCanToggleNotificationsViaAccountDataUseCase.execute(fakeSession, A_DEVICE_ID) } returns flowOf(true) every { fakeCanToggleNotificationsViaPusherUseCase.execute(fakeSession) } returns flowOf(false) // When @@ -138,5 +139,11 @@ class GetNotificationsStatusUseCaseTest { // Then result.firstOrNull() shouldBeEqualTo NotificationsStatus.ENABLED + verify { + fakeCanToggleNotificationsViaAccountDataUseCase.execute(fakeSession, A_DEVICE_ID) + } + verify(inverse = true) { + fakeCanToggleNotificationsViaPusherUseCase.execute(fakeSession) + } } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSessionAccountDataService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSessionAccountDataService.kt index c44fc4a497..f1a0ae7452 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeSessionAccountDataService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSessionAccountDataService.kt @@ -16,6 +16,8 @@ package im.vector.app.test.fakes +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every @@ -25,6 +27,8 @@ import io.mockk.runs import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.api.util.toOptional class FakeSessionAccountDataService : SessionAccountDataService by mockk(relaxed = true) { @@ -32,6 +36,13 @@ class FakeSessionAccountDataService : SessionAccountDataService by mockk(relaxed every { getUserAccountDataEvent(type) } returns content?.let { UserAccountDataEvent(type, it) } } + fun givenGetLiveUserAccountDataEventReturns(type: String, content: Content?): LiveData> { + return MutableLiveData(content?.let { UserAccountDataEvent(type, it) }.toOptional()) + .also { + every { getLiveUserAccountDataEvent(type) } returns it + } + } + fun givenUpdateUserAccountDataEventSucceeds() { coEvery { updateUserAccountData(any(), any()) } just runs }