Extracting logic to compute the badge state into a usecase

This commit is contained in:
Maxime NATUREL 2023-02-23 13:59:32 +01:00
parent f782a31592
commit 2bd0126523
6 changed files with 174 additions and 66 deletions

View file

@ -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,
)
}
}
}

View file

@ -64,7 +64,6 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo 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.model.RoomSummary
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -185,7 +184,7 @@ class NewHomeDetailFragment :
} }
newHomeDetailViewModel.onEach { viewState -> newHomeDetailViewModel.onEach { viewState ->
refreshUnreadCounterBadge(viewState.spacesNotificationCount, viewState.hasPendingSpaceInvites) refreshUnreadCounterBadge(viewState.spacesNotificationCounterBadgeState)
} }
} }
@ -386,21 +385,7 @@ class NewHomeDetailFragment :
} }
} }
private fun refreshUnreadCounterBadge( private fun refreshUnreadCounterBadge(badgeState: UnreadCounterBadgeView.State) {
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,
)
}
views.spacesUnreadCounterBadge.render(badgeState) views.spacesUnreadCounterBadge.render(badgeState)
} }

View file

@ -25,19 +25,12 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel 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.launchIn
import kotlinx.coroutines.flow.onEach 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( class NewHomeDetailViewModel @AssistedInject constructor(
@Assisted initialState: NewHomeDetailViewState, @Assisted initialState: NewHomeDetailViewState,
private val getNotificationCountForSpacesUseCase: GetNotificationCountForSpacesUseCase, private val getSpacesNotificationBadgeStateUseCase: GetSpacesNotificationBadgeStateUseCase,
private val getSpacesUseCase: GetSpacesUseCase,
) : VectorViewModel<NewHomeDetailViewState, EmptyAction, EmptyViewEvents>(initialState) { ) : VectorViewModel<NewHomeDetailViewState, EmptyAction, EmptyViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
@ -48,23 +41,12 @@ class NewHomeDetailViewModel @AssistedInject constructor(
companion object : MavericksViewModelFactory<NewHomeDetailViewModel, NewHomeDetailViewState> by hiltMavericksViewModelFactory() companion object : MavericksViewModelFactory<NewHomeDetailViewModel, NewHomeDetailViewState> by hiltMavericksViewModelFactory()
init { init {
observeSpacesNotificationCount() observeSpacesNotificationBadgeState()
observeSpacesInvite()
} }
private fun observeSpacesNotificationCount() { private fun observeSpacesNotificationBadgeState() {
getNotificationCountForSpacesUseCase.execute(SpaceFilter.NoFilter) getSpacesNotificationBadgeStateUseCase.execute()
.onEach { setState { copy(spacesNotificationCount = it) } } .onEach { badgeState -> setState { copy(spacesNotificationCounterBadgeState = badgeState) } }
.launchIn(viewModelScope)
}
private fun observeSpacesInvite() {
val params = spaceSummaryQueryParams {
memberships = listOf(Membership.INVITE)
displayName = QueryStringValue.IsNotEmpty
}
getSpacesUseCase.execute(params)
.onEach { setState { copy(hasPendingSpaceInvites = it.isNotEmpty()) } }
.launchIn(viewModelScope) .launchIn(viewModelScope)
} }

View file

@ -17,9 +17,8 @@
package im.vector.app.features.home package im.vector.app.features.home
import com.airbnb.mvrx.MavericksState 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( data class NewHomeDetailViewState(
val spacesNotificationCount: RoomAggregateNotificationCount = RoomAggregateNotificationCount(notificationCount = 0, highlightCount = 0), val spacesNotificationCounterBadgeState: UnreadCounterBadgeView.State = UnreadCounterBadgeView.State.Count(count = 0, highlighted = false),
val hasPendingSpaceInvites: Boolean = false,
) : MavericksState ) : MavericksState

View file

@ -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) }
}
}

View file

@ -17,8 +17,8 @@
package im.vector.app.features.home package im.vector.app.features.home
import com.airbnb.mvrx.test.MavericksTestRule 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.GetSpacesUseCase
import im.vector.app.features.spaces.notification.GetNotificationCountForSpacesUseCase
import im.vector.app.test.test import im.vector.app.test.test
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
@ -27,10 +27,6 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.junit.Rule import org.junit.Rule
import org.junit.Test 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 { internal class NewHomeDetailViewModelTest {
@ -38,43 +34,34 @@ internal class NewHomeDetailViewModelTest {
val mavericksTestRule = MavericksTestRule(testDispatcher = UnconfinedTestDispatcher()) val mavericksTestRule = MavericksTestRule(testDispatcher = UnconfinedTestDispatcher())
private val initialState = NewHomeDetailViewState() private val initialState = NewHomeDetailViewState()
private val fakeGetNotificationCountForSpacesUseCase = mockk<GetNotificationCountForSpacesUseCase>() private val fakeGetSpacesNotificationBadgeStateUseCase = mockk<GetSpacesNotificationBadgeStateUseCase>()
private val fakeGetSpacesUseCase = mockk<GetSpacesUseCase>()
private fun createViewModel(): NewHomeDetailViewModel { private fun createViewModel(): NewHomeDetailViewModel {
return NewHomeDetailViewModel( return NewHomeDetailViewModel(
initialState = initialState, initialState = initialState,
getNotificationCountForSpacesUseCase = fakeGetNotificationCountForSpacesUseCase, getSpacesNotificationBadgeStateUseCase = fakeGetSpacesNotificationBadgeStateUseCase,
getSpacesUseCase = fakeGetSpacesUseCase,
) )
} }
@Test @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 // Given
val spacesNotificationCount = RoomAggregateNotificationCount( val aBadgeState = UnreadCounterBadgeView.State.Count(count = 1, highlighted = false)
notificationCount = 1, every { fakeGetSpacesNotificationBadgeStateUseCase.execute() } returns flowOf(aBadgeState)
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,
)
// When // When
val viewModel = createViewModel() val viewModel = createViewModel()
val viewModelTest = viewModel.test() val viewModelTest = viewModel.test()
// Then // Then
val expectedViewState = initialState.copy(
spacesNotificationCounterBadgeState = aBadgeState,
)
viewModelTest viewModelTest
.assertLatestState(expectedViewState) .assertLatestState(expectedViewState)
.finish() .finish()
verify { verify {
fakeGetNotificationCountForSpacesUseCase.execute(SpaceFilter.NoFilter) fakeGetSpacesNotificationBadgeStateUseCase.execute()
fakeGetSpacesUseCase.execute(match { it.memberships == listOf(Membership.INVITE) })
} }
} }
} }