feat: styling for timestamps

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2023-09-17 17:55:52 +02:00
parent a3c5e43c5d
commit 800aa68083
5 changed files with 87 additions and 86 deletions

View File

@ -58,7 +58,7 @@ fun UIMarkdown(
.addRules( .addRules(
SimpleMarkdownRules.createEscapeRule() SimpleMarkdownRules.createEscapeRule()
) )
.addRevoltRules() .addRevoltRules(context)
.addRules( .addRules(
createCodeRule(context, codeBlockColor.toArgb()), createCodeRule(context, codeBlockColor.toArgb()),
createInlineCodeRule(context, codeBlockColor.toArgb()), createInlineCodeRule(context, codeBlockColor.toArgb()),

View File

@ -2,15 +2,7 @@ package chat.revolt.internals.markdown
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.Spanned import android.text.Spanned
import android.text.format.DateUtils
import android.util.Log
import com.discord.simpleast.core.node.Node import com.discord.simpleast.core.node.Node
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.toJavaInstant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Locale
class UserMentionNode(private val userId: String) : Node<MarkdownContext>() { class UserMentionNode(private val userId: String) : Node<MarkdownContext>() {
override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) { override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) {
@ -62,70 +54,6 @@ class CustomEmoteNode(private val emoteId: String) : Node<MarkdownContext>() {
} }
} }
class TimestampNode(private val timestamp: Long, private val modifier: String? = null) :
Node<MarkdownContext>() {
override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) {
val normalisedModifier = modifier.orEmpty().removePrefix(":")
val instant = Instant.fromEpochSeconds(timestamp)
val javaInstant = instant.toJavaInstant()
try {
if (timestamp < 0) {
builder.append("<invalid timestamp>")
return
}
val outString = when (normalisedModifier) {
// 22:22
"t" -> DateTimeFormatter.ofPattern("HH:mm")
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault())
.format(javaInstant)
// 22:22:22
"T" -> DateTimeFormatter.ofPattern("HH:mm:ss")
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault())
.format(javaInstant)
// 22 September 2022
"D" -> DateTimeFormatter.ofPattern("dd MMMM yyyy")
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault())
.format(javaInstant)
// 22 September 2022 22:22
"f" -> DateTimeFormatter.ofPattern("dd MMMM yyyy HH:mm")
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault())
.format(javaInstant)
// Thursday, 22 September 2022 22:22
"F" -> DateTimeFormatter.ofPattern("EEEE, dd MMMM yyyy HH:mm")
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault())
.format(javaInstant)
// 9 months ago
"R" -> DateUtils.getRelativeTimeSpanString(
timestamp * 1000,
Clock.System.now().toEpochMilliseconds(),
DateUtils.MINUTE_IN_MILLIS
)
// Fallback. Shouldn't happen
else -> timestamp.toString()
}
builder.append(outString)
} catch (e: Exception) {
Log.e("TimestampNode", "Failed to parse timestamp", e)
builder.append("<invalid timestamp>")
}
}
}
class LinkNode(val content: String, val url: String = content) : Node<MarkdownContext>() { class LinkNode(val content: String, val url: String = content) : Node<MarkdownContext>() {
override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) { override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) {
builder.append(content) builder.append(content)

View File

@ -47,21 +47,24 @@ class CustomEmoteRule<S> :
} }
} }
class TimestampRule<S> : class TimestampRule<S>(private val context: Context) :
Rule<MarkdownContext, TimestampNode, S>(Pattern.compile("^<t:([0-9]+?)(:[tTDfFR])?>")) { Rule<MarkdownContext, Node<MarkdownContext>, S>(Pattern.compile("^<t:([0-9]+?)(:[tTDfFR])?>")) {
override fun parse( override fun parse(
matcher: Matcher, matcher: Matcher,
parser: Parser<MarkdownContext, in TimestampNode, S>, parser: Parser<MarkdownContext, in Node<MarkdownContext>, S>,
state: S state: S
): ParseSpec<MarkdownContext, S> { ): ParseSpec<MarkdownContext, S> {
return ParseSpec.createTerminal( return ParseSpec.createTerminal(
TimestampNode( StyleNode.wrapText(
try { resolveTimestamp(
matcher.group(1)!!.toLong() try {
} catch (e: NumberFormatException) { matcher.group(1)!!.toLong()
-1 } catch (e: NumberFormatException) {
}, -1
matcher.group(2) },
matcher.group(2)
),
listOf(TextAppearanceSpan(context, R.style.Code_TextAppearance))
), state ), state
) )
} }
@ -193,12 +196,12 @@ fun <RC> createCodeRule(
} }
} }
fun MarkdownParser.addRevoltRules(): MarkdownParser { fun MarkdownParser.addRevoltRules(context: Context): MarkdownParser {
return addRules( return addRules(
UserMentionRule(), UserMentionRule(),
ChannelMentionRule(), ChannelMentionRule(),
CustomEmoteRule(), CustomEmoteRule(),
TimestampRule(), TimestampRule(context),
NamedLinkRule(), NamedLinkRule(),
LinkRule(), LinkRule(),
) )

View File

@ -0,0 +1,70 @@
package chat.revolt.internals.markdown
import android.text.format.DateUtils
import android.util.Log
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.toJavaInstant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Locale
fun resolveTimestamp(timestamp: Long, modifier: String? = null): String {
val normalisedModifier = modifier.orEmpty().removePrefix(":")
val instant = Instant.fromEpochSeconds(timestamp)
val javaInstant = instant.toJavaInstant()
try {
if (timestamp < 0) {
return "<invalid timestamp>"
}
val outString = when (normalisedModifier) {
// 22:22
"t" -> DateTimeFormatter.ofPattern("HH:mm")
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault())
.format(javaInstant)
// 22:22:22
"T" -> DateTimeFormatter.ofPattern("HH:mm:ss")
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault())
.format(javaInstant)
// 22 September 2022
"D" -> DateTimeFormatter.ofPattern("dd MMMM yyyy")
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault())
.format(javaInstant)
// 22 September 2022 22:22
"f" -> DateTimeFormatter.ofPattern("dd MMMM yyyy HH:mm")
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault())
.format(javaInstant)
// Thursday, 22 September 2022 22:22
"F" -> DateTimeFormatter.ofPattern("EEEE, dd MMMM yyyy HH:mm")
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault())
.format(javaInstant)
// 9 months ago
"R" -> DateUtils.getRelativeTimeSpanString(
timestamp * 1000,
Clock.System.now().toEpochMilliseconds(),
DateUtils.MINUTE_IN_MILLIS
)
// Fallback. Shouldn't happen, regex already checks for this
else -> timestamp.toString()
}
return outString.toString()
} catch (e: Exception) {
Log.e("Timestamp", "Failed to parse timestamp", e)
return "<invalid timestamp>"
}
}

View File

@ -298,7 +298,7 @@ fun ChannelScreen(
.addRules( .addRules(
SimpleMarkdownRules.createEscapeRule(), SimpleMarkdownRules.createEscapeRule(),
) )
.addRevoltRules() .addRevoltRules(context)
.addRules( .addRules(
createCodeRule(context, codeBlockColor.toArgb()), createCodeRule(context, codeBlockColor.toArgb()),
createInlineCodeRule(context, codeBlockColor.toArgb()), createInlineCodeRule(context, codeBlockColor.toArgb()),