mirror of
https://github.com/element-hq/element-android
synced 2024-11-26 19:35:42 +03:00
Extracting logic to compute the badge state into a usecase
This commit is contained in:
parent
f782a31592
commit
2bd0126523
6 changed files with 174 additions and 66 deletions
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue