From 7cfbc3fdacbc8d9e00dce6a4a7fb771882850e0b Mon Sep 17 00:00:00 2001 From: Infi Date: Fri, 13 Sep 2024 00:04:00 +0200 Subject: [PATCH] feat(jbm): continue development Signed-off-by: Infi --- app/build.gradle | 1 + .../chat/revolt/components/chat/Message.kt | 56 +++- .../components/markdown/jbm/JBMRenderer.kt | 256 +++++++++++++++--- .../screens/labs/ui/sandbox/JBMSandbox.kt | 58 +++- .../main/java/chat/revolt/ui/theme/Theme.kt | 13 +- 5 files changed, 333 insertions(+), 51 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b20f39de..5cd86c55 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -269,6 +269,7 @@ dependencies { // Markup implementation "org.jetbrains:markdown:0.7.3" + implementation "dev.snipme:highlights:0.9.1" // Livekit // FIXME temporarily not included, re-add when realtime media is to be implemented diff --git a/app/src/main/java/chat/revolt/components/chat/Message.kt b/app/src/main/java/chat/revolt/components/chat/Message.kt index dabae9a6..f4898583 100644 --- a/app/src/main/java/chat/revolt/components/chat/Message.kt +++ b/app/src/main/java/chat/revolt/components/chat/Message.kt @@ -34,13 +34,17 @@ import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -78,6 +82,9 @@ import chat.revolt.components.generic.UserAvatar import chat.revolt.components.generic.UserAvatarWidthPlaceholder import chat.revolt.components.markdown.LocalMarkdownTreeConfig import chat.revolt.components.markdown.RichMarkdown +import chat.revolt.components.markdown.jbm.JBM +import chat.revolt.components.markdown.jbm.JBMRenderer +import chat.revolt.components.markdown.jbm.LocalJBMarkdownTreeState import chat.revolt.internals.text.Gigamoji import kotlinx.coroutines.launch import chat.revolt.api.schemas.Message as MessageSchema @@ -167,7 +174,7 @@ fun formatLongAsTime(time: Long): String { } } -@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class) +@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class, JBM::class) @Composable fun Message( message: MessageSchema, @@ -192,6 +199,8 @@ fun Message( val authorIsBlocked = remember(author) { author.relationship == "Blocked" } + var __TEMPORARY_useJbm by remember { mutableStateOf(false) } + Column(Modifier.animateContentSize()) { if (message.tail == false) { Spacer(modifier = Modifier.height(10.dp)) @@ -361,17 +370,40 @@ fun Message( message.content?.let { if (message.content.isBlank()) return@let // if only an attachment is sent - CompositionLocalProvider( - LocalMarkdownTreeConfig provides LocalMarkdownTreeConfig.current.copy( - currentServer = RevoltAPI.channelCache[message.channel]?.server, - fontSizeMultiplier = Gigamoji.useGigamojiForMessage(message.content) - .let { - if (it) 2f else 1f - } - ) - ) { - Spacer(modifier = Modifier.height(2.dp)) - RichMarkdown(input = message.content) + Switch( + checked = __TEMPORARY_useJbm, + onCheckedChange = { __TEMPORARY_useJbm = it }, + ) + + if (__TEMPORARY_useJbm == false) { + CompositionLocalProvider( + LocalMarkdownTreeConfig provides LocalMarkdownTreeConfig.current.copy( + currentServer = RevoltAPI.channelCache[message.channel]?.server, + fontSizeMultiplier = Gigamoji.useGigamojiForMessage( + message.content + ) + .let { + if (it) 2f else 1f + } + ) + ) { + Spacer(modifier = Modifier.height(2.dp)) + RichMarkdown(input = message.content) + } + } else { + CompositionLocalProvider( + LocalJBMarkdownTreeState provides LocalJBMarkdownTreeState.current.copy( + fontSizeMultiplier = Gigamoji.useGigamojiForMessage( + message.content + ) + .let { + if (it) 2f else 1f + } + ) + ) { + Spacer(modifier = Modifier.height(2.dp)) + JBMRenderer(message.content) + } } } } diff --git a/app/src/main/java/chat/revolt/components/markdown/jbm/JBMRenderer.kt b/app/src/main/java/chat/revolt/components/markdown/jbm/JBMRenderer.kt index 741223a8..52af38b2 100644 --- a/app/src/main/java/chat/revolt/components/markdown/jbm/JBMRenderer.kt +++ b/app/src/main/java/chat/revolt/components/markdown/jbm/JBMRenderer.kt @@ -1,10 +1,18 @@ package chat.revolt.components.markdown.jbm +import android.content.res.Configuration import android.util.Log import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.InlineTextContent import androidx.compose.foundation.text.appendInlineContent import androidx.compose.material3.LocalTextStyle @@ -20,12 +28,14 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.structuralEqualityPolicy import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.Placeholder @@ -39,8 +49,17 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import chat.revolt.api.settings.GlobalState import chat.revolt.components.markdown.Annotations import chat.revolt.components.utils.detectTapGesturesConditionalConsume +import chat.revolt.ui.theme.FragmentMono +import chat.revolt.ui.theme.isThemeDark +import dev.snipme.highlights.Highlights +import dev.snipme.highlights.model.BoldHighlight +import dev.snipme.highlights.model.CodeHighlight +import dev.snipme.highlights.model.ColorHighlight +import dev.snipme.highlights.model.SyntaxLanguage +import dev.snipme.highlights.model.SyntaxThemes import org.intellij.markdown.MarkdownElementTypes import org.intellij.markdown.MarkdownTokenTypes import org.intellij.markdown.ast.ASTNode @@ -50,10 +69,11 @@ import org.intellij.markdown.flavours.gfm.GFMTokenTypes data class JBMarkdownTreeState( val sourceText: String = "", - val ignoreLineBreaks: Boolean = false, val listDepth: Int = 0, val fontSizeMultiplier: Float = 1f, - val linksClickable: Boolean = true + val linksClickable: Boolean = true, + val currentServer: String? = null, + val embedded: Boolean = false, ) val LocalJBMarkdownTreeState = @@ -66,15 +86,21 @@ fun JBMRenderer(content: String, modifier: Modifier = Modifier) { LaunchedEffect(content) { tree = JBMApi.parse(content) - - Log.d("JBMRenderer", "Parsed tree: ${tree.children.map { it.type.name }}") } CompositionLocalProvider( - LocalJBMarkdownTreeState provides JBMarkdownTreeState(content) + LocalJBMarkdownTreeState provides LocalJBMarkdownTreeState.current.copy( + sourceText = content + ) ) { - tree.children.map { - JBMBlock(it, modifier) + if (LocalJBMarkdownTreeState.current.embedded) { + tree.children.getOrNull(0)?.let { + JBMBlock(it, modifier) + } + } else { + tree.children.map { + JBMBlock(it, modifier) + } } } } @@ -89,12 +115,34 @@ private fun annotateText( buildAnnotatedString { when (node.type) { MarkdownTokenTypes.TEXT -> { - append(node.getTextInNode(sourceText)) + val source = if (state.embedded) { + node.getTextInNode(sourceText).toString().replace("\n", " ") + } else { + node.getTextInNode(sourceText) + } + append(source) + } + + MarkdownTokenTypes.ATX_HEADER -> { + // Do not need to do anything + } + + MarkdownElementTypes.ATX_1, + MarkdownElementTypes.ATX_2, + MarkdownElementTypes.ATX_3, + MarkdownElementTypes.ATX_4, + MarkdownElementTypes.ATX_5, + MarkdownElementTypes.ATX_6 -> { + for (child in node.children) { + append(annotateText(state, child)) + } } MarkdownElementTypes.EMPH -> { withStyle(SpanStyle(fontStyle = FontStyle.Italic)) { - for (child in node.children) { + // Skip the first child and the last child + // because they are the asterisk characters + for (child in node.children.subList(1, node.children.size - 1)) { append(annotateText(state, child)) } } @@ -102,7 +150,9 @@ private fun annotateText( MarkdownElementTypes.STRONG -> { withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { - for (child in node.children) { + // Skip the first two children and the last two children + // because they are the asterisk characters + for (child in node.children.subList(2, node.children.size - 2)) { append(annotateText(state, child)) } } @@ -118,8 +168,24 @@ private fun annotateText( } } + MarkdownElementTypes.CODE_SPAN -> { + withStyle(SpanStyle(fontFamily = FragmentMono)) { + val startsWithTwoBackticks = + node.children.getOrNull(1)?.type == MarkdownTokenTypes.BACKTICK + val removeItemCount = if (startsWithTwoBackticks) 2 else 1 + // Skip the first and last 1 or 2 children + // because they are the backtick characters + for (child in node.children.subList( + removeItemCount, + node.children.size - removeItemCount + )) { + append(annotateText(state, child)) + } + } + } + MarkdownTokenTypes.LIST_BULLET -> { - append(" ".repeat(state.listDepth) + " " + if (state.listDepth % 2 == 0) "•" else "◦" + " ") + append(" ".repeat(state.listDepth) + " • ") } MarkdownTokenTypes.LIST_NUMBER -> { @@ -145,7 +211,9 @@ private fun annotateText( append(" ") } - MarkdownElementTypes.PARAGRAPH, MarkdownElementTypes.HTML_BLOCK -> { + MarkdownElementTypes.PARAGRAPH, + MarkdownElementTypes.HTML_BLOCK, + MarkdownTokenTypes.ATX_CONTENT -> { for (child in node.children) { append(annotateText(state, child)) } @@ -167,15 +235,11 @@ private fun annotateText( MarkdownTokenTypes.EOL, MarkdownTokenTypes.WHITE_SPACE, MarkdownTokenTypes.COLON, + MarkdownTokenTypes.EMPH, GFMTokenTypes.TILDE -> { append(node.getTextInNode(sourceText)) } - // no-op types - // for example, the special characters that are used to denote the markup are here - MarkdownTokenTypes.EMPH -> { - } - else -> { append("[${node.type.name}]{\n") append(node.getTextInNode(sourceText)) @@ -292,14 +356,132 @@ private fun JBMText(node: ASTNode, modifier: Modifier) { ) } +private fun annotateHighlights( + source: String, + highlights: List +): AnnotatedString { + val highlightStyles = highlights.map { + when (it) { + is BoldHighlight -> AnnotatedString.Range( + SpanStyle(fontWeight = FontWeight.Bold), + it.location.start, + it.location.end + ) + + is ColorHighlight -> { + AnnotatedString.Range( + SpanStyle(color = Color(0xFF000000 or it.rgb.toLong())), + it.location.start, + it.location.end + ) + } + + else -> null + } + }.filterNotNull() + + return AnnotatedString(source, spanStyles = highlightStyles) +} + +// ======== TODO ======== +// - Add aliases for languages. For example, "js" should be an alias for "javascript" and "ts" should be an alias for "typescript", etc. +// - Better looking language name display. Ideally a dictionary of language names to display names with proper brand casing. +@Composable +private fun JBMCodeBlockContent(node: ASTNode, modifier: Modifier) { + val state = LocalJBMarkdownTreeState.current + + /*val colours = MaterialTheme.colorScheme + val contentColour = LocalContentColor.current + val syntaxTheme = remember { + SyntaxTheme( + key = "chat.revolt.M3Dynamic", + code = contentColour.toArgb() and 0xFFFFFF, + comment = colours.outline.toArgb() and 0xFFFFFF, + multilineComment = colours.outline.toArgb() and 0xFFFFFF, + keyword = colours.primary.toArgb() and 0xFFFFFF, + string = colours.secondary.toArgb() and 0xFFFFFF, + literal = colours.tertiary.toArgb() and 0xFFFFFF, + mark = colours.error.toArgb() and 0xFFFFFF, + punctuation = colours.inversePrimary.toArgb() and 0xFFFFFF, + metadata = colours.inverseSurface.toArgb() and 0xFFFFFF, + ) + }*/ + + val uiMode = LocalConfiguration.current.uiMode + val systemIsDark = + (uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES + val themeIsDark = remember(GlobalState.theme) { isThemeDark(GlobalState.theme, systemIsDark) } + + val codeFenceLanguage = remember(node) { + node.children.firstOrNull { it.type == MarkdownTokenTypes.FENCE_LANG } + ?.getTextInNode(state.sourceText)?.toString() + } + val codeFenceContent = remember(node) { + node.children + .filter { it.type == MarkdownTokenTypes.CODE_FENCE_CONTENT || it.type == MarkdownTokenTypes.EOL } + .joinToString("") { + it.getTextInNode(state.sourceText).toString() + } + .trim() + } + val annotatedContent = remember(codeFenceLanguage, codeFenceContent) { + val canAnnotate = codeFenceLanguage != null + val language = codeFenceLanguage?.let { SyntaxLanguage.getByName(it) } + val shouldAnnotate = language != null + + if (canAnnotate && shouldAnnotate) { + buildAnnotatedString { + val highlights = Highlights.Builder().apply { + code(codeFenceContent) + language(language ?: SyntaxLanguage.DEFAULT) + theme(SyntaxThemes.notepad(themeIsDark)) + //theme(syntaxTheme) + }.build() + append(annotateHighlights(codeFenceContent, highlights.getHighlights())) + } + } else { + buildAnnotatedString { + append(codeFenceContent) + } + } + } + + Column( + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = modifier + .clip(MaterialTheme.shapes.medium) + .background(MaterialTheme.colorScheme.surfaceContainer) + .padding(8.dp) + ) { + if (codeFenceLanguage != null) { + Text( + text = codeFenceLanguage, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(vertical = 4.dp) + ) + } + Box( + modifier = Modifier.horizontalScroll(rememberScrollState()) + ) { + Text( + text = annotatedContent, + fontFamily = FragmentMono, + modifier = Modifier + ) + } + } +} + @Composable private fun JBMBlock(node: ASTNode, modifier: Modifier) { + val state = LocalJBMarkdownTreeState.current + when (node.type) { MarkdownElementTypes.PARAGRAPH, MarkdownElementTypes.HTML_BLOCK -> { CompositionLocalProvider( LocalTextStyle provides LocalTextStyle.current.copy( - fontSize = LocalTextStyle.current.fontSize * LocalJBMarkdownTreeState.current.fontSizeMultiplier + fontSize = LocalTextStyle.current.fontSize * state.fontSizeMultiplier ) ) { JBMText(node, modifier) @@ -315,42 +497,44 @@ private fun JBMBlock(node: ASTNode, modifier: Modifier) { CompositionLocalProvider( LocalTextStyle provides LocalTextStyle.current.copy( fontWeight = FontWeight.Bold, - fontSize = when (node.type) { - MarkdownElementTypes.ATX_1 -> 32.sp * LocalJBMarkdownTreeState.current.fontSizeMultiplier - MarkdownElementTypes.ATX_2 -> 24.sp * LocalJBMarkdownTreeState.current.fontSizeMultiplier - MarkdownElementTypes.ATX_3 -> 20.sp * LocalJBMarkdownTreeState.current.fontSizeMultiplier - MarkdownElementTypes.ATX_4 -> 16.sp * LocalJBMarkdownTreeState.current.fontSizeMultiplier - MarkdownElementTypes.ATX_5 -> 14.sp * LocalJBMarkdownTreeState.current.fontSizeMultiplier - else -> 12.sp * LocalJBMarkdownTreeState.current.fontSizeMultiplier - }, - color = when (node.type) { - MarkdownElementTypes.ATX_1 -> Color(0xFFFF0000) - MarkdownElementTypes.ATX_2 -> Color(0xFF00FF00) - MarkdownElementTypes.ATX_3 -> Color(0xFF0000FF) - MarkdownElementTypes.ATX_4 -> Color(0xFFFF00FF) - MarkdownElementTypes.ATX_5 -> Color(0xFF00FFFF) - else -> Color(0xFFFFFF00) + fontSize = if (state.embedded) LocalTextStyle.current.fontSize + else when (node.type) { + MarkdownElementTypes.ATX_1 -> 32.sp * state.fontSizeMultiplier + MarkdownElementTypes.ATX_2 -> 24.sp * state.fontSizeMultiplier + MarkdownElementTypes.ATX_3 -> 20.sp * state.fontSizeMultiplier + MarkdownElementTypes.ATX_4 -> 16.sp * state.fontSizeMultiplier + MarkdownElementTypes.ATX_5 -> 14.sp * state.fontSizeMultiplier + else -> 12.sp * state.fontSizeMultiplier } ) ) { if (node.startOffset != 0) { - Box(Modifier.padding(top = 8.dp)) + Spacer(Modifier.height(8.dp)) } JBMText(node, modifier) + Spacer(Modifier.height(4.dp)) } } MarkdownElementTypes.ORDERED_LIST, MarkdownElementTypes.UNORDERED_LIST -> { CompositionLocalProvider( - LocalJBMarkdownTreeState provides LocalJBMarkdownTreeState.current.copy( - listDepth = LocalJBMarkdownTreeState.current.listDepth + 1 + LocalJBMarkdownTreeState provides state.copy( + listDepth = state.listDepth + 1 ) ) { JBMText(node, modifier) } } + MarkdownTokenTypes.EOL -> { + Spacer(Modifier.height(4.dp)) + } + + MarkdownElementTypes.CODE_FENCE -> { + JBMCodeBlockContent(node, modifier) + } + else -> { Text( text = buildAnnotatedString { diff --git a/app/src/main/java/chat/revolt/screens/labs/ui/sandbox/JBMSandbox.kt b/app/src/main/java/chat/revolt/screens/labs/ui/sandbox/JBMSandbox.kt index 00b4bfa0..0c7dd7b1 100644 --- a/app/src/main/java/chat/revolt/screens/labs/ui/sandbox/JBMSandbox.kt +++ b/app/src/main/java/chat/revolt/screens/labs/ui/sandbox/JBMSandbox.kt @@ -1,19 +1,27 @@ package chat.revolt.screens.labs.ui.sandbox +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Checkbox import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TextField import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp import androidx.navigation.NavController import chat.revolt.components.markdown.jbm.JBM import chat.revolt.components.markdown.jbm.JBMRenderer +import chat.revolt.components.markdown.jbm.LocalJBMarkdownTreeState import chat.revolt.settings.dsl.SettingsPage @OptIn(JBM::class) @@ -21,6 +29,7 @@ import chat.revolt.settings.dsl.SettingsPage fun JBMSandbox(navController: NavController) { var mdSource by remember { mutableStateOf("") } var submitMdSource by remember { mutableStateOf(null) } + var isEmbedded by remember { mutableStateOf(false) } SettingsPage( navController = navController, @@ -32,6 +41,20 @@ fun JBMSandbox(navController: NavController) { ) } ) { + Subcategory( + title = { Text("Options", maxLines = 1, overflow = TextOverflow.Ellipsis) }, + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Checkbox( + checked = isEmbedded, + onCheckedChange = { isEmbedded = it } + ) + Spacer(Modifier.width(8.dp)) + Text("Embedded", maxLines = 1, overflow = TextOverflow.Ellipsis) + } + } Subcategory( title = { Text("Source", maxLines = 1, overflow = TextOverflow.Ellipsis) }, ) { @@ -47,12 +70,43 @@ fun JBMSandbox(navController: NavController) { }) { Text("Submit") } + TextButton(onClick = { + submitMdSource = """# Full range of MD now supported! +1. Text with **bold**, *italics*, and ***both***! +2. You ~~can't see me~~. +3. [I'm a link to another website.]() +4. I'm a spoiler with ||**bold text inside it**|| + - I'm a sub-item on this list... + - Let's go even deeper... + +`Inline code` + +```js +let x = "I'm a multi-line code block!"; +``` + +> > ${'$'}${'$'}E = mc^2${'$'}${'$'} +> +> — Albert Einstein + +| Timestamp | Mention | Channel Link | Message Link | +|:-:|:-:|:-:|:-:| +| | <@01EX2NCWQ0CHS3QJF0FEQS1GR4> | <#01H73F4RAHTPBHKJ1XBQDXK3NQ> | https://revolt.chat/server/01F7ZSBSFHQ8TA81725KQCSDDP/channel/01F92C5ZXBQWQ8KY7J8KY917NM/01J25XZM9JXVVJDDKFPB7Q48HZ |""" + }) { + Text("Submit test document") + } } Subcategory( title = { Text("Output", maxLines = 1, overflow = TextOverflow.Ellipsis) }, ) { - submitMdSource?.let { JBMRenderer(it, Modifier) } - ?: Text("Submit some Markdown and see the output.") + CompositionLocalProvider( + LocalJBMarkdownTreeState provides LocalJBMarkdownTreeState.current.copy( + embedded = isEmbedded + ) + ) { + submitMdSource?.let { JBMRenderer(it, Modifier) } + ?: Text("Submit some Markdown and see the output.") + } } } } \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/ui/theme/Theme.kt b/app/src/main/java/chat/revolt/ui/theme/Theme.kt index c13df492..2e622f2b 100644 --- a/app/src/main/java/chat/revolt/ui/theme/Theme.kt +++ b/app/src/main/java/chat/revolt/ui/theme/Theme.kt @@ -171,4 +171,15 @@ object ClearRippleTheme : RippleTheme { hoveredAlpha = 0.0f, pressedAlpha = 0.0f, ) -} \ No newline at end of file +} + +fun isThemeDark(theme: Theme, systemIsDark: Boolean): Boolean { + return when (theme) { + Theme.Revolt, Theme.Amoled -> true + Theme.Light -> false + Theme.M3Dynamic, Theme.None -> systemIsDark + } +} + +@Composable +fun isThemeDark(theme: Theme) = isThemeDark(theme, isSystemInDarkTheme()) \ No newline at end of file