From e6649feafa0d7df43b09e5aa9159031946a7ab4f Mon Sep 17 00:00:00 2001 From: Artem Chepurnoy Date: Thu, 7 Mar 2024 18:55:40 +0200 Subject: [PATCH] improvement: Ofload Markdown parsing (Vault, Send screens) from UI thread --- .../home/vault/component/VaultViewNoteItem.kt | 15 ++++---- .../feature/home/vault/model/VaultViewItem.kt | 34 ++++++++++++++++-- .../vault/screen/VaultViewStateProducer.kt | 20 ++++++++--- .../send/view/SendViewStateProducer.kt | 12 +++++-- .../keyguard/ui/markdown/Markdown.kt | 35 +++++++++++++++---- 5 files changed, 94 insertions(+), 22 deletions(-) diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/component/VaultViewNoteItem.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/component/VaultViewNoteItem.kt index dda733a..99c40b4 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/component/VaultViewNoteItem.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/component/VaultViewNoteItem.kt @@ -78,12 +78,15 @@ fun VaultViewNoteItem( .fillMaxWidth(), ) { SelectionContainer { - if (item.markdown) { - MarkdownText( - markdown = item.text, - ) - } else { - Text(item.text) + when (item.content) { + is VaultViewItem.Note.Content.Markdown -> { + MarkdownText( + markdown = item.content.node, + ) + } + is VaultViewItem.Note.Content.Text -> { + Text(item.content.text) + } } } } diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/model/VaultViewItem.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/model/VaultViewItem.kt index f2b0740..d601842 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/model/VaultViewItem.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/model/VaultViewItem.kt @@ -16,6 +16,8 @@ import com.artemchep.keyguard.common.usecase.CopyText import com.artemchep.keyguard.feature.attachments.model.AttachmentItem import com.artemchep.keyguard.ui.ContextItem import com.artemchep.keyguard.ui.FlatItemAction +import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser +import com.halilibo.richtext.markdown.node.AstNode import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf @@ -144,13 +146,39 @@ sealed interface VaultViewItem { data class Note( override val id: String, - val text: String, - val markdown: Boolean, + val content: Content, val elevation: Dp = 0.dp, val conceal: Boolean = false, val verify: ((() -> Unit) -> Unit)? = null, ) : VaultViewItem { - companion object + companion object; + + sealed interface Content { + companion object { + fun of( + parser: CommonmarkAstNodeParser, + markdown: Boolean, + text: String, + ): Content = + if (markdown) { + kotlin.runCatching { + val data = text.trimIndent() + val node = parser.parse(data) + Markdown(node) + }.getOrNull() + } else { + null + } ?: Text(text) + } + + data class Markdown( + val node: AstNode, + ) : Content + + data class Text( + val text: String, + ) : Content + } } data class Spacer( diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/screen/VaultViewStateProducer.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/screen/VaultViewStateProducer.kt index d7338e2..1f33784 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/screen/VaultViewStateProducer.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/screen/VaultViewStateProducer.kt @@ -192,6 +192,7 @@ import com.artemchep.keyguard.ui.text.annotate import com.artemchep.keyguard.ui.theme.Dimens import com.artemchep.keyguard.ui.theme.combineAlpha import com.artemchep.keyguard.ui.totp.formatCode2 +import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow @@ -387,6 +388,7 @@ fun vaultViewScreenState( val selectionHandle = selectionHandle("selection") val markdown = getMarkdown().first() + val markdownParser = CommonmarkAstNodeParser() val accountFlow = getAccounts() .map { accounts -> @@ -715,6 +717,7 @@ fun vaultViewScreenState( primaryAction = primaryAction, items = oh( mode = mode, + markdownParser = markdownParser, sharingScope = screenScope, // FIXME: must not be a screen scope!! selectionHandle = selectionHandle, canEdit = canEdit, @@ -767,6 +770,7 @@ fun vaultViewScreenState( private fun RememberStateFlowScope.oh( mode: AppMode, + markdownParser: CommonmarkAstNodeParser, sharingScope: CoroutineScope, selectionHandle: SelectionHandle, canEdit: Boolean, @@ -1423,10 +1427,14 @@ private fun RememberStateFlowScope.oh( } if (cipher.type == DSecret.Type.SecureNote && cipher.notes.isNotEmpty()) { + val content = VaultViewItem.Note.Content.of( + parser = markdownParser, + markdown = markdown, + text = cipher.notes, + ) val note = VaultViewItem.Note( id = "note.text", - text = if (markdown) cipher.notes.trimIndent() else cipher.notes, - markdown = markdown, + content = content, verify = verify, conceal = verify != null, ) @@ -1633,10 +1641,14 @@ private fun RememberStateFlowScope.oh( text = translate(Res.strings.notes), ) emit(section) + val content = VaultViewItem.Note.Content.of( + parser = markdownParser, + markdown = markdown, + text = cipher.notes, + ) val note = VaultViewItem.Note( id = "note.text", - text = if (markdown) cipher.notes.trimIndent() else cipher.notes, - markdown = markdown, + content = content, verify = verify, conceal = verify != null, ) diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/send/view/SendViewStateProducer.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/send/view/SendViewStateProducer.kt index c8273cb..6ddbe69 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/send/view/SendViewStateProducer.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/send/view/SendViewStateProducer.kt @@ -87,6 +87,7 @@ import com.artemchep.keyguard.ui.icons.KeyguardView import com.artemchep.keyguard.ui.selection.SelectionHandle import com.artemchep.keyguard.ui.selection.selectionHandle import com.artemchep.keyguard.ui.text.annotate +import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser import io.ktor.http.Url import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.combine @@ -226,6 +227,7 @@ fun sendViewScreenState( ) val markdown = getMarkdown().first() + val markdownParser = CommonmarkAstNodeParser() val accountFlow = getAccounts() .map { accounts -> @@ -297,6 +299,7 @@ fun sendViewScreenState( null }, items = oh( + markdownParser = markdownParser, canEdit = canAddSecret, contentColor = contentColor, disabledContentColor = disabledContentColor, @@ -323,6 +326,7 @@ fun sendViewScreenState( } private fun RememberStateFlowScope.oh( + markdownParser: CommonmarkAstNodeParser, canEdit: Boolean, contentColor: Color, disabledContentColor: Color, @@ -438,10 +442,14 @@ private fun RememberStateFlowScope.oh( text = translate(Res.strings.notes), ) emit(section) + val content = VaultViewItem.Note.Content.of( + parser = markdownParser, + markdown = markdown, + text = send.notes, + ) val note = VaultViewItem.Note( id = "note.text", - text = if (markdown) send.notes.trimIndent() else send.notes, - markdown = markdown, + content = content, ) emit(note) } diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/ui/markdown/Markdown.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/ui/markdown/Markdown.kt index cda57d5..b97e96d 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/ui/markdown/Markdown.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/ui/markdown/Markdown.kt @@ -4,6 +4,8 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.halilibo.richtext.commonmark.Markdown +import com.halilibo.richtext.markdown.BasicMarkdown +import com.halilibo.richtext.markdown.node.AstNode import com.halilibo.richtext.ui.RichTextStyle import com.halilibo.richtext.ui.material3.RichText import com.halilibo.richtext.ui.resolveDefaults @@ -14,13 +16,7 @@ fun MarkdownText( modifier: Modifier = Modifier, markdown: String, ) { - val richTextStyle = RichTextStyle().resolveDefaults().copy( - stringStyle = RichTextStringStyle().copy( - linkStyle = MaterialTheme.typography.bodyLarge.toSpanStyle().copy( - color = MaterialTheme.colorScheme.primary, - ), - ), - ) + val richTextStyle = getRichTextStyle() RichText( modifier = modifier, style = richTextStyle, @@ -28,3 +24,28 @@ fun MarkdownText( Markdown(markdown) } } + +@Composable +fun MarkdownText( + modifier: Modifier = Modifier, + markdown: AstNode, +) { + val richTextStyle = getRichTextStyle() + RichText( + modifier = modifier, + style = richTextStyle, + ) { + BasicMarkdown(markdown) + } +} + +@Composable +private fun getRichTextStyle(): RichTextStyle { + return RichTextStyle().resolveDefaults().copy( + stringStyle = RichTextStringStyle().copy( + linkStyle = MaterialTheme.typography.bodyLarge.toSpanStyle().copy( + color = MaterialTheme.colorScheme.primary, + ), + ), + ) +}