mirror of
https://github.com/msfjarvis/compose-lobsters
synced 2025-08-14 22:17:03 +05:30
feat: revert back to old text pipeline
Things have been entirely too slow on the lobste.rs front and I don't care much to continue pushing for it. Fixes #382 Fixes #383
This commit is contained in:
parent
c089e5ae5a
commit
b5c57500b1
16 changed files with 171 additions and 229 deletions
|
@ -9,7 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Changed
|
||||
|
||||
* Comments and user profile text rendering was overhauled, there should be no visual changes
|
||||
* Added another workaround for native library loading crash
|
||||
|
||||
## [1.29.0] - 2023-06-08
|
||||
|
|
|
@ -70,6 +70,7 @@ dependencies {
|
|||
implementation(libs.androidx.profileinstaller)
|
||||
implementation(libs.androidx.work.runtime.ktx)
|
||||
implementation(libs.coil)
|
||||
implementation(libs.copydown)
|
||||
implementation(libs.crux)
|
||||
implementation(libs.dagger)
|
||||
implementation(libs.jsoup)
|
||||
|
|
|
@ -18,6 +18,7 @@ import androidx.core.view.WindowCompat
|
|||
import com.deliveryhero.whetstone.Whetstone
|
||||
import com.deliveryhero.whetstone.activity.ContributesActivityInjector
|
||||
import dev.msfjarvis.claw.android.ui.LobstersApp
|
||||
import dev.msfjarvis.claw.common.comments.HTMLConverter
|
||||
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -25,6 +26,7 @@ import javax.inject.Inject
|
|||
class MainActivity : ComponentActivity() {
|
||||
|
||||
@Inject lateinit var urlLauncher: UrlLauncher
|
||||
@Inject lateinit var htmlConverter: HTMLConverter
|
||||
private var webUri: String? = null
|
||||
|
||||
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
|
||||
|
@ -38,6 +40,7 @@ class MainActivity : ComponentActivity() {
|
|||
|
||||
LobstersApp(
|
||||
urlLauncher = urlLauncher,
|
||||
htmlConverter = htmlConverter,
|
||||
windowSizeClass = windowSizeClass,
|
||||
setWebUri = { url -> webUri = url },
|
||||
)
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright © 2021-2023 Harsh Shandilya.
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
package dev.msfjarvis.claw.android.injection
|
||||
|
||||
import com.deliveryhero.whetstone.app.ApplicationScope
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dev.msfjarvis.claw.android.ui.util.HTMLConverterImpl
|
||||
import dev.msfjarvis.claw.common.comments.HTMLConverter
|
||||
|
||||
@Module
|
||||
@ContributesTo(ApplicationScope::class)
|
||||
interface HTMLConverterModule {
|
||||
@Binds fun HTMLConverterImpl.bind(): HTMLConverter
|
||||
}
|
|
@ -64,6 +64,7 @@ import dev.msfjarvis.claw.android.ui.navigation.Destinations
|
|||
import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
|
||||
import dev.msfjarvis.claw.api.LobstersApi
|
||||
import dev.msfjarvis.claw.common.comments.CommentsPage
|
||||
import dev.msfjarvis.claw.common.comments.HTMLConverter
|
||||
import dev.msfjarvis.claw.common.theme.LobstersTheme
|
||||
import dev.msfjarvis.claw.common.ui.decorations.ClawAppBar
|
||||
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
|
||||
|
@ -76,6 +77,7 @@ import kotlinx.coroutines.launch
|
|||
@Composable
|
||||
fun LobstersApp(
|
||||
urlLauncher: UrlLauncher,
|
||||
htmlConverter: HTMLConverter,
|
||||
windowSizeClass: WindowSizeClass,
|
||||
setWebUri: (String?) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
|
@ -236,6 +238,7 @@ fun LobstersApp(
|
|||
CommentsPage(
|
||||
postId = postId,
|
||||
postActions = postActions,
|
||||
htmlConverter = htmlConverter,
|
||||
getSeenComments = viewModel::getSeenComments,
|
||||
markSeenComments = viewModel::markSeenComments,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright © 2023 Harsh Shandilya.
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
package dev.msfjarvis.claw.android.ui.util
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import dev.msfjarvis.claw.common.comments.HTMLConverter
|
||||
import io.github.furstenheim.CopyDown
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
class HTMLConverterImpl @Inject constructor() : HTMLConverter {
|
||||
private val copydown = CopyDown()
|
||||
|
||||
override fun convertHTMLToMarkdown(html: String): String {
|
||||
return copydown.convert(html)
|
||||
}
|
||||
}
|
|
@ -45,6 +45,9 @@ dependencies {
|
|||
implementation(libs.androidx.compose.runtime)
|
||||
implementation(libs.androidx.compose.ui.text)
|
||||
implementation(libs.coil.compose)
|
||||
implementation(libs.compose.richtext.markdown)
|
||||
implementation(libs.compose.richtext.material3)
|
||||
implementation(libs.compose.richtext.ui)
|
||||
implementation(libs.kotlinx.collections.immutable)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(libs.napier)
|
||||
|
|
|
@ -41,8 +41,8 @@ import dev.msfjarvis.claw.common.posts.PostActions
|
|||
import dev.msfjarvis.claw.common.posts.PostTitle
|
||||
import dev.msfjarvis.claw.common.posts.Submitter
|
||||
import dev.msfjarvis.claw.common.posts.TagRow
|
||||
import dev.msfjarvis.claw.common.ui.HTMLText
|
||||
import dev.msfjarvis.claw.common.ui.NetworkImage
|
||||
import dev.msfjarvis.claw.common.ui.ThemedRichText
|
||||
import dev.msfjarvis.claw.model.LinkMetadata
|
||||
import dev.msfjarvis.claw.model.LobstersPostDetails
|
||||
import java.time.Instant
|
||||
|
@ -53,6 +53,7 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
internal fun CommentsHeader(
|
||||
postDetails: LobstersPostDetails,
|
||||
postActions: PostActions,
|
||||
htmlConverter: HTMLConverter,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
@ -83,7 +84,7 @@ internal fun CommentsHeader(
|
|||
}
|
||||
|
||||
if (postDetails.description.isNotBlank()) {
|
||||
HTMLText(postDetails.description)
|
||||
ThemedRichText(htmlConverter.convertHTMLToMarkdown(postDetails.description))
|
||||
Spacer(Modifier.height(4.dp))
|
||||
}
|
||||
Submitter(
|
||||
|
@ -133,6 +134,7 @@ private val CommentEntryPadding = 16f.dp
|
|||
@Composable
|
||||
internal fun CommentEntry(
|
||||
commentNode: CommentNode,
|
||||
htmlConverter: HTMLConverter,
|
||||
toggleExpanded: (CommentNode) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
@ -167,7 +169,10 @@ internal fun CommentEntry(
|
|||
Modifier.clickable { uriHandler.openUri("https://lobste.rs/u/${comment.user.username}") },
|
||||
)
|
||||
if (commentNode.isExpanded) {
|
||||
HTMLText(text = comment.comment, modifier = Modifier.padding(top = 8.dp))
|
||||
ThemedRichText(
|
||||
text = htmlConverter.convertHTMLToMarkdown(comment.comment),
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,11 +68,13 @@ internal fun findTopMostParent(node: CommentNode): CommentNode {
|
|||
|
||||
internal fun LazyListScope.nodes(
|
||||
nodes: List<CommentNode>,
|
||||
htmlConverter: HTMLConverter,
|
||||
toggleExpanded: (CommentNode) -> Unit,
|
||||
) {
|
||||
nodes.forEach { node ->
|
||||
node(
|
||||
node = node,
|
||||
htmlConverter = htmlConverter,
|
||||
toggleExpanded = toggleExpanded,
|
||||
)
|
||||
}
|
||||
|
@ -80,6 +82,7 @@ internal fun LazyListScope.nodes(
|
|||
|
||||
private fun LazyListScope.node(
|
||||
node: CommentNode,
|
||||
htmlConverter: HTMLConverter,
|
||||
toggleExpanded: (CommentNode) -> Unit,
|
||||
) {
|
||||
// Skip the node if neither the node nor its parent is expanded
|
||||
|
@ -89,6 +92,7 @@ private fun LazyListScope.node(
|
|||
item {
|
||||
CommentEntry(
|
||||
commentNode = node,
|
||||
htmlConverter = htmlConverter,
|
||||
toggleExpanded = toggleExpanded,
|
||||
)
|
||||
Divider()
|
||||
|
@ -96,6 +100,7 @@ private fun LazyListScope.node(
|
|||
if (node.children.isNotEmpty()) {
|
||||
nodes(
|
||||
node.children,
|
||||
htmlConverter = htmlConverter,
|
||||
toggleExpanded = toggleExpanded,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ import dev.msfjarvis.claw.model.LobstersPostDetails
|
|||
private fun CommentsPageInternal(
|
||||
details: LobstersPostDetails,
|
||||
postActions: PostActions,
|
||||
htmlConverter: HTMLConverter,
|
||||
commentState: PostComments?,
|
||||
markSeenComments: (String, List<Comment>) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
|
@ -54,6 +55,7 @@ private fun CommentsPageInternal(
|
|||
CommentsHeader(
|
||||
postDetails = details,
|
||||
postActions = postActions,
|
||||
htmlConverter = htmlConverter,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -68,6 +70,7 @@ private fun CommentsPageInternal(
|
|||
|
||||
nodes(
|
||||
nodes = commentNodes,
|
||||
htmlConverter = htmlConverter,
|
||||
toggleExpanded = { node ->
|
||||
val newNode = setExpanded(node, !node.isExpanded)
|
||||
val parent = findTopMostParent(newNode)
|
||||
|
@ -99,6 +102,7 @@ private fun CommentsPageInternal(
|
|||
fun CommentsPage(
|
||||
postId: String,
|
||||
postActions: PostActions,
|
||||
htmlConverter: HTMLConverter,
|
||||
getSeenComments: suspend (String) -> PostComments?,
|
||||
markSeenComments: (String, List<Comment>) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
|
@ -119,6 +123,7 @@ fun CommentsPage(
|
|||
CommentsPageInternal(
|
||||
details = (postDetails as Success<LobstersPostDetails>).data,
|
||||
postActions = postActions,
|
||||
htmlConverter = htmlConverter,
|
||||
commentState = commentState,
|
||||
markSeenComments = markSeenComments,
|
||||
modifier = modifier.fillMaxSize(),
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright © 2023 Harsh Shandilya.
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
package dev.msfjarvis.claw.common.comments
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
|
||||
/** Defines a contract to convert strings of HTML to Markdown. */
|
||||
@Stable
|
||||
fun interface HTMLConverter {
|
||||
fun convertHTMLToMarkdown(html: String): String
|
||||
}
|
|
@ -17,6 +17,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.ProvidedValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.halilibo.richtext.ui.material3.SetupMaterial3RichText
|
||||
|
||||
private val LightThemeColors =
|
||||
lightColorScheme(
|
||||
|
@ -97,6 +98,8 @@ fun LobstersTheme(
|
|||
else -> if (darkTheme) DarkThemeColors else LightThemeColors
|
||||
}
|
||||
CompositionLocalProvider(*providedValues) {
|
||||
MaterialTheme(colorScheme = colorScheme, typography = AppTypography, content = content)
|
||||
MaterialTheme(colorScheme = colorScheme, typography = AppTypography) {
|
||||
SetupMaterial3RichText { content() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,220 +0,0 @@
|
|||
/*
|
||||
* Copyright © 2023 Harsh Shandilya.
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
package dev.msfjarvis.claw.common.ui
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.text.Spanned
|
||||
import android.text.style.BulletSpan
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.QuoteSpan
|
||||
import android.text.style.RelativeSizeSpan
|
||||
import android.text.style.StrikethroughSpan
|
||||
import android.text.style.StyleSpan
|
||||
import android.text.style.SubscriptSpan
|
||||
import android.text.style.SuperscriptSpan
|
||||
import android.text.style.URLSpan
|
||||
import android.text.style.UnderlineSpan
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextLayoutResult
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.BaselineShift
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.text.HtmlCompat
|
||||
import dev.msfjarvis.claw.common.theme.LobstersTheme
|
||||
|
||||
private const val URL_TAG = "url_tag"
|
||||
|
||||
@Composable
|
||||
fun HTMLText(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
style: TextStyle = MaterialTheme.typography.bodyLarge,
|
||||
softWrap: Boolean = true,
|
||||
overflow: TextOverflow = TextOverflow.Clip,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
onTextLayout: (TextLayoutResult) -> Unit = {},
|
||||
fontSize: TextUnit = 14.sp,
|
||||
flags: Int = HtmlCompat.FROM_HTML_MODE_COMPACT,
|
||||
customSpannedHandler: ((Spanned) -> AnnotatedString)? = null
|
||||
) {
|
||||
val content = text.asHTML(fontSize, flags, customSpannedHandler)
|
||||
val uriHandler = LocalUriHandler.current
|
||||
ClickableText(
|
||||
modifier = modifier,
|
||||
text = content,
|
||||
style = style,
|
||||
softWrap = softWrap,
|
||||
overflow = overflow,
|
||||
maxLines = maxLines,
|
||||
onTextLayout = onTextLayout,
|
||||
onClick = { offset ->
|
||||
content.getStringAnnotations(URL_TAG, offset, offset).firstOrNull()?.let { stringAnnotation ->
|
||||
uriHandler.openUri(stringAnnotation.item)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun String.asHTML(
|
||||
fontSize: TextUnit,
|
||||
flags: Int,
|
||||
customSpannedHandler: ((Spanned) -> AnnotatedString)? = null
|
||||
) = buildAnnotatedString {
|
||||
val spanned = HtmlCompat.fromHtml(this@asHTML, flags)
|
||||
val spans = spanned.getSpans(0, spanned.length, Any::class.java)
|
||||
|
||||
if (customSpannedHandler != null) {
|
||||
append(customSpannedHandler(spanned))
|
||||
} else {
|
||||
append(spanned.toString())
|
||||
}
|
||||
|
||||
spans
|
||||
.filter { it !is BulletSpan }
|
||||
.forEach { span ->
|
||||
val start = spanned.getSpanStart(span)
|
||||
val end = spanned.getSpanEnd(span)
|
||||
when (span) {
|
||||
is RelativeSizeSpan -> span.spanStyle(fontSize)
|
||||
is StyleSpan -> span.spanStyle()
|
||||
is UnderlineSpan -> span.spanStyle()
|
||||
is ForegroundColorSpan -> span.spanStyle()
|
||||
is StrikethroughSpan -> span.spanStyle()
|
||||
is SuperscriptSpan -> span.spanStyle()
|
||||
is SubscriptSpan -> span.spanStyle()
|
||||
is QuoteSpan -> span.spanStyle()
|
||||
is URLSpan -> {
|
||||
addStringAnnotation(tag = URL_TAG, annotation = span.url, start = start, end = end)
|
||||
span.spanStyle()
|
||||
}
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
}?.let { spanStyle -> addStyle(spanStyle, start, end) }
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
@Composable
|
||||
private fun URLSpan.spanStyle(): SpanStyle =
|
||||
SpanStyle(
|
||||
background = MaterialTheme.colorScheme.surfaceVariant,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
fontWeight = FontWeight.Bold,
|
||||
textDecoration = TextDecoration.Underline,
|
||||
)
|
||||
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
private fun UnderlineSpan.spanStyle(): SpanStyle =
|
||||
SpanStyle(textDecoration = TextDecoration.Underline)
|
||||
|
||||
private fun ForegroundColorSpan.spanStyle(): SpanStyle = SpanStyle(color = Color(foregroundColor))
|
||||
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
private fun StrikethroughSpan.spanStyle(): SpanStyle =
|
||||
SpanStyle(textDecoration = TextDecoration.LineThrough)
|
||||
|
||||
private fun RelativeSizeSpan.spanStyle(fontSize: TextUnit): SpanStyle =
|
||||
SpanStyle(fontSize = (fontSize.value * sizeChange).sp)
|
||||
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
private fun QuoteSpan.spanStyle(): SpanStyle = SpanStyle(fontStyle = FontStyle.Italic)
|
||||
|
||||
private fun StyleSpan.spanStyle(): SpanStyle? =
|
||||
when (style) {
|
||||
Typeface.BOLD -> SpanStyle(fontWeight = FontWeight.Bold)
|
||||
Typeface.ITALIC -> SpanStyle(fontStyle = FontStyle.Italic)
|
||||
Typeface.BOLD_ITALIC ->
|
||||
SpanStyle(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontStyle = FontStyle.Italic,
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
private fun SubscriptSpan.spanStyle(): SpanStyle =
|
||||
SpanStyle(baselineShift = BaselineShift.Subscript)
|
||||
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
private fun SuperscriptSpan.spanStyle(): SpanStyle =
|
||||
SpanStyle(baselineShift = BaselineShift.Superscript)
|
||||
|
||||
@Composable
|
||||
private fun ClickableText(
|
||||
text: AnnotatedString,
|
||||
modifier: Modifier = Modifier,
|
||||
style: TextStyle = TextStyle.Default,
|
||||
softWrap: Boolean = true,
|
||||
overflow: TextOverflow = TextOverflow.Clip,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
onTextLayout: (TextLayoutResult) -> Unit = {},
|
||||
onClick: (Int) -> Unit
|
||||
) {
|
||||
val layoutResult = remember { mutableStateOf<TextLayoutResult?>(null) }
|
||||
val pressIndicator =
|
||||
Modifier.pointerInput(onClick) {
|
||||
detectTapGestures { pos ->
|
||||
layoutResult.value?.let { layoutResult -> onClick(layoutResult.getOffsetForPosition(pos)) }
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = text,
|
||||
modifier = modifier.then(pressIndicator),
|
||||
style = style,
|
||||
softWrap = softWrap,
|
||||
overflow = overflow,
|
||||
maxLines = maxLines,
|
||||
onTextLayout = {
|
||||
layoutResult.value = it
|
||||
onTextLayout(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun HTMLTextPreview() {
|
||||
LobstersTheme {
|
||||
Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
|
||||
HTMLText(
|
||||
text =
|
||||
"""
|
||||
<h3 id="heading">Heading</h3>
|
||||
<p>This is a paragraph body</p>
|
||||
<pre><code>This is <span class="hljs-selector-tag">a</span> <span class="hljs-selector-tag">code</span> block
|
||||
</code></pre><p>This is an <code>inline code block</code></p>
|
||||
<blockquote><p>This is a blockquote</p></blockquote>
|
||||
<p><a href="https://github.com/msfjarvis/compose-lobsters">This is a link</a></p>
|
||||
<p><img src="https://avatars.githubusercontent.com/u/13348378?v=4" alt="Image"></p>
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright © 2023 Harsh Shandilya.
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
package dev.msfjarvis.claw.common.ui
|
||||
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import com.halilibo.richtext.markdown.Markdown
|
||||
import com.halilibo.richtext.ui.RichText
|
||||
import com.halilibo.richtext.ui.RichTextStyle
|
||||
import com.halilibo.richtext.ui.string.RichTextStringStyle
|
||||
import dev.msfjarvis.claw.common.theme.LobstersTheme
|
||||
import dev.msfjarvis.claw.common.ui.preview.ThemePreviews
|
||||
|
||||
@Composable
|
||||
internal fun ThemedRichText(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val linkStyle =
|
||||
SpanStyle(
|
||||
background = MaterialTheme.colorScheme.surfaceVariant,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
fontWeight = FontWeight.Bold,
|
||||
textDecoration = TextDecoration.Underline,
|
||||
)
|
||||
val stringStyle = RichTextStringStyle.Default.copy(linkStyle = linkStyle)
|
||||
CompositionLocalProvider(
|
||||
LocalTextStyle provides MaterialTheme.typography.bodyLarge,
|
||||
LocalContentColor provides MaterialTheme.colorScheme.onBackground,
|
||||
) {
|
||||
RichText(
|
||||
modifier = modifier,
|
||||
style = RichTextStyle.Default.copy(stringStyle = stringStyle),
|
||||
) {
|
||||
Markdown(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@Composable
|
||||
internal fun ThemedRichTextPreview() {
|
||||
val text =
|
||||
"""
|
||||
### Heading
|
||||
This is a paragraph body
|
||||
|
||||
```
|
||||
This is a code block
|
||||
```
|
||||
|
||||
This is an `inline code block`
|
||||
|
||||
[This is a link](https://github.com/msfjarvis/compose-lobsters)
|
||||
|
||||

|
||||
"""
|
||||
.trimIndent()
|
||||
|
||||
LobstersTheme { ThemedRichText(text = text) }
|
||||
}
|
|
@ -29,10 +29,10 @@ import dev.msfjarvis.claw.common.NetworkState
|
|||
import dev.msfjarvis.claw.common.NetworkState.Error
|
||||
import dev.msfjarvis.claw.common.NetworkState.Loading
|
||||
import dev.msfjarvis.claw.common.NetworkState.Success
|
||||
import dev.msfjarvis.claw.common.ui.HTMLText
|
||||
import dev.msfjarvis.claw.common.ui.NetworkError
|
||||
import dev.msfjarvis.claw.common.ui.NetworkImage
|
||||
import dev.msfjarvis.claw.common.ui.ProgressBar
|
||||
import dev.msfjarvis.claw.common.ui.ThemedRichText
|
||||
import dev.msfjarvis.claw.model.User
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
|
@ -99,10 +99,12 @@ private fun UserProfileInternal(
|
|||
text = user.username,
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
)
|
||||
HTMLText(text = user.about)
|
||||
ThemedRichText(
|
||||
text = user.about,
|
||||
)
|
||||
user.invitedBy?.let { invitedBy ->
|
||||
HTMLText(
|
||||
text = """Invited by <a href="https://lobste.rs/u/${invitedBy}">${invitedBy}</a>""",
|
||||
ThemedRichText(
|
||||
text = "Invited by [${invitedBy}](https://lobste.rs/u/${user.invitedBy})",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ dagger = "2.46.1"
|
|||
junit = "5.9.3"
|
||||
kotlin = "1.8.10"
|
||||
retrofit = "2.9.0"
|
||||
richtext = "0.16.0"
|
||||
sentry-sdk = "6.24.0"
|
||||
serialization = "1.5.1"
|
||||
sqldelight = "2.0.0-rc02"
|
||||
|
@ -56,6 +57,10 @@ build-vcu = "nl.littlerobots.version-catalog-update:nl.littlerobots.version-cata
|
|||
build-versions = "com.github.ben-manes:gradle-versions-plugin:0.47.0"
|
||||
coil = { module = "io.coil-kt:coil", version.ref = "coil" }
|
||||
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
|
||||
compose-richtext-markdown = { module = "com.halilibo.compose-richtext:richtext-commonmark", version.ref = "richtext" }
|
||||
compose-richtext-material3 = { module = "com.halilibo.compose-richtext:richtext-ui-material3", version.ref = "richtext" }
|
||||
compose-richtext-ui = { module = "com.halilibo.compose-richtext:richtext-ui", version.ref = "richtext" }
|
||||
copydown = "io.github.furstenheim:copy_down:1.1"
|
||||
crux = "com.chimbori.crux:crux:3.12.0"
|
||||
dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" }
|
||||
dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue