Skip to content

Commit a6f7302

Browse files
authored
Merge pull request #8159 from vector-im/feature/mna/aggregated-unread-indicator
Add aggregated unread indicator for spaces in the new layout
2 parents ef38ba0 + 53d0333 commit a6f7302

18 files changed

+790
-39
lines changed

changelog.d/8157.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add aggregated unread indicator for spaces in the new layout

vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt

+6
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import im.vector.app.features.discovery.DiscoverySettingsViewModel
4040
import im.vector.app.features.discovery.change.SetIdentityServerViewModel
4141
import im.vector.app.features.home.HomeActivityViewModel
4242
import im.vector.app.features.home.HomeDetailViewModel
43+
import im.vector.app.features.home.NewHomeDetailViewModel
4344
import im.vector.app.features.home.UnknownDeviceDetectorSharedViewModel
4445
import im.vector.app.features.home.UnreadMessagesSharedViewModel
4546
import im.vector.app.features.home.UserColorAccountDataViewModel
@@ -717,4 +718,9 @@ interface MavericksViewModelModule {
717718
@IntoMap
718719
@MavericksViewModelKey(RoomPollDetailViewModel::class)
719720
fun roomPollDetailViewModelFactory(factory: RoomPollDetailViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
721+
722+
@Binds
723+
@IntoMap
724+
@MavericksViewModelKey(NewHomeDetailViewModel::class)
725+
fun newHomeDetailViewModelFactory(factory: NewHomeDetailViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
720726
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright (c) 2023 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.features.home
18+
19+
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
20+
import im.vector.app.features.spaces.GetSpacesUseCase
21+
import im.vector.app.features.spaces.notification.GetNotificationCountForSpacesUseCase
22+
import kotlinx.coroutines.flow.Flow
23+
import kotlinx.coroutines.flow.combine
24+
import org.matrix.android.sdk.api.query.QueryStringValue
25+
import org.matrix.android.sdk.api.query.SpaceFilter
26+
import org.matrix.android.sdk.api.session.room.model.Membership
27+
import org.matrix.android.sdk.api.session.room.model.RoomSummary
28+
import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams
29+
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
30+
import javax.inject.Inject
31+
32+
class GetSpacesNotificationBadgeStateUseCase @Inject constructor(
33+
private val getNotificationCountForSpacesUseCase: GetNotificationCountForSpacesUseCase,
34+
private val getSpacesUseCase: GetSpacesUseCase,
35+
) {
36+
37+
fun execute(): Flow<UnreadCounterBadgeView.State> {
38+
val params = spaceSummaryQueryParams {
39+
memberships = listOf(Membership.INVITE)
40+
displayName = QueryStringValue.IsNotEmpty
41+
}
42+
return combine(
43+
getNotificationCountForSpacesUseCase.execute(SpaceFilter.NoFilter),
44+
getSpacesUseCase.execute(params),
45+
) { spacesNotificationCount, spaceInvites ->
46+
computeSpacesNotificationCounterBadgeState(spacesNotificationCount, spaceInvites)
47+
}
48+
}
49+
50+
private fun computeSpacesNotificationCounterBadgeState(
51+
spacesNotificationCount: RoomAggregateNotificationCount,
52+
spaceInvites: List<RoomSummary>,
53+
): UnreadCounterBadgeView.State {
54+
val hasPendingSpaceInvites = spaceInvites.isNotEmpty()
55+
return if (hasPendingSpaceInvites && spacesNotificationCount.notificationCount == 0) {
56+
UnreadCounterBadgeView.State.Text(
57+
text = "!",
58+
highlighted = true,
59+
)
60+
} else {
61+
UnreadCounterBadgeView.State.Count(
62+
count = spacesNotificationCount.notificationCount,
63+
highlighted = spacesNotificationCount.isHighlight || hasPendingSpaceInvites,
64+
)
65+
}
66+
}
67+
}

vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt

+10
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import im.vector.app.features.call.SharedKnownCallsViewModel
4747
import im.vector.app.features.call.VectorCallActivity
4848
import im.vector.app.features.call.dialpad.PstnDialActivity
4949
import im.vector.app.features.call.webrtc.WebRtcCallManager
50+
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
5051
import im.vector.app.features.home.room.list.actions.RoomListSharedAction
5152
import im.vector.app.features.home.room.list.actions.RoomListSharedActionViewModel
5253
import im.vector.app.features.home.room.list.home.HomeRoomListFragment
@@ -82,6 +83,7 @@ class NewHomeDetailFragment :
8283
@Inject lateinit var buildMeta: BuildMeta
8384

8485
private val viewModel: HomeDetailViewModel by fragmentViewModel()
86+
private val newHomeDetailViewModel: NewHomeDetailViewModel by fragmentViewModel()
8587
private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel()
8688
private val serverBackupStatusViewModel: ServerBackupStatusViewModel by activityViewModel()
8789

@@ -180,6 +182,10 @@ class NewHomeDetailFragment :
180182
currentCallsViewPresenter.updateCall(callManager.getCurrentCall(), callManager.getCalls())
181183
invalidateOptionsMenu()
182184
}
185+
186+
newHomeDetailViewModel.onEach { viewState ->
187+
refreshUnreadCounterBadge(viewState.spacesNotificationCounterBadgeState)
188+
}
183189
}
184190

185191
private fun setupObservers() {
@@ -379,6 +385,10 @@ class NewHomeDetailFragment :
379385
}
380386
}
381387

388+
private fun refreshUnreadCounterBadge(badgeState: UnreadCounterBadgeView.State) {
389+
views.spacesUnreadCounterBadge.render(badgeState)
390+
}
391+
382392
override fun onTapToReturnToCall() {
383393
callManager.getCurrentCall()?.let { call ->
384394
VectorCallActivity.newIntent(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright (c) 2023 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.features.home
18+
19+
import com.airbnb.mvrx.MavericksViewModelFactory
20+
import dagger.assisted.Assisted
21+
import dagger.assisted.AssistedFactory
22+
import dagger.assisted.AssistedInject
23+
import im.vector.app.core.di.MavericksAssistedViewModelFactory
24+
import im.vector.app.core.di.hiltMavericksViewModelFactory
25+
import im.vector.app.core.platform.EmptyAction
26+
import im.vector.app.core.platform.EmptyViewEvents
27+
import im.vector.app.core.platform.VectorViewModel
28+
import kotlinx.coroutines.flow.launchIn
29+
import kotlinx.coroutines.flow.onEach
30+
31+
class NewHomeDetailViewModel @AssistedInject constructor(
32+
@Assisted initialState: NewHomeDetailViewState,
33+
private val getSpacesNotificationBadgeStateUseCase: GetSpacesNotificationBadgeStateUseCase,
34+
) : VectorViewModel<NewHomeDetailViewState, EmptyAction, EmptyViewEvents>(initialState) {
35+
36+
@AssistedFactory
37+
interface Factory : MavericksAssistedViewModelFactory<NewHomeDetailViewModel, NewHomeDetailViewState> {
38+
override fun create(initialState: NewHomeDetailViewState): NewHomeDetailViewModel
39+
}
40+
41+
companion object : MavericksViewModelFactory<NewHomeDetailViewModel, NewHomeDetailViewState> by hiltMavericksViewModelFactory()
42+
43+
init {
44+
observeSpacesNotificationBadgeState()
45+
}
46+
47+
private fun observeSpacesNotificationBadgeState() {
48+
getSpacesNotificationBadgeStateUseCase.execute()
49+
.onEach { badgeState -> setState { copy(spacesNotificationCounterBadgeState = badgeState) } }
50+
.launchIn(viewModelScope)
51+
}
52+
53+
override fun handle(action: EmptyAction) {
54+
// do nothing
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright (c) 2023 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.features.home
18+
19+
import com.airbnb.mvrx.MavericksState
20+
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
21+
22+
data class NewHomeDetailViewState(
23+
val spacesNotificationCounterBadgeState: UnreadCounterBadgeView.State = UnreadCounterBadgeView.State.Count(count = 0, highlighted = false),
24+
) : MavericksState
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (c) 2023 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.features.spaces
18+
19+
import im.vector.app.core.di.ActiveSessionHolder
20+
import kotlinx.coroutines.flow.Flow
21+
import kotlinx.coroutines.flow.emptyFlow
22+
import org.matrix.android.sdk.api.session.room.model.RoomSummary
23+
import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
24+
import org.matrix.android.sdk.flow.flow
25+
import javax.inject.Inject
26+
27+
class GetSpacesUseCase @Inject constructor(
28+
private val activeSessionHolder: ActiveSessionHolder,
29+
) {
30+
31+
fun execute(params: SpaceSummaryQueryParams): Flow<List<RoomSummary>> {
32+
val session = activeSessionHolder.getSafeActiveSession()
33+
34+
return session?.flow()?.liveSpaceSummaries(params) ?: emptyFlow()
35+
}
36+
}

vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt

+6-37
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,13 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
2929
import im.vector.app.core.platform.VectorViewModel
3030
import im.vector.app.features.analytics.AnalyticsTracker
3131
import im.vector.app.features.analytics.plan.Interaction
32-
import im.vector.app.features.invite.AutoAcceptInvites
3332
import im.vector.app.features.session.coroutineScope
3433
import im.vector.app.features.settings.VectorPreferences
35-
import kotlinx.coroutines.Dispatchers
34+
import im.vector.app.features.spaces.notification.GetNotificationCountForSpacesUseCase
3635
import kotlinx.coroutines.flow.combine
3736
import kotlinx.coroutines.flow.distinctUntilChanged
38-
import kotlinx.coroutines.flow.flowOn
3937
import kotlinx.coroutines.flow.launchIn
4038
import kotlinx.coroutines.flow.onEach
41-
import kotlinx.coroutines.flow.sample
4239
import kotlinx.coroutines.launch
4340
import org.matrix.android.sdk.api.extensions.tryOrNull
4441
import org.matrix.android.sdk.api.query.QueryStringValue
@@ -48,25 +45,22 @@ import org.matrix.android.sdk.api.session.events.model.toContent
4845
import org.matrix.android.sdk.api.session.events.model.toModel
4946
import org.matrix.android.sdk.api.session.getRoom
5047
import org.matrix.android.sdk.api.session.getUserOrDefault
51-
import org.matrix.android.sdk.api.session.room.RoomSortOrder
5248
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes
5349
import org.matrix.android.sdk.api.session.room.model.Membership
54-
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
5550
import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams
56-
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
5751
import org.matrix.android.sdk.api.session.space.SpaceOrderUtils
5852
import org.matrix.android.sdk.api.session.space.model.SpaceOrderContent
5953
import org.matrix.android.sdk.api.session.space.model.TopLevelSpaceComparator
6054
import org.matrix.android.sdk.api.util.toMatrixItem
61-
import org.matrix.android.sdk.flow.flow
6255

6356
class SpaceListViewModel @AssistedInject constructor(
6457
@Assisted initialState: SpaceListViewState,
6558
private val spaceStateHandler: SpaceStateHandler,
6659
private val session: Session,
6760
private val vectorPreferences: VectorPreferences,
68-
private val autoAcceptInvites: AutoAcceptInvites,
6961
private val analyticsTracker: AnalyticsTracker,
62+
getNotificationCountForSpacesUseCase: GetNotificationCountForSpacesUseCase,
63+
private val getSpacesUseCase: GetSpacesUseCase,
7064
) : VectorViewModel<SpaceListViewState, SpaceListAction, SpaceListViewEvents>(initialState) {
7165

7266
@AssistedFactory
@@ -92,39 +86,14 @@ class SpaceListViewModel @AssistedInject constructor(
9286
copy(selectedSpace = selectedSpaceOption.orNull())
9387
}
9488

95-
// XXX there should be a way to refactor this and share it
96-
session.roomService().getPagedRoomSummariesLive(
97-
roomSummaryQueryParams {
98-
this.memberships = listOf(Membership.JOIN)
99-
this.spaceFilter = roomsInSpaceFilter()
100-
}, sortOrder = RoomSortOrder.NONE
101-
).asFlow()
102-
.sample(300)
103-
.onEach {
104-
val inviteCount = if (autoAcceptInvites.hideInvites) {
105-
0
106-
} else {
107-
session.roomService().getRoomSummaries(
108-
roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) }
109-
).size
110-
}
111-
val totalCount = session.roomService().getNotificationCountForRooms(
112-
roomSummaryQueryParams {
113-
this.memberships = listOf(Membership.JOIN)
114-
this.spaceFilter = roomsInSpaceFilter()
115-
}
116-
)
117-
val counts = RoomAggregateNotificationCount(
118-
totalCount.notificationCount + inviteCount,
119-
totalCount.highlightCount + inviteCount
120-
)
89+
getNotificationCountForSpacesUseCase.execute(roomsInSpaceFilter())
90+
.onEach { counts ->
12191
setState {
12292
copy(
12393
homeAggregateCount = counts
12494
)
12595
}
12696
}
127-
.flowOn(Dispatchers.Default)
12897
.launchIn(viewModelScope)
12998
}
13099

@@ -267,7 +236,7 @@ class SpaceListViewModel @AssistedInject constructor(
267236
}
268237

269238
combine(
270-
session.flow().liveSpaceSummaries(params),
239+
getSpacesUseCase.execute(params),
271240
session.accountDataService()
272241
.getLiveRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER))
273242
.asFlow()

0 commit comments

Comments
 (0)