Improving markdown support

This commit is contained in:
Vitor Pamplona
2023-06-10 21:17:50 -04:00
parent e98bb76ee0
commit ba884bd1aa
2 changed files with 92 additions and 71 deletions
@@ -12,6 +12,7 @@ import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.LocalTextStyle import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.ProvideTextStyle
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
@@ -23,11 +24,15 @@ import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.* import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.text.style.TextDirection
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.sp import androidx.compose.ui.unit.sp
import com.halilibo.richtext.markdown.Markdown import com.halilibo.richtext.markdown.Markdown
import com.halilibo.richtext.markdown.MarkdownParseOptions import com.halilibo.richtext.markdown.MarkdownParseOptions
import com.halilibo.richtext.ui.HeadingStyle
import com.halilibo.richtext.ui.RichTextStyle import com.halilibo.richtext.ui.RichTextStyle
import com.halilibo.richtext.ui.material.MaterialRichText import com.halilibo.richtext.ui.material.MaterialRichText
import com.halilibo.richtext.ui.resolveDefaults import com.halilibo.richtext.ui.resolveDefaults
@@ -84,6 +89,36 @@ fun isValidURL(url: String?): Boolean {
} }
} }
internal val DefaultHeadingStyle: HeadingStyle = { level, textStyle ->
when (level) {
0 -> textStyle.copy(
fontSize = 30.sp,
fontWeight = FontWeight.Light
)
1 -> textStyle.copy(
fontSize = 26.sp,
fontWeight = FontWeight.Light
)
2 -> textStyle.copy(
fontSize = 22.sp,
fontWeight = FontWeight.Light
)
3 -> textStyle.copy(
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
4 -> textStyle.copy(
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
5 -> textStyle.copy(
fontWeight = FontWeight.Bold
)
else -> textStyle
}
}
internal val DefaultParagraphSpacing: TextUnit = 12.sp
val richTextDefaults = RichTextStyle().resolveDefaults() val richTextDefaults = RichTextStyle().resolveDefaults()
fun isMarkdown(content: String): Boolean { fun isMarkdown(content: String): Boolean {
@@ -405,6 +440,8 @@ fun RenderCustomEmoji(word: String, state: RichTextViewerState) {
@Composable @Composable
private fun RenderContentAsMarkdown(content: String, backgroundColor: MutableState<Color>, tags: ImmutableListOfLists<String>?, nav: (String) -> Unit) { private fun RenderContentAsMarkdown(content: String, backgroundColor: MutableState<Color>, tags: ImmutableListOfLists<String>?, nav: (String) -> Unit) {
val myMarkDownStyle = richTextDefaults.copy( val myMarkDownStyle = richTextDefaults.copy(
paragraphSpacing = DefaultParagraphSpacing,
headingStyle = DefaultHeadingStyle,
codeBlockStyle = richTextDefaults.codeBlockStyle?.copy( codeBlockStyle = richTextDefaults.codeBlockStyle?.copy(
textStyle = TextStyle( textStyle = TextStyle(
fontFamily = FontFamily.Monospace, fontFamily = FontFamily.Monospace,
@@ -449,15 +486,17 @@ private fun RenderContentAsMarkdown(content: String, backgroundColor: MutableSta
Unit Unit
} }
MaterialRichText( ProvideTextStyle(TextStyle(lineHeight = 1.30.em)) {
style = myMarkDownStyle MaterialRichText(
) { style = myMarkDownStyle
RefreshableContent(content, tags) { ) {
Markdown( RefreshableContent(content, tags) {
content = it, Markdown(
markdownParseOptions = MarkdownParseOptions.Default, content = it,
onLinkClicked = onClick markdownParseOptions = MarkdownParseOptions.Default,
) onLinkClicked = onClick
)
}
} }
} }
} }
@@ -652,7 +691,7 @@ private fun returnMarkdownWithSpecialContent(content: String, tags: ImmutableLis
if (isValidURL(word)) { if (isValidURL(word)) {
val removedParamsFromUrl = word.split("?")[0].lowercase() val removedParamsFromUrl = word.split("?")[0].lowercase()
if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) { if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) {
returnContent += "$word " returnContent += "![]($word) "
} else { } else {
returnContent += "[$word]($word) " returnContent += "[$word]($word) "
} }
@@ -22,6 +22,7 @@ import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.IconButton import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.ProvideTextStyle
import androidx.compose.material.SnackbarDefaults.backgroundColor import androidx.compose.material.SnackbarDefaults.backgroundColor
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -45,8 +46,10 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
@@ -60,18 +63,13 @@ import com.vitorpamplona.amethyst.service.model.PeopleListEvent
import com.vitorpamplona.amethyst.service.model.PinListEvent import com.vitorpamplona.amethyst.service.model.PinListEvent
import com.vitorpamplona.amethyst.service.model.PollNoteEvent import com.vitorpamplona.amethyst.service.model.PollNoteEvent
import com.vitorpamplona.amethyst.service.model.RelaySetEvent import com.vitorpamplona.amethyst.service.model.RelaySetEvent
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
import com.vitorpamplona.amethyst.ui.note.* import com.vitorpamplona.amethyst.ui.note.*
import com.vitorpamplona.amethyst.ui.note.BadgeDisplay import com.vitorpamplona.amethyst.ui.note.BadgeDisplay
import com.vitorpamplona.amethyst.ui.note.BlankNote import com.vitorpamplona.amethyst.ui.note.BlankNote
import com.vitorpamplona.amethyst.ui.note.DisplayFollowingHashtagsInPost import com.vitorpamplona.amethyst.ui.note.DisplayFollowingHashtagsInPost
import com.vitorpamplona.amethyst.ui.note.DisplayPoW import com.vitorpamplona.amethyst.ui.note.DisplayPoW
import com.vitorpamplona.amethyst.ui.note.DisplayReward import com.vitorpamplona.amethyst.ui.note.DisplayReward
import com.vitorpamplona.amethyst.ui.note.DisplayUncitedHashtags
import com.vitorpamplona.amethyst.ui.note.HiddenNote import com.vitorpamplona.amethyst.ui.note.HiddenNote
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.note.NoteCompose
@@ -84,7 +82,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.lessImportantLink import com.vitorpamplona.amethyst.ui.theme.lessImportantLink
import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.amethyst.ui.theme.selectedNote import com.vitorpamplona.amethyst.ui.theme.selectedNote
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableSet import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@@ -139,16 +136,18 @@ fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: A
) { ) {
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { index, item -> itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { index, item ->
if (index == 0) { if (index == 0) {
NoteMaster( ProvideTextStyle(TextStyle(fontSize = 16.sp, lineHeight = 1.20.em)) {
item, NoteMaster(
modifier = Modifier.drawReplyLevel( item,
item.replyLevel(), modifier = Modifier.drawReplyLevel(
MaterialTheme.colors.placeholderText, item.replyLevel(),
if (item.idHex == noteId) MaterialTheme.colors.lessImportantLink else MaterialTheme.colors.placeholderText MaterialTheme.colors.placeholderText,
), if (item.idHex == noteId) MaterialTheme.colors.lessImportantLink else MaterialTheme.colors.placeholderText
accountViewModel = accountViewModel, ),
nav = nav accountViewModel = accountViewModel,
) nav = nav
)
}
} else { } else {
Column() { Column() {
Row() { Row() {
@@ -326,7 +325,7 @@ fun NoteMaster(
if (noteEvent is BadgeDefinitionEvent) { if (noteEvent is BadgeDefinitionEvent) {
BadgeDisplay(baseNote = note) BadgeDisplay(baseNote = note)
} else if (noteEvent is LongTextNoteEvent) { } else if (noteEvent is LongTextNoteEvent) {
Row(modifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 10.dp, bottom = 12.dp)) { Row(modifier = Modifier.padding(start = 12.dp, end = 12.dp, bottom = 12.dp)) {
Column { Column {
noteEvent.image()?.let { noteEvent.image()?.let {
AsyncImage( AsyncImage(
@@ -338,25 +337,23 @@ fun NoteMaster(
contentScale = ContentScale.FillWidth, contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Spacer(modifier = Modifier.height(10.dp))
} }
noteEvent.title()?.let { noteEvent.title()?.let {
Text( Text(
text = it, text = it,
fontSize = 30.sp, fontSize = 28.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Light,
modifier = Modifier modifier = Modifier.fillMaxWidth()
.fillMaxWidth()
.padding(top = 10.dp)
) )
Spacer(modifier = Modifier.height(10.dp))
} }
noteEvent.summary()?.let { noteEvent.summary()?.let {
Text( Text(
text = it, text = it,
modifier = Modifier modifier = Modifier.fillMaxWidth(),
.fillMaxWidth()
.padding(top = 10.dp),
color = Color.Gray color = Color.Gray
) )
} }
@@ -404,47 +401,32 @@ fun NoteMaster(
accountViewModel, accountViewModel,
nav nav
) )
} else { } else if (noteEvent is PollNoteEvent) {
val eventContent = note.event?.content()
val canPreview = note.author == account.userProfile() || val canPreview = note.author == account.userProfile() ||
(note.author?.let { account.userProfile().isFollowingCached(it) } ?: true) || (note.author?.let { account.userProfile().isFollowingCached(it) } ?: true) ||
!noteForReports.hasAnyReports() !noteForReports.hasAnyReports()
if (eventContent != null) { RenderPoll(
val hasSensitiveContent = remember(note.event) { note.event?.isSensitive() ?: false } baseNote,
val tags = remember(note) { note.event?.tags()?.toImmutableListOfLists() ?: ImmutableListOfLists() } false,
canPreview,
backgroundColor,
accountViewModel,
nav
)
} else {
val canPreview = note.author == account.userProfile() ||
(note.author?.let { account.userProfile().isFollowingCached(it) } ?: true) ||
!noteForReports.hasAnyReports()
SensitivityWarning( RenderTextEvent(
hasSensitiveContent = hasSensitiveContent, baseNote,
accountViewModel = accountViewModel false,
) { canPreview,
TranslatableRichTextViewer( backgroundColor,
eventContent, accountViewModel,
canPreview, nav
remember { Modifier.fillMaxWidth() }, )
tags,
backgroundColor,
accountViewModel,
nav
)
}
val hashtags = remember {
noteEvent.hashtags().toImmutableList()
}
DisplayUncitedHashtags(hashtags, eventContent, nav)
if (noteEvent is PollNoteEvent) {
PollNote(
note,
canPreview,
backgroundColor,
accountViewModel,
nav
)
}
}
} }
} }
} }