feat: styling for timestamps
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
a3c5e43c5d
commit
800aa68083
|
|
@ -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()),
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue