feat: xml messageview (for future optimisations)
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
b25ea4dffc
commit
98bbc09cd2
|
|
@ -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<Pair<Float, Color>>()
|
||||
|
|
@ -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(
|
||||
|
|
@ -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<Shader, Int> {
|
||||
val stops = mutableListOf<Pair<Float, Int>>()
|
||||
|
||||
val parts = mutableListOf<String>()
|
||||
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"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,15 @@ fun LabsHomeScreen(navController: NavController) {
|
|||
}
|
||||
)
|
||||
Divider()
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text("XML Message Column")
|
||||
},
|
||||
modifier = Modifier.clickable {
|
||||
navController.navigate("mockups/xmlmessage")
|
||||
}
|
||||
)
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Message?>(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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:longClickable="true"
|
||||
android:id="@+id/message_container"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="42dp"
|
||||
android:id="@+id/avatar"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginStart="8dp"
|
||||
tools:src="@drawable/ic_launcher_background"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:textSize="16sp"
|
||||
android:textFontWeight="700"
|
||||
android:fontFamily="@font/inter"
|
||||
android:id="@+id/author"
|
||||
android:layout_marginStart="10dp"
|
||||
tools:text="Jennifer"
|
||||
app:layout_constraintStart_toEndOf="@id/avatar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constrainedWidth="true"
|
||||
tools:targetApi="p" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:textSize="12sp"
|
||||
android:fontFamily="@font/inter"
|
||||
android:id="@+id/timestamp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:alpha="0.6"
|
||||
tools:text="10:00 AM"
|
||||
app:layout_constraintStart_toEndOf="@id/author"
|
||||
app:layout_constraintTop_toTopOf="@id/author"
|
||||
app:layout_constraintBottom_toBottomOf="@id/author"
|
||||
tools:targetApi="p" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:fontFamily="@font/inter"
|
||||
android:id="@+id/message_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
tools:text="Hello fdsfdsfoijsdijofsjoi oijjofsdjois fdsfds fdsfsddoijsdf"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintStart_toStartOf="@id/author"
|
||||
app:layout_constraintTop_toBottomOf="@id/author"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
Loading…
Reference in New Issue