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 155ad8ac..435f5685 100644 --- a/app/src/main/java/chat/revolt/components/generic/Markdown.kt +++ b/app/src/main/java/chat/revolt/components/generic/Markdown.kt @@ -29,6 +29,7 @@ import chat.revolt.internals.markdown.CustomEmoteRule import chat.revolt.internals.markdown.MarkdownContext import chat.revolt.internals.markdown.MarkdownParser import chat.revolt.internals.markdown.MarkdownState +import chat.revolt.internals.markdown.TimestampRule import chat.revolt.internals.markdown.UserMentionRule import chat.revolt.internals.markdown.createCodeRule import chat.revolt.internals.markdown.createInlineCodeRule @@ -61,6 +62,7 @@ fun UIMarkdown( UserMentionRule(), ChannelMentionRule(), CustomEmoteRule(), + TimestampRule(), ) .addRules( createCodeRule(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 010ba495..913f4cea 100644 --- a/app/src/main/java/chat/revolt/internals/markdown/MarkdownNodes.kt +++ b/app/src/main/java/chat/revolt/internals/markdown/MarkdownNodes.kt @@ -1,7 +1,15 @@ package chat.revolt.internals.markdown import android.text.SpannableStringBuilder +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) { @@ -30,3 +38,67 @@ 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("") + } + } +} \ No newline at end of file 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 10119c5b..a2e7aa24 100644 --- a/app/src/main/java/chat/revolt/internals/markdown/MarkdownRules.kt +++ b/app/src/main/java/chat/revolt/internals/markdown/MarkdownRules.kt @@ -47,6 +47,26 @@ class CustomEmoteRule : } } +class TimestampRule : + Rule(Pattern.compile("^")) { + override fun parse( + matcher: Matcher, + parser: Parser, + state: S + ): ParseSpec { + return ParseSpec.createTerminal( + TimestampNode( + try { + matcher.group(1)!!.toLong() + } catch (e: NumberFormatException) { + -1 + }, + matcher.group(2) + ), state + ) + } +} + fun createInlineCodeRule(context: Context, backgroundColor: Int): Rule, S> { return CodeRules.createInlineCodeRule( { listOf(TextAppearanceSpan(context, R.style.Code_TextAppearance)) }, 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 95592280..8a8c424a 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 @@ -70,6 +70,7 @@ import chat.revolt.internals.markdown.CustomEmoteRule import chat.revolt.internals.markdown.MarkdownContext import chat.revolt.internals.markdown.MarkdownParser import chat.revolt.internals.markdown.MarkdownState +import chat.revolt.internals.markdown.TimestampRule import chat.revolt.internals.markdown.UserMentionRule import chat.revolt.internals.markdown.createCodeRule import chat.revolt.internals.markdown.createInlineCodeRule @@ -256,6 +257,7 @@ fun ChannelScreen( UserMentionRule(), ChannelMentionRule(), CustomEmoteRule(), + TimestampRule(), ) .addRules( createCodeRule(context, codeBlockColor.toArgb()),