diff --git a/vector/src/main/java/im/vector/app/features/home/GetSpacesNotificationBadgeStateUseCase.kt b/vector/src/main/java/im/vector/app/features/home/GetSpacesNotificationBadgeStateUseCase.kt new file mode 100644 index 0000000000..62d1501dab --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/GetSpacesNotificationBadgeStateUseCase.kt @@ -0,0 +1,67 @@ +/* + * 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 im.vector.app.features.home + +import im.vector.app.features.home.room.list.UnreadCounterBadgeView +import im.vector.app.features.spaces.GetSpacesUseCase +import im.vector.app.features.spaces.notification.GetNotificationCountForSpacesUseCase +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.SpaceFilter +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams +import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount +import javax.inject.Inject + +class GetSpacesNotificationBadgeStateUseCase @Inject constructor( + private val getNotificationCountForSpacesUseCase: GetNotificationCountForSpacesUseCase, + private val getSpacesUseCase: GetSpacesUseCase, +) { + + fun execute(): Flow<UnreadCounterBadgeView.State> { + val params = spaceSummaryQueryParams { + memberships = listOf(Membership.INVITE) + displayName = QueryStringValue.IsNotEmpty + } + return combine( + getNotificationCountForSpacesUseCase.execute(SpaceFilter.NoFilter), + getSpacesUseCase.execute(params), + ) { spacesNotificationCount, spaceInvites -> + computeSpacesNotificationCounterBadgeState(spacesNotificationCount, spaceInvites) + } + } + + private fun computeSpacesNotificationCounterBadgeState( + spacesNotificationCount: RoomAggregateNotificationCount, + spaceInvites: List<RoomSummary>, + ): UnreadCounterBadgeView.State { + val hasPendingSpaceInvites = spaceInvites.isNotEmpty() + return if (hasPendingSpaceInvites && spacesNotificationCount.notificationCount == 0) { + UnreadCounterBadgeView.State.Text( + text = "!", + highlighted = true, + ) + } else { + UnreadCounterBadgeView.State.Count( + count = spacesNotificationCount.notificationCount, + highlighted = spacesNotificationCount.isHighlight || hasPendingSpaceInvites, + ) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt index a13487afc8..ef855ff15b 100644 --- a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt @@ -64,7 +64,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import org.matrix.android.sdk.api.session.room.model.RoomSummary -import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import javax.inject.Inject @AndroidEntryPoint @@ -185,7 +184,7 @@ class NewHomeDetailFragment : } newHomeDetailViewModel.onEach { viewState -> - refreshUnreadCounterBadge(viewState.spacesNotificationCount, viewState.hasPendingSpaceInvites) + refreshUnreadCounterBadge(viewState.spacesNotificationCounterBadgeState) } } @@ -386,21 +385,7 @@ class NewHomeDetailFragment : } } - private fun refreshUnreadCounterBadge( - spacesNotificationCount: RoomAggregateNotificationCount, - hasPendingSpaceInvites: Boolean, - ) { - val badgeState = if (hasPendingSpaceInvites && spacesNotificationCount.notificationCount == 0) { - UnreadCounterBadgeView.State.Text( - text = "!", - highlighted = true, - ) - } else { - UnreadCounterBadgeView.State.Count( - count = spacesNotificationCount.notificationCount, - highlighted = spacesNotificationCount.isHighlight || hasPendingSpaceInvites, - ) - } + private fun refreshUnreadCounterBadge(badgeState: UnreadCounterBadgeView.State) { views.spacesUnreadCounterBadge.render(badgeState) } diff --git a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailViewModel.kt index b26d010137..67b4645944 100644 --- a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailViewModel.kt @@ -25,19 +25,12 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.spaces.GetSpacesUseCase -import im.vector.app.features.spaces.notification.GetNotificationCountForSpacesUseCase import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import org.matrix.android.sdk.api.query.QueryStringValue -import org.matrix.android.sdk.api.query.SpaceFilter -import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams class NewHomeDetailViewModel @AssistedInject constructor( @Assisted initialState: NewHomeDetailViewState, - private val getNotificationCountForSpacesUseCase: GetNotificationCountForSpacesUseCase, - private val getSpacesUseCase: GetSpacesUseCase, + private val getSpacesNotificationBadgeStateUseCase: GetSpacesNotificationBadgeStateUseCase, ) : VectorViewModel<NewHomeDetailViewState, EmptyAction, EmptyViewEvents>(initialState) { @AssistedFactory @@ -48,23 +41,12 @@ class NewHomeDetailViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory<NewHomeDetailViewModel, NewHomeDetailViewState> by hiltMavericksViewModelFactory() init { - observeSpacesNotificationCount() - observeSpacesInvite() + observeSpacesNotificationBadgeState() } - private fun observeSpacesNotificationCount() { - getNotificationCountForSpacesUseCase.execute(SpaceFilter.NoFilter) - .onEach { setState { copy(spacesNotificationCount = it) } } - .launchIn(viewModelScope) - } - - private fun observeSpacesInvite() { - val params = spaceSummaryQueryParams { - memberships = listOf(Membership.INVITE) - displayName = QueryStringValue.IsNotEmpty - } - getSpacesUseCase.execute(params) - .onEach { setState { copy(hasPendingSpaceInvites = it.isNotEmpty()) } } + private fun observeSpacesNotificationBadgeState() { + getSpacesNotificationBadgeStateUseCase.execute() + .onEach { badgeState -> setState { copy(spacesNotificationCounterBadgeState = badgeState) } } .launchIn(viewModelScope) } diff --git a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailViewState.kt index 1ff0b86511..7e368fb2d1 100644 --- a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailViewState.kt @@ -17,9 +17,8 @@ package im.vector.app.features.home import com.airbnb.mvrx.MavericksState -import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount +import im.vector.app.features.home.room.list.UnreadCounterBadgeView data class NewHomeDetailViewState( - val spacesNotificationCount: RoomAggregateNotificationCount = RoomAggregateNotificationCount(notificationCount = 0, highlightCount = 0), - val hasPendingSpaceInvites: Boolean = false, + val spacesNotificationCounterBadgeState: UnreadCounterBadgeView.State = UnreadCounterBadgeView.State.Count(count = 0, highlighted = false), ) : MavericksState diff --git a/vector/src/test/java/im/vector/app/features/home/GetSpacesNotificationBadgeStateUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/GetSpacesNotificationBadgeStateUseCaseTest.kt new file mode 100644 index 0000000000..4d7d0d98f4 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/home/GetSpacesNotificationBadgeStateUseCaseTest.kt @@ -0,0 +1,88 @@ +/* + * 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 im.vector.app.features.home + +import im.vector.app.features.home.room.list.UnreadCounterBadgeView +import im.vector.app.features.spaces.GetSpacesUseCase +import im.vector.app.features.spaces.notification.GetNotificationCountForSpacesUseCase +import im.vector.app.test.test +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.SpaceFilter +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount + +internal class GetSpacesNotificationBadgeStateUseCaseTest { + + private val fakeGetNotificationCountForSpacesUseCase = mockk<GetNotificationCountForSpacesUseCase>() + private val fakeGetSpacesUseCase = mockk<GetSpacesUseCase>() + + private val getSpacesNotificationBadgeStateUseCase = GetSpacesNotificationBadgeStateUseCase( + getNotificationCountForSpacesUseCase = fakeGetNotificationCountForSpacesUseCase, + getSpacesUseCase = fakeGetSpacesUseCase, + ) + + @Test + fun `given flow of spaces invite and notification count then flow of state is correct`() = runTest { + // Given + val noSpacesInvite = emptyList<RoomSummary>() + val existingSpaceInvite = listOf<RoomSummary>(mockk()) + val noNotification = RoomAggregateNotificationCount( + notificationCount = 0, + highlightCount = 0, + ) + val existingNotificationNotHighlighted = RoomAggregateNotificationCount( + notificationCount = 1, + highlightCount = 0, + ) + val existingNotificationHighlighted = RoomAggregateNotificationCount( + notificationCount = 1, + highlightCount = 1, + ) + every { fakeGetSpacesUseCase.execute(any()) } returns + flowOf(noSpacesInvite, existingSpaceInvite, existingSpaceInvite, noSpacesInvite, noSpacesInvite) + every { fakeGetNotificationCountForSpacesUseCase.execute(any()) } returns + flowOf(noNotification, noNotification, existingNotificationNotHighlighted, existingNotificationNotHighlighted, existingNotificationHighlighted) + + // When + val testObserver = getSpacesNotificationBadgeStateUseCase.execute().test(this) + advanceUntilIdle() + + // Then + val expectedState1 = UnreadCounterBadgeView.State.Count(count = 0, highlighted = false) + val expectedState2 = UnreadCounterBadgeView.State.Text(text = "!", highlighted = true) + val expectedState3 = UnreadCounterBadgeView.State.Count(count = 1, highlighted = true) + val expectedState4 = UnreadCounterBadgeView.State.Count(count = 1, highlighted = false) + val expectedState5 = UnreadCounterBadgeView.State.Count(count = 1, highlighted = true) + testObserver + .assertValues(expectedState1, expectedState2, expectedState3, expectedState4, expectedState5) + .finish() + verify { + fakeGetSpacesUseCase.execute(match { + it.memberships == listOf(Membership.INVITE) && it.displayName == QueryStringValue.IsNotEmpty + }) + } + verify { fakeGetNotificationCountForSpacesUseCase.execute(SpaceFilter.NoFilter) } + } +} diff --git a/vector/src/test/java/im/vector/app/features/home/NewHomeDetailViewModelTest.kt b/vector/src/test/java/im/vector/app/features/home/NewHomeDetailViewModelTest.kt index 23882bf7c4..a92c4be0d7 100644 --- a/vector/src/test/java/im/vector/app/features/home/NewHomeDetailViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/NewHomeDetailViewModelTest.kt @@ -17,8 +17,8 @@ package im.vector.app.features.home import com.airbnb.mvrx.test.MavericksTestRule +import im.vector.app.features.home.room.list.UnreadCounterBadgeView import im.vector.app.features.spaces.GetSpacesUseCase -import im.vector.app.features.spaces.notification.GetNotificationCountForSpacesUseCase import im.vector.app.test.test import io.mockk.every import io.mockk.mockk @@ -27,10 +27,6 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.UnconfinedTestDispatcher import org.junit.Rule import org.junit.Test -import org.matrix.android.sdk.api.query.SpaceFilter -import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.RoomSummary -import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount internal class NewHomeDetailViewModelTest { @@ -38,43 +34,34 @@ internal class NewHomeDetailViewModelTest { val mavericksTestRule = MavericksTestRule(testDispatcher = UnconfinedTestDispatcher()) private val initialState = NewHomeDetailViewState() - private val fakeGetNotificationCountForSpacesUseCase = mockk<GetNotificationCountForSpacesUseCase>() - private val fakeGetSpacesUseCase = mockk<GetSpacesUseCase>() + private val fakeGetSpacesNotificationBadgeStateUseCase = mockk<GetSpacesNotificationBadgeStateUseCase>() private fun createViewModel(): NewHomeDetailViewModel { return NewHomeDetailViewModel( initialState = initialState, - getNotificationCountForSpacesUseCase = fakeGetNotificationCountForSpacesUseCase, - getSpacesUseCase = fakeGetSpacesUseCase, + getSpacesNotificationBadgeStateUseCase = fakeGetSpacesNotificationBadgeStateUseCase, ) } @Test - fun `given the viewModel is created then viewState is updated with space notifications count and pending space invites`() { + fun `given the viewModel is created then viewState is updated with space notifications badge state`() { // Given - val spacesNotificationCount = RoomAggregateNotificationCount( - notificationCount = 1, - highlightCount = 1, - ) - every { fakeGetNotificationCountForSpacesUseCase.execute(any()) } returns flowOf(spacesNotificationCount) - val spaceInvites = listOf<RoomSummary>(mockk()) - every { fakeGetSpacesUseCase.execute(any()) } returns flowOf(spaceInvites) - val expectedViewState = initialState.copy( - spacesNotificationCount = spacesNotificationCount, - hasPendingSpaceInvites = true, - ) + val aBadgeState = UnreadCounterBadgeView.State.Count(count = 1, highlighted = false) + every { fakeGetSpacesNotificationBadgeStateUseCase.execute() } returns flowOf(aBadgeState) // When val viewModel = createViewModel() val viewModelTest = viewModel.test() // Then + val expectedViewState = initialState.copy( + spacesNotificationCounterBadgeState = aBadgeState, + ) viewModelTest .assertLatestState(expectedViewState) .finish() verify { - fakeGetNotificationCountForSpacesUseCase.execute(SpaceFilter.NoFilter) - fakeGetSpacesUseCase.execute(match { it.memberships == listOf(Membership.INVITE) }) + fakeGetSpacesNotificationBadgeStateUseCase.execute() } } }