diff --git a/app/src/main/java/chat/stoat/composables/chat/InReplyTo.kt b/app/src/main/java/chat/stoat/composables/chat/InReplyTo.kt index d77c99a7..3cce7622 100644 --- a/app/src/main/java/chat/stoat/composables/chat/InReplyTo.kt +++ b/app/src/main/java/chat/stoat/composables/chat/InReplyTo.kt @@ -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)) } diff --git a/app/src/main/java/chat/stoat/composables/chat/Message.kt b/app/src/main/java/chat/stoat/composables/chat/Message.kt index f543939b..3d43e005 100644 --- a/app/src/main/java/chat/stoat/composables/chat/Message.kt +++ b/app/src/main/java/chat/stoat/composables/chat/Message.kt @@ -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,9 +540,12 @@ fun Message( } Spacer(modifier = Modifier.height(8.dp)) - Embed(embed = embed, serverId = StoatAPI.channelCache[message.channel]?.server, onLinkClick = { - viewUrlInBrowser(context, it) - }) + Embed( + embed = embed, + serverId = StoatAPI.channelCache[message.channel]?.server, + onLinkClick = { + viewUrlInBrowser(context, it) + }) Spacer(modifier = Modifier.height(8.dp)) } diff --git a/app/src/main/java/chat/stoat/composables/generic/ListHeader.kt b/app/src/main/java/chat/stoat/composables/generic/ListHeader.kt index ee75f74a..553ce25b 100644 --- a/app/src/main/java/chat/stoat/composables/generic/ListHeader.kt +++ b/app/src/main/java/chat/stoat/composables/generic/ListHeader.kt @@ -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,23 +40,29 @@ fun ListHeader( fun CountableListHeader( text: String, count: Int, + icon: @Composable (() -> Unit)? = null, backgroundColor: Color = MaterialTheme.colorScheme.surface ) { ListHeader(backgroundColor = backgroundColor) { - Text( - text = AnnotatedString.Builder().apply { - append(text) + Row { + if (icon != null) { + icon() + } + Text( + text = AnnotatedString.Builder().apply { + append(text) - pushStyle( - SpanStyle( - fontWeight = FontWeight.Medium, - fontSize = LocalTextStyle.current.fontSize * 0.8, - color = LocalContentColor.current.copy(alpha = 0.6f) + pushStyle( + SpanStyle( + fontWeight = FontWeight.Medium, + fontSize = LocalTextStyle.current.fontSize * 0.8, + color = LocalContentColor.current.copy(alpha = 0.6f) + ) ) - ) - append("—$count") - pop() - }.toAnnotatedString() - ) + append("—$count") + pop() + }.toAnnotatedString() + ) + } } } diff --git a/app/src/main/java/chat/stoat/sheets/MemberListSheet.kt b/app/src/main/java/chat/stoat/sheets/MemberListSheet.kt index 5cf4dc9e..a43bb4c0 100644 --- a/app/src/main/java/chat/stoat/sheets/MemberListSheet.kt +++ b/app/src/main/java/chat/stoat/sheets/MemberListSheet.kt @@ -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 ) } diff --git a/core/model/src/main/java/chat/stoat/core/model/schemas/Server.kt b/core/model/src/main/java/chat/stoat/core/model/schemas/Server.kt index 4b7d2e09..d6ade96c 100644 --- a/core/model/src/main/java/chat/stoat/core/model/schemas/Server.kt +++ b/core/model/src/main/java/chat/stoat/core/model/schemas/Server.kt @@ -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(