diff --git a/app/src/main/java/chat/revolt/components/generic/Markdown.kt b/app/src/main/java/chat/revolt/components/generic/Markdown.kt index 5848a29c..4efa5b85 100644 --- a/app/src/main/java/chat/revolt/components/generic/Markdown.kt +++ b/app/src/main/java/chat/revolt/components/generic/Markdown.kt @@ -58,7 +58,7 @@ fun UIMarkdown( .addRules( SimpleMarkdownRules.createEscapeRule() ) - .addRevoltRules() + .addRevoltRules(context) .addRules( createCodeRule(context, codeBlockColor.toArgb()), createInlineCodeRule(context, codeBlockColor.toArgb()), diff --git a/app/src/main/java/chat/revolt/internals/markdown/MarkdownNodes.kt b/app/src/main/java/chat/revolt/internals/markdown/MarkdownNodes.kt index f0225b44..878e7e33 100644 --- a/app/src/main/java/chat/revolt/internals/markdown/MarkdownNodes.kt +++ b/app/src/main/java/chat/revolt/internals/markdown/MarkdownNodes.kt @@ -2,15 +2,7 @@ package chat.revolt.internals.markdown import android.text.SpannableStringBuilder import android.text.Spanned -import android.text.format.DateUtils -import android.util.Log 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() { override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) { @@ -62,70 +54,6 @@ class CustomEmoteNode(private val emoteId: String) : Node() { } } -class TimestampNode(private val timestamp: Long, private val modifier: String? = null) : - Node() { - 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("") - 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("") - } - } -} - class LinkNode(val content: String, val url: String = content) : Node() { override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) { builder.append(content) diff --git a/app/src/main/java/chat/revolt/internals/markdown/MarkdownRules.kt b/app/src/main/java/chat/revolt/internals/markdown/MarkdownRules.kt index e10da4a0..46ff97b4 100644 --- a/app/src/main/java/chat/revolt/internals/markdown/MarkdownRules.kt +++ b/app/src/main/java/chat/revolt/internals/markdown/MarkdownRules.kt @@ -47,21 +47,24 @@ class CustomEmoteRule : } } -class TimestampRule : - Rule(Pattern.compile("^")) { +class TimestampRule(private val context: Context) : + Rule, S>(Pattern.compile("^")) { override fun parse( matcher: Matcher, - parser: Parser, + parser: Parser, S>, state: S ): ParseSpec { return ParseSpec.createTerminal( - TimestampNode( - try { - matcher.group(1)!!.toLong() - } catch (e: NumberFormatException) { - -1 - }, - matcher.group(2) + StyleNode.wrapText( + resolveTimestamp( + try { + matcher.group(1)!!.toLong() + } catch (e: NumberFormatException) { + -1 + }, + matcher.group(2) + ), + listOf(TextAppearanceSpan(context, R.style.Code_TextAppearance)) ), state ) } @@ -193,12 +196,12 @@ fun createCodeRule( } } -fun MarkdownParser.addRevoltRules(): MarkdownParser { +fun MarkdownParser.addRevoltRules(context: Context): MarkdownParser { return addRules( UserMentionRule(), ChannelMentionRule(), CustomEmoteRule(), - TimestampRule(), + TimestampRule(context), NamedLinkRule(), LinkRule(), ) diff --git a/app/src/main/java/chat/revolt/internals/markdown/Timestamp.kt b/app/src/main/java/chat/revolt/internals/markdown/Timestamp.kt new file mode 100644 index 00000000..d3ec0b05 --- /dev/null +++ b/app/src/main/java/chat/revolt/internals/markdown/Timestamp.kt @@ -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 "" + } + + 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 "" + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt index ad223ce5..58b70d9a 100644 --- a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt @@ -298,7 +298,7 @@ fun ChannelScreen( .addRules( SimpleMarkdownRules.createEscapeRule(), ) - .addRevoltRules() + .addRevoltRules(context) .addRules( createCodeRule(context, codeBlockColor.toArgb()), createInlineCodeRule(context, codeBlockColor.toArgb()),