Skip to content

Commit f8d5d25

Browse files
authored
Merge pull request #907 from vector-im/feature/fga/fix_room_list_scroll_position
RoomList: introduce RoomListDataSource
2 parents 39a8d9d + 60d0b5c commit f8d5d25

File tree

9 files changed

+212
-228
lines changed

9 files changed

+212
-228
lines changed

features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt

Lines changed: 11 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -30,43 +30,30 @@ import io.element.android.features.leaveroom.api.LeaveRoomEvent
3030
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
3131
import io.element.android.features.networkmonitor.api.NetworkMonitor
3232
import io.element.android.features.networkmonitor.api.NetworkStatus
33-
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
34-
import io.element.android.features.roomlist.impl.model.RoomListRoomSummaryPlaceholders
33+
import io.element.android.features.roomlist.impl.datasource.InviteStateDataSource
34+
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
3535
import io.element.android.libraries.architecture.Presenter
36-
import io.element.android.libraries.core.coroutine.parallelMap
37-
import io.element.android.libraries.core.extensions.orEmpty
38-
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
39-
import io.element.android.libraries.designsystem.components.avatar.AvatarData
40-
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
4136
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
4237
import io.element.android.libraries.designsystem.utils.collectSnackbarMessageAsState
43-
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
4438
import io.element.android.libraries.matrix.api.MatrixClient
45-
import io.element.android.libraries.matrix.api.core.RoomId
46-
import io.element.android.libraries.matrix.api.room.RoomSummary
4739
import io.element.android.libraries.matrix.api.user.MatrixUser
4840
import io.element.android.libraries.matrix.api.user.getCurrentUser
4941
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
5042
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
51-
import kotlinx.collections.immutable.ImmutableList
52-
import kotlinx.collections.immutable.persistentListOf
53-
import kotlinx.collections.immutable.toImmutableList
5443
import kotlinx.coroutines.CoroutineScope
5544
import kotlinx.coroutines.launch
56-
import timber.log.Timber
5745
import javax.inject.Inject
5846

5947
private const val extendedRangeSize = 40
6048

6149
class RoomListPresenter @Inject constructor(
6250
private val client: MatrixClient,
63-
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
64-
private val roomLastMessageFormatter: RoomLastMessageFormatter,
6551
private val sessionVerificationService: SessionVerificationService,
6652
private val networkMonitor: NetworkMonitor,
6753
private val snackbarDispatcher: SnackbarDispatcher,
6854
private val inviteStateDataSource: InviteStateDataSource,
6955
private val leaveRoomPresenter: LeaveRoomPresenter,
56+
private val roomListDataSource: RoomListDataSource,
7057
) : Presenter<RoomListState> {
7158

7259
@Composable
@@ -75,21 +62,13 @@ class RoomListPresenter @Inject constructor(
7562
val matrixUser: MutableState<MatrixUser?> = rememberSaveable {
7663
mutableStateOf(null)
7764
}
78-
var filter by rememberSaveable { mutableStateOf("") }
79-
val roomSummaries by client
80-
.roomSummaryDataSource
81-
.allRooms()
82-
.collectAsState()
83-
65+
val roomList by roomListDataSource.allRooms.collectAsState()
66+
val filteredRoomList by roomListDataSource.filteredRooms.collectAsState()
67+
val filter by roomListDataSource.filter.collectAsState()
8468
val networkConnectionStatus by networkMonitor.connectivity.collectAsState()
8569

86-
Timber.v("RoomSummaries size = ${roomSummaries.size}")
87-
88-
val mappedRoomSummaries: MutableState<ImmutableList<RoomListRoomSummary>> = remember { mutableStateOf(persistentListOf()) }
89-
val filteredRoomSummaries: MutableState<ImmutableList<RoomListRoomSummary>> = remember {
90-
mutableStateOf(persistentListOf())
91-
}
9270
LaunchedEffect(Unit) {
71+
roomListDataSource.launchIn(this)
9372
initialLoad(matrixUser)
9473
}
9574

@@ -107,12 +86,12 @@ class RoomListPresenter @Inject constructor(
10786

10887
fun handleEvents(event: RoomListEvents) {
10988
when (event) {
110-
is RoomListEvents.UpdateFilter -> filter = event.newFilter
89+
is RoomListEvents.UpdateFilter -> roomListDataSource.updateFilter(event.newFilter)
11190
is RoomListEvents.UpdateVisibleRange -> updateVisibleRange(event.range)
11291
RoomListEvents.DismissRequestVerificationPrompt -> verificationPromptDismissed = true
11392
RoomListEvents.ToggleSearchResults -> {
11493
if (displaySearchResults) {
115-
filter = ""
94+
roomListDataSource.updateFilter("")
11695
}
11796
displaySearchResults = !displaySearchResults
11897
}
@@ -127,22 +106,13 @@ class RoomListPresenter @Inject constructor(
127106
}
128107
}
129108

130-
LaunchedEffect(roomSummaries, filter) {
131-
mappedRoomSummaries.value = if (roomSummaries.isEmpty()) {
132-
RoomListRoomSummaryPlaceholders.createFakeList(16).toImmutableList()
133-
} else {
134-
mapRoomSummaries(roomSummaries).toImmutableList()
135-
}
136-
filteredRoomSummaries.value = updateFilteredRoomSummaries(mappedRoomSummaries.value, filter)
137-
}
138-
139109
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
140110

141111
return RoomListState(
142112
matrixUser = matrixUser.value,
143-
roomList = mappedRoomSummaries.value,
113+
roomList = roomList,
144114
filter = filter,
145-
filteredRoomList = filteredRoomSummaries.value,
115+
filteredRoomList = filteredRoomList,
146116
displayVerificationPrompt = displayVerificationPrompt,
147117
snackbarMessage = snackbarMessage,
148118
hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online,
@@ -154,13 +124,6 @@ class RoomListPresenter @Inject constructor(
154124
)
155125
}
156126

157-
private fun updateFilteredRoomSummaries(mappedRoomSummaries: ImmutableList<RoomListRoomSummary>, filter: String): ImmutableList<RoomListRoomSummary> {
158-
return when {
159-
filter.isEmpty() -> emptyList()
160-
else -> mappedRoomSummaries.filter { it.name.contains(filter, ignoreCase = true) }
161-
}.toImmutableList()
162-
}
163-
164127
private fun CoroutineScope.initialLoad(matrixUser: MutableState<MatrixUser?>) = launch {
165128
matrixUser.value = client.getCurrentUser()
166129
}
@@ -174,34 +137,4 @@ class RoomListPresenter @Inject constructor(
174137
val extendedRange = IntRange(extendedRangeStart, extendedRangeEnd)
175138
client.roomSummaryDataSource.updateAllRoomsVisibleRange(extendedRange)
176139
}
177-
178-
private suspend fun mapRoomSummaries(
179-
roomSummaries: List<RoomSummary>
180-
): List<RoomListRoomSummary> {
181-
return roomSummaries.parallelMap { roomSummary ->
182-
when (roomSummary) {
183-
is RoomSummary.Empty -> RoomListRoomSummaryPlaceholders.create(roomSummary.identifier)
184-
is RoomSummary.Filled -> {
185-
val avatarData = AvatarData(
186-
id = roomSummary.identifier(),
187-
name = roomSummary.details.name,
188-
url = roomSummary.details.avatarURLString,
189-
size = AvatarSize.RoomListItem,
190-
)
191-
val roomIdentifier = roomSummary.identifier()
192-
RoomListRoomSummary(
193-
id = roomSummary.identifier(),
194-
roomId = RoomId(roomIdentifier),
195-
name = roomSummary.details.name,
196-
hasUnread = roomSummary.details.unreadNotificationCount > 0,
197-
timestamp = lastMessageTimestampFormatter.format(roomSummary.details.lastMessageTimestamp),
198-
lastMessage = roomSummary.details.lastMessage?.let { message ->
199-
roomLastMessageFormatter.format(message.event, roomSummary.details.isDirect)
200-
}.orEmpty(),
201-
avatarData = avatarData,
202-
)
203-
}
204-
}
205-
}
206-
}
207140
}

features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSource.kt renamed to features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSource.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package io.element.android.features.roomlist.impl
17+
package io.element.android.features.roomlist.impl.datasource
1818

1919
import androidx.compose.runtime.Composable
2020
import androidx.compose.runtime.LaunchedEffect
@@ -25,6 +25,7 @@ import androidx.compose.runtime.remember
2525
import androidx.compose.runtime.setValue
2626
import com.squareup.anvil.annotations.ContributesBinding
2727
import io.element.android.features.invitelist.api.SeenInvitesStore
28+
import io.element.android.features.roomlist.impl.InvitesState
2829
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
2930
import io.element.android.libraries.di.SessionScope
3031
import io.element.android.libraries.matrix.api.MatrixClient

features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/InviteStateDataSource.kt renamed to features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/datasource/InviteStateDataSource.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414
* limitations under the License.
1515
*/
1616

17-
package io.element.android.features.roomlist.impl
17+
package io.element.android.features.roomlist.impl.datasource
1818

1919
import androidx.compose.runtime.Composable
20+
import io.element.android.features.roomlist.impl.InvitesState
2021

2122
interface InviteStateDataSource {
2223

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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 io.element.android.features.roomlist.impl.datasource
18+
19+
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
20+
import io.element.android.features.roomlist.impl.model.RoomListRoomSummaryPlaceholders
21+
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
22+
import io.element.android.libraries.core.extensions.orEmpty
23+
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
24+
import io.element.android.libraries.designsystem.components.avatar.AvatarData
25+
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
26+
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
27+
import io.element.android.libraries.matrix.api.core.RoomId
28+
import io.element.android.libraries.matrix.api.room.RoomSummary
29+
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
30+
import kotlinx.collections.immutable.ImmutableList
31+
import kotlinx.collections.immutable.persistentListOf
32+
import kotlinx.collections.immutable.toImmutableList
33+
import kotlinx.coroutines.CoroutineScope
34+
import kotlinx.coroutines.flow.MutableStateFlow
35+
import kotlinx.coroutines.flow.StateFlow
36+
import kotlinx.coroutines.flow.combine
37+
import kotlinx.coroutines.flow.launchIn
38+
import kotlinx.coroutines.flow.onEach
39+
import kotlinx.coroutines.withContext
40+
import javax.inject.Inject
41+
42+
class RoomListDataSource @Inject constructor(
43+
private val roomSummaryDataSource: RoomSummaryDataSource,
44+
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
45+
private val roomLastMessageFormatter: RoomLastMessageFormatter,
46+
private val coroutineDispatchers: CoroutineDispatchers,
47+
) {
48+
49+
private val _filter = MutableStateFlow("")
50+
private val _allRooms = MutableStateFlow<ImmutableList<RoomListRoomSummary>>(persistentListOf())
51+
private val _filteredRooms = MutableStateFlow<ImmutableList<RoomListRoomSummary>>(persistentListOf())
52+
53+
fun launchIn(coroutineScope: CoroutineScope) {
54+
roomSummaryDataSource
55+
.allRooms()
56+
.onEach { roomSummaries ->
57+
_allRooms.value = if (roomSummaries.isEmpty()) {
58+
RoomListRoomSummaryPlaceholders.createFakeList(16)
59+
} else {
60+
mapRoomSummaries(roomSummaries)
61+
}.toImmutableList()
62+
}
63+
.launchIn(coroutineScope)
64+
65+
combine(
66+
_filter,
67+
_allRooms
68+
) { filterValue, allRoomsValue ->
69+
when {
70+
filterValue.isEmpty() -> emptyList()
71+
else -> allRoomsValue.filter { it.name.contains(filterValue, ignoreCase = true) }
72+
}.toImmutableList()
73+
}
74+
.onEach {
75+
_filteredRooms.value = it
76+
}.launchIn(coroutineScope)
77+
}
78+
79+
fun updateFilter(filterValue: String) {
80+
_filter.value = filterValue
81+
}
82+
83+
val filter: StateFlow<String> = _filter
84+
val allRooms: StateFlow<ImmutableList<RoomListRoomSummary>> = _allRooms
85+
val filteredRooms: StateFlow<ImmutableList<RoomListRoomSummary>> = _filteredRooms
86+
87+
private suspend fun mapRoomSummaries(
88+
roomSummaries: List<RoomSummary>
89+
): List<RoomListRoomSummary> = withContext(coroutineDispatchers.computation) {
90+
roomSummaries.map { roomSummary ->
91+
when (roomSummary) {
92+
is RoomSummary.Empty -> RoomListRoomSummaryPlaceholders.create(roomSummary.identifier)
93+
is RoomSummary.Filled -> {
94+
val avatarData = AvatarData(
95+
id = roomSummary.identifier(),
96+
name = roomSummary.details.name,
97+
url = roomSummary.details.avatarURLString,
98+
size = AvatarSize.RoomListItem,
99+
)
100+
val roomIdentifier = roomSummary.identifier()
101+
RoomListRoomSummary(
102+
id = roomSummary.identifier(),
103+
roomId = RoomId(roomIdentifier),
104+
name = roomSummary.details.name,
105+
hasUnread = roomSummary.details.unreadNotificationCount > 0,
106+
timestamp = lastMessageTimestampFormatter.format(roomSummary.details.lastMessageTimestamp),
107+
lastMessage = roomSummary.details.lastMessage?.let { message ->
108+
roomLastMessageFormatter.format(message.event, roomSummary.details.isDirect)
109+
}.orEmpty(),
110+
avatarData = avatarData,
111+
)
112+
}
113+
}
114+
}
115+
}
116+
}

0 commit comments

Comments
 (0)