diff --git a/app/src/main/java/chat/revolt/api/internals/ChannelUtils.kt b/app/src/main/java/chat/revolt/api/internals/ChannelUtils.kt index 32f354df..a5d43751 100644 --- a/app/src/main/java/chat/revolt/api/internals/ChannelUtils.kt +++ b/app/src/main/java/chat/revolt/api/internals/ChannelUtils.kt @@ -2,11 +2,16 @@ package chat.revolt.api.internals import chat.revolt.api.RevoltAPI import chat.revolt.api.schemas.Channel +import chat.revolt.api.schemas.User object ChannelUtils { fun resolveDMName(channel: Channel): String? { return channel.name - ?: RevoltAPI.userCache[channel.recipients?.first { u -> u != RevoltAPI.selfId }]?.username + ?: RevoltAPI.userCache[channel.recipients?.first { u -> u != RevoltAPI.selfId }]?.let { + User.resolveDefaultName( + it + ) + } } fun resolveDMPartner(channel: Channel): String? { diff --git a/app/src/main/java/chat/revolt/api/schemas/User.kt b/app/src/main/java/chat/revolt/api/schemas/User.kt index 1c373c79..9d460d2a 100644 --- a/app/src/main/java/chat/revolt/api/schemas/User.kt +++ b/app/src/main/java/chat/revolt/api/schemas/User.kt @@ -1,15 +1,16 @@ package chat.revolt.api.schemas -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.json.* +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable @Serializable data class User( @SerialName("_id") val id: String? = null, val username: String? = null, + val discriminator: String? = null, + @SerialName("display_name") + val displayName: String? = null, val avatar: AutumnResource? = null, val relations: List? = null, val badges: Long? = null, @@ -25,6 +26,8 @@ data class User( return User( id = partial.id ?: id, username = partial.username ?: username, + discriminator = partial.discriminator ?: discriminator, + displayName = partial.displayName ?: displayName, avatar = partial.avatar ?: avatar, relations = partial.relations ?: relations, badges = partial.badges ?: badges, @@ -42,6 +45,8 @@ data class User( fun getPlaceholder(forId: String) = User( id = forId, username = "Unknown User", + discriminator = "0000", + displayName = null, avatar = null, badges = 0, status = null, @@ -52,6 +57,11 @@ data class User( relationship = null, online = false ) + + fun resolveDefaultName(user: User, withDiscriminator: Boolean = false): String { + val maybeDiscriminator = if (withDiscriminator) "#${user.discriminator}" else "" + return user.displayName ?: "${user.username}$maybeDiscriminator" + } } } diff --git a/app/src/main/java/chat/revolt/components/chat/InReplyTo.kt b/app/src/main/java/chat/revolt/components/chat/InReplyTo.kt index 626fa2ae..c982cfe2 100644 --- a/app/src/main/java/chat/revolt/components/chat/InReplyTo.kt +++ b/app/src/main/java/chat/revolt/components/chat/InReplyTo.kt @@ -22,8 +22,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.revolt.R import chat.revolt.api.RevoltAPI -import chat.revolt.api.internals.WebCompat import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl +import chat.revolt.api.schemas.User import chat.revolt.components.generic.UserAvatar @Composable @@ -36,12 +36,12 @@ fun InReplyTo( val message = RevoltAPI.messageCache[messageId] val author = RevoltAPI.userCache[message?.author ?: ""] - val username = message?.masquerade?.name ?: author?.username ?: "" + val username = message?.let { authorName(it) } + ?: author?.let { User.resolveDefaultName(it) } + ?: stringResource(id = R.string.unknown) val contentColor = LocalContentColor.current - val usernameColor = message?.masquerade?.colour?.let { - WebCompat.parseColour(it) - } ?: contentColor + val usernameColor = message?.let { authorColour(it) } ?: contentColor Box( modifier = modifier diff --git a/app/src/main/java/chat/revolt/components/chat/Message.kt b/app/src/main/java/chat/revolt/components/chat/Message.kt index 78598ac5..eacdd149 100644 --- a/app/src/main/java/chat/revolt/components/chat/Message.kt +++ b/app/src/main/java/chat/revolt/components/chat/Message.kt @@ -52,6 +52,7 @@ import chat.revolt.api.internals.ULID import chat.revolt.api.internals.WebCompat import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl import chat.revolt.api.schemas.AutumnResource +import chat.revolt.api.schemas.User import chat.revolt.components.generic.UserAvatar import chat.revolt.components.generic.UserAvatarWidthPlaceholder import chat.revolt.api.schemas.Message as MessageSchema @@ -67,11 +68,9 @@ fun authorColour(message: MessageSchema): Color { @Composable fun authorName(message: MessageSchema): String { - return if (message.masquerade?.name != null) { - message.masquerade.name - } else { - RevoltAPI.userCache[message.author]?.username ?: stringResource(id = R.string.unknown) - } + return message.masquerade?.name + ?: RevoltAPI.userCache[message.author]?.let { User.resolveDefaultName(it) } + ?: stringResource(R.string.unknown) } fun viewUrlInBrowser(ctx: android.content.Context, url: String) { @@ -194,7 +193,7 @@ fun Message( Column { Spacer(modifier = Modifier.height(4.dp)) UserAvatar( - username = author.username ?: "", + username = User.resolveDefaultName(author), userId = author.id ?: message.id ?: ULID.makeSpecial(0), avatar = author.avatar, rawUrl = message.masquerade?.avatar?.let { asJanuaryProxyUrl(it) } diff --git a/app/src/main/java/chat/revolt/components/screens/chat/ReplyManager.kt b/app/src/main/java/chat/revolt/components/screens/chat/ReplyManager.kt index f675d201..304b97e7 100644 --- a/app/src/main/java/chat/revolt/components/screens/chat/ReplyManager.kt +++ b/app/src/main/java/chat/revolt/components/screens/chat/ReplyManager.kt @@ -14,7 +14,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material3.Icon -import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.surfaceColorAtElevation @@ -29,10 +28,12 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.revolt.R import chat.revolt.api.RevoltAPI -import chat.revolt.api.internals.WebCompat +import chat.revolt.api.internals.ULID import chat.revolt.api.routes.channel.SendMessageReply import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl import chat.revolt.api.schemas.Message +import chat.revolt.components.chat.authorColour +import chat.revolt.components.chat.authorName import chat.revolt.components.generic.UserAvatar @Composable @@ -76,8 +77,8 @@ fun ManageableReply( Spacer(modifier = Modifier.width(8.dp)) UserAvatar( - username = replyAuthor.username!!, - userId = replyAuthor.id!!, + username = authorName(message = replyMessage), + userId = replyAuthor.id ?: ULID.makeSpecial(0), avatar = replyAuthor.avatar, rawUrl = replyMessage.masquerade?.avatar?.let { asJanuaryProxyUrl(it) }, size = 16.dp @@ -86,16 +87,14 @@ fun ManageableReply( Spacer(modifier = Modifier.width(4.dp)) Text( - text = replyMessage.masquerade?.name ?: replyAuthor.username, + text = authorName(message = replyMessage), fontSize = 12.sp, modifier = Modifier .clickable { onToggleMention() } .padding(4.dp), - color = if (replyMessage.masquerade?.colour != null) { - WebCompat.parseColour(replyMessage.masquerade.colour) - } else LocalContentColor.current, + color = authorColour(message = replyMessage), fontWeight = FontWeight.Bold, ) diff --git a/app/src/main/java/chat/revolt/components/screens/chat/TypingIndicator.kt b/app/src/main/java/chat/revolt/components/screens/chat/TypingIndicator.kt index 58d3f290..ed78e1a7 100644 --- a/app/src/main/java/chat/revolt/components/screens/chat/TypingIndicator.kt +++ b/app/src/main/java/chat/revolt/components/screens/chat/TypingIndicator.kt @@ -1,8 +1,17 @@ package chat.revolt.components.screens.chat -import androidx.compose.animation.* +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -17,6 +26,7 @@ import chat.revolt.R import chat.revolt.activities.RevoltTweenFloat import chat.revolt.activities.RevoltTweenInt import chat.revolt.api.RevoltAPI +import chat.revolt.api.schemas.User import chat.revolt.components.generic.UserAvatar @Composable @@ -33,7 +43,7 @@ fun StackedUserAvatars( UserAvatar( avatar = user?.avatar, userId = userId, - username = user?.username ?: stringResource(id = R.string.unknown), + username = user?.let {User.resolveDefaultName(it)} ?: stringResource(id = R.string.unknown), size = 16.dp, modifier = Modifier .offset( @@ -87,7 +97,7 @@ fun TypingIndicator( id = typingMessageResource(), users.joinToString { RevoltAPI.userCache[it]?.let { u -> - u.username ?: u.id + User.resolveDefaultName(u) } ?: it } ), diff --git a/app/src/main/java/chat/revolt/components/screens/chat/drawer/channel/ChannelList.kt b/app/src/main/java/chat/revolt/components/screens/chat/drawer/channel/ChannelList.kt index 41b663ef..d731662a 100644 --- a/app/src/main/java/chat/revolt/components/screens/chat/drawer/channel/ChannelList.kt +++ b/app/src/main/java/chat/revolt/components/screens/chat/drawer/channel/ChannelList.kt @@ -31,6 +31,7 @@ import chat.revolt.R import chat.revolt.api.RevoltAPI import chat.revolt.api.internals.ChannelUtils import chat.revolt.api.schemas.ChannelType +import chat.revolt.api.schemas.User import chat.revolt.components.generic.presenceFromStatus import chat.revolt.components.screens.chat.drawer.server.DrawerChannel import chat.revolt.sheets.ChannelContextSheet @@ -157,7 +158,7 @@ fun RowScope.ChannelList( )] else null DrawerChannel( - name = partner?.username ?: channel.name + name = partner?.let { p -> User.resolveDefaultName(p) } ?: channel.name ?: stringResource(R.string.unknown), channelType = channel.channelType ?: ChannelType.TextChannel, selected = currentDestination == "channel/{channelId}" && currentChannel == channel.id, @@ -169,7 +170,7 @@ fun RowScope.ChannelList( } ?: false, dmPartnerIcon = partner?.avatar ?: channel.icon, dmPartnerId = partner?.id, - dmPartnerName = partner?.username, + dmPartnerName = partner?.let { p -> User.resolveDefaultName(p) }, dmPartnerStatus = presenceFromStatus( partner?.status?.presence ?: "Offline" ), diff --git a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt index 6c31f287..1143919c 100644 --- a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt @@ -54,6 +54,7 @@ import chat.revolt.R import chat.revolt.api.RevoltAPI import chat.revolt.api.realtime.DisconnectionState import chat.revolt.api.realtime.RealtimeSocket +import chat.revolt.api.schemas.User import chat.revolt.api.settings.SyncedSettings import chat.revolt.components.chat.DisconnectedNotice import chat.revolt.components.generic.UserAvatar @@ -332,7 +333,11 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = hil horizontalAlignment = Alignment.CenterHorizontally ) { UserAvatar( - username = RevoltAPI.userCache[RevoltAPI.selfId]?.username + username = RevoltAPI.userCache[RevoltAPI.selfId]?.let { + User.resolveDefaultName( + it + ) + } ?: "", presence = presenceFromStatus( RevoltAPI.userCache[RevoltAPI.selfId]?.status?.presence diff --git a/app/src/main/java/chat/revolt/sheets/StatusSheet.kt b/app/src/main/java/chat/revolt/sheets/StatusSheet.kt index e441294b..535336d6 100644 --- a/app/src/main/java/chat/revolt/sheets/StatusSheet.kt +++ b/app/src/main/java/chat/revolt/sheets/StatusSheet.kt @@ -1,9 +1,11 @@ package chat.revolt.sheets import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons @@ -11,12 +13,19 @@ import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import chat.revolt.R import chat.revolt.api.RevoltAPI +import chat.revolt.api.internals.ULID import chat.revolt.components.generic.SheetClickable +import chat.revolt.components.generic.UserAvatar +import chat.revolt.components.generic.presenceFromStatus @Composable fun StatusSheet( @@ -30,7 +39,32 @@ fun StatusSheet( .padding(horizontal = 16.dp, vertical = 8.dp) .verticalScroll(rememberScrollState()) ) { - Text(text = "Logged in as @${selfUser.username} (${selfUser.id})") + Row( + verticalAlignment = Alignment.CenterVertically + ) { + UserAvatar( + username = selfUser.displayName ?: stringResource(id = R.string.unknown), + userId = selfUser.id ?: ULID.makeSpecial(0), + avatar = selfUser.avatar, + size = 48.dp, + presence = presenceFromStatus(selfUser.status?.presence ?: "offline"), + ) + + Spacer(modifier = Modifier.width(12.dp)) + + Text(text = AnnotatedString.Builder().apply { + if (selfUser.displayName != null) { + pushStyle(SpanStyle(fontWeight = FontWeight.Bold)) + append(selfUser.displayName) + pop() + append("\n") + } + append("${selfUser.username}") + pushStyle(SpanStyle(fontWeight = FontWeight.ExtraLight)) + append("#${selfUser.discriminator}") + pop() + }.toAnnotatedString()) + } Spacer(modifier = Modifier.height(8.dp))