diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 5e3c93e2..49a14191 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -13,7 +13,6 @@ diff --git a/.idea/misc.xml b/.idea/misc.xml index b36f4a6d..2161c1f5 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -13,7 +13,7 @@ - + diff --git a/app/build.gradle b/app/build.gradle index 03a6bfd0..2dff1ff7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -163,7 +163,8 @@ dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.2' // Markdown - implementation project(':markdown') + implementation "com.github.discord:SimpleAST:2.7.0" + implementation "androidx.appcompat:appcompat:1.7.0-alpha02" } kapt { 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 ac09b65c..ece442a6 100644 --- a/app/src/main/java/chat/revolt/components/chat/Message.kt +++ b/app/src/main/java/chat/revolt/components/chat/Message.kt @@ -1,6 +1,8 @@ package chat.revolt.components.chat import android.net.Uri +import android.text.SpannableStringBuilder +import android.text.TextUtils import android.widget.Toast import androidx.browser.customtabs.CustomTabsIntent import androidx.compose.foundation.* @@ -9,11 +11,15 @@ import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.content.res.ResourcesCompat +import chat.revolt.R import chat.revolt.api.REVOLT_FILES import chat.revolt.api.RevoltAPI import chat.revolt.api.asJanuaryProxyUrl @@ -22,7 +28,6 @@ import chat.revolt.api.internals.WebCompat import chat.revolt.api.schemas.AutumnResource import chat.revolt.components.generic.UserAvatar import chat.revolt.components.generic.UserAvatarWidthPlaceholder -import chat.revolt.markdown.Markdown import chat.revolt.api.schemas.Message as MessageSchema fun viewAttachmentInBrowser(ctx: android.content.Context, attachment: AutumnResource) { @@ -49,10 +54,12 @@ fun formatLongAsTime(time: Long): String { fun Message( message: MessageSchema, truncate: Boolean = false, + parse: (MessageSchema) -> SpannableStringBuilder = { SpannableStringBuilder(it.content) }, onMessageContextMenu: () -> Unit = {}, ) { val author = RevoltAPI.userCache[message.author] ?: return CircularProgressIndicator() val context = LocalContext.current + val contentColor = LocalContentColor.current Column { if (message.tail == false) { @@ -134,11 +141,17 @@ fun Message( message.content?.let { if (message.content.isBlank()) return@let // if only an attachment is sent - Text( - text = Markdown.annotate(it), - maxLines = if (truncate) 1 else Int.MAX_VALUE, - overflow = TextOverflow.Ellipsis - ) + AndroidView(factory = { ctx -> + androidx.appcompat.widget.AppCompatTextView(ctx).apply { + text = parse(message) + maxLines = if (truncate) 1 else Int.MAX_VALUE + ellipsize = TextUtils.TruncateAt.END + textSize = 16f + typeface = ResourcesCompat.getFont(ctx, R.font.inter) + + setTextColor(contentColor.toArgb()) + } + }) } message.attachments?.let { diff --git a/app/src/main/java/chat/revolt/components/generic/Markdown.kt b/app/src/main/java/chat/revolt/components/generic/Markdown.kt new file mode 100644 index 00000000..b348c236 --- /dev/null +++ b/app/src/main/java/chat/revolt/components/generic/Markdown.kt @@ -0,0 +1,118 @@ +package chat.revolt.components.generic + +import android.text.SpannableStringBuilder +import android.text.TextUtils +import android.util.Log +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.content.res.ResourcesCompat +import chat.revolt.R +import chat.revolt.api.RevoltAPI +import chat.revolt.internals.markdown.ChannelMentionRule +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.UserMentionRule +import chat.revolt.internals.markdown.createCodeRule +import chat.revolt.internals.markdown.createInlineCodeRule +import com.discord.simpleast.core.simple.SimpleMarkdownRules +import com.discord.simpleast.core.simple.SimpleRenderer + +/** + * A Markdown rendering component for Markdown embedded in UI (e.g. in a button). + * @param text The text to render. + * @param fontSize The font size to use. + * @param modifier The modifier to apply to the rendered text. Will be applied to AndroidView and thus subject to AndroidView's limitations. + * @param maxLines The maximum number of lines to display. Text will always be ellipsized on overflow. Defaults to [Int.MAX_VALUE]. + */ +@Composable +fun UIMarkdown( + text: String, + fontSize: TextUnit, + modifier: Modifier = Modifier, + maxLines: Int = Int.MAX_VALUE, +) { + val context = LocalContext.current + val foregroundColor = LocalContentColor.current + val codeBlockColor = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp) + val spannableStringBuilder = remember { mutableStateOf(SpannableStringBuilder()) } + + LaunchedEffect(text) { + val parser = MarkdownParser() + .addRules( + SimpleMarkdownRules.createEscapeRule(), + UserMentionRule(), + ChannelMentionRule(), + CustomEmoteRule(), + ) + .addRules( + createCodeRule(context, codeBlockColor.toArgb()), + createInlineCodeRule(context, codeBlockColor.toArgb()), + ) + .addRules( + SimpleMarkdownRules.createSimpleMarkdownRules( + includeEscapeRule = false + ) + ) + + spannableStringBuilder.value = SimpleRenderer.render( + source = text, + parser = parser, + initialState = MarkdownState(0), + renderContext = MarkdownContext( + memberMap = mapOf(), + userMap = RevoltAPI.userCache.toMap(), + channelMap = RevoltAPI.channelCache.mapValues { ch -> + ch.value.name ?: ch.value.id!! + }, + emojiMap = RevoltAPI.emojiCache, + serverId = null + ) + ) + + Log.d("Markdown", "Rendered: ${spannableStringBuilder.value}") + } + + AndroidView( + factory = { + androidx.appcompat.widget.AppCompatTextView(it).apply { + ellipsize = TextUtils.TruncateAt.END + typeface = ResourcesCompat.getFont(it, R.font.inter) + + setTextColor(foregroundColor.toArgb()) + setMaxLines(maxLines) + setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, fontSize.value) + + setText(spannableStringBuilder.value) + } + }, + modifier = modifier, + update = { + it.text = spannableStringBuilder.value + }, + ) +} + +@Preview +@Composable +fun UIMarkdownPreview() { + // Will not render in side preview but will render on device + UIMarkdown( + text = "Hello, **world**!", + fontSize = 16.sp, + ) +} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/internals/markdown/BlockBackgroundNode.kt b/app/src/main/java/chat/revolt/internals/markdown/BlockBackgroundNode.kt new file mode 100644 index 00000000..4942e6d1 --- /dev/null +++ b/app/src/main/java/chat/revolt/internals/markdown/BlockBackgroundNode.kt @@ -0,0 +1,129 @@ +package chat.revolt.internals.markdown + +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.RectF +import android.text.SpannableStringBuilder +import android.text.Spanned +import android.text.style.LeadingMarginSpan +import android.text.style.LineBackgroundSpan +import androidx.annotation.ColorInt +import com.discord.simpleast.core.node.Node + +// Attribution: +// https://github.com/discord/SimpleAST/blob/567b61c51056cbdec39e839100690c576c26a4c6/app/src/main/java/com/discord/simpleast/sample/spans/BlockBackgroundNode.kt +// LICENSED UNDER THE APACHE LICENSE, VERSION 2.0 +// Adapted for Revolt. + +/** + * Creates a block background for code sections. + */ +class BlockBackgroundNode( + private val quoteDepth: Int, + private val fillColor: Int = Color.DKGRAY, + private val strokeColor: Int = Color.BLACK, + vararg children: Node, +) : Node.Parent(*children) { + + override fun render(builder: SpannableStringBuilder, renderContext: R) { + // Ensure the block we want to append starts on a newline. + ensureEndsWithNewline(builder) + + val codeStartIndex = builder.length + super.render(builder, renderContext) + // BlockBackgroundSpan requires this to function + ensureEndsWithNewline(builder) + + val backgroundSpan = BlockBackgroundSpan( + fillColor, strokeColor, + strokeWidth = 2, + strokeRadius = 15, + leftMargin = 40 * quoteDepth + ) + builder.setSpan( + backgroundSpan, + codeStartIndex, + builder.length, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + + // Apply a leading margin to all lines in the block. + val leadingMarginSpan = LeadingMarginSpan.Standard(15) + builder.setSpan( + leadingMarginSpan, + codeStartIndex, + builder.length, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + + private fun ensureEndsWithNewline(builder: SpannableStringBuilder) { + if (builder.isNotEmpty()) { + val lastChar = CharArray(6) + builder.getChars(builder.length - 1, builder.length, lastChar, 0) + if (lastChar[0] != '\n') { + builder.append('\n') + } + } + } +} + +/** + * Computes the position of the paragraph on the screen and draws the desired background. + */ +class BlockBackgroundSpan( + @ColorInt fillColor: Int, + @ColorInt strokeColor: Int, + strokeWidth: Int, + strokeRadius: Int, + val leftMargin: Int +) : LineBackgroundSpan { + + private val fillPaint = Paint().apply { + this.style = Paint.Style.FILL + this.color = fillColor + } + + private val strokePaint = Paint().apply { + this.style = Paint.Style.STROKE + this.color = strokeColor + this.strokeWidth = strokeWidth.toFloat() + this.isAntiAlias = true + } + + private val rect = RectF() + private val radius = strokeRadius.toFloat() + + fun draw(canvas: Canvas) { + canvas.drawRoundRect(rect, radius, radius, fillPaint) + canvas.drawRoundRect(rect, radius, radius, strokePaint) + } + + override fun drawBackground( + canvas: Canvas, + paint: Paint, + left: Int, + right: Int, + top: Int, + baseline: Int, + bottom: Int, + text: CharSequence, + start: Int, + end: Int, + lnum: Int + ) { + if (text !is Spanned) return + + if (text.getSpanStart(this) == start) { + rect.left = left.toFloat() + leftMargin + rect.top = top.toFloat() + } + + if (text.getSpanEnd(this) == end) { + rect.right = right.toFloat() + rect.bottom = bottom.toFloat() + draw(canvas) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/internals/markdown/MarkdownContext.kt b/app/src/main/java/chat/revolt/internals/markdown/MarkdownContext.kt index 16e2a0a1..362d9f98 100644 --- a/app/src/main/java/chat/revolt/internals/markdown/MarkdownContext.kt +++ b/app/src/main/java/chat/revolt/internals/markdown/MarkdownContext.kt @@ -1,11 +1,20 @@ package chat.revolt.internals.markdown -import androidx.compose.runtime.snapshots.SnapshotStateMap +import chat.revolt.api.schemas.Emoji import chat.revolt.api.schemas.User +import com.discord.simpleast.core.node.Node +import com.discord.simpleast.core.parser.Parser + +typealias MarkdownParser = Parser, MarkdownState> + +data class MarkdownState(val currentQuoteDepth: Int) { + fun newQuoteDepth(depth: Int): MarkdownState = MarkdownState(depth) +} data class MarkdownContext( - val memberMap: SnapshotStateMap, - val userMap: SnapshotStateMap, - val channelMap: SnapshotStateMap, - val serverId: String, + val memberMap: Map, + val userMap: Map, + val channelMap: Map, + val emojiMap: Map, + val serverId: String? ) \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/internals/markdown/MarkdownNodes.kt b/app/src/main/java/chat/revolt/internals/markdown/MarkdownNodes.kt new file mode 100644 index 00000000..010ba495 --- /dev/null +++ b/app/src/main/java/chat/revolt/internals/markdown/MarkdownNodes.kt @@ -0,0 +1,32 @@ +package chat.revolt.internals.markdown + +import android.text.SpannableStringBuilder +import com.discord.simpleast.core.node.Node + +class UserMentionNode(private val userId: String) : Node() { + override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) { + builder.append( + renderContext.memberMap[userId]?.let { "@$it" } + ?: renderContext.userMap[userId]?.let { "@${it.username}" } + ?: "<@${userId}>" + ) + } +} + +class ChannelMentionNode(private val channelId: String) : Node() { + override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) { + builder.append( + renderContext.channelMap[channelId]?.let { "#$it" } + ?: "<#${channelId}>" + ) + } +} + +class CustomEmoteNode(private val emoteId: String) : Node() { + override fun render(builder: SpannableStringBuilder, renderContext: MarkdownContext) { + builder.append( + renderContext.emojiMap[emoteId]?.let { ":${it.name}:" } + ?: ":${emoteId}:" + ) + } +} diff --git a/app/src/main/java/chat/revolt/internals/markdown/MarkdownRules.kt b/app/src/main/java/chat/revolt/internals/markdown/MarkdownRules.kt new file mode 100644 index 00000000..8d6d4266 --- /dev/null +++ b/app/src/main/java/chat/revolt/internals/markdown/MarkdownRules.kt @@ -0,0 +1,138 @@ +package chat.revolt.internals.markdown + +import android.content.Context +import android.text.style.BackgroundColorSpan +import android.text.style.TextAppearanceSpan +import chat.revolt.R +import com.discord.simpleast.code.CodeRules +import com.discord.simpleast.code.CodeStyleProviders +import com.discord.simpleast.core.node.Node +import com.discord.simpleast.core.node.StyleNode +import com.discord.simpleast.core.parser.ParseSpec +import com.discord.simpleast.core.parser.Parser +import com.discord.simpleast.core.parser.Rule +import java.util.regex.Matcher +import java.util.regex.Pattern + +class UserMentionRule : + Rule(Pattern.compile("^<@([0-9A-Z]{26})>")) { + override fun parse( + matcher: Matcher, + parser: Parser, + state: S + ): ParseSpec { + return ParseSpec.createTerminal(UserMentionNode(matcher.group(1)!!), state) + } +} + +class ChannelMentionRule : + Rule(Pattern.compile("^<#([0-9A-Z]{26})>")) { + override fun parse( + matcher: Matcher, + parser: Parser, + state: S + ): ParseSpec { + return ParseSpec.createTerminal(ChannelMentionNode(matcher.group(1)!!), state) + } +} + +class CustomEmoteRule : + Rule(Pattern.compile("^:([0-9A-Z]{26}):")) { + override fun parse( + matcher: Matcher, + parser: Parser, + state: S + ): ParseSpec { + return ParseSpec.createTerminal(CustomEmoteNode(matcher.group(1)!!), state) + } +} + +fun createInlineCodeRule(context: Context, backgroundColor: Int): Rule, S> { + return CodeRules.createInlineCodeRule( + { listOf(TextAppearanceSpan(context, R.style.Code_TextAppearance)) }, + { listOf(BackgroundColorSpan(backgroundColor)) } + ) +} + +fun createCodeRule( + context: Context, + backgroundColor: Int +): Rule, S> { + val codeStyleProviders = CodeStyleProviders( + defaultStyleProvider = { listOf(TextAppearanceSpan(context, R.style.Code_TextAppearance)) }, + commentStyleProvider = { + listOf( + TextAppearanceSpan( + context, + R.style.Code_TextAppearance_Comment + ) + ) + }, + literalStyleProvider = { + listOf( + TextAppearanceSpan( + context, + R.style.Code_TextAppearance_Literal + ) + ) + }, + keywordStyleProvider = { + listOf( + TextAppearanceSpan( + context, + R.style.Code_TextAppearance_Keyword + ) + ) + }, + identifierStyleProvider = { + listOf( + TextAppearanceSpan( + context, + R.style.Code_TextAppearance_Identifier + ) + ) + }, + typesStyleProvider = { + listOf( + TextAppearanceSpan( + context, + R.style.Code_TextAppearance_Types + ) + ) + }, + genericsStyleProvider = { + listOf( + TextAppearanceSpan( + context, + R.style.Code_TextAppearance_Generics + ) + ) + }, + paramsStyleProvider = { + listOf( + TextAppearanceSpan( + context, + R.style.Code_TextAppearance_Params + ) + ) + }, + ) + val languageMap = CodeRules.createCodeLanguageMap(codeStyleProviders) + + return CodeRules.createCodeRule( + codeStyleProviders.defaultStyleProvider, + languageMap + ) { codeNode, block, state -> + if (!block) { + StyleNode(listOf(BackgroundColorSpan(backgroundColor))) + .apply { addChild(codeNode) } + } else { + BlockBackgroundNode( + state.currentQuoteDepth, + backgroundColor, + backgroundColor, + codeNode + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/screens/chat/dialogs/safety/ReportMessageDialog.kt b/app/src/main/java/chat/revolt/screens/chat/dialogs/safety/ReportMessageDialog.kt index ba31df98..83bef37e 100644 --- a/app/src/main/java/chat/revolt/screens/chat/dialogs/safety/ReportMessageDialog.kt +++ b/app/src/main/java/chat/revolt/screens/chat/dialogs/safety/ReportMessageDialog.kt @@ -28,7 +28,6 @@ import chat.revolt.api.routes.user.blockUser import chat.revolt.api.schemas.ContentReportReason import chat.revolt.components.chat.Message import chat.revolt.components.generic.FormTextField -import chat.revolt.markdown.Markdown import kotlinx.coroutines.launch enum class ReportingState { @@ -112,7 +111,7 @@ fun ReportMessageDialog( if (messageIsBridged) { Spacer(modifier = Modifier.height(8.dp)) Text( - text = Markdown.annotate(stringResource(id = R.string.report_message_bridge_notice)), + text = stringResource(id = R.string.report_message_bridge_notice), fontSize = 12.sp ) } diff --git a/app/src/main/java/chat/revolt/screens/chat/views/ChannelScreen.kt b/app/src/main/java/chat/revolt/screens/chat/views/ChannelScreen.kt index f4906d59..a266b3ac 100644 --- a/app/src/main/java/chat/revolt/screens/chat/views/ChannelScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/views/ChannelScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -55,6 +56,16 @@ import chat.revolt.components.screens.chat.AttachmentManager import chat.revolt.components.screens.chat.ChannelIcon import chat.revolt.components.screens.chat.ReplyManager import chat.revolt.components.screens.chat.TypingIndicator +import chat.revolt.internals.markdown.ChannelMentionRule +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.UserMentionRule +import chat.revolt.internals.markdown.createCodeRule +import chat.revolt.internals.markdown.createInlineCodeRule +import com.discord.simpleast.core.simple.SimpleMarkdownRules +import com.discord.simpleast.core.simple.SimpleRenderer import io.ktor.http.* import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -401,6 +412,8 @@ fun ChannelScreen( val lazyListState = rememberLazyListState() val coroutineScope = rememberCoroutineScope() + val codeBlockColor = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp) + val pickFileLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.OpenMultipleDocuments() ) { uriList -> @@ -520,7 +533,39 @@ fun ChannelScreen( items = viewModel.renderableMessages, key = { it.id!! } ) { message -> - Message(message) { + Message(message, parse = { + val parser = MarkdownParser() + .addRules( + SimpleMarkdownRules.createEscapeRule(), + UserMentionRule(), + ChannelMentionRule(), + CustomEmoteRule(), + ) + .addRules( + createCodeRule(context, codeBlockColor.toArgb()), + createInlineCodeRule(context, codeBlockColor.toArgb()), + ) + .addRules( + SimpleMarkdownRules.createSimpleMarkdownRules( + includeEscapeRule = false + ) + ) + + SimpleRenderer.render( + source = it.content ?: "", + parser = parser, + initialState = MarkdownState(0), + renderContext = MarkdownContext( + memberMap = mapOf(), + userMap = RevoltAPI.userCache.toMap(), + channelMap = RevoltAPI.channelCache.mapValues { ch -> + ch.value.name ?: ch.value.id!! + }, + emojiMap = RevoltAPI.emojiCache, + serverId = channel.server ?: "", + ) + ) + }) { navController.navigate("message/${message.id}/menu") } } diff --git a/app/src/main/res/font/inter.xml b/app/src/main/res/font/inter.xml new file mode 100644 index 00000000..888c31f4 --- /dev/null +++ b/app/src/main/res/font/inter.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/font/jetbrainsmono_regular.ttf b/app/src/main/res/font/jetbrainsmono_regular.ttf new file mode 100644 index 00000000..7d26f5a5 Binary files /dev/null and b/app/src/main/res/font/jetbrainsmono_regular.ttf differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 059450d3..a16f502f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -155,7 +155,7 @@ Thank you for taking the time to report this message. Please provide a reason for reporting this message. Selected message: - **Note:** This message may have been sent from another platform. It is recommended to also report the message on the platform it was sent from. + Note: This message may have been sent from another platform. It is recommended to also report the message on the platform it was sent from. Thank you for taking the time to report this server. Please provide a reason for reporting this server. Selected server: Thank you for taking the time to report this user. Please provide a reason for reporting this user. diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..dc10fba1 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 940f944c..135e8333 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,5 +1,5 @@ -