Improving markdown support
This commit is contained in:
@@ -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 += " "
|
||||||
} 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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user