Reverses claude new rendering scheme because we need to break into new paragraphs before coming to the rendering stage.
This commit is contained in:
@@ -21,12 +21,10 @@
|
|||||||
package com.vitorpamplona.amethyst.model
|
package com.vitorpamplona.amethyst.model
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -48,32 +46,28 @@ import com.vitorpamplona.amethyst.commons.hashtags.Tunestr
|
|||||||
import com.vitorpamplona.amethyst.commons.hashtags.Weed
|
import com.vitorpamplona.amethyst.commons.hashtags.Weed
|
||||||
import com.vitorpamplona.amethyst.commons.hashtags.Zap
|
import com.vitorpamplona.amethyst.commons.hashtags.Zap
|
||||||
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||||
|
import com.vitorpamplona.amethyst.commons.richtext.HashTagSegment
|
||||||
|
import com.vitorpamplona.amethyst.commons.richtext.RegularTextSegment
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.HashTag
|
||||||
import com.vitorpamplona.amethyst.ui.components.RenderRegular
|
import com.vitorpamplona.amethyst.ui.components.RenderRegular
|
||||||
import com.vitorpamplona.amethyst.ui.components.RenderTextParagraph
|
import com.vitorpamplona.amethyst.ui.components.RenderTextParagraph
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun RenderHashTagIconsPreview() {
|
fun RenderHashTagIconsPreview() {
|
||||||
val accountViewModel = mockAccountViewModel()
|
|
||||||
ThemeComparisonColumn {
|
ThemeComparisonColumn {
|
||||||
RenderRegular(
|
RenderRegular(
|
||||||
"Testing rendering of hashtags: #flowerstr #Bitcoin, #nostr, #lightning, #zap, #amethyst, #cashu, #plebs, #coffee, #skullofsatoshi, #grownostr, #footstr, #tunestr, #weed, #mate, #gamestr, #gamechain",
|
"Testing rendering of hashtags: #flowerstr #Bitcoin, #nostr, #lightning, #zap, #amethyst, #cashu, #plebs, #coffee, #skullofsatoshi, #grownostr, #footstr, #tunestr, #weed, #mate, #gamestr, #gamechain",
|
||||||
EmptyTagList,
|
EmptyTagList,
|
||||||
) { paragraph, state, modifier ->
|
) { paragraph, state, spaceWidth, modifier ->
|
||||||
RenderTextParagraph(
|
RenderTextParagraph(paragraph, spaceWidth, modifier) { word ->
|
||||||
paragraph = paragraph,
|
when (word) {
|
||||||
modifier = modifier,
|
is HashTagSegment -> HashTag(word, EmptyNav())
|
||||||
state = state,
|
is RegularTextSegment -> Text(word.segmentText)
|
||||||
canPreview = false,
|
}
|
||||||
quotesLeft = 0,
|
}
|
||||||
backgroundColor = remember { mutableStateOf(Color.Transparent) },
|
|
||||||
callbackUri = null,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
nav = EmptyNav(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+297
-393
@@ -20,11 +20,14 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.components
|
package com.vitorpamplona.amethyst.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.text.InlineTextContent
|
import androidx.compose.foundation.text.InlineTextContent
|
||||||
import androidx.compose.foundation.text.appendInlineContent
|
import androidx.compose.foundation.text.appendInlineContent
|
||||||
@@ -47,24 +50,16 @@ import androidx.compose.ui.layout.ContentScale
|
|||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalFontFamilyResolver
|
import androidx.compose.ui.platform.LocalFontFamilyResolver
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.text.LinkAnnotation
|
|
||||||
import androidx.compose.ui.text.Placeholder
|
|
||||||
import androidx.compose.ui.text.PlaceholderVerticalAlign
|
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.TextLinkStyles
|
|
||||||
import androidx.compose.ui.text.TextMeasurer
|
import androidx.compose.ui.text.TextMeasurer
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.withLink
|
|
||||||
import androidx.compose.ui.text.withStyle
|
import androidx.compose.ui.text.withStyle
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.TextUnit
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.em
|
import androidx.compose.ui.unit.em
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import coil3.compose.AsyncImage
|
|
||||||
import com.vitorpamplona.amethyst.commons.compose.produceCachedState
|
import com.vitorpamplona.amethyst.commons.compose.produceCachedState
|
||||||
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
||||||
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||||
@@ -87,6 +82,7 @@ import com.vitorpamplona.amethyst.commons.richtext.RegularTextSegment
|
|||||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState
|
import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.SchemelessUrlSegment
|
import com.vitorpamplona.amethyst.commons.richtext.SchemelessUrlSegment
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.SecretEmoji
|
import com.vitorpamplona.amethyst.commons.richtext.SecretEmoji
|
||||||
|
import com.vitorpamplona.amethyst.commons.richtext.Segment
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.VideoSegment
|
import com.vitorpamplona.amethyst.commons.richtext.VideoSegment
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.WithdrawSegment
|
import com.vitorpamplona.amethyst.commons.richtext.WithdrawSegment
|
||||||
import com.vitorpamplona.amethyst.model.HashtagIcon
|
import com.vitorpamplona.amethyst.model.HashtagIcon
|
||||||
@@ -109,6 +105,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.LoadUser
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.LoadUser
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.CashuCardBorders
|
import com.vitorpamplona.amethyst.ui.theme.CashuCardBorders
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.HalfVertPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||||
import com.vitorpamplona.amethyst.ui.theme.inlinePlaceholder
|
import com.vitorpamplona.amethyst.ui.theme.inlinePlaceholder
|
||||||
import com.vitorpamplona.amethyst.ui.theme.innerPostModifier
|
import com.vitorpamplona.amethyst.ui.theme.innerPostModifier
|
||||||
@@ -153,8 +150,21 @@ fun RenderStrangeNamePreview() {
|
|||||||
RenderRegular(
|
RenderRegular(
|
||||||
"If you want to stream or download the music from nostr:npub1sctag667a7np6p6ety2up94pnwwxhd2ep8n8afr2gtr47cwd4ewsvdmmjm can you here",
|
"If you want to stream or download the music from nostr:npub1sctag667a7np6p6ety2up94pnwwxhd2ep8n8afr2gtr47cwd4ewsvdmmjm can you here",
|
||||||
EmptyTagList,
|
EmptyTagList,
|
||||||
) { paragraph, state, modifier ->
|
) { paragraph, state, spaceWidth, modifier ->
|
||||||
RenderTextParagraph(paragraph, modifier, state, canPreview = false, quotesLeft = 0, backgroundColor = remember { mutableStateOf(Color.Transparent) }, callbackUri = null, accountViewModel = mockAccountViewModel(), nav = EmptyNav())
|
RenderTextParagraph(paragraph, spaceWidth, modifier) { word ->
|
||||||
|
when (word) {
|
||||||
|
is BechSegment -> {
|
||||||
|
Text(
|
||||||
|
"FreeFrom Official \uD80C\uDD66",
|
||||||
|
modifier = Modifier.border(1.dp, Color.Red),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is RegularTextSegment -> {
|
||||||
|
Text(word.segmentText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,8 +177,51 @@ fun RenderRegularPreview() {
|
|||||||
"nostr:npub1e0z776cpe0gllgktjk54fuzv8pdfxmq6smsmh8xd7t8s7n474n9smk0txy but i'm Monthly funding" +
|
"nostr:npub1e0z776cpe0gllgktjk54fuzv8pdfxmq6smsmh8xd7t8s7n474n9smk0txy but i'm Monthly funding" +
|
||||||
" 7 other humans vitor@vitorpamplona.com at the moment so spread #test a bit thin, but won't always be the case.",
|
" 7 other humans vitor@vitorpamplona.com at the moment so spread #test a bit thin, but won't always be the case.",
|
||||||
EmptyTagList,
|
EmptyTagList,
|
||||||
) { paragraph, state, modifier ->
|
) { paragraph, state, spaceWidth, modifier ->
|
||||||
RenderTextParagraph(paragraph, modifier, state, canPreview = false, quotesLeft = 0, backgroundColor = remember { mutableStateOf(Color.Transparent) }, callbackUri = null, accountViewModel = mockAccountViewModel(), nav = EmptyNav())
|
RenderTextParagraph(paragraph, spaceWidth, modifier) { word ->
|
||||||
|
when (word) {
|
||||||
|
// is ImageSegment -> ZoomableContentView(word.segmentText, state, accountViewModel)
|
||||||
|
// is LinkSegment -> LoadUrlPreview(word.segmentText, word.segmentText, accountViewModel)
|
||||||
|
is EmojiSegment -> {
|
||||||
|
RenderCustomEmoji(word.segmentText, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
// is InvoiceSegment -> MayBeInvoicePreview(word.segmentText)
|
||||||
|
// is WithdrawSegment -> MayBeWithdrawal(word.segmentText)
|
||||||
|
// is CashuSegment -> CashuPreview(word.segmentText, accountViewModel)
|
||||||
|
is EmailSegment -> {
|
||||||
|
ClickableEmail(word.segmentText)
|
||||||
|
}
|
||||||
|
|
||||||
|
is PhoneSegment -> {
|
||||||
|
ClickablePhone(word.segmentText)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BechSegment -> {
|
||||||
|
CreateClickableText(
|
||||||
|
word.segmentText.substring(0, 10),
|
||||||
|
"",
|
||||||
|
1,
|
||||||
|
route = Route.EventRedirect(word.segmentText),
|
||||||
|
nav = EmptyNav(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is HashTagSegment -> {
|
||||||
|
HashTag(word, EmptyNav())
|
||||||
|
}
|
||||||
|
|
||||||
|
// is HashIndexUserSegment -> TagLink(word, accountViewModel, nav)
|
||||||
|
// is HashIndexEventSegment -> TagLink(word, true, backgroundColorState, accountViewModel, nav)
|
||||||
|
is SchemelessUrlSegment -> {
|
||||||
|
NoProtocolUrlRenderer(word)
|
||||||
|
}
|
||||||
|
|
||||||
|
is RegularTextSegment -> {
|
||||||
|
Text(word.segmentText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,8 +232,30 @@ fun RenderRegularPreview2() {
|
|||||||
RenderRegular(
|
RenderRegular(
|
||||||
"#Amethyst v0.84.1: ncryptsec support (NIP-49)",
|
"#Amethyst v0.84.1: ncryptsec support (NIP-49)",
|
||||||
EmptyTagList,
|
EmptyTagList,
|
||||||
) { paragraph, state, modifier ->
|
) { paragraph, state, spaceWidth, modifier ->
|
||||||
RenderTextParagraph(paragraph, modifier, state, canPreview = false, quotesLeft = 0, backgroundColor = remember { mutableStateOf(Color.Transparent) }, callbackUri = null, accountViewModel = mockAccountViewModel(), nav = EmptyNav())
|
RenderTextParagraph(paragraph, spaceWidth, modifier) { word ->
|
||||||
|
when (word) {
|
||||||
|
// is ImageSegment -> ZoomableContentView(word.segmentText, state, accountViewModel)
|
||||||
|
// is LinkSegment -> LoadUrlPreview(word.segmentText, word.segmentText, accountViewModel)
|
||||||
|
is EmojiSegment -> RenderCustomEmoji(word.segmentText, state)
|
||||||
|
|
||||||
|
// is InvoiceSegment -> MayBeInvoicePreview(word.segmentText)
|
||||||
|
// is WithdrawSegment -> MayBeWithdrawal(word.segmentText)
|
||||||
|
// is CashuSegment -> CashuPreview(word.segmentText, accountViewModel)
|
||||||
|
is EmailSegment -> ClickableEmail(word.segmentText)
|
||||||
|
|
||||||
|
is PhoneSegment -> ClickablePhone(word.segmentText)
|
||||||
|
|
||||||
|
// is BechSegment -> BechLink(word.segmentText, true, backgroundColor, accountViewModel, nav)
|
||||||
|
is HashTagSegment -> HashTag(word, EmptyNav())
|
||||||
|
|
||||||
|
// is HashIndexUserSegment -> TagLink(word, accountViewModel, nav)
|
||||||
|
// is HashIndexEventSegment -> TagLink(word, true, backgroundColorState, accountViewModel, nav)
|
||||||
|
is SchemelessUrlSegment -> NoProtocolUrlRenderer(word)
|
||||||
|
|
||||||
|
is RegularTextSegment -> Text(word.segmentText)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,8 +279,31 @@ fun RenderRegularPreview3() {
|
|||||||
"#ioメシヨソイゲーム\n" +
|
"#ioメシヨソイゲーム\n" +
|
||||||
"https://misskey.io/play/9g3qza4jow",
|
"https://misskey.io/play/9g3qza4jow",
|
||||||
tags,
|
tags,
|
||||||
) { paragraph, state, modifier ->
|
) { paragraph, state, spaceWidth, modifier ->
|
||||||
RenderTextParagraph(paragraph, modifier, state, canPreview = true, quotesLeft = 1, backgroundColor = remember { mutableStateOf(Color.Transparent) }, callbackUri = null, accountViewModel = accountViewModel, nav = EmptyNav())
|
RenderTextParagraph(paragraph, spaceWidth, modifier) { word ->
|
||||||
|
when (word) {
|
||||||
|
// is ImageSegment -> ZoomableContentView(word.segmentText, state, accountViewModel)
|
||||||
|
is LinkSegment -> LoadUrlPreview(word.segmentText, word.segmentText, null, accountViewModel)
|
||||||
|
|
||||||
|
is EmojiSegment -> RenderCustomEmoji(word.segmentText, state)
|
||||||
|
|
||||||
|
// is InvoiceSegment -> MayBeInvoicePreview(word.segmentText)
|
||||||
|
// is WithdrawSegment -> MayBeWithdrawal(word.segmentText)
|
||||||
|
// is CashuSegment -> CashuPreview(word.segmentText, accountViewModel)
|
||||||
|
is EmailSegment -> ClickableEmail(word.segmentText)
|
||||||
|
|
||||||
|
is PhoneSegment -> ClickablePhone(word.segmentText)
|
||||||
|
|
||||||
|
// is BechSegment -> BechLink(word.segmentText, true, backgroundColor, accountViewModel, nav)
|
||||||
|
is HashTagSegment -> HashTag(word, EmptyNav())
|
||||||
|
|
||||||
|
// is HashIndexUserSegment -> TagLink(word, accountViewModel, nav)
|
||||||
|
// is HashIndexEventSegment -> TagLink(word, true, backgroundColorState, accountViewModel, nav)
|
||||||
|
is SchemelessUrlSegment -> NoProtocolUrlRenderer(word)
|
||||||
|
|
||||||
|
is RegularTextSegment -> Text(word.segmentText)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,27 +318,41 @@ private fun RenderRegular(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
RenderRegular(content, tags, callbackUri) { paragraph, state, modifier ->
|
if (canPreview) {
|
||||||
if (paragraph is ImageGalleryParagraph) {
|
RenderRegular(content, tags, callbackUri) { paragraph, state, spaceWidth, modifier ->
|
||||||
ImageGallery(
|
if (paragraph is ImageGalleryParagraph) {
|
||||||
images = paragraph,
|
ImageGallery(
|
||||||
state = state,
|
images = paragraph,
|
||||||
accountViewModel = accountViewModel,
|
state = state,
|
||||||
modifier = modifier,
|
accountViewModel = accountViewModel,
|
||||||
roundedCorner = true,
|
modifier = modifier,
|
||||||
)
|
roundedCorner = true,
|
||||||
} else {
|
)
|
||||||
RenderTextParagraph(
|
} else {
|
||||||
paragraph = paragraph,
|
RenderTextParagraph(paragraph, spaceWidth, modifier) { word ->
|
||||||
modifier = modifier,
|
RenderWordWithPreview(
|
||||||
state = state,
|
word,
|
||||||
canPreview = canPreview,
|
state,
|
||||||
quotesLeft = quotesLeft,
|
backgroundColor,
|
||||||
backgroundColor = backgroundColor,
|
quotesLeft,
|
||||||
callbackUri = callbackUri,
|
callbackUri,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel,
|
||||||
nav = nav,
|
nav,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RenderRegular(content, tags, callbackUri) { paragraph, state, spaceWidth, modifier ->
|
||||||
|
RenderTextParagraph(paragraph, spaceWidth, modifier) { word ->
|
||||||
|
RenderWordWithoutPreview(
|
||||||
|
word,
|
||||||
|
state,
|
||||||
|
backgroundColor,
|
||||||
|
accountViewModel,
|
||||||
|
nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,352 +360,32 @@ private fun RenderRegular(
|
|||||||
@Composable
|
@Composable
|
||||||
fun RenderTextParagraph(
|
fun RenderTextParagraph(
|
||||||
paragraph: ParagraphState,
|
paragraph: ParagraphState,
|
||||||
|
spaceWidth: Dp,
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
state: RichTextViewerState,
|
renderWord: @Composable (word: Segment) -> Unit,
|
||||||
canPreview: Boolean,
|
|
||||||
quotesLeft: Int,
|
|
||||||
backgroundColor: MutableState<Color>,
|
|
||||||
callbackUri: String?,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
nav: INav,
|
|
||||||
) {
|
) {
|
||||||
val primaryColor = MaterialTheme.colorScheme.primary
|
FlowRow(
|
||||||
val onBackgroundColor = MaterialTheme.colorScheme.onBackground
|
|
||||||
val fontSize = LocalTextStyle.current.fontSize
|
|
||||||
|
|
||||||
val emojiSize = if (fontSize == TextUnit.Unspecified) 20.sp else fontSize.times(1.1f)
|
|
||||||
val emojiPlaceholder = Placeholder(emojiSize, emojiSize, PlaceholderVerticalAlign.Center)
|
|
||||||
|
|
||||||
// Fixed-size placeholder for media (images / videos)
|
|
||||||
val mediaPlaceholder = Placeholder(300.sp, 200.sp, PlaceholderVerticalAlign.Bottom)
|
|
||||||
|
|
||||||
// Fixed-size placeholder for block-level widgets (URL previews, invoices, note cards, etc.)
|
|
||||||
val blockPlaceholder = Placeholder(300.sp, 300.sp, PlaceholderVerticalAlign.Bottom)
|
|
||||||
|
|
||||||
// Inline-text-height placeholder for user / event references shown as links
|
|
||||||
val refPlaceholder = Placeholder(150.sp, emojiSize, PlaceholderVerticalAlign.TextCenter)
|
|
||||||
|
|
||||||
// Both the annotated string and the inlineContent map are built in a single pass so
|
|
||||||
// their "inline_$idx" keys always match.
|
|
||||||
val inlineContent = mutableMapOf<String, InlineTextContent>()
|
|
||||||
|
|
||||||
val annotatedText =
|
|
||||||
buildAnnotatedString {
|
|
||||||
paragraph.words.forEachIndexed { idx, word ->
|
|
||||||
if (idx > 0) append(" ")
|
|
||||||
|
|
||||||
when (word) {
|
|
||||||
is RegularTextSegment -> {
|
|
||||||
append(word.segmentText)
|
|
||||||
}
|
|
||||||
|
|
||||||
is EmojiSegment -> {
|
|
||||||
val emojiList =
|
|
||||||
com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji
|
|
||||||
.assembleAnnotatedList(word.segmentText, state.customEmoji)
|
|
||||||
if (emojiList != null) {
|
|
||||||
emojiList.forEachIndexed { emojiIdx, renderable ->
|
|
||||||
when (renderable) {
|
|
||||||
is com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji.TextType -> {
|
|
||||||
append(renderable.text)
|
|
||||||
}
|
|
||||||
|
|
||||||
is com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji.ImageUrlType -> {
|
|
||||||
val key = "inline_${idx}_$emojiIdx"
|
|
||||||
appendInlineContent(key, renderable.url)
|
|
||||||
val url = renderable.url
|
|
||||||
inlineContent[key] =
|
|
||||||
InlineTextContent(emojiPlaceholder) {
|
|
||||||
AsyncImage(
|
|
||||||
model = url,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.fillMaxSize().padding(1.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
append(word.segmentText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is HashTagSegment -> {
|
|
||||||
val hashtagIcon = checkForHashtagWithIcon(word.hashtag)
|
|
||||||
withLink(
|
|
||||||
LinkAnnotation.Clickable(
|
|
||||||
tag = "hashtag_$idx",
|
|
||||||
styles = TextLinkStyles(SpanStyle(color = primaryColor)),
|
|
||||||
) {
|
|
||||||
nav.nav(Route.Hashtag(word.hashtag.lowercase()))
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
append("#${word.hashtag}")
|
|
||||||
if (hashtagIcon != null) {
|
|
||||||
val key = "inline_$idx"
|
|
||||||
appendInlineContent(key, "[icon]")
|
|
||||||
val icon = hashtagIcon
|
|
||||||
inlineContent[key] =
|
|
||||||
InlineTextContent(inlinePlaceholder) {
|
|
||||||
Icon(
|
|
||||||
imageVector = icon.icon,
|
|
||||||
contentDescription = icon.description,
|
|
||||||
tint = Color.Unspecified,
|
|
||||||
modifier = icon.modifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
word.extras?.let { withStyle(SpanStyle(color = onBackgroundColor)) { append(it) } }
|
|
||||||
}
|
|
||||||
|
|
||||||
is ImageSegment -> {
|
|
||||||
val key = "inline_$idx"
|
|
||||||
if (canPreview && state.imagesForPager.containsKey(word.segmentText)) {
|
|
||||||
appendInlineContent(key, "[image]")
|
|
||||||
val url = word.segmentText
|
|
||||||
inlineContent[key] =
|
|
||||||
InlineTextContent(mediaPlaceholder) {
|
|
||||||
state.imagesForPager[url]?.let { media ->
|
|
||||||
Box(Modifier.fillMaxSize()) {
|
|
||||||
ZoomableContentView(
|
|
||||||
content = media,
|
|
||||||
images = state.imageList,
|
|
||||||
roundedCorner = true,
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
withLink(
|
|
||||||
LinkAnnotation.Url(
|
|
||||||
word.segmentText,
|
|
||||||
TextLinkStyles(SpanStyle(color = primaryColor)),
|
|
||||||
),
|
|
||||||
) { append(word.segmentText) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is VideoSegment -> {
|
|
||||||
val key = "inline_$idx"
|
|
||||||
if (canPreview && state.imagesForPager.containsKey(word.segmentText)) {
|
|
||||||
appendInlineContent(key, "[video]")
|
|
||||||
val url = word.segmentText
|
|
||||||
inlineContent[key] =
|
|
||||||
InlineTextContent(mediaPlaceholder) {
|
|
||||||
state.imagesForPager[url]?.let { media ->
|
|
||||||
Box(Modifier.fillMaxSize()) {
|
|
||||||
ZoomableContentView(
|
|
||||||
content = media,
|
|
||||||
images = state.imageList,
|
|
||||||
roundedCorner = true,
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
withLink(
|
|
||||||
LinkAnnotation.Url(
|
|
||||||
word.segmentText,
|
|
||||||
TextLinkStyles(SpanStyle(color = primaryColor)),
|
|
||||||
),
|
|
||||||
) { append(word.segmentText) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is Base64Segment -> {
|
|
||||||
val key = "inline_$idx"
|
|
||||||
if (canPreview) {
|
|
||||||
appendInlineContent(key, "[image]")
|
|
||||||
val url = word.segmentText
|
|
||||||
inlineContent[key] =
|
|
||||||
InlineTextContent(mediaPlaceholder) {
|
|
||||||
state.imagesForPager[url]?.let { media ->
|
|
||||||
Box(Modifier.fillMaxSize()) {
|
|
||||||
ZoomableContentView(
|
|
||||||
content = media,
|
|
||||||
images = state.imageList,
|
|
||||||
roundedCorner = true,
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
append(word.segmentText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is LinkSegment -> {
|
|
||||||
val key = "inline_$idx"
|
|
||||||
if (canPreview) {
|
|
||||||
appendInlineContent(key, word.segmentText)
|
|
||||||
val url = word.segmentText
|
|
||||||
inlineContent[key] =
|
|
||||||
InlineTextContent(blockPlaceholder) {
|
|
||||||
LoadUrlPreview(url, url, callbackUri, accountViewModel)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
withLink(
|
|
||||||
LinkAnnotation.Url(
|
|
||||||
word.segmentText,
|
|
||||||
TextLinkStyles(SpanStyle(color = primaryColor)),
|
|
||||||
),
|
|
||||||
) { append(word.segmentText) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is InvoiceSegment -> {
|
|
||||||
if (canPreview) {
|
|
||||||
val key = "inline_$idx"
|
|
||||||
appendInlineContent(key, word.segmentText)
|
|
||||||
val invoice = word.segmentText
|
|
||||||
inlineContent[key] =
|
|
||||||
InlineTextContent(blockPlaceholder) {
|
|
||||||
MayBeInvoicePreview(invoice, accountViewModel)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
append(word.segmentText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is WithdrawSegment -> {
|
|
||||||
if (canPreview) {
|
|
||||||
val key = "inline_$idx"
|
|
||||||
appendInlineContent(key, word.segmentText)
|
|
||||||
val withdraw = word.segmentText
|
|
||||||
inlineContent[key] =
|
|
||||||
InlineTextContent(blockPlaceholder) {
|
|
||||||
MayBeWithdrawal(withdraw, accountViewModel)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
append(word.segmentText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is CashuSegment -> {
|
|
||||||
if (canPreview) {
|
|
||||||
val key = "inline_$idx"
|
|
||||||
appendInlineContent(key, word.segmentText)
|
|
||||||
val cashu = word.segmentText
|
|
||||||
inlineContent[key] =
|
|
||||||
InlineTextContent(blockPlaceholder) {
|
|
||||||
CashuPreview(cashu, accountViewModel)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
append(word.segmentText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is EmailSegment -> {
|
|
||||||
withLink(
|
|
||||||
LinkAnnotation.Url(
|
|
||||||
"mailto:${word.segmentText}",
|
|
||||||
TextLinkStyles(SpanStyle(color = primaryColor)),
|
|
||||||
),
|
|
||||||
) { append(word.segmentText) }
|
|
||||||
}
|
|
||||||
|
|
||||||
is PhoneSegment -> {
|
|
||||||
withLink(
|
|
||||||
LinkAnnotation.Url(
|
|
||||||
"tel:${word.segmentText}",
|
|
||||||
TextLinkStyles(SpanStyle(color = primaryColor)),
|
|
||||||
),
|
|
||||||
) { append(word.segmentText) }
|
|
||||||
}
|
|
||||||
|
|
||||||
is SecretEmoji -> {
|
|
||||||
if (canPreview && quotesLeft > 0) {
|
|
||||||
val key = "inline_$idx"
|
|
||||||
appendInlineContent(key, word.segmentText)
|
|
||||||
val segment = word
|
|
||||||
inlineContent[key] =
|
|
||||||
InlineTextContent(blockPlaceholder) {
|
|
||||||
DisplaySecretEmoji(
|
|
||||||
segment = segment,
|
|
||||||
state = state,
|
|
||||||
callbackUri = callbackUri,
|
|
||||||
canPreview = true,
|
|
||||||
quotesLeft = quotesLeft,
|
|
||||||
backgroundColor = backgroundColor,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
nav = nav,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
append(word.segmentText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is BechSegment -> {
|
|
||||||
val key = "inline_$idx"
|
|
||||||
appendInlineContent(key, word.segmentText)
|
|
||||||
val bech = word.segmentText
|
|
||||||
val noteBlockPlaceholder =
|
|
||||||
if (canPreview && quotesLeft > 0) blockPlaceholder else refPlaceholder
|
|
||||||
inlineContent[key] =
|
|
||||||
InlineTextContent(noteBlockPlaceholder) {
|
|
||||||
BechLink(bech, canPreview, quotesLeft, backgroundColor, accountViewModel, nav)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is HashIndexUserSegment -> {
|
|
||||||
val key = "inline_$idx"
|
|
||||||
appendInlineContent(key, word.segmentText)
|
|
||||||
val segment = word
|
|
||||||
inlineContent[key] =
|
|
||||||
InlineTextContent(refPlaceholder) {
|
|
||||||
TagLink(segment, accountViewModel, nav)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is HashIndexEventSegment -> {
|
|
||||||
val key = "inline_$idx"
|
|
||||||
appendInlineContent(key, word.segmentText)
|
|
||||||
val segment = word
|
|
||||||
val eventPlaceholder =
|
|
||||||
if (canPreview && quotesLeft > 0) blockPlaceholder else refPlaceholder
|
|
||||||
inlineContent[key] =
|
|
||||||
InlineTextContent(eventPlaceholder) {
|
|
||||||
TagLink(segment, canPreview, quotesLeft, backgroundColor, accountViewModel, nav)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is SchemelessUrlSegment -> {
|
|
||||||
withLink(
|
|
||||||
LinkAnnotation.Url(
|
|
||||||
"https://${word.url}",
|
|
||||||
TextLinkStyles(SpanStyle(color = primaryColor)),
|
|
||||||
),
|
|
||||||
) { append(word.url) }
|
|
||||||
word.extras?.let { withStyle(SpanStyle(color = onBackgroundColor)) { append(it) } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = annotatedText,
|
|
||||||
inlineContent = inlineContent,
|
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
horizontalArrangement = Arrangement.spacedBy(spaceWidth),
|
||||||
|
) {
|
||||||
|
paragraph.words.forEach { word ->
|
||||||
|
renderWord(word)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun RenderRegular(
|
fun RenderRegular(
|
||||||
content: String,
|
content: String,
|
||||||
tags: ImmutableListOfLists<String>,
|
tags: ImmutableListOfLists<String>,
|
||||||
callbackUri: String? = null,
|
callbackUri: String? = null,
|
||||||
renderParagraph: @Composable (ParagraphState, state: RichTextViewerState, modifier: Modifier) -> Unit,
|
renderParagraph: @Composable (ParagraphState, state: RichTextViewerState, Dp, modifier: Modifier) -> Unit,
|
||||||
) {
|
) {
|
||||||
val state by remember(content, tags) { mutableStateOf(CachedRichTextParser.parseText(content, tags, callbackUri)) }
|
val state by remember(content, tags) { mutableStateOf(CachedRichTextParser.parseText(content, tags, callbackUri)) }
|
||||||
|
|
||||||
|
val spaceWidth = measureSpaceWidth(LocalTextStyle.current)
|
||||||
|
|
||||||
val currentTextStyle = LocalTextStyle.current
|
val currentTextStyle = LocalTextStyle.current
|
||||||
|
|
||||||
val textStyle =
|
val textStyle =
|
||||||
@@ -618,6 +410,7 @@ fun RenderRegular(
|
|||||||
renderParagraph(
|
renderParagraph(
|
||||||
paragraph,
|
paragraph,
|
||||||
state,
|
state,
|
||||||
|
spaceWidth,
|
||||||
Modifier.align(if (paragraph.isRTL) Alignment.End else Alignment.Start),
|
Modifier.align(if (paragraph.isRTL) Alignment.End else Alignment.Start),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -641,6 +434,114 @@ fun measureSpaceWidth(textStyle: TextStyle): Dp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RenderWordWithoutPreview(
|
||||||
|
word: Segment,
|
||||||
|
state: RichTextViewerState,
|
||||||
|
backgroundColor: MutableState<Color>,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
when (word) {
|
||||||
|
// Don't preview Images
|
||||||
|
is ImageSegment -> ClickableUrl(word.segmentText, word.segmentText)
|
||||||
|
|
||||||
|
// Don't preview Videos
|
||||||
|
is VideoSegment -> ClickableUrl(word.segmentText, word.segmentText)
|
||||||
|
|
||||||
|
is LinkSegment -> ClickableUrl(word.segmentText, word.segmentText)
|
||||||
|
|
||||||
|
is EmojiSegment -> RenderCustomEmoji(word.segmentText, state)
|
||||||
|
|
||||||
|
// Don't offer to pay invoices
|
||||||
|
is InvoiceSegment -> Text(word.segmentText)
|
||||||
|
|
||||||
|
// Don't offer to withdraw
|
||||||
|
is WithdrawSegment -> Text(word.segmentText)
|
||||||
|
|
||||||
|
is CashuSegment -> Text(word.segmentText)
|
||||||
|
|
||||||
|
is EmailSegment -> ClickableEmail(word.segmentText)
|
||||||
|
|
||||||
|
is SecretEmoji -> Text(word.segmentText)
|
||||||
|
|
||||||
|
is PhoneSegment -> ClickablePhone(word.segmentText)
|
||||||
|
|
||||||
|
is BechSegment -> BechLink(word.segmentText, false, 0, backgroundColor, accountViewModel, nav)
|
||||||
|
|
||||||
|
is HashTagSegment -> HashTag(word, nav)
|
||||||
|
|
||||||
|
is HashIndexUserSegment -> TagLink(word, accountViewModel, nav)
|
||||||
|
|
||||||
|
is HashIndexEventSegment -> TagLink(word, false, 0, backgroundColor, accountViewModel, nav)
|
||||||
|
|
||||||
|
is SchemelessUrlSegment -> NoProtocolUrlRenderer(word)
|
||||||
|
|
||||||
|
is RegularTextSegment -> Text(word.segmentText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RenderWordWithPreview(
|
||||||
|
word: Segment,
|
||||||
|
state: RichTextViewerState,
|
||||||
|
backgroundColor: MutableState<Color>,
|
||||||
|
quotesLeft: Int,
|
||||||
|
callbackUri: String? = null,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
when (word) {
|
||||||
|
is ImageSegment -> ZoomableContentView(word.segmentText, state, accountViewModel)
|
||||||
|
is VideoSegment -> ZoomableContentView(word.segmentText, state, accountViewModel)
|
||||||
|
is LinkSegment -> LoadUrlPreview(word.segmentText, word.segmentText, callbackUri, accountViewModel)
|
||||||
|
is EmojiSegment -> RenderCustomEmoji(word.segmentText, state)
|
||||||
|
is InvoiceSegment -> MayBeInvoicePreview(word.segmentText, accountViewModel)
|
||||||
|
is WithdrawSegment -> MayBeWithdrawal(word.segmentText, accountViewModel)
|
||||||
|
is CashuSegment -> CashuPreview(word.segmentText, accountViewModel)
|
||||||
|
is EmailSegment -> ClickableEmail(word.segmentText)
|
||||||
|
is SecretEmoji -> DisplaySecretEmoji(word, state, callbackUri, true, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||||
|
is PhoneSegment -> ClickablePhone(word.segmentText)
|
||||||
|
is BechSegment -> BechLink(word.segmentText, true, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||||
|
is HashTagSegment -> HashTag(word, nav)
|
||||||
|
is HashIndexUserSegment -> TagLink(word, accountViewModel, nav)
|
||||||
|
is HashIndexEventSegment -> TagLink(word, true, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||||
|
is SchemelessUrlSegment -> NoProtocolUrlRenderer(word)
|
||||||
|
is RegularTextSegment -> Text(word.segmentText)
|
||||||
|
is Base64Segment -> ZoomableContentView(word.segmentText, state, accountViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ZoomableContentView(
|
||||||
|
word: String,
|
||||||
|
state: RichTextViewerState,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
) {
|
||||||
|
state.imagesForPager[word]?.let {
|
||||||
|
Box(modifier = HalfVertPadding) {
|
||||||
|
ZoomableContentView(it, state.imageList, roundedCorner = true, contentScale = ContentScale.FillWidth, accountViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NoProtocolUrlRenderer(segment: SchemelessUrlSegment) {
|
||||||
|
ClickableUrl(segment.url, "https://${segment.url}")
|
||||||
|
segment.extras?.let { it1 -> Text(it1) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RenderCustomEmoji(
|
||||||
|
word: String,
|
||||||
|
state: RichTextViewerState,
|
||||||
|
) {
|
||||||
|
CreateTextWithEmoji(
|
||||||
|
text = word,
|
||||||
|
emojis = state.customEmoji,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BechLink(
|
fun BechLink(
|
||||||
word: String,
|
word: String,
|
||||||
@@ -756,6 +657,7 @@ fun DisplaySecretEmoji(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun CoreSecretMessage(
|
fun CoreSecretMessage(
|
||||||
localSecretContent: RichTextViewerState,
|
localSecretContent: RichTextViewerState,
|
||||||
@@ -766,33 +668,35 @@ fun CoreSecretMessage(
|
|||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
if (localSecretContent.paragraphs.size == 1) {
|
if (localSecretContent.paragraphs.size == 1) {
|
||||||
RenderTextParagraph(
|
localSecretContent.paragraphs[0].words.forEach { word ->
|
||||||
paragraph = localSecretContent.paragraphs[0],
|
RenderWordWithPreview(
|
||||||
modifier = Modifier,
|
word = word,
|
||||||
state = localSecretContent,
|
state = localSecretContent,
|
||||||
canPreview = true,
|
backgroundColor = backgroundColor,
|
||||||
quotesLeft = quotesLeft,
|
quotesLeft = quotesLeft,
|
||||||
backgroundColor = backgroundColor,
|
callbackUri = callbackUri,
|
||||||
callbackUri = callbackUri,
|
accountViewModel = accountViewModel,
|
||||||
accountViewModel = accountViewModel,
|
nav = nav,
|
||||||
nav = nav,
|
)
|
||||||
)
|
}
|
||||||
} else if (localSecretContent.paragraphs.size > 1) {
|
} else if (localSecretContent.paragraphs.size > 1) {
|
||||||
|
val spaceWidth = measureSpaceWidth(LocalTextStyle.current)
|
||||||
|
|
||||||
Column(CashuCardBorders) {
|
Column(CashuCardBorders) {
|
||||||
localSecretContent.paragraphs.forEach { paragraph ->
|
localSecretContent.paragraphs.forEach { paragraph ->
|
||||||
val modifier = Modifier.align(if (paragraph.isRTL) Alignment.End else Alignment.Start)
|
val modifier = Modifier.align(if (paragraph.isRTL) Alignment.End else Alignment.Start)
|
||||||
|
|
||||||
RenderTextParagraph(
|
RenderTextParagraph(paragraph, spaceWidth, modifier) { word ->
|
||||||
paragraph = paragraph,
|
RenderWordWithPreview(
|
||||||
modifier = modifier,
|
word,
|
||||||
state = localSecretContent,
|
localSecretContent,
|
||||||
canPreview = true,
|
backgroundColor,
|
||||||
quotesLeft = quotesLeft,
|
quotesLeft,
|
||||||
backgroundColor = backgroundColor,
|
callbackUri,
|
||||||
callbackUri = callbackUri,
|
accountViewModel,
|
||||||
accountViewModel = accountViewModel,
|
nav,
|
||||||
nav = nav,
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user