Render errors in room member list view.
This commit is contained in:
@@ -31,6 +31,7 @@ import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationEvents
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationPresenter
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
@@ -59,10 +60,10 @@ class RoomMemberListPresenter @AssistedInject constructor(
|
||||
@Composable
|
||||
override fun present(): RoomMemberListState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var roomMembers by remember { mutableStateOf(RoomMembers.loading()) }
|
||||
var roomMembers: AsyncData<RoomMembers> by remember { mutableStateOf(AsyncData.Loading()) }
|
||||
var searchQuery by rememberSaveable { mutableStateOf("") }
|
||||
var searchResults by remember {
|
||||
mutableStateOf<SearchBarResultState<RoomMembers>>(SearchBarResultState.Initial())
|
||||
mutableStateOf<SearchBarResultState<AsyncData<RoomMembers>>>(SearchBarResultState.Initial())
|
||||
}
|
||||
var isSearchActive by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
@@ -82,6 +83,12 @@ class RoomMemberListPresenter @AssistedInject constructor(
|
||||
if (membersState is MatrixRoomMembersState.Unknown) {
|
||||
return@LaunchedEffect
|
||||
}
|
||||
val _membersState = membersState
|
||||
if (_membersState is MatrixRoomMembersState.Error && _membersState.roomMembers().orEmpty().isEmpty()) {
|
||||
// Cannot fetch members and no cached members, display the error
|
||||
roomMembers = AsyncData.Failure(_membersState.failure)
|
||||
return@LaunchedEffect
|
||||
}
|
||||
withContext(coroutineDispatchers.io) {
|
||||
val members = membersState.roomMembers().orEmpty().groupBy { it.membership }
|
||||
val info = room.roomInfoFlow.first()
|
||||
@@ -90,14 +97,18 @@ class RoomMemberListPresenter @AssistedInject constructor(
|
||||
// This result will come from the timeline loading membership events and it'll be wrong.
|
||||
return@withContext
|
||||
}
|
||||
roomMembers = RoomMembers(
|
||||
val result = RoomMembers(
|
||||
invited = members.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(),
|
||||
joined = members.getOrDefault(RoomMembershipState.JOIN, emptyList())
|
||||
.sortedWith(PowerLevelRoomMemberComparator())
|
||||
.toImmutableList(),
|
||||
banned = members.getOrDefault(RoomMembershipState.BAN, emptyList()).sortedBy { it.userId.value }.toImmutableList(),
|
||||
isLoading = membersState is MatrixRoomMembersState.Pending,
|
||||
)
|
||||
roomMembers = if (membersState is MatrixRoomMembersState.Pending) {
|
||||
AsyncData.Loading(result)
|
||||
} else {
|
||||
AsyncData.Success(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,15 +121,19 @@ class RoomMemberListPresenter @AssistedInject constructor(
|
||||
if (results.isEmpty()) {
|
||||
SearchBarResultState.NoResultsFound()
|
||||
} else {
|
||||
val result = RoomMembers(
|
||||
invited = results.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(),
|
||||
joined = results.getOrDefault(RoomMembershipState.JOIN, emptyList())
|
||||
.sortedWith(PowerLevelRoomMemberComparator())
|
||||
.toImmutableList(),
|
||||
banned = results.getOrDefault(RoomMembershipState.BAN, emptyList()).sortedBy { it.userId.value }.toImmutableList(),
|
||||
)
|
||||
SearchBarResultState.Results(
|
||||
RoomMembers(
|
||||
invited = results.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(),
|
||||
joined = results.getOrDefault(RoomMembershipState.JOIN, emptyList())
|
||||
.sortedWith(PowerLevelRoomMemberComparator())
|
||||
.toImmutableList(),
|
||||
banned = results.getOrDefault(RoomMembershipState.BAN, emptyList()).sortedBy { it.userId.value }.toImmutableList(),
|
||||
isLoading = membersState is MatrixRoomMembersState.Pending,
|
||||
)
|
||||
if (membersState is MatrixRoomMembersState.Pending) {
|
||||
AsyncData.Loading(result)
|
||||
} else {
|
||||
AsyncData.Success(result)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
package io.element.android.features.roomdetails.impl.members
|
||||
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
data class RoomMemberListState(
|
||||
val roomMembers: RoomMembers,
|
||||
val roomMembers: AsyncData<RoomMembers>,
|
||||
val searchQuery: String,
|
||||
val searchResults: SearchBarResultState<RoomMembers>,
|
||||
val searchResults: SearchBarResultState<AsyncData<RoomMembers>>,
|
||||
val isSearchActive: Boolean,
|
||||
val canInvite: Boolean,
|
||||
val moderationState: RoomMembersModerationState,
|
||||
@@ -36,14 +36,4 @@ data class RoomMembers(
|
||||
val invited: ImmutableList<RoomMember>,
|
||||
val joined: ImmutableList<RoomMember>,
|
||||
val banned: ImmutableList<RoomMember>,
|
||||
val isLoading: Boolean,
|
||||
) {
|
||||
companion object {
|
||||
fun loading() = RoomMembers(
|
||||
invited = persistentListOf(),
|
||||
joined = persistentListOf(),
|
||||
banned = persistentListOf(),
|
||||
isLoading = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@ package io.element.android.features.roomdetails.impl.members
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.aRoomMembersModerationState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
@@ -29,14 +30,15 @@ internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMember
|
||||
override val values: Sequence<RoomMemberListState>
|
||||
get() = sequenceOf(
|
||||
aRoomMemberListState(
|
||||
roomMembers = RoomMembers(
|
||||
invited = persistentListOf(aVictor(), aWalter()),
|
||||
joined = persistentListOf(anAlice(), aBob(), aWalter()),
|
||||
banned = persistentListOf(),
|
||||
isLoading = false,
|
||||
roomMembers = AsyncData.Success(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(aVictor(), aWalter()),
|
||||
joined = persistentListOf(anAlice(), aBob(), aWalter()),
|
||||
banned = persistentListOf(),
|
||||
)
|
||||
)
|
||||
),
|
||||
aRoomMemberListState(roomMembers = RoomMembers.loading()),
|
||||
aRoomMemberListState(roomMembers = AsyncData.Loading()),
|
||||
aRoomMemberListState().copy(canInvite = true),
|
||||
aRoomMemberListState().copy(isSearchActive = false),
|
||||
aRoomMemberListState().copy(isSearchActive = true),
|
||||
@@ -45,11 +47,12 @@ internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMember
|
||||
isSearchActive = true,
|
||||
searchQuery = "@someone:matrix.org",
|
||||
searchResults = SearchBarResultState.Results(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(aVictor()),
|
||||
joined = persistentListOf(anAlice()),
|
||||
banned = persistentListOf(),
|
||||
isLoading = false,
|
||||
AsyncData.Success(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(aVictor()),
|
||||
joined = persistentListOf(anAlice()),
|
||||
banned = persistentListOf(),
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
@@ -58,6 +61,9 @@ internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMember
|
||||
searchQuery = "something-with-no-results",
|
||||
searchResults = SearchBarResultState.NoResultsFound()
|
||||
),
|
||||
aRoomMemberListState(
|
||||
roomMembers = AsyncData.Failure(Exception("Error details")),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -65,37 +71,40 @@ internal class RoomMemberListStateBannedProvider : PreviewParameterProvider<Room
|
||||
override val values: Sequence<RoomMemberListState>
|
||||
get() = sequenceOf(
|
||||
aRoomMemberListState(
|
||||
roomMembers = RoomMembers(
|
||||
invited = persistentListOf(),
|
||||
joined = persistentListOf(),
|
||||
banned = persistentListOf(
|
||||
aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice"),
|
||||
aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob"),
|
||||
aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie"),
|
||||
),
|
||||
isLoading = false,
|
||||
roomMembers = AsyncData.Success(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(),
|
||||
joined = persistentListOf(),
|
||||
banned = persistentListOf(
|
||||
aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice"),
|
||||
aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob"),
|
||||
aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie"),
|
||||
),
|
||||
)
|
||||
),
|
||||
moderationState = aRoomMembersModerationState(canDisplayBannedUsers = true),
|
||||
),
|
||||
aRoomMemberListState(
|
||||
roomMembers = RoomMembers(
|
||||
invited = persistentListOf(),
|
||||
joined = persistentListOf(),
|
||||
banned = persistentListOf(
|
||||
aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice"),
|
||||
aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob"),
|
||||
aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie"),
|
||||
),
|
||||
isLoading = true,
|
||||
roomMembers = AsyncData.Loading(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(),
|
||||
joined = persistentListOf(),
|
||||
banned = persistentListOf(
|
||||
aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice"),
|
||||
aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob"),
|
||||
aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie"),
|
||||
),
|
||||
)
|
||||
),
|
||||
moderationState = aRoomMembersModerationState(canDisplayBannedUsers = true),
|
||||
),
|
||||
aRoomMemberListState(
|
||||
roomMembers = RoomMembers(
|
||||
invited = persistentListOf(),
|
||||
joined = persistentListOf(),
|
||||
banned = persistentListOf(),
|
||||
isLoading = false,
|
||||
roomMembers = AsyncData.Success(
|
||||
RoomMembers(
|
||||
invited = persistentListOf(),
|
||||
joined = persistentListOf(),
|
||||
banned = persistentListOf(),
|
||||
)
|
||||
),
|
||||
moderationState = aRoomMembersModerationState(canDisplayBannedUsers = true),
|
||||
)
|
||||
@@ -103,8 +112,8 @@ internal class RoomMemberListStateBannedProvider : PreviewParameterProvider<Room
|
||||
}
|
||||
|
||||
internal fun aRoomMemberListState(
|
||||
roomMembers: RoomMembers = RoomMembers.loading(),
|
||||
searchResults: SearchBarResultState<RoomMembers> = SearchBarResultState.Initial(),
|
||||
roomMembers: AsyncData<RoomMembers> = AsyncData.Loading(),
|
||||
searchResults: SearchBarResultState<AsyncData<RoomMembers>> = SearchBarResultState.Initial(),
|
||||
moderationState: RoomMembersModerationState = aRoomMembersModerationState(),
|
||||
) = RoomMemberListState(
|
||||
roomMembers = roomMembers,
|
||||
|
||||
@@ -53,6 +53,7 @@ import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.roomdetails.impl.R
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationView
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
@@ -128,7 +129,6 @@ fun RoomMemberListView(
|
||||
|
||||
if (!state.isSearchActive) {
|
||||
RoomMemberList(
|
||||
isLoading = state.roomMembers.isLoading,
|
||||
roomMembers = state.roomMembers,
|
||||
showMembersCount = true,
|
||||
canDisplayBannedUsersControls = state.moderationState.canDisplayBannedUsers,
|
||||
@@ -149,8 +149,7 @@ fun RoomMemberListView(
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun RoomMemberList(
|
||||
isLoading: Boolean,
|
||||
roomMembers: RoomMembers,
|
||||
roomMembers: AsyncData<RoomMembers>,
|
||||
showMembersCount: Boolean,
|
||||
selectedSection: SelectedSection,
|
||||
onSelectedSectionChange: (SelectedSection) -> Unit,
|
||||
@@ -183,7 +182,7 @@ private fun RoomMemberList(
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
visible = isLoading,
|
||||
visible = roomMembers.isLoading(),
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically(),
|
||||
) {
|
||||
@@ -191,47 +190,72 @@ private fun RoomMemberList(
|
||||
}
|
||||
}
|
||||
}
|
||||
when (selectedSection) {
|
||||
SelectedSection.MEMBERS -> {
|
||||
if (roomMembers.invited.isNotEmpty()) {
|
||||
roomMemberListSection(
|
||||
headerText = { stringResource(id = R.string.screen_room_member_list_pending_header_title) },
|
||||
members = roomMembers.invited,
|
||||
onMemberSelected = { onSelectUser(it) }
|
||||
)
|
||||
}
|
||||
if (roomMembers.joined.isNotEmpty()) {
|
||||
roomMemberListSection(
|
||||
headerText = {
|
||||
if (showMembersCount) {
|
||||
val memberCount = roomMembers.joined.count()
|
||||
pluralStringResource(id = R.plurals.screen_room_member_list_header_title, count = memberCount, memberCount)
|
||||
} else {
|
||||
stringResource(id = R.string.screen_room_member_list_room_members_header_title)
|
||||
}
|
||||
},
|
||||
members = roomMembers.joined,
|
||||
onMemberSelected = { onSelectUser(it) }
|
||||
)
|
||||
}
|
||||
when (roomMembers) {
|
||||
is AsyncData.Failure -> failureItem(roomMembers.error)
|
||||
is AsyncData.Loading,
|
||||
is AsyncData.Success -> memberItems(
|
||||
roomMembers = roomMembers.dataOrNull() ?: return@LazyColumn,
|
||||
selectedSection = selectedSection,
|
||||
onSelectUser = onSelectUser,
|
||||
showMembersCount = showMembersCount,
|
||||
)
|
||||
AsyncData.Uninitialized -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.memberItems(
|
||||
roomMembers: RoomMembers,
|
||||
selectedSection: SelectedSection,
|
||||
onSelectUser: (RoomMember) -> Unit,
|
||||
showMembersCount: Boolean,
|
||||
) {
|
||||
when (selectedSection) {
|
||||
SelectedSection.MEMBERS -> {
|
||||
if (roomMembers.invited.isNotEmpty()) {
|
||||
roomMemberListSection(
|
||||
headerText = { stringResource(id = R.string.screen_room_member_list_pending_header_title) },
|
||||
members = roomMembers.invited,
|
||||
onMemberSelected = { onSelectUser(it) }
|
||||
)
|
||||
}
|
||||
SelectedSection.BANNED -> { // Banned users
|
||||
if (roomMembers.banned.isNotEmpty()) {
|
||||
roomMemberListSection(
|
||||
headerText = null,
|
||||
members = roomMembers.banned,
|
||||
onMemberSelected = { onSelectUser(it) }
|
||||
)
|
||||
} else {
|
||||
item {
|
||||
Box(Modifier.fillParentMaxSize().padding(horizontal = 16.dp)) {
|
||||
Text(
|
||||
modifier = Modifier.padding(bottom = 56.dp).align(Alignment.Center),
|
||||
text = stringResource(id = R.string.screen_room_member_list_banned_empty),
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
if (roomMembers.joined.isNotEmpty()) {
|
||||
roomMemberListSection(
|
||||
headerText = {
|
||||
if (showMembersCount) {
|
||||
val memberCount = roomMembers.joined.count()
|
||||
pluralStringResource(id = R.plurals.screen_room_member_list_header_title, count = memberCount, memberCount)
|
||||
} else {
|
||||
stringResource(id = R.string.screen_room_member_list_room_members_header_title)
|
||||
}
|
||||
},
|
||||
members = roomMembers.joined,
|
||||
onMemberSelected = { onSelectUser(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
SelectedSection.BANNED -> { // Banned users
|
||||
if (roomMembers.banned.isNotEmpty()) {
|
||||
roomMemberListSection(
|
||||
headerText = null,
|
||||
members = roomMembers.banned,
|
||||
onMemberSelected = { onSelectUser(it) }
|
||||
)
|
||||
} else {
|
||||
item {
|
||||
Box(
|
||||
Modifier
|
||||
.fillParentMaxSize()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(bottom = 56.dp)
|
||||
.align(Alignment.Center),
|
||||
text = stringResource(id = R.string.screen_room_member_list_banned_empty),
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,9 +263,22 @@ private fun RoomMemberList(
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.failureItem(failure: Throwable) {
|
||||
item {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 32.dp),
|
||||
text = stringResource(id = CommonStrings.error_unknown) + "\n\n" + failure.localizedMessage,
|
||||
color = ElementTheme.colors.textCriticalPrimary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.roomMemberListSection(
|
||||
headerText: @Composable (() -> String)?,
|
||||
members: ImmutableList<RoomMember>,
|
||||
members: ImmutableList<RoomMember>?,
|
||||
onMemberSelected: (RoomMember) -> Unit,
|
||||
) {
|
||||
headerText?.let {
|
||||
@@ -254,7 +291,7 @@ private fun LazyListScope.roomMemberListSection(
|
||||
)
|
||||
}
|
||||
}
|
||||
items(members) { matrixUser ->
|
||||
items(members.orEmpty()) { matrixUser ->
|
||||
RoomMemberListItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
roomMember = matrixUser,
|
||||
@@ -320,7 +357,7 @@ private fun RoomMemberListTopBar(
|
||||
@Composable
|
||||
private fun RoomMemberSearchBar(
|
||||
query: String,
|
||||
state: SearchBarResultState<RoomMembers>,
|
||||
state: SearchBarResultState<AsyncData<RoomMembers>>,
|
||||
active: Boolean,
|
||||
placeHolderTitle: String,
|
||||
onActiveChange: (Boolean) -> Unit,
|
||||
@@ -339,7 +376,6 @@ private fun RoomMemberSearchBar(
|
||||
resultState = state,
|
||||
resultHandler = { results ->
|
||||
RoomMemberList(
|
||||
isLoading = false,
|
||||
roomMembers = results,
|
||||
showMembersCount = false,
|
||||
onSelectUser = { onSelectUser(it) },
|
||||
|
||||
Reference in New Issue
Block a user