From 88a5a3e14ae7341127d0ed8a0c19a7d63ff5eb89 Mon Sep 17 00:00:00 2001 From: Infi Date: Mon, 2 Oct 2023 02:11:10 +0200 Subject: [PATCH] fix: opening member list makes app crash sometimes Signed-off-by: Infi --- .../chat/revolt/api/routes/channel/Channel.kt | 11 ++ .../chat/revolt/sheets/ChannelInfoSheet.kt | 39 +++--- .../chat/revolt/sheets/MemberListSheet.kt | 111 +++++++++++++++--- 3 files changed, 131 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/chat/revolt/api/routes/channel/Channel.kt b/app/src/main/java/chat/revolt/api/routes/channel/Channel.kt index b8fb1502..c16c47dc 100644 --- a/app/src/main/java/chat/revolt/api/routes/channel/Channel.kt +++ b/app/src/main/java/chat/revolt/api/routes/channel/Channel.kt @@ -7,6 +7,7 @@ import chat.revolt.api.internals.ULID import chat.revolt.api.schemas.Channel import chat.revolt.api.schemas.Message import chat.revolt.api.schemas.MessagesInChannel +import chat.revolt.api.schemas.User import io.ktor.client.request.get import io.ktor.client.request.parameter import io.ktor.client.request.patch @@ -135,4 +136,14 @@ suspend fun fetchSingleChannel(channelId: String): Channel { Channel.serializer(), response ) +} + +suspend fun fetchGroupParticipants(channelId: String): List { + val response = RevoltHttp.get("/channels/$channelId/members") + .bodyAsText() + + return RevoltJson.decodeFromString( + ListSerializer(User.serializer()), + response + ) } \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/sheets/ChannelInfoSheet.kt b/app/src/main/java/chat/revolt/sheets/ChannelInfoSheet.kt index ba0bb40a..66470ced 100644 --- a/app/src/main/java/chat/revolt/sheets/ChannelInfoSheet.kt +++ b/app/src/main/java/chat/revolt/sheets/ChannelInfoSheet.kt @@ -52,7 +52,8 @@ fun ChannelInfoSheet( } ) { MemberListSheet( - serverId = channel?.server ?: "" + channelId = channelId, + serverId = channel?.server, ) } } @@ -100,22 +101,28 @@ fun ChannelInfoSheet( modifier = Modifier.padding(bottom = 4.dp) ) - SheetClickable( - icon = { modifier -> - Icon( - imageVector = Icons.Default.List, - contentDescription = null, - modifier = modifier - ) - }, - label = { style -> - Text( - text = stringResource(id = R.string.channel_info_sheet_options_members), - style = style - ) + when (channel.channelType) { + ChannelType.TextChannel, ChannelType.VoiceChannel, ChannelType.Group -> { + SheetClickable( + icon = { modifier -> + Icon( + imageVector = Icons.Default.List, + contentDescription = null, + modifier = modifier + ) + }, + label = { style -> + Text( + text = stringResource(id = R.string.channel_info_sheet_options_members), + style = style + ) + } + ) { + memberListSheetShown = true + } } - ) { - memberListSheetShown = true + + else -> {} } SheetClickable( diff --git a/app/src/main/java/chat/revolt/sheets/MemberListSheet.kt b/app/src/main/java/chat/revolt/sheets/MemberListSheet.kt index d29efcb7..ce38e257 100644 --- a/app/src/main/java/chat/revolt/sheets/MemberListSheet.kt +++ b/app/src/main/java/chat/revolt/sheets/MemberListSheet.kt @@ -46,13 +46,17 @@ import androidx.lifecycle.viewModelScope import chat.revolt.R import chat.revolt.api.REVOLT_FILES import chat.revolt.api.RevoltAPI +import chat.revolt.api.internals.PermissionBit import chat.revolt.api.internals.Roles import chat.revolt.api.internals.WebCompat +import chat.revolt.api.internals.hasPermission import chat.revolt.api.internals.solidColor +import chat.revolt.api.routes.channel.fetchGroupParticipants import chat.revolt.api.routes.server.fetchMembers import chat.revolt.api.schemas.Member import chat.revolt.api.schemas.User import chat.revolt.components.generic.PageHeader +import chat.revolt.components.generic.Presence import chat.revolt.components.generic.UserAvatar import chat.revolt.components.generic.presenceFromStatus import dagger.hilt.android.lifecycle.HiltViewModel @@ -67,6 +71,7 @@ val DO_NOT_FETCH_OFFLINE_MEMBERS_SERVERS = listOf( sealed class MemberListItem { data class MemberItem(val member: Member) : MemberListItem() + data class UserItem(val user: User) : MemberListItem() data class CategoryItem(val category: String, val count: Int) : MemberListItem() } @@ -77,14 +82,16 @@ class MemberListSheetViewModel @Inject constructor( ) : ViewModel() { val fullItemList = mutableStateListOf() - fun fetchMemberList( - serverId: String + fun fetchServerMemberList( + serverId: String, + channelId: String ) { viewModelScope.launch { val memberList = fetchMembers( serverId = serverId, includeOffline = serverId !in DO_NOT_FETCH_OFFLINE_MEMBERS_SERVERS ).members + val channel = RevoltAPI.channelCache[channelId] ?: return@launch val categories = mutableMapOf>() @@ -115,6 +122,12 @@ class MemberListSheetViewModel @Inject constructor( defaultCategoryName } + if (!Roles.permissionFor(channel, user, member) + .hasPermission(PermissionBit.ViewChannel) + ) { + return@forEach + } + categories[category] = (categories[category] ?: listOf()) + member } @@ -156,12 +169,60 @@ class MemberListSheetViewModel @Inject constructor( } } } + + fun fetchGroupMemberList(channelId: String) { + viewModelScope.launch { + val userList = fetchGroupParticipants(channelId) + + val onlinePredicate = { user: User -> + presenceFromStatus( + user.status?.presence, + user.online ?: false + ) != Presence.Offline + } + val offlinePredicate = { user: User -> + presenceFromStatus( + user.status?.presence, + user.online ?: false + ) == Presence.Offline + } + + fullItemList.clear() + + if (userList.count(onlinePredicate) > 0) { + fullItemList.add( + MemberListItem.CategoryItem( + context.getString(R.string.status_online), + userList.count(onlinePredicate) + ) + ) + + userList.filter(onlinePredicate).forEach { user -> + fullItemList.add(MemberListItem.UserItem(user)) + } + } + + if (userList.count(offlinePredicate) > 0) { + fullItemList.add( + MemberListItem.CategoryItem( + context.getString(R.string.status_offline), + userList.count(offlinePredicate) + ) + ) + + userList.filter(offlinePredicate).forEach { user -> + fullItemList.add(MemberListItem.UserItem(user)) + } + } + } + } } @OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @Composable fun MemberListSheet( - serverId: String, + channelId: String, + serverId: String? = null, viewModel: MemberListSheetViewModel = hiltViewModel() ) { var showUserContextSheet by remember { mutableStateOf(false) } @@ -170,7 +231,11 @@ fun MemberListSheet( // We use LaunchedEffect to make sure that this is called every time any of the users status changes LaunchedEffect(RevoltAPI.userCache) { snapshotFlow { RevoltAPI.userCache }.distinctUntilChanged().collect { - viewModel.fetchMemberList(serverId) + if (serverId != null) { + viewModel.fetchServerMemberList(serverId, channelId) + } else { + viewModel.fetchGroupMemberList(channelId) + } } } @@ -211,9 +276,21 @@ fun MemberListSheet( } is MemberListItem.MemberItem -> item(key = item.member.id!!.user) { - MemberListMember( - member = item.member, + MemberListMemberUser( user = RevoltAPI.userCache[item.member.id.user]!!, + member = item.member, + serverId = serverId, + onSelectUser = { + userContextSheetTarget = it + showUserContextSheet = true + } + ) + } + + is MemberListItem.UserItem -> item(key = item.user.id!!) { + MemberListMemberUser( + user = item.user, + member = null, serverId = serverId, onSelectUser = { userContextSheetTarget = it @@ -228,32 +305,38 @@ fun MemberListSheet( } @Composable -fun MemberListMember( - member: Member, +fun MemberListMemberUser( user: User, - serverId: String, + member: Member?, + serverId: String?, onSelectUser: (String) -> Unit ) { - val highestColourRole = Roles.resolveHighestRole(serverId, member.id!!.user, true) + val highestColourRole = serverId?.let { + Roles.resolveHighestRole( + it, + user.id!!, + true + ) + } val colour = highestColourRole?.colour?.let { WebCompat.parseColour(it) } ?: Brush.solidColor(LocalContentColor.current) Row( modifier = Modifier .clickable { - onSelectUser(member.id.user) + onSelectUser(user.id!!) } .fillMaxWidth() .padding(horizontal = 12.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { UserAvatar( - username = member.nickname + username = member?.nickname ?: user.displayName ?: user.username ?: user.id!!, avatar = user.avatar, - rawUrl = member.avatar?.let { "$REVOLT_FILES/avatars/${it.id}?max_side=256" }, + rawUrl = member?.avatar?.let { "$REVOLT_FILES/avatars/${it.id}?max_side=256" }, userId = user.id!!, presence = presenceFromStatus( user.status?.presence, @@ -264,7 +347,7 @@ fun MemberListMember( Spacer(modifier = Modifier.width(12.dp)) Text( - text = member.nickname + text = member?.nickname ?: user.displayName ?: user.username ?: user.id,