fix: opening member list makes app crash sometimes

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2023-10-02 02:11:10 +02:00
parent 8212dd0fe5
commit 88a5a3e14a
3 changed files with 131 additions and 30 deletions

View File

@ -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<User> {
val response = RevoltHttp.get("/channels/$channelId/members")
.bodyAsText()
return RevoltJson.decodeFromString(
ListSerializer(User.serializer()),
response
)
}

View File

@ -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(

View File

@ -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<MemberListItem>()
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<String, List<Member>>()
@ -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,