feat: display role icons

This commit is contained in:
infi 2026-06-20 16:10:53 +02:00
parent 55dbb2a8e5
commit 126011a3e3
5 changed files with 101 additions and 25 deletions

View File

@ -13,12 +13,12 @@ import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
@ -28,9 +28,12 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.stoat.R
import chat.stoat.api.StoatAPI
import chat.stoat.api.internals.SpecialUsers
import chat.stoat.api.internals.solidColor
import chat.stoat.api.routes.channel.fetchSingleMessage
import chat.stoat.composables.generic.RemoteImage
import chat.stoat.composables.generic.UserAvatar
import chat.stoat.core.model.data.STOAT_FILES
import chat.stoat.core.model.schemas.User
import java.util.concurrent.CancellationException
@ -52,6 +55,7 @@ fun InReplyTo(
val contentColor = LocalContentColor.current
val usernameColor =
message?.let { authorColour(it) } ?: Brush.solidColor(contentColor)
val roleIcon = message?.let { authorRoleIcon(it) }
val serverId = remember(channelId) { StoatAPI.channelCache[channelId]?.server }
@ -107,11 +111,26 @@ fun InReplyTo(
modifier = Modifier.padding(horizontal = 4.dp)
)
if (roleIcon != null) {
RemoteImage(
url = "$STOAT_FILES/icons/${roleIcon.id}",
contentScale = ContentScale.Fit,
description = null,
modifier = Modifier
.size(12.dp)
)
Spacer(modifier = Modifier.width(4.dp))
}
InlineBadges(
bot = message.masquerade == null && author?.bot != null,
bridge = message.masquerade != null && author?.bot != null,
platformModeration = author?.id == SpecialUsers.PLATFORM_MODERATION_USER,
teamMember = author?.id in SpecialUsers.TEAM_MEMBER_FLAIRS.keys,
webhook = message.webhook != null,
colour = contentColor.copy(alpha = 0.5f),
modifier = Modifier.size(8.dp),
modifier = Modifier.size(12.dp),
followingIfAny = {
Spacer(modifier = Modifier.width(4.dp))
}

View File

@ -5,7 +5,6 @@ import android.content.Intent
import android.icu.text.DateFormat
import android.net.Uri
import android.text.format.DateUtils
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.browser.customtabs.CustomTabsIntent
@ -111,6 +110,17 @@ fun authorColour(message: MessageSchema): Brush {
}
}
@Composable
fun authorRoleIcon(message: MessageSchema): AutumnResource? {
val serverId = StoatAPI.channelCache[message.channel]?.server ?: return null
val highestRole = message.author?.let {
Roles.resolveHighestRole(serverId, it)
} ?: return null
return highestRole.icon
}
@Composable
fun displayNameInChannel(userId: String, channelId: String): String {
val serverId =
@ -321,9 +331,6 @@ fun Message(
} == true),
) {
// TODO Add jump to message
if (replyMessage == null) {
Toast.makeText(context, "lmao prankd", Toast.LENGTH_SHORT).show()
}
}
}
}
@ -399,6 +406,19 @@ fun Message(
)
)
val roleIcon = authorRoleIcon(message)
if (roleIcon != null) {
Spacer(modifier = Modifier.width(4.dp))
RemoteImage(
url = "$STOAT_FILES/icons/${roleIcon.id}",
contentScale = ContentScale.Fit,
description = null,
modifier = Modifier
.size(16.dp)
)
}
InlineBadges(
bot = author.bot != null && message.masquerade == null,
bridge = message.masquerade != null && author.bot != null,
@ -520,7 +540,10 @@ fun Message(
}
Spacer(modifier = Modifier.height(8.dp))
Embed(embed = embed, serverId = StoatAPI.channelCache[message.channel]?.server, onLinkClick = {
Embed(
embed = embed,
serverId = StoatAPI.channelCache[message.channel]?.server,
onLinkClick = {
viewUrlInBrowser(context, it)
})
Spacer(modifier = Modifier.height(8.dp))

View File

@ -2,6 +2,7 @@ package chat.stoat.composables.generic
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.LocalContentColor
@ -39,9 +40,14 @@ fun ListHeader(
fun CountableListHeader(
text: String,
count: Int,
icon: @Composable (() -> Unit)? = null,
backgroundColor: Color = MaterialTheme.colorScheme.surface
) {
ListHeader(backgroundColor = backgroundColor) {
Row {
if (icon != null) {
icon()
}
Text(
text = AnnotatedString.Builder().apply {
append(text)
@ -59,3 +65,4 @@ fun CountableListHeader(
)
}
}
}

View File

@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
@ -28,6 +29,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
@ -42,8 +44,11 @@ import chat.stoat.api.routes.server.fetchMembers
import chat.stoat.composables.chat.MemberListItem
import chat.stoat.composables.generic.CountableListHeader
import chat.stoat.composables.generic.Presence
import chat.stoat.composables.generic.RemoteImage
import chat.stoat.composables.generic.SheetHeaderPadding
import chat.stoat.composables.generic.presenceFromStatus
import chat.stoat.core.model.data.STOAT_FILES
import chat.stoat.core.model.schemas.AutumnResource
import chat.stoat.core.model.schemas.Member
import chat.stoat.core.model.schemas.User
import kotlinx.coroutines.flow.distinctUntilChanged
@ -57,7 +62,11 @@ val DO_NOT_FETCH_OFFLINE_MEMBERS_SERVERS = listOf(
sealed class MemberListSheetItem {
data class MemberItem(val member: Member) : MemberListSheetItem()
data class UserItem(val user: User) : MemberListSheetItem()
data class CategoryItem(val category: String, val count: Int) : MemberListSheetItem()
data class CategoryItem(
val category: String,
val count: Int,
val icon: AutumnResource? = null
) : MemberListSheetItem()
}
class MemberListSheetViewModel(
@ -116,7 +125,13 @@ class MemberListSheetViewModel(
// Hoisted roles
Roles.inOrder(serverId) { it.hoist == true }.forEach { role ->
val members = categories[role.name] ?: return@forEach
fullItemList.add(MemberListSheetItem.CategoryItem(role.name ?: "", members.size))
fullItemList.add(
MemberListSheetItem.CategoryItem(
role.name ?: "",
members.size,
role.icon
)
)
members.forEach { member ->
fullItemList.add(MemberListSheetItem.MemberItem(member))
}
@ -312,6 +327,17 @@ fun MemberListSheet(
CountableListHeader(
text = item.category,
count = item.count,
icon = {
if (item.icon == null) return@CountableListHeader
RemoteImage(
url = "$STOAT_FILES/icons/${item.icon.id}",
contentScale = ContentScale.Fit,
description = null,
modifier = Modifier
.padding(end = 8.dp)
.size(16.dp)
)
},
backgroundColor = MaterialTheme.colorScheme.surfaceContainerLow
)
}

View File

@ -74,7 +74,8 @@ data class Role(
val permissions: PermissionDescription? = null,
val colour: String? = null,
val hoist: Boolean? = null,
val rank: Double? = null
val rank: Double? = null,
val icon: AutumnResource? = null
) {
fun mergeWithPartial(other: Role): Role {
return Role(