diff --git a/README.md b/README.md
index 61706199..c60171a2 100644
--- a/README.md
+++ b/README.md
@@ -28,13 +28,13 @@ You can download the latest APK [here](https://github.com/alexjyong/android/rele
## Features Added
- |
+ |
Emojis next to channel names
|
-
+ |
Jump to replied message
-
+
|
@@ -43,10 +43,17 @@ You can download the latest APK [here](https://github.com/alexjyong/android/rele
+
+
+ Recently used emojis
+
+ |
+
+
The codebase includes the app itself, as well as an internal library for interacting with the Revolt
API. The app is written in Kotlin, and wholly
uses [Jetpack Compose](https://developer.android.com/jetpack/compose).
diff --git a/app/src/main/java/chat/revolt/api/settings/Experiments.kt b/app/src/main/java/chat/revolt/api/settings/Experiments.kt
index cfed6040..868d36a9 100644
--- a/app/src/main/java/chat/revolt/api/settings/Experiments.kt
+++ b/app/src/main/java/chat/revolt/api/settings/Experiments.kt
@@ -26,7 +26,8 @@ class ExperimentInstance(default: Boolean) {
* - All experiments can be disabled at once with a single toggle.
*/
object Experiments {
- val useKotlinBasedMarkdownRenderer = ExperimentInstance(true)
+ val useKotlinBasedMarkdownRenderer = ExperimentInstance(false)
+ val useEnhancedMarkdownRenderer = ExperimentInstance(true)
val usePolar = ExperimentInstance(false)
val enableServerIdentityOptions = ExperimentInstance(false)
val useFinalMarkdownRenderer = ExperimentInstance(false)
@@ -43,6 +44,9 @@ object Experiments {
useKotlinBasedMarkdownRenderer.setEnabled(
kvStorage.getBoolean("exp/useKotlinBasedMarkdownRenderer") == true
)
+ useEnhancedMarkdownRenderer.setEnabled(
+ kvStorage.getBoolean("exp/useEnhancedMarkdownRenderer") == true
+ )
usePolar.setEnabled(
kvStorage.getBoolean("exp/usePolar") == true
)
@@ -53,8 +57,16 @@ object Experiments {
kvStorage.getBoolean("exp/useFinalMarkdownRenderer") == true
)
- if (useFinalMarkdownRenderer.isEnabled && useKotlinBasedMarkdownRenderer.isEnabled) {
- // if jbm and fm are enabled, fm takes precedence. this should not be possible in practice
+ if (useFinalMarkdownRenderer.isEnabled) {
+ if (useEnhancedMarkdownRenderer.isEnabled) {
+ useEnhancedMarkdownRenderer.setEnabled(false)
+ kvStorage.set("exp/useEnhancedMarkdownRenderer", false)
+ }
+ if (useKotlinBasedMarkdownRenderer.isEnabled) {
+ useKotlinBasedMarkdownRenderer.setEnabled(false)
+ kvStorage.set("exp/useKotlinBasedMarkdownRenderer", false)
+ }
+ } else if (useEnhancedMarkdownRenderer.isEnabled && useKotlinBasedMarkdownRenderer.isEnabled) {
useKotlinBasedMarkdownRenderer.setEnabled(false)
kvStorage.set("exp/useKotlinBasedMarkdownRenderer", false)
}
diff --git a/app/src/main/java/chat/revolt/composables/generic/EmojiAwareText.kt b/app/src/main/java/chat/revolt/composables/generic/EmojiAwareText.kt
new file mode 100644
index 00000000..54166958
--- /dev/null
+++ b/app/src/main/java/chat/revolt/composables/generic/EmojiAwareText.kt
@@ -0,0 +1,144 @@
+package chat.revolt.composables.generic
+
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.text.InlineTextContent
+import androidx.compose.foundation.text.appendInlineContent
+import androidx.compose.material3.LocalTextStyle
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.Placeholder
+import androidx.compose.ui.text.PlaceholderVerticalAlign
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.unit.isUnspecified
+import chat.revolt.api.REVOLT_FILES
+
+/**
+ * Text composable that automatically detects and renders custom emojis in the format :ULID:
+ * This bypasses all parser-level emoji handling and works at the Compose rendering level.
+ */
+@Composable
+fun EmojiAwareText(
+ text: AnnotatedString,
+ modifier: Modifier = Modifier,
+ maxLines: Int = Int.MAX_VALUE,
+ inlineContent: Map = emptyMap(),
+ onTextLayout: ((androidx.compose.ui.text.TextLayoutResult) -> Unit)? = null
+) {
+ val fontSize = LocalTextStyle.current.fontSize
+ val emojiSize = if (fontSize.isUnspecified) 16.sp else fontSize
+
+ val customEmojiRegex = Regex(":([0-9A-HJKMNP-TV-Z]{26}):")
+ val matches = customEmojiRegex.findAll(text.text).toList()
+
+ if (matches.isEmpty()) {
+ Text(
+ text = text,
+ modifier = modifier,
+ maxLines = maxLines,
+ inlineContent = inlineContent,
+ onTextLayout = onTextLayout ?: {}
+ )
+ return
+ }
+
+ val processedText = buildAnnotatedString {
+ val sourceText = text.text
+ var lastIndex = 0
+
+ matches.forEach { match ->
+ val emojiId = match.groupValues[1]
+ val placeholderKey = "emoji_$emojiId"
+
+ if (match.range.first > lastIndex) {
+ val beforeEmoji = sourceText.substring(lastIndex, match.range.first)
+ append(beforeEmoji)
+
+ text.spanStyles.forEach { spanStyle ->
+ if (spanStyle.start < match.range.first && spanStyle.end > lastIndex) {
+ val spanStart = maxOf(spanStyle.start - lastIndex, 0)
+ val spanEnd = minOf(spanStyle.end - lastIndex, beforeEmoji.length)
+ if (spanStart < spanEnd) {
+ addStyle(spanStyle.item, length - beforeEmoji.length + spanStart, length - beforeEmoji.length + spanEnd)
+ }
+ }
+ }
+ }
+
+ appendInlineContent(placeholderKey, "[emoji]")
+
+ lastIndex = match.range.last + 1
+ }
+
+ if (lastIndex < sourceText.length) {
+ val remainingText = sourceText.substring(lastIndex)
+ append(remainingText)
+
+ text.spanStyles.forEach { spanStyle ->
+ if (spanStyle.start < sourceText.length && spanStyle.end > lastIndex) {
+ val spanStart = maxOf(spanStyle.start - lastIndex, 0)
+ val spanEnd = minOf(spanStyle.end - lastIndex, remainingText.length)
+ if (spanStart < spanEnd) {
+ addStyle(spanStyle.item, length - remainingText.length + spanStart, length - remainingText.length + spanEnd)
+ }
+ }
+ }
+ }
+ }
+
+ val emojiInlineContent = matches.associate { match ->
+ val emojiId = match.groupValues[1]
+ val placeholderKey = "emoji_$emojiId"
+
+ placeholderKey to InlineTextContent(
+ placeholder = Placeholder(
+ width = emojiSize,
+ height = emojiSize,
+ placeholderVerticalAlign = PlaceholderVerticalAlign.Center
+ )
+ ) {
+ RemoteImage(
+ url = "$REVOLT_FILES/emojis/$emojiId",
+ description = ":$emojiId:",
+ modifier = Modifier
+ .size(emojiSize.value.dp)
+ .clip(CircleShape)
+ )
+ }
+ }
+
+ val mergedInlineContent = inlineContent + emojiInlineContent
+
+ Text(
+ text = processedText,
+ inlineContent = mergedInlineContent,
+ modifier = modifier,
+ maxLines = maxLines,
+ onTextLayout = onTextLayout ?: {}
+ )
+}
+
+/**
+ * Overload for plain string input
+ */
+@Composable
+fun EmojiAwareText(
+ text: String,
+ modifier: Modifier = Modifier,
+ maxLines: Int = Int.MAX_VALUE,
+ inlineContent: Map = emptyMap(),
+ onTextLayout: ((androidx.compose.ui.text.TextLayoutResult) -> Unit)? = null
+) {
+ EmojiAwareText(
+ text = AnnotatedString(text),
+ modifier = modifier,
+ maxLines = maxLines,
+ inlineContent = inlineContent,
+ onTextLayout = onTextLayout
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/chat/revolt/composables/markdown/MarkdownText.kt b/app/src/main/java/chat/revolt/composables/markdown/MarkdownText.kt
index 6fcac15b..721147c4 100644
--- a/app/src/main/java/chat/revolt/composables/markdown/MarkdownText.kt
+++ b/app/src/main/java/chat/revolt/composables/markdown/MarkdownText.kt
@@ -43,6 +43,7 @@ import chat.revolt.api.routes.custom.fetchEmoji
import chat.revolt.api.schemas.isInviteUri
import chat.revolt.callbacks.Action
import chat.revolt.callbacks.ActionChannel
+import chat.revolt.composables.generic.EmojiAwareText
import chat.revolt.composables.generic.RemoteImage
import chat.revolt.composables.utils.detectTapGesturesConditionalConsume
import chat.revolt.internals.EmojiRepository
@@ -484,43 +485,9 @@ fun MarkdownText(textNode: AstNode, modifier: Modifier = Modifier) {
false
}
- Text(
+ EmojiAwareText(
text = annotatedText,
- style = LocalTextStyle.current,
- color = LocalContentColor.current,
onTextLayout = { layoutResult = it },
- inlineContent = mapOf(
- "CustomEmote" to InlineTextContent(
- placeholder = Placeholder(
- width = LocalTextStyle.current.fontSize * 1.5,
- height = LocalTextStyle.current.fontSize * 1.5,
- placeholderVerticalAlign = PlaceholderVerticalAlign.Center
- ),
- ) { id ->
- val emote = RevoltAPI.emojiCache[id]
- if (emote == null) {
- scope.launch {
- try {
- RevoltAPI.emojiCache[id] = fetchEmoji(id)
- } catch (e: Exception) {
- // no-op
- }
- }
- return@InlineTextContent
- } else {
- with(LocalDensity.current) {
- RemoteImage(
- url = "$REVOLT_FILES/emojis/${id}",
- description = emote.name,
- contentScale = ContentScale.Fit,
- modifier = Modifier
- .width((LocalTextStyle.current.fontSize * 1.5).toDp())
- .height((LocalTextStyle.current.fontSize * 1.5).toDp())
- )
- }
- }
- }
- ),
modifier = modifier.pointerInput(onClick, onLongClick) {
detectTapGesturesConditionalConsume(
onTap = { pos ->
diff --git a/app/src/main/java/chat/revolt/composables/markdown/RichMarkdown.kt b/app/src/main/java/chat/revolt/composables/markdown/RichMarkdown.kt
index 492dffb2..31883a02 100644
--- a/app/src/main/java/chat/revolt/composables/markdown/RichMarkdown.kt
+++ b/app/src/main/java/chat/revolt/composables/markdown/RichMarkdown.kt
@@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier
import chat.revolt.api.settings.Experiments
import chat.revolt.markdown.jbm.JBM
import chat.revolt.markdown.jbm.JBMRenderer
+import chat.revolt.markdown.jbm.JBMEnhancedRenderer
import chat.revolt.ndk.Stendal
@OptIn(JBM::class)
@@ -16,10 +17,15 @@ fun RichMarkdown(input: String, modifier: Modifier = Modifier) {
Experiments.useFinalMarkdownRenderer.isEnabled -> {
JBMRenderer(input)
}
- else -> {
+ Experiments.useEnhancedMarkdownRenderer.isEnabled -> {
+ JBMEnhancedRenderer(input)
+ }
+ Experiments.useKotlinBasedMarkdownRenderer.isEnabled -> {
JBMRenderer(input)
}
+ else -> {
+ JBMEnhancedRenderer(input)
+ }
}
-
}
}
\ No newline at end of file
diff --git a/app/src/main/java/chat/revolt/markdown/jbm/JBMApi.kt b/app/src/main/java/chat/revolt/markdown/jbm/JBMApi.kt
index 4ce186b1..fdd13a7c 100644
--- a/app/src/main/java/chat/revolt/markdown/jbm/JBMApi.kt
+++ b/app/src/main/java/chat/revolt/markdown/jbm/JBMApi.kt
@@ -10,7 +10,7 @@ annotation class JBM
@JBM
object JBMApi {
- fun parse(src: String): ASTNode {
- return MarkdownParser(RSMFlavourDescriptor()).buildMarkdownTreeFromString(src)
+ fun parse(src: String, flavor: RSMFlavourDescriptor = RSMFlavourDescriptor()): ASTNode {
+ return MarkdownParser(flavor).buildMarkdownTreeFromString(src)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/chat/revolt/markdown/jbm/JBMEnhancedRenderer.kt b/app/src/main/java/chat/revolt/markdown/jbm/JBMEnhancedRenderer.kt
new file mode 100644
index 00000000..47c526ab
--- /dev/null
+++ b/app/src/main/java/chat/revolt/markdown/jbm/JBMEnhancedRenderer.kt
@@ -0,0 +1,23 @@
+package chat.revolt.markdown.jbm
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Modifier
+
+/**
+ * Enhanced JBM Renderer that provides additional markdown features.
+ * This renderer inherits all functionality from the original JBMRenderer while adding support
+ * for spoilers and other enhancements through a simple preprocessing approach.
+ */
+
+@JBM
+@Composable
+fun JBMEnhancedRenderer(content: String, modifier: Modifier = Modifier) {
+ CompositionLocalProvider(
+ LocalJBMarkdownTreeState provides LocalJBMarkdownTreeState.current.copy(
+ enhanced = true
+ )
+ ) {
+ JBMRenderer(content, modifier)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/chat/revolt/markdown/jbm/JBMRenderer.kt b/app/src/main/java/chat/revolt/markdown/jbm/JBMRenderer.kt
index 2b9d7c05..0450008c 100644
--- a/app/src/main/java/chat/revolt/markdown/jbm/JBMRenderer.kt
+++ b/app/src/main/java/chat/revolt/markdown/jbm/JBMRenderer.kt
@@ -83,6 +83,7 @@ import chat.revolt.api.schemas.isInviteUri
import chat.revolt.api.settings.LoadedSettings
import chat.revolt.callbacks.Action
import chat.revolt.callbacks.ActionChannel
+import chat.revolt.composables.generic.EmojiAwareText
import chat.revolt.composables.generic.RemoteImage
import chat.revolt.composables.generic.UserAvatar
import chat.revolt.composables.markdown.Annotations
@@ -138,6 +139,7 @@ data class JBMarkdownTreeState(
val currentServer: String? = null,
val embedded: Boolean = false,
val singleLine: Boolean = false,
+ val enhanced: Boolean = false,
val colors: JBMColors = JBMColors(
clickable = Color(0xFFFF00FF),
clickableBackground = Color(0x2000FF00)
@@ -153,11 +155,14 @@ val avatarPadding = 2.dp
@Composable
@JBM
fun JBMRenderer(content: String, modifier: Modifier = Modifier) {
- var tree by remember { mutableStateOf(JBMApi.parse(content)) }
+ val state = LocalJBMarkdownTreeState.current
+ val flavor = if (state.enhanced) RSMEnhancedFlavourDescriptor() else RSMFlavourDescriptor()
+
+ var tree by remember { mutableStateOf(JBMApi.parse(content, flavor)) }
var revealedSpoilers by remember { mutableStateOf(setOf()) }
- LaunchedEffect(content) {
- tree = JBMApi.parse(content)
+ LaunchedEffect(content, state.enhanced) {
+ tree = JBMApi.parse(content, flavor)
}
CompositionLocalProvider(
@@ -324,23 +329,6 @@ private fun annotateText(
}
}
- RSMElementTypes.CUSTOM_EMOTE -> {
- val contents = node.getTextInNode(sourceText).toString()
- val emoteId = contents.removeSurrounding(":", ":")
- if (emoteId == contents || !emoteId.isUlid()) {
- // Invalid custom emote. Append as if it were regular text.
- for (child in node.children) {
- append(annotateText(state, child, revealedSpoilers))
- }
- } else {
- pushStringAnnotation(
- tag = JBMAnnotations.CustomEmote.tag,
- annotation = emoteId
- )
- appendInlineContent(JBMAnnotations.CustomEmote.tag, emoteId)
- pop()
- }
- }
RSMElementTypes.SPOILER -> {
android.util.Log.d("JBMRenderer", "Found SPOILER node with ${node.children.size} children")
@@ -714,14 +702,6 @@ private fun JBMText(node: ASTNode, modifier: Modifier) {
return@handler true
}
- JBMAnnotations.CustomEmote.tag -> {
- scope.launch {
- ActionChannel.send(
- Action.EmoteInfo(item)
- )
- }
- return@handler true
- }
JBMAnnotations.Spoiler.tag -> {
mdState.onSpoilerToggle?.invoke(item)
@@ -753,11 +733,10 @@ private fun JBMText(node: ASTNode, modifier: Modifier) {
return@handler false
}
- Text(
+ EmojiAwareText(
text = annotatedText,
onTextLayout = { layoutResult = it },
maxLines = if (mdState.singleLine) 1 else Int.MAX_VALUE,
- overflow = if (mdState.singleLine) TextOverflow.Ellipsis else TextOverflow.Clip,
modifier = modifier.pointerInput(onClick, onLongClick) {
detectTapGesturesConditionalConsume(
onTap = { pos ->
@@ -851,36 +830,6 @@ private fun JBMText(node: ASTNode, modifier: Modifier) {
}
}
},
- JBMAnnotations.CustomEmote.tag to InlineTextContent(
- placeholder = Placeholder(
- width = LocalTextStyle.current.fontSize * 1.5,
- height = LocalTextStyle.current.fontSize * 1.5,
- placeholderVerticalAlign = PlaceholderVerticalAlign.Center
- ),
- ) { id ->
- val emote = RevoltAPI.emojiCache[id]
- if (emote == null) {
- scope.launch {
- try {
- RevoltAPI.emojiCache[id] = fetchEmoji(id)
- } catch (e: Exception) {
- // no-op
- }
- }
- return@InlineTextContent
- } else {
- with(LocalDensity.current) {
- RemoteImage(
- url = "$REVOLT_FILES/emojis/${id}",
- description = emote.name,
- contentScale = ContentScale.Fit,
- modifier = Modifier
- .width((LocalTextStyle.current.fontSize * 1.5).toDp())
- .height((LocalTextStyle.current.fontSize * 1.5).toDp())
- )
- }
- }
- },
JBMAnnotations.UserAvatar.tag to with(LocalDensity.current) {
val placeholderBaseWidth =
(LocalTextStyle.current.fontSize * 1.5).toPx() - (avatarPadding * 2).toPx()
diff --git a/app/src/main/java/chat/revolt/markdown/jbm/RSMEnhancedFlavourDescriptor.kt b/app/src/main/java/chat/revolt/markdown/jbm/RSMEnhancedFlavourDescriptor.kt
new file mode 100644
index 00000000..e336d756
--- /dev/null
+++ b/app/src/main/java/chat/revolt/markdown/jbm/RSMEnhancedFlavourDescriptor.kt
@@ -0,0 +1,20 @@
+package chat.revolt.markdown.jbm
+
+import chat.revolt.markdown.jbm.sequentialparsers.SpoilerParser
+import org.intellij.markdown.parser.sequentialparsers.SequentialParser
+import org.intellij.markdown.parser.sequentialparsers.SequentialParserManager
+
+/**
+ * Enhanced RSM Flavour Descriptor that inherits from the original working RSMFlavourDescriptor.
+ * This enhanced version adds support for spoilers by carefully adding SpoilerParser
+ * at the end of the parsing sequence to avoid conflicts with existing parsers.
+ */
+class RSMEnhancedFlavourDescriptor : RSMFlavourDescriptor() {
+ override val sequentialParserManager = object : SequentialParserManager() {
+ override fun getParserSequence(): List {
+ val originalParsers = super@RSMEnhancedFlavourDescriptor.sequentialParserManager.getParserSequence()
+
+ return originalParsers + SpoilerParser()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/chat/revolt/markdown/jbm/RSMFlavourDescriptor.kt b/app/src/main/java/chat/revolt/markdown/jbm/RSMFlavourDescriptor.kt
index cffda89d..798f5e30 100644
--- a/app/src/main/java/chat/revolt/markdown/jbm/RSMFlavourDescriptor.kt
+++ b/app/src/main/java/chat/revolt/markdown/jbm/RSMFlavourDescriptor.kt
@@ -1,10 +1,8 @@
package chat.revolt.markdown.jbm
import chat.revolt.markdown.jbm.sequentialparsers.ChannelMentionParser
-import chat.revolt.markdown.jbm.sequentialparsers.CustomEmoteParser
import chat.revolt.markdown.jbm.sequentialparsers.MassMentionParser
import chat.revolt.markdown.jbm.sequentialparsers.RoleMentionParser
-import chat.revolt.markdown.jbm.sequentialparsers.SpoilerParser
import chat.revolt.markdown.jbm.sequentialparsers.UserMentionParser
import org.intellij.markdown.MarkdownTokenTypes
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
@@ -21,7 +19,7 @@ import org.intellij.markdown.parser.sequentialparsers.impl.InlineLinkParser
import org.intellij.markdown.parser.sequentialparsers.impl.MathParser
import org.intellij.markdown.parser.sequentialparsers.impl.ReferenceLinkParser
-class RSMFlavourDescriptor : GFMFlavourDescriptor() {
+open class RSMFlavourDescriptor : GFMFlavourDescriptor() {
override val sequentialParserManager = object : SequentialParserManager() {
override fun getParserSequence(): List {
return listOf(
@@ -35,9 +33,7 @@ class RSMFlavourDescriptor : GFMFlavourDescriptor() {
UserMentionParser(),
ChannelMentionParser(),
RoleMentionParser(),
- MassMentionParser(),
- CustomEmoteParser(),
- SpoilerParser()
+ MassMentionParser()
)
}
}
diff --git a/app/src/main/java/chat/revolt/markdown/jbm/sequentialparsers/CustomEmoteParser.kt b/app/src/main/java/chat/revolt/markdown/jbm/sequentialparsers/CustomEmoteParser.kt
index 95ac1eb8..b3acf8d5 100644
--- a/app/src/main/java/chat/revolt/markdown/jbm/sequentialparsers/CustomEmoteParser.kt
+++ b/app/src/main/java/chat/revolt/markdown/jbm/sequentialparsers/CustomEmoteParser.kt
@@ -16,11 +16,14 @@ class CustomEmoteParser : SequentialParser {
var iterator: TokensCache.Iterator = tokens.RangesListIterator(rangesToGlue)
while (iterator.type != null) {
+ android.util.Log.d("CustomEmoteParser", "Processing token: type=${iterator.type}, text='${iterator.toString()}'")
+
if (iterator.type == MarkdownTokenTypes.COLON) {
-
+ android.util.Log.d("CustomEmoteParser", "Found COLON token")
val endIterator = findNextColon(iterator.advance())
if (endIterator != null) {
+ android.util.Log.d("CustomEmoteParser", "Found matching closing colon, creating CUSTOM_EMOTE node")
result.withNode(
SequentialParser.Node(
iterator.index..endIterator.index + 1,
diff --git a/app/src/main/java/chat/revolt/screens/settings/ExperimentsSettingsScreen.kt b/app/src/main/java/chat/revolt/screens/settings/ExperimentsSettingsScreen.kt
index e0c33cd7..c45b0c75 100644
--- a/app/src/main/java/chat/revolt/screens/settings/ExperimentsSettingsScreen.kt
+++ b/app/src/main/java/chat/revolt/screens/settings/ExperimentsSettingsScreen.kt
@@ -47,7 +47,7 @@ import chat.revolt.settings.dsl.SubcategoryContentInsets
import kotlinx.coroutines.launch
enum class MarkdownRenderer {
- JetBrains, FinalMarkdown
+ JetBrains, JetBrainsEnhanced, FinalMarkdown
}
class ExperimentsSettingsScreenViewModel : ViewModel() {
@@ -56,17 +56,21 @@ class ExperimentsSettingsScreenViewModel : ViewModel() {
fun init() {
viewModelScope.launch {
when {
- Experiments.useKotlinBasedMarkdownRenderer.isEnabled -> {
- mdRenderer.value = MarkdownRenderer.JetBrains
- }
-
Experiments.useFinalMarkdownRenderer.isEnabled -> {
mdRenderer.value = MarkdownRenderer.FinalMarkdown
}
- else -> {
+ Experiments.useEnhancedMarkdownRenderer.isEnabled -> {
+ mdRenderer.value = MarkdownRenderer.JetBrainsEnhanced
+ }
+
+ Experiments.useKotlinBasedMarkdownRenderer.isEnabled -> {
mdRenderer.value = MarkdownRenderer.JetBrains
}
+
+ else -> {
+ mdRenderer.value = MarkdownRenderer.JetBrainsEnhanced
+ }
}
usePolarChecked.value = Experiments.usePolar.isEnabled
enableServerIdentityOptionsChecked.value =
@@ -98,7 +102,7 @@ class ExperimentsSettingsScreenViewModel : ViewModel() {
}
}
- val mdRenderer = mutableStateOf(MarkdownRenderer.JetBrains)
+ val mdRenderer = mutableStateOf(MarkdownRenderer.JetBrainsEnhanced)
fun setMdRenderer(value: MarkdownRenderer) {
viewModelScope.launch {
@@ -106,6 +110,17 @@ class ExperimentsSettingsScreenViewModel : ViewModel() {
MarkdownRenderer.JetBrains -> {
kv.set("exp/useKotlinBasedMarkdownRenderer", true)
Experiments.useKotlinBasedMarkdownRenderer.setEnabled(true)
+ kv.set("exp/useEnhancedMarkdownRenderer", false)
+ Experiments.useEnhancedMarkdownRenderer.setEnabled(false)
+ kv.set("exp/useFinalMarkdownRenderer", false)
+ Experiments.useFinalMarkdownRenderer.setEnabled(false)
+ }
+
+ MarkdownRenderer.JetBrainsEnhanced -> {
+ kv.set("exp/useKotlinBasedMarkdownRenderer", false)
+ Experiments.useKotlinBasedMarkdownRenderer.setEnabled(false)
+ kv.set("exp/useEnhancedMarkdownRenderer", true)
+ Experiments.useEnhancedMarkdownRenderer.setEnabled(true)
kv.set("exp/useFinalMarkdownRenderer", false)
Experiments.useFinalMarkdownRenderer.setEnabled(false)
}
@@ -113,6 +128,8 @@ class ExperimentsSettingsScreenViewModel : ViewModel() {
MarkdownRenderer.FinalMarkdown -> {
kv.set("exp/useKotlinBasedMarkdownRenderer", false)
Experiments.useKotlinBasedMarkdownRenderer.setEnabled(false)
+ kv.set("exp/useEnhancedMarkdownRenderer", false)
+ Experiments.useEnhancedMarkdownRenderer.setEnabled(false)
kv.set("exp/useFinalMarkdownRenderer", true)
Experiments.useFinalMarkdownRenderer.setEnabled(true)
}
@@ -203,7 +220,8 @@ fun ExperimentsSettingsScreen(
},
supportingContent = {
when (viewModel.mdRenderer.value) {
- MarkdownRenderer.JetBrains -> Text("Use the Kotlin-based JetBrains Markdown renderer for messages. This renderer is feature-complete with support for strikethrough, spoilers, and more. (Default)")
+ MarkdownRenderer.JetBrains -> Text("Use the original Kotlin-based JetBrains Markdown renderer for messages. Stable but without spoiler support.")
+ MarkdownRenderer.JetBrainsEnhanced -> Text("Use the enhanced JetBrains Markdown renderer with spoiler support and other improvements. Recommended for best experience. (Default)")
MarkdownRenderer.FinalMarkdown -> Text("Use a new blazingly fast markdown renderer for messages. This renderer is experimental and may have missing features.")
}
},
@@ -226,6 +244,15 @@ fun ExperimentsSettingsScreen(
) {
Text("JetBrains")
}
+ ToggleButton(
+ checked = viewModel.mdRenderer.value == MarkdownRenderer.JetBrainsEnhanced,
+ onCheckedChange = { viewModel.setMdRenderer(MarkdownRenderer.JetBrainsEnhanced) },
+ modifier = Modifier
+ .fillMaxWidth()
+ .semantics { role = Role.RadioButton }
+ ) {
+ Text("JetBrains Enhanced")
+ }
if (FeatureFlags.finalMarkdownGranted || viewModel.mdRenderer.value == MarkdownRenderer.FinalMarkdown) {
ToggleButton(
checked = viewModel.mdRenderer.value == MarkdownRenderer.FinalMarkdown,