mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-05 19:08:23 +00:00
Remove richtext dependency and replace with lightweight MarkdownText composable
The compose-richtext library (com.github.vitorpamplona.compose-richtext) is replaced with a self-contained MarkdownText composable that renders the subset of Markdown used across the app: ## headings, **bold**, [link](url), bullet lists (- item), and numbered lists (1. item). Affected screens: ConnectOrbotDialog, AccountBackupScreen, ExportAllAccountsScreen, LoginScreen (two Orbot instruction blocks). RichTextDefaults is removed from Theme. https://claude.ai/code/session_01UCZWyaGovLVMnSET3hCuKK
This commit is contained in:
@@ -216,11 +216,6 @@ dependencies {
|
||||
implementation libs.core
|
||||
implementation libs.zxing.android.embedded
|
||||
|
||||
// Markdown (With fix for full-image bleeds)
|
||||
implementation(libs.richtext.ui)
|
||||
implementation(libs.richtext.ui.material3)
|
||||
implementation(libs.richtext.commonmark)
|
||||
|
||||
// Biometrics
|
||||
implementation libs.biometric.ktx
|
||||
|
||||
|
||||
@@ -112,14 +112,10 @@ import com.greenart7c3.nostrsigner.service.AccountExportService
|
||||
import com.greenart7c3.nostrsigner.ui.components.AmberButton
|
||||
import com.greenart7c3.nostrsigner.ui.components.AmberElevatedButton
|
||||
import com.greenart7c3.nostrsigner.ui.components.IconRow
|
||||
import com.greenart7c3.nostrsigner.ui.components.MarkdownText
|
||||
import com.greenart7c3.nostrsigner.ui.components.MnemonicLoginInput
|
||||
import com.greenart7c3.nostrsigner.ui.components.TitleExplainer
|
||||
import com.greenart7c3.nostrsigner.ui.navigation.Route
|
||||
import com.greenart7c3.nostrsigner.ui.theme.RichTextDefaults
|
||||
import com.halilibo.richtext.commonmark.CommonMarkdownParseOptions
|
||||
import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser
|
||||
import com.halilibo.richtext.markdown.BasicMarkdown
|
||||
import com.halilibo.richtext.ui.material3.RichText
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip06KeyDerivation.Bip39Mnemonics
|
||||
import com.vitorpamplona.quartz.nip06KeyDerivation.Nip06
|
||||
@@ -717,31 +713,11 @@ fun SignUpPage(
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
val myMarkDownStyle =
|
||||
RichTextDefaults.copy(
|
||||
stringStyle = RichTextDefaults.stringStyle?.copy(
|
||||
linkStyle = TextLinkStyles(
|
||||
SpanStyle(
|
||||
textDecoration = TextDecoration.Underline,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
val content1 = stringResource(R.string.connect_through_your_orbot_setup_markdown2)
|
||||
|
||||
val astNode1 =
|
||||
remember {
|
||||
CommonmarkAstNodeParser(CommonMarkdownParseOptions.MarkdownWithLinks).parse(content1)
|
||||
}
|
||||
|
||||
RichText(
|
||||
MarkdownText(
|
||||
markdown = content1,
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
style = myMarkDownStyle,
|
||||
renderer = null,
|
||||
) {
|
||||
BasicMarkdown(astNode1)
|
||||
}
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = proxyPort,
|
||||
@@ -1376,31 +1352,11 @@ fun LoginPage(
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
val myMarkDownStyle =
|
||||
RichTextDefaults.copy(
|
||||
stringStyle = RichTextDefaults.stringStyle?.copy(
|
||||
linkStyle = TextLinkStyles(
|
||||
SpanStyle(
|
||||
textDecoration = TextDecoration.Underline,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
val content1 = stringResource(R.string.connect_through_your_orbot_setup_markdown2)
|
||||
|
||||
val astNode1 =
|
||||
remember {
|
||||
CommonmarkAstNodeParser(CommonMarkdownParseOptions.MarkdownWithLinks).parse(content1)
|
||||
}
|
||||
|
||||
RichText(
|
||||
MarkdownText(
|
||||
markdown = content1,
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
style = myMarkDownStyle,
|
||||
renderer = null,
|
||||
) {
|
||||
BasicMarkdown(astNode1)
|
||||
}
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = proxyPort,
|
||||
|
||||
@@ -104,16 +104,11 @@ import com.greenart7c3.nostrsigner.ui.InnerQrCodeDrawer
|
||||
import com.greenart7c3.nostrsigner.ui.QrCodeDrawer
|
||||
import com.greenart7c3.nostrsigner.ui.components.CloseButton
|
||||
import com.greenart7c3.nostrsigner.ui.components.IconRow
|
||||
import com.greenart7c3.nostrsigner.ui.components.MarkdownText
|
||||
import com.greenart7c3.nostrsigner.ui.components.SeedWordsPage
|
||||
import com.greenart7c3.nostrsigner.ui.navigation.Route
|
||||
import com.greenart7c3.nostrsigner.ui.theme.Size35dp
|
||||
import com.greenart7c3.nostrsigner.ui.theme.fromHex
|
||||
import com.halilibo.richtext.commonmark.CommonMarkdownParseOptions
|
||||
import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser
|
||||
import com.halilibo.richtext.markdown.BasicMarkdown
|
||||
import com.halilibo.richtext.ui.RichTextStyle
|
||||
import com.halilibo.richtext.ui.material3.RichText
|
||||
import com.halilibo.richtext.ui.resolveDefaults
|
||||
import com.vitorpamplona.quartz.nip06KeyDerivation.Nip06
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -257,15 +252,10 @@ private fun TipsCard(expanded: Boolean, onToggle: () -> Unit) {
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))
|
||||
|
||||
val content = stringResource(R.string.account_backup_tips_md)
|
||||
val astNode = remember {
|
||||
CommonmarkAstNodeParser(CommonMarkdownParseOptions.MarkdownWithLinks).parse(content)
|
||||
}
|
||||
RichText(
|
||||
style = RichTextStyle().resolveDefaults(),
|
||||
MarkdownText(
|
||||
markdown = content,
|
||||
modifier = Modifier.padding(bottom = 4.dp),
|
||||
) {
|
||||
BasicMarkdown(astNode)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -300,15 +290,10 @@ private fun PasswordCard(
|
||||
}
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
val tipsContent = stringResource(R.string.account_backup_tips3_md)
|
||||
val tipsAstNode = remember {
|
||||
CommonmarkAstNodeParser(CommonMarkdownParseOptions.MarkdownWithLinks).parse(tipsContent)
|
||||
}
|
||||
RichText(
|
||||
style = RichTextStyle().resolveDefaults(),
|
||||
MarkdownText(
|
||||
markdown = tipsContent,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
BasicMarkdown(tipsAstNode)
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
package com.greenart7c3.nostrsigner.ui.actions
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
@@ -38,22 +37,15 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextLinkStyles
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.models.TorMode
|
||||
import com.greenart7c3.nostrsigner.ui.components.AmberButton
|
||||
import com.greenart7c3.nostrsigner.ui.theme.RichTextDefaults
|
||||
import com.halilibo.richtext.commonmark.CommonMarkdownParseOptions
|
||||
import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser
|
||||
import com.halilibo.richtext.markdown.BasicMarkdown
|
||||
import com.halilibo.richtext.ui.material3.RichText
|
||||
import com.greenart7c3.nostrsigner.ui.components.MarkdownText
|
||||
import kotlinx.coroutines.CancellationException
|
||||
|
||||
@Composable
|
||||
@@ -108,33 +100,8 @@ fun ConnectOrbotScreen(
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
val myMarkDownStyle =
|
||||
RichTextDefaults.copy(
|
||||
stringStyle = RichTextDefaults.stringStyle?.copy(
|
||||
linkStyle = TextLinkStyles(
|
||||
SpanStyle(
|
||||
textDecoration = TextDecoration.Underline,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
Row {
|
||||
val content1 = stringResource(R.string.connect_through_your_orbot_setup_markdown)
|
||||
|
||||
val astNode1 =
|
||||
remember {
|
||||
CommonmarkAstNodeParser(CommonMarkdownParseOptions.MarkdownWithLinks).parse(content1)
|
||||
}
|
||||
|
||||
RichText(
|
||||
style = myMarkDownStyle,
|
||||
renderer = null,
|
||||
) {
|
||||
BasicMarkdown(astNode1)
|
||||
}
|
||||
}
|
||||
val content1 = stringResource(R.string.connect_through_your_orbot_setup_markdown)
|
||||
MarkdownText(content1)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
|
||||
+4
-15
@@ -55,12 +55,7 @@ import com.greenart7c3.nostrsigner.service.AccountExportService
|
||||
import com.greenart7c3.nostrsigner.service.Biometrics.authenticate
|
||||
import com.greenart7c3.nostrsigner.ui.CenterCircularProgressIndicator
|
||||
import com.greenart7c3.nostrsigner.ui.components.AmberButton
|
||||
import com.halilibo.richtext.commonmark.CommonMarkdownParseOptions
|
||||
import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser
|
||||
import com.halilibo.richtext.markdown.BasicMarkdown
|
||||
import com.halilibo.richtext.ui.RichTextStyle
|
||||
import com.halilibo.richtext.ui.material3.RichText
|
||||
import com.halilibo.richtext.ui.resolveDefaults
|
||||
import com.greenart7c3.nostrsigner.ui.components.MarkdownText
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
@@ -188,16 +183,10 @@ fun ExportAllAccountsScreen(
|
||||
} else {
|
||||
// Security warning
|
||||
val warningContent = stringResource(R.string.export_all_accounts_warning)
|
||||
val astNode = remember {
|
||||
CommonmarkAstNodeParser(CommonMarkdownParseOptions.MarkdownWithLinks).parse(warningContent)
|
||||
}
|
||||
|
||||
RichText(
|
||||
style = RichTextStyle().resolveDefaults(),
|
||||
MarkdownText(
|
||||
markdown = warningContent,
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
) {
|
||||
BasicMarkdown(astNode)
|
||||
}
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.accounts_to_export, accountCount),
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.greenart7c3.nostrsigner.ui.components
|
||||
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.LinkAnnotation
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextLinkStyles
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.withLink
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
|
||||
/**
|
||||
* Renders a subset of Markdown as styled Compose text.
|
||||
*
|
||||
* Supported syntax:
|
||||
* ## Heading
|
||||
* **bold**
|
||||
* [link text](url)
|
||||
* - bullet item
|
||||
* 1. numbered item
|
||||
*/
|
||||
@Composable
|
||||
fun MarkdownText(
|
||||
markdown: String,
|
||||
modifier: Modifier = Modifier,
|
||||
style: TextStyle = LocalTextStyle.current,
|
||||
linkColor: Color = MaterialTheme.colorScheme.primary,
|
||||
) {
|
||||
val headingFontSize: TextUnit = MaterialTheme.typography.titleMedium.fontSize
|
||||
|
||||
val annotatedString = remember(markdown, linkColor, headingFontSize) {
|
||||
buildAnnotatedString {
|
||||
val lines = markdown.trim().lines()
|
||||
for ((index, line) in lines.withIndex()) {
|
||||
if (index > 0) append("\n")
|
||||
val trimmed = line.trim()
|
||||
when {
|
||||
trimmed.startsWith("## ") -> {
|
||||
withStyle(SpanStyle(fontWeight = FontWeight.Bold, fontSize = headingFontSize)) {
|
||||
appendMarkdownInline(trimmed.removePrefix("## "), linkColor)
|
||||
}
|
||||
}
|
||||
trimmed.startsWith("### ") -> {
|
||||
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
appendMarkdownInline(trimmed.removePrefix("### "), linkColor)
|
||||
}
|
||||
}
|
||||
trimmed.startsWith("- ") -> {
|
||||
append("\u2022 ")
|
||||
appendMarkdownInline(trimmed.removePrefix("- "), linkColor)
|
||||
}
|
||||
trimmed.matches(Regex("^\\d+\\. .+")) -> {
|
||||
val dotIdx = trimmed.indexOf(". ")
|
||||
append(trimmed.substring(0, dotIdx + 2))
|
||||
appendMarkdownInline(trimmed.substring(dotIdx + 2), linkColor)
|
||||
}
|
||||
else -> {
|
||||
appendMarkdownInline(trimmed, linkColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = annotatedString,
|
||||
modifier = modifier,
|
||||
style = style,
|
||||
)
|
||||
}
|
||||
|
||||
private fun AnnotatedString.Builder.appendMarkdownInline(
|
||||
text: String,
|
||||
linkColor: Color,
|
||||
) {
|
||||
var remaining = text
|
||||
while (remaining.isNotEmpty()) {
|
||||
when {
|
||||
remaining.startsWith("**") -> {
|
||||
val endIdx = remaining.indexOf("**", 2)
|
||||
if (endIdx != -1) {
|
||||
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append(remaining.substring(2, endIdx))
|
||||
}
|
||||
remaining = remaining.substring(endIdx + 2)
|
||||
} else {
|
||||
append("**")
|
||||
remaining = remaining.substring(2)
|
||||
}
|
||||
}
|
||||
remaining.startsWith("[") -> {
|
||||
val textEnd = remaining.indexOf("](")
|
||||
if (textEnd > 0) {
|
||||
val urlEnd = remaining.indexOf(")", textEnd + 2)
|
||||
if (urlEnd != -1) {
|
||||
val linkText = remaining.substring(1, textEnd)
|
||||
val url = remaining.substring(textEnd + 2, urlEnd)
|
||||
withLink(
|
||||
LinkAnnotation.Url(
|
||||
url,
|
||||
TextLinkStyles(
|
||||
SpanStyle(
|
||||
color = linkColor,
|
||||
textDecoration = TextDecoration.Underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
) {
|
||||
append(linkText)
|
||||
}
|
||||
remaining = remaining.substring(urlEnd + 1)
|
||||
} else {
|
||||
append(remaining[0])
|
||||
remaining = remaining.substring(1)
|
||||
}
|
||||
} else {
|
||||
append(remaining[0])
|
||||
remaining = remaining.substring(1)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val boldIdx = remaining.indexOf("**")
|
||||
val linkIdx = remaining.indexOf("[")
|
||||
val nextSpecial = when {
|
||||
boldIdx == -1 && linkIdx == -1 -> remaining.length
|
||||
boldIdx == -1 -> linkIdx
|
||||
linkIdx == -1 -> boldIdx
|
||||
else -> minOf(boldIdx, linkIdx)
|
||||
}
|
||||
val count = if (nextSpecial == 0) 1 else nextSpecial
|
||||
append(remaining.substring(0, count))
|
||||
remaining = remaining.substring(count)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,6 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.toColorInt
|
||||
import androidx.core.view.WindowCompat
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.halilibo.richtext.ui.RichTextStyle
|
||||
import com.halilibo.richtext.ui.resolveDefaults
|
||||
|
||||
val Shapes =
|
||||
Shapes(
|
||||
@@ -39,7 +37,6 @@ val primaryColor = Color(0xFFFFCA62)
|
||||
val primaryVariant = Color(0xFFC8541A)
|
||||
val secondaryColor = Color(0xFFFFCA62)
|
||||
val orange = Color(0xFFFF6B00)
|
||||
val RichTextDefaults = RichTextStyle().resolveDefaults()
|
||||
|
||||
private val DarkColorPalette =
|
||||
darkColorScheme(
|
||||
|
||||
@@ -13,7 +13,6 @@ material3 = "1.4.0"
|
||||
nav_version = "2.9.7"
|
||||
quartz = "1.08.0"
|
||||
compose_ui = "1.10.6"
|
||||
richtextUi = "a02a03a3e9"
|
||||
roomKtx = "2.8.4"
|
||||
securityCryptoKtx = "1.1.0"
|
||||
zxingAndroidEmbedded = "4.3.0"
|
||||
@@ -54,9 +53,6 @@ material3 = { module = "androidx.compose.material3:material3", version.ref = "ma
|
||||
navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "nav_version" }
|
||||
quartz = { module = "com.vitorpamplona.quartz:quartz-android", version.ref = "quartz" }
|
||||
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "collections" }
|
||||
richtext-commonmark = { module = "com.github.vitorpamplona.compose-richtext:richtext-commonmark", version.ref = "richtextUi" }
|
||||
richtext-ui = { module = "com.github.vitorpamplona.compose-richtext:richtext-ui", version.ref = "richtextUi" }
|
||||
richtext-ui-material3 = { module = "com.github.vitorpamplona.compose-richtext:richtext-ui-material3", version.ref = "richtextUi" }
|
||||
room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomKtx" }
|
||||
room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomKtx" }
|
||||
room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomKtx" }
|
||||
|
||||
Reference in New Issue
Block a user