From 98bbc09cd2839be7fb559d5fd774dc30046a4284 Mon Sep 17 00:00:00 2001 From: Infi Date: Wed, 17 Jan 2024 17:54:46 +0100 Subject: [PATCH] feat: xml messageview (for future optimisations) Signed-off-by: Infi --- .../{WebCompat.kt => BrushCompat.kt} | 13 +- .../revolt/api/internals/TextViewCompat.kt | 138 ++++++++++++ .../java/chat/revolt/components/chat/Embed.kt | 4 +- .../revolt/components/chat/MemberListItem.kt | 4 +- .../chat/revolt/components/chat/Message.kt | 6 +- .../revolt/screens/labs/LabsHomeScreen.kt | 9 + .../revolt/screens/labs/LabsRootScreen.kt | 5 + .../labs/ui/mockups/XMLMessageMockup.kt | 122 ++++++++++ .../java/chat/revolt/sheets/UserInfoSheet.kt | 6 +- .../java/chat/revolt/views/MessageView.kt | 212 ++++++++++++++++++ app/src/main/res/layout/view_message.xml | 65 ++++++ 11 files changed, 567 insertions(+), 17 deletions(-) rename app/src/main/java/chat/revolt/api/internals/{WebCompat.kt => BrushCompat.kt} (96%) create mode 100644 app/src/main/java/chat/revolt/api/internals/TextViewCompat.kt create mode 100644 app/src/main/java/chat/revolt/screens/labs/ui/mockups/XMLMessageMockup.kt create mode 100644 app/src/main/java/chat/revolt/views/MessageView.kt create mode 100644 app/src/main/res/layout/view_message.xml diff --git a/app/src/main/java/chat/revolt/api/internals/WebCompat.kt b/app/src/main/java/chat/revolt/api/internals/BrushCompat.kt similarity index 96% rename from app/src/main/java/chat/revolt/api/internals/WebCompat.kt rename to app/src/main/java/chat/revolt/api/internals/BrushCompat.kt index 1071c8f2..9fe79a69 100644 --- a/app/src/main/java/chat/revolt/api/internals/WebCompat.kt +++ b/app/src/main/java/chat/revolt/api/internals/BrushCompat.kt @@ -15,7 +15,7 @@ fun Brush.Companion.solidColor(colour: Color) = SolidColor(colour) // not exhaustive, but covers most of the ones I've seen in the wild // for the sake of all of us, please just use hex codes // reference: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value -private val ADDITIONAL_WEB_COLOURS = mapOf( +val ADDITIONAL_WEB_COLOURS = mapOf( "orange" to Color(0xFFFFA500), "rebeccapurple" to Color(0xFF663399), "transparent" to Color.Transparent, @@ -24,7 +24,7 @@ private val ADDITIONAL_WEB_COLOURS = mapOf( "unset" to Color.Unspecified ) -object WebCompat { +object BrushCompat { @Composable private fun parseLinearGradient(gradient: String): Brush { val stops = mutableListOf>() @@ -85,8 +85,7 @@ object WebCompat { ) } - @Composable - private fun parseFunctionColour(colourString: String): Color? { + fun parseFunctionColour(colourString: String): Color? { val cleanedString = colourString.trim() return try { @@ -151,7 +150,7 @@ object WebCompat { val additionalWebColour = ADDITIONAL_WEB_COLOURS[colour] if (additionalWebColour != null) { Log.d( - "WebCompat", + "BrushCompat", "Parsed additional web colour $colour to $additionalWebColour" ) return additionalWebColour @@ -160,7 +159,7 @@ object WebCompat { Color(android.graphics.Color.parseColor(colour)) } catch (e: IllegalArgumentException) { Log.d( - "WebCompat", + "BrushCompat", "Failed to parse colour $colour, falling back to LocalContentColor.current" ) LocalContentColor.current @@ -172,7 +171,7 @@ object WebCompat { when { colour.startsWith("var(") -> { Log.d( - "WebCompat", + "BrushCompat", "Parsing variable $colour" ) return parseVar( diff --git a/app/src/main/java/chat/revolt/api/internals/TextViewCompat.kt b/app/src/main/java/chat/revolt/api/internals/TextViewCompat.kt new file mode 100644 index 00000000..a611a5d3 --- /dev/null +++ b/app/src/main/java/chat/revolt/api/internals/TextViewCompat.kt @@ -0,0 +1,138 @@ +package chat.revolt.api.internals + +import android.graphics.Color +import android.graphics.LinearGradient +import android.graphics.Shader +import android.util.Log +import android.widget.TextView +import androidx.compose.ui.graphics.toArgb +import com.google.android.material.color.MaterialColors +import com.google.android.material.elevation.SurfaceColors + +object TextViewCompat { + private fun tryParseDirectColour(colour: String): Int { + val additionalWebColour = ADDITIONAL_WEB_COLOURS[colour] + if (additionalWebColour != null) { + return additionalWebColour.toArgb() + } + + return Color.parseColor(colour) + } + + private fun tryParseVariable(tv: TextView, varName: String): Int { + return when (varName) { + "--accent" -> MaterialColors.getColor( + tv, + com.google.android.material.R.attr.colorPrimary + ) + + "--foreground" -> MaterialColors.getColor( + tv, + com.google.android.material.R.attr.colorOnBackground + ) + + "--background" -> SurfaceColors.SURFACE_0.getColor(tv.context) + + "--error" -> MaterialColors.getColor(tv, com.google.android.material.R.attr.colorError) + + else -> tv.currentTextColor + } + } + + private fun tryParseSetLinearGradient(tv: TextView, gradient: String): Pair { + val stops = mutableListOf>() + + val parts = mutableListOf() + var startIndex = 0 + var openParenthesesCount = 0 + + for (i in gradient.indices) { + when (gradient[i]) { + '(' -> openParenthesesCount++ + ')' -> openParenthesesCount-- + ',' -> { + if (openParenthesesCount == 0) { + val part = gradient.substring(startIndex, i).trim() + parts.add(part) + startIndex = i + 1 + } + } + } + } + + val lastPart = gradient.substring(startIndex).trim() + if (lastPart.isNotEmpty()) { + parts.add(lastPart) + } + + parts.forEachIndexed { index, part -> + if (part.startsWith("to") || part.endsWith("deg")) { + // we don't support any other direction / blocked on compose supporting them + // TODO could probably emulate this by swapping the values around + } else { + val splitPart = part.split(" ") + + val colourPart = splitPart[0] + val colour = when { + colourPart.startsWith("var(") -> { + tryParseVariable( + tv, + colourPart.substringAfter("var(").substringBeforeLast(")") + ) + } + + else -> BrushCompat.parseFunctionColour(colourPart)?.toArgb() + ?: tryParseDirectColour(colourPart) + } + + val stop = if (splitPart.size == 2) { + splitPart[1].removeSuffix("%").toFloat() / 100f + } else { + index.toFloat() / (parts.size - 1) + } + + stops.add(stop to colour) + } + } + + val width = tv.paint.measureText(tv.text.toString()) + + return LinearGradient( + 0f, + 0f, + width, + 0f, + stops.map { it.second }.toIntArray(), + stops.map { it.first }.toFloatArray(), + Shader.TileMode.CLAMP + ) to stops.first().second + } + + fun setColourFromRoleColour(tv: TextView, colour: String) { + when { + colour.startsWith("var(") -> { + val varName = colour.substringAfter("var(").substringBeforeLast(")") + val parsedColour = tryParseVariable(tv, varName) + tv.setTextColor(parsedColour) + } + + colour.startsWith("linear-gradient(") || colour.startsWith("repeating-linear-gradient(") -> { + val gradient = colour.substringAfter("(").substringBeforeLast(")") + val shader = tryParseSetLinearGradient(tv, gradient) + tv.paint.shader = shader.first + } + + else -> { + try { + val directColour = tryParseDirectColour(colour) + tv.setTextColor(directColour) + } catch (e: IllegalArgumentException) { + Log.d( + "TextViewCompat", + "Failed to parse colour $colour, not setting colour" + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/components/chat/Embed.kt b/app/src/main/java/chat/revolt/components/chat/Embed.kt index 85c42235..79b3bbce 100644 --- a/app/src/main/java/chat/revolt/components/chat/Embed.kt +++ b/app/src/main/java/chat/revolt/components/chat/Embed.kt @@ -24,7 +24,7 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import chat.revolt.api.internals.WebCompat +import chat.revolt.api.internals.BrushCompat import chat.revolt.api.internals.solidColor import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl import chat.revolt.api.schemas.Embed @@ -51,7 +51,7 @@ fun RegularEmbed( .width(4.dp) .fillMaxHeight() .background( - embed.colour?.let { WebCompat.parseColour(it) } + embed.colour?.let { BrushCompat.parseColour(it) } ?: Brush.solidColor(MaterialTheme.colorScheme.primary) ) ) diff --git a/app/src/main/java/chat/revolt/components/chat/MemberListItem.kt b/app/src/main/java/chat/revolt/components/chat/MemberListItem.kt index 0eaab1b2..69efc38b 100644 --- a/app/src/main/java/chat/revolt/components/chat/MemberListItem.kt +++ b/app/src/main/java/chat/revolt/components/chat/MemberListItem.kt @@ -10,7 +10,7 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.text.style.TextOverflow import chat.revolt.api.REVOLT_FILES import chat.revolt.api.internals.Roles -import chat.revolt.api.internals.WebCompat +import chat.revolt.api.internals.BrushCompat import chat.revolt.api.internals.solidColor import chat.revolt.api.schemas.Member import chat.revolt.api.schemas.User @@ -35,7 +35,7 @@ fun MemberListItem( } } - val colour = highestColourRole?.colour?.let { WebCompat.parseColour(it) } + val colour = highestColourRole?.colour?.let { BrushCompat.parseColour(it) } ?: Brush.solidColor(LocalContentColor.current) ListItem( 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 4d83b59b..987ddd0c 100644 --- a/app/src/main/java/chat/revolt/components/chat/Message.kt +++ b/app/src/main/java/chat/revolt/components/chat/Message.kt @@ -68,7 +68,7 @@ import chat.revolt.api.RevoltAPI import chat.revolt.api.internals.Roles import chat.revolt.api.internals.SpecialUsers import chat.revolt.api.internals.ULID -import chat.revolt.api.internals.WebCompat +import chat.revolt.api.internals.BrushCompat import chat.revolt.api.internals.solidColor import chat.revolt.api.routes.channel.react import chat.revolt.api.routes.channel.unreact @@ -86,7 +86,7 @@ import chat.revolt.api.schemas.Message as MessageSchema @Composable fun authorColour(message: MessageSchema): Brush { return if (message.masquerade?.colour != null) { - WebCompat.parseColour(message.masquerade.colour) + BrushCompat.parseColour(message.masquerade.colour) } else { val defaultColour = Brush.solidColor(LocalContentColor.current) @@ -96,7 +96,7 @@ fun authorColour(message: MessageSchema): Brush { Roles.resolveHighestRole(serverId, it, withColour = true) } ?: return defaultColour - highestRole.colour?.let { WebCompat.parseColour(it) } + highestRole.colour?.let { BrushCompat.parseColour(it) } ?: defaultColour } } diff --git a/app/src/main/java/chat/revolt/screens/labs/LabsHomeScreen.kt b/app/src/main/java/chat/revolt/screens/labs/LabsHomeScreen.kt index adf07e92..98ec527c 100644 --- a/app/src/main/java/chat/revolt/screens/labs/LabsHomeScreen.kt +++ b/app/src/main/java/chat/revolt/screens/labs/LabsHomeScreen.kt @@ -126,6 +126,15 @@ fun LabsHomeScreen(navController: NavController) { } ) Divider() + ListItem( + headlineContent = { + Text("XML Message Column") + }, + modifier = Modifier.clickable { + navController.navigate("mockups/xmlmessage") + } + ) + Divider() } } } diff --git a/app/src/main/java/chat/revolt/screens/labs/LabsRootScreen.kt b/app/src/main/java/chat/revolt/screens/labs/LabsRootScreen.kt index 69d244a1..2abf2fd3 100644 --- a/app/src/main/java/chat/revolt/screens/labs/LabsRootScreen.kt +++ b/app/src/main/java/chat/revolt/screens/labs/LabsRootScreen.kt @@ -14,6 +14,7 @@ import androidx.navigation.compose.rememberNavController import chat.revolt.api.settings.FeatureFlags import chat.revolt.api.settings.LabsAccessControlVariates import chat.revolt.screens.labs.ui.mockups.CallScreenMockup +import chat.revolt.screens.labs.ui.mockups.XMLMessageMockup annotation class LabsFeature @@ -65,6 +66,10 @@ fun LabsRootScreen(topNav: NavController) { composable("mockups/call") { CallScreenMockup() } + + composable("mockups/xmlmessage") { + XMLMessageMockup() + } } } } diff --git a/app/src/main/java/chat/revolt/screens/labs/ui/mockups/XMLMessageMockup.kt b/app/src/main/java/chat/revolt/screens/labs/ui/mockups/XMLMessageMockup.kt new file mode 100644 index 00000000..8ae85797 --- /dev/null +++ b/app/src/main/java/chat/revolt/screens/labs/ui/mockups/XMLMessageMockup.kt @@ -0,0 +1,122 @@ +package chat.revolt.screens.labs.ui.mockups + +import android.widget.Toast +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.safeDrawingPadding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import chat.revolt.api.RevoltAPI +import chat.revolt.api.schemas.Message +import chat.revolt.components.chat.Message +import chat.revolt.internals.markdown.MarkdownContext +import chat.revolt.internals.markdown.MarkdownParser +import chat.revolt.internals.markdown.MarkdownState +import chat.revolt.internals.markdown.addRevoltRules +import chat.revolt.internals.markdown.createCodeRule +import chat.revolt.internals.markdown.createInlineCodeRule +import chat.revolt.views.MessageView +import com.discord.simpleast.core.simple.SimpleMarkdownRules +import com.discord.simpleast.core.simple.SimpleRenderer + +@Composable +fun XMLMessageMockup() { + var message by remember { mutableStateOf(null) } + val context = LocalContext.current + val codeBlockColor = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp) + + fun reroll() { + message = RevoltAPI.messageCache.values.random() + } + + LaunchedEffect(Unit) { + reroll() + } + + Column( + modifier = Modifier + .fillMaxSize() + .safeDrawingPadding() + ) { + message?.let { + AndroidView( + factory = { + MessageView(it, onLongPress = { + Toast.makeText(context, "Long pressed!", Toast.LENGTH_SHORT).show() + }) + }, + update = { + it.fromMessage(message!!) + }, + modifier = Modifier + .fillMaxWidth() + ) + + Message( + message = message!!.copy(tail = false), + truncate = false, + onMessageContextMenu = { + Toast.makeText(context, "Context menu!", Toast.LENGTH_SHORT).show() + }, + parse = { + val parser = MarkdownParser() + .addRules( + SimpleMarkdownRules.createEscapeRule() + ) + .addRevoltRules(context) + .addRules( + createCodeRule(context, codeBlockColor.toArgb()), + createInlineCodeRule( + context, + codeBlockColor.toArgb() + ) + ) + .addRules( + SimpleMarkdownRules.createSimpleMarkdownRules( + includeEscapeRule = false + ) + ) + + SimpleRenderer.render( + source = it.content ?: "", + parser = parser, + initialState = MarkdownState(0), + renderContext = MarkdownContext( + memberMap = mapOf(), + userMap = RevoltAPI.userCache.toMap(), + channelMap = RevoltAPI.channelCache.mapValues { ch -> + ch.value.name ?: ch.value.id + ?: "#DeletedChannel" + }, + emojiMap = RevoltAPI.emojiCache, + serverId = message!!.channel?.let { x -> RevoltAPI.channelCache[x] }?.server + ?: "", + // check if message consists solely of one *or more* custom emotes + useLargeEmojis = it.content?.matches( + Regex("(:([0-9A-Z]{26}):)+") + ) == true + ) + ) + }, + ) + + TextButton(onClick = { reroll() }) { + Text("Different message") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/sheets/UserInfoSheet.kt b/app/src/main/java/chat/revolt/sheets/UserInfoSheet.kt index e845c921..75990d15 100644 --- a/app/src/main/java/chat/revolt/sheets/UserInfoSheet.kt +++ b/app/src/main/java/chat/revolt/sheets/UserInfoSheet.kt @@ -30,7 +30,7 @@ import androidx.compose.ui.unit.sp import chat.revolt.R import chat.revolt.api.RevoltAPI import chat.revolt.api.internals.ULID -import chat.revolt.api.internals.WebCompat +import chat.revolt.api.internals.BrushCompat import chat.revolt.api.internals.solidColor import chat.revolt.api.routes.user.fetchUserProfile import chat.revolt.api.schemas.Profile @@ -120,7 +120,7 @@ fun UserInfoSheet( role?.let { RoleListEntry( label = role.name ?: "null", - brush = role.colour?.let { WebCompat.parseColour(it) } + brush = role.colour?.let { BrushCompat.parseColour(it) } ?: Brush.solidColor(LocalContentColor.current) ) } @@ -136,7 +136,7 @@ fun UserInfoSheet( role?.let { RoleListEntry( label = role.name ?: "null", - brush = role.colour?.let { WebCompat.parseColour(it) } + brush = role.colour?.let { BrushCompat.parseColour(it) } ?: Brush.solidColor(LocalContentColor.current) ) } diff --git a/app/src/main/java/chat/revolt/views/MessageView.kt b/app/src/main/java/chat/revolt/views/MessageView.kt new file mode 100644 index 00000000..815c4863 --- /dev/null +++ b/app/src/main/java/chat/revolt/views/MessageView.kt @@ -0,0 +1,212 @@ +package chat.revolt.views + +import android.content.Context +import android.icu.text.DateFormat +import android.text.format.DateUtils +import androidx.constraintlayout.widget.ConstraintLayout +import chat.revolt.R +import chat.revolt.api.REVOLT_FILES +import chat.revolt.api.RevoltAPI +import chat.revolt.api.internals.Roles +import chat.revolt.api.internals.TextViewCompat +import chat.revolt.api.internals.ULID +import chat.revolt.api.schemas.Message +import chat.revolt.api.schemas.User +import chat.revolt.databinding.ViewMessageBinding +import chat.revolt.internals.markdown.MarkdownContext +import chat.revolt.internals.markdown.MarkdownParser +import chat.revolt.internals.markdown.MarkdownState +import chat.revolt.internals.markdown.addRevoltRules +import chat.revolt.internals.markdown.createCodeRule +import chat.revolt.internals.markdown.createInlineCodeRule +import com.bumptech.glide.GenericTransitionOptions +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.request.transition.DrawableCrossFadeFactory +import com.discord.simpleast.core.simple.SimpleMarkdownRules +import com.discord.simpleast.core.simple.SimpleRenderer +import com.google.android.material.color.MaterialColors +import com.google.android.material.elevation.SurfaceColors + + +class MessageView( + ctx: Context +) : ConstraintLayout(ctx) { + private var binding: ViewMessageBinding + private val parser = MarkdownParser() + .addRules( + SimpleMarkdownRules.createEscapeRule() + ) + .addRevoltRules(context) + .addRules( + createCodeRule(context, SurfaceColors.SURFACE_2.getColor(ctx)), + createInlineCodeRule( + context, + SurfaceColors.SURFACE_2.getColor(ctx), + ) + ) + .addRules( + SimpleMarkdownRules.createSimpleMarkdownRules( + includeEscapeRule = false + ) + ) + + private var messageServer: String? = null + + constructor( + ctx: Context, + onLongPress: (() -> Unit)? = null + ) : this(ctx) { + binding.root.setOnLongClickListener { + onLongPress?.invoke() + onLongPress != null + } + } + + init { + inflate(ctx, R.layout.view_message, this) + binding = ViewMessageBinding.bind(this) + } + + fun setAuthor(author: String) { + binding.author.text = author + } + + fun setContent(content: String) { + binding.messageContent.text = SimpleRenderer.render( + source = content, + parser = parser, + initialState = MarkdownState(0), + renderContext = MarkdownContext( + memberMap = messageServer?.let { RevoltAPI.members.markdownMemberMapFor(it) } + ?: mapOf(), + userMap = RevoltAPI.userCache.toMap(), + channelMap = RevoltAPI.channelCache.mapValues { ch -> + ch.value.name ?: ch.value.id + ?: "#DeletedChannel" + }, + emojiMap = RevoltAPI.emojiCache, + serverId = messageServer, + // check if message consists solely of one *or more* custom emotes + useLargeEmojis = content.matches( + Regex("(:([0-9A-Z]{26}):)+") + ) + ) + ) + } + + fun setTimestamp(timestamp: String) { + binding.timestamp.text = timestamp + } + + fun setAvatarUrl(avatar: String?) { + if (avatar == null) { + Glide.with(this).clear(binding.avatar) + } + + val factory = DrawableCrossFadeFactory.Builder().setCrossFadeEnabled(true).build() + + Glide.with(this).load(avatar).diskCacheStrategy(DiskCacheStrategy.ALL) + .transition(GenericTransitionOptions.with(factory)) + .circleCrop() + .into(binding.avatar) + } + + private fun formatLongAsTime(time: Long): String { + val date = java.util.Date(time) + + val withinLastWeek = System.currentTimeMillis() - time < 604800000 + + return if (withinLastWeek) { + val relativeDate = DateUtils.getRelativeTimeSpanString( + time, + System.currentTimeMillis(), + DateUtils.DAY_IN_MILLIS, + DateUtils.FORMAT_ABBREV_ALL + ) + val relativeTime = DateFormat.getTimeInstance(DateFormat.SHORT).format(date) + + "$relativeDate $relativeTime" + } else { + val absoluteDate = DateFormat.getDateInstance(DateFormat.SHORT).format(date) + val absoluteTime = DateFormat.getTimeInstance(DateFormat.SHORT).format(date) + + "$absoluteDate $absoluteTime" + } + } + + private fun authorName(message: Message): String { + if (message.masquerade?.name != null) { + return message.masquerade.name + } + + messageServer + ?: return RevoltAPI.userCache[message.author]?.let { User.resolveDefaultName(it) } + ?: context.getString(R.string.unknown) + + val member = messageServer?.let { sid -> + message.author?.let { + RevoltAPI.members.getMember( + sid, + it + ) + } + } + ?: return context.getString(R.string.unknown) + + return member.nickname + ?: RevoltAPI.userCache[message.author]?.let { User.resolveDefaultName(it) } + ?: context.getString(R.string.unknown) + } + + private fun resetAuthorColour() { + binding.author.setTextColor( + MaterialColors.getColor( + binding.author, + com.google.android.material.R.attr.colorOnBackground + ) + ) + binding.author.paint.shader = null + } + + private fun setAuthorColour(message: Message) { + resetAuthorColour() + + if (message.masquerade?.colour != null) { + TextViewCompat.setColourFromRoleColour(binding.author, message.masquerade.colour) + } else { + val serverId = RevoltAPI.channelCache[message.channel]?.server ?: return + + val highestRole = message.author?.let { + Roles.resolveHighestRole(serverId, it, withColour = true) + } ?: return + + highestRole.colour?.let { + TextViewCompat.setColourFromRoleColour(binding.author, it) + } + } + } + + fun fromMessage(message: Message) { + messageServer = RevoltAPI.channelCache[message.channel]?.server + + message.content?.let { setContent(it) } + message.id?.let { setTimestamp(formatLongAsTime(ULID.asTimestamp(it))) } + // dont have this + val resolvedAuthor = RevoltAPI.userCache[message.author] + // dont inline this + setAvatarUrl(resolvedAuthor?.avatar?.let { "$REVOLT_FILES/avatars/${it.id}?max_side=256" } + ?: "") + setAuthor(authorName(message)) + + setAuthorColour(message) + } + + fun reset() { + resetAuthorColour() + setContent("") + setTimestamp("") + setAuthor("") + setAvatarUrl(null) + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/view_message.xml b/app/src/main/res/layout/view_message.xml new file mode 100644 index 00000000..7fa7b0ef --- /dev/null +++ b/app/src/main/res/layout/view_message.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + \ No newline at end of file