diff --git a/app/design-system/design-system-hedvig/build.gradle.kts b/app/design-system/design-system-hedvig/build.gradle.kts index a974b9fe04..41ddca940c 100644 --- a/app/design-system/design-system-hedvig/build.gradle.kts +++ b/app/design-system/design-system-hedvig/build.gradle.kts @@ -45,18 +45,13 @@ kotlin { implementation(libs.jetbrains.lifecycle.runtime.compose) implementation(libs.jetbrains.navigationevent.compose) implementation(libs.kotlinx.datetime) + implementation(libs.mikepenz.markdown) implementation(projects.composeUi) implementation(projects.coreResources) implementation(projects.coreUiData) implementation(projects.designSystemInternals) implementation(projects.navigationCore) } - val jvmAndAndroidMain by getting { - dependencies { - implementation(libs.compose.richtext) - implementation(libs.compose.richtextCommonmark) - } - } androidMain.dependencies { implementation(libs.androidx.other.core) implementation(libs.media3.exoplayer) diff --git a/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/RichText.kt b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/RichText.kt new file mode 100644 index 0000000000..bbb25dc965 --- /dev/null +++ b/app/design-system/design-system-hedvig/src/commonMain/kotlin/com/hedvig/android/design/system/hedvig/RichText.kt @@ -0,0 +1,118 @@ +package com.hedvig.android.design.system.hedvig + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.runtime.Composable +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.style.TextDecoration +import androidx.compose.ui.text.withLink +import androidx.compose.ui.unit.dp +import com.mikepenz.markdown.compose.Markdown +import com.mikepenz.markdown.model.MarkdownColors +import com.mikepenz.markdown.model.MarkdownPadding +import com.mikepenz.markdown.model.MarkdownTypography + +/** + * Renders Markdown content with Hedvig theming. + */ +@Composable +fun HedvigMarkdownText(content: String, modifier: Modifier = Modifier) { + val colors = HedvigTheme.colorScheme + val typography = HedvigTheme.typography + + val markdownColors = object : MarkdownColors { + override val text: Color = colors.textPrimary + override val codeBackground: Color = colors.surfaceSecondary + override val inlineCodeBackground: Color = colors.surfaceSecondary + override val dividerColor: Color = colors.borderPrimary + override val tableBackground: Color = colors.surfaceSecondary + } + + val markdownTypography = object : MarkdownTypography { + override val h1 = typography.headlineLarge.copy(color = colors.textPrimary) + override val h2 = typography.headlineMedium.copy(color = colors.textPrimary) + override val h3 = typography.headlineSmall.copy(color = colors.textPrimary) + override val h4 = typography.displaySmall.copy(color = colors.textPrimary) + override val h5 = typography.bodyLarge.copy(color = colors.textPrimary) + override val h6 = typography.bodyMedium.copy(color = colors.textPrimary) + override val text = typography.bodySmall.copy(color = colors.textPrimary) + override val paragraph = typography.bodySmall.copy(color = colors.textPrimary) + override val code = typography.label + override val inlineCode = typography.label + override val bullet = typography.bodySmall.copy(color = colors.textPrimary) + override val list = typography.bodySmall.copy(color = colors.textPrimary) + override val ordered = typography.bodySmall.copy(color = colors.textPrimary) + override val quote = typography.bodySmall.copy(color = colors.textPrimary) + override val table = typography.bodySmall.copy(color = colors.textPrimary) + override val textLink = TextLinkStyles( + style = typography.bodySmall.copy(color = colors.link).toSpanStyle(), + focusedStyle = typography.bodySmall.copy(color = colors.link).toSpanStyle(), + hoveredStyle = typography.bodySmall.copy(color = colors.link).toSpanStyle(), + pressedStyle = typography.bodySmall.copy(color = colors.link).toSpanStyle(), + ) + } + + Markdown( + content = content, + modifier = modifier, + colors = markdownColors, + typography = markdownTypography, + padding = object : MarkdownPadding { + override val block = 0.dp + override val blockQuote = PaddingValues(0.dp) + override val blockQuoteBar = PaddingValues.Absolute(0.dp) + override val blockQuoteText = PaddingValues(0.dp) + override val codeBlock = PaddingValues(0.dp) + override val list = 0.dp + override val listIndent = 0.dp + override val listItemBottom = 0.dp + override val listItemTop = 0.dp + }, + ) +} + +/** + * [tag] is also used to name the a11y action, so it must be an appropriate user-facing copy + */ +@Composable +inline fun AnnotatedString.Builder.withHedvigLink( + tag: String, + noinline onClick: () -> Unit, + block: AnnotatedString.Builder.() -> R, +): R { + return withLink( + LinkAnnotation.Clickable( + tag = tag, + linkInteractionListener = { onClick() }, + styles = TextLinkStyles( + SpanStyle( + textDecoration = TextDecoration.Underline, + color = HedvigTheme.colorScheme.link, + ), + ), + ), + ) { + block() + } +} + +@Composable +inline fun AnnotatedString.Builder.withHedvigLink(url: String, block: AnnotatedString.Builder.() -> R): R { + return withLink( + LinkAnnotation.Url( + url = url, + styles = TextLinkStyles( + SpanStyle( + textDecoration = TextDecoration.Underline, + color = HedvigTheme.colorScheme.link, + ), + ), + ), + ) { + block() + } +} diff --git a/app/design-system/design-system-hedvig/src/jvmAndAndroidMain/kotlin/com/hedvig/android/design/system/hedvig/RichText.kt b/app/design-system/design-system-hedvig/src/jvmAndAndroidMain/kotlin/com/hedvig/android/design/system/hedvig/RichText.kt deleted file mode 100644 index 1a31361a68..0000000000 --- a/app/design-system/design-system-hedvig/src/jvmAndAndroidMain/kotlin/com/hedvig/android/design/system/hedvig/RichText.kt +++ /dev/null @@ -1,124 +0,0 @@ -package com.hedvig.android.design.system.hedvig - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.compositionLocalOf -import androidx.compose.ui.Modifier -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.style.TextDecoration -import androidx.compose.ui.text.withLink -import com.halilibo.richtext.ui.BasicRichText -import com.halilibo.richtext.ui.RichTextScope -import com.halilibo.richtext.ui.RichTextStyle -import com.halilibo.richtext.ui.RichTextThemeProvider -import com.halilibo.richtext.ui.merge -import com.halilibo.richtext.ui.string.RichTextStringStyle - -/** - * RichText implementation that integrates with HedvigTheme. - * - */ -@Composable -fun RichText( - modifier: Modifier = Modifier, - style: RichTextStyle? = null, - children: @Composable RichTextScope.() -> Unit, -) { - val linkColor = HedvigTheme.colorScheme.link - val linkStyle = RichTextStyle.Default.copy( - stringStyle = RichTextStringStyle( - linkStyle = TextLinkStyles( - SpanStyle( - textDecoration = TextDecoration.Underline, - color = linkColor, - ), - ), - ), - ) - RichTextHedvigTheme { - BasicRichText( - modifier = modifier, - style = linkStyle.merge(style), - children = children, - ) - } -} - -/** - * [tag] is also used to name the a11y action, so it must be an appropriate user-facing copy - */ -@Composable -inline fun AnnotatedString.Builder.withHedvigLink( - tag: String, - noinline onClick: () -> Unit, - block: AnnotatedString.Builder.() -> R, -): R { - return withLink( - LinkAnnotation.Clickable( - tag = tag, - linkInteractionListener = { onClick() }, - styles = TextLinkStyles( - SpanStyle( - textDecoration = TextDecoration.Underline, - color = HedvigTheme.colorScheme.link, - ), - ), - ), - ) { - block() - } -} - -@Composable -inline fun AnnotatedString.Builder.withHedvigLink(url: String, block: AnnotatedString.Builder.() -> R): R { - return withLink( - LinkAnnotation.Url( - url = url, - styles = TextLinkStyles( - SpanStyle( - textDecoration = TextDecoration.Underline, - color = HedvigTheme.colorScheme.link, - ), - ), - ), - ) { - block() - } -} - -/** - * Wraps the given [child] with Hedvig Theme integration for [com.halilibo.richtext.ui.BasicRichText]. - * - * This function also keeps track of the parent context by using CompositionLocals - * to not apply Material Theming if it already exists in the current composition. - */ -@Composable -private fun RichTextHedvigTheme(child: @Composable () -> Unit) { - val isApplied = LocalThemingApplied.current - - if (!isApplied) { - RichTextThemeProvider( - textStyleProvider = { LocalTextStyle.current }, - contentColorProvider = { LocalContentColor.current }, - textStyleBackProvider = { textStyle, content -> - ProvideTextStyle(textStyle, content) - }, - contentColorBackProvider = { color, content -> - CompositionLocalProvider(LocalContentColor provides color) { - content() - } - }, - ) { - CompositionLocalProvider(LocalThemingApplied provides true) { - child() - } - } - } else { - child() - } -} - -private val LocalThemingApplied = compositionLocalOf { false } diff --git a/app/feature/feature-chat/build.gradle.kts b/app/feature/feature-chat/build.gradle.kts index fa25515dd1..f902753fd4 100644 --- a/app/feature/feature-chat/build.gradle.kts +++ b/app/feature/feature-chat/build.gradle.kts @@ -21,10 +21,9 @@ dependencies { implementation(libs.arrow.core) implementation(libs.arrow.fx) implementation(libs.coil.compose) - implementation(libs.compose.richtext) - implementation(libs.compose.richtextCommonmark) implementation(libs.coroutines.core) implementation(libs.jetbrains.lifecycle.runtime.compose) + implementation(libs.jetbrains.markdown) implementation(libs.koin.composeViewModel) implementation(libs.koin.core) implementation(libs.kotlinx.datetime) diff --git a/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/inbox/MarkdownPlainText.kt b/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/inbox/MarkdownPlainText.kt index 22466fe926..ddd676e4b8 100644 --- a/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/inbox/MarkdownPlainText.kt +++ b/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/inbox/MarkdownPlainText.kt @@ -1,47 +1,46 @@ package com.hedvig.android.feature.chat.inbox -import com.halilibo.richtext.commonmark.CommonMarkdownParseOptions -import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser -import com.halilibo.richtext.markdown.node.AstBlockNodeType -import com.halilibo.richtext.markdown.node.AstCode -import com.halilibo.richtext.markdown.node.AstHardLineBreak -import com.halilibo.richtext.markdown.node.AstImage -import com.halilibo.richtext.markdown.node.AstNode -import com.halilibo.richtext.markdown.node.AstSoftLineBreak -import com.halilibo.richtext.markdown.node.AstText +import org.intellij.markdown.IElementType +import org.intellij.markdown.MarkdownElementTypes +import org.intellij.markdown.ast.ASTNode +import org.intellij.markdown.ast.getTextInNode +import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor +import org.intellij.markdown.parser.MarkdownParser // The inbox conversation row shows a single ellipsized preview line with no clickable regions, -// so we strip markdown formatting rather than render it. Walking the commonmark AST means any +// so we strip markdown formatting rather than render it. Walking the markdown AST means any // syntax the parser understands collapses to its text content, so future markdown features need // no changes here. -internal fun String.markdownToPlainText(): String = buildString { - appendPlainText(inboxMarkdownParser.parse(this@markdownToPlainText)) +internal fun String.markdownToPlainText(): String { + val flavour = CommonMarkFlavourDescriptor() + val tree = MarkdownParser(flavour).buildMarkdownTreeFromString(this) + return buildString { + appendPlainText(tree, this@markdownToPlainText) + } } -private val inboxMarkdownParser = CommonmarkAstNodeParser(CommonMarkdownParseOptions(autolink = false)) +private fun StringBuilder.appendPlainText(node: ASTNode, src: String) { + val nodeType = node.type -private fun StringBuilder.appendPlainText(node: AstNode) { - val type = node.type - if (type is AstText) { - append(type.literal) - return - } - if (type is AstCode) { - append(type.literal) + // Check if this is a text node by comparing with known IElementType instances + if (nodeType.toString().contains("TEXT") || nodeType.toString().contains("Code")) { + append(node.getTextInNode(src)) return } - if (type === AstSoftLineBreak || type === AstHardLineBreak) { + + // EOL creates a space + if (nodeType.toString().contains("EOL")) { append(' ') return } - // Alt text isn't useful in a one-line preview, so we drop the image entirely. - if (type is AstImage) return - // Container node — recurse into children, separating block-level siblings with a space so - // paragraphs don't run together. - var child = node.links.firstChild - while (child != null) { - appendPlainText(child) - if (child.links.next != null && child.type is AstBlockNodeType) append(' ') - child = child.links.next + + // Drop images + if (nodeType == MarkdownElementTypes.IMAGE) return + + // Recurse into children + node.children.forEach { child -> + appendPlainText(child, src) + // Add space between block elements + if (child.type.toString().contains("Block")) append(' ') } } diff --git a/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/ui/ChatBanner.kt b/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/ui/ChatBanner.kt index 3e18927c2c..f1655d459e 100644 --- a/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/ui/ChatBanner.kt +++ b/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/ui/ChatBanner.kt @@ -4,7 +4,7 @@ import androidx.compose.foundation.background import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewFontScale -import com.halilibo.richtext.commonmark.Markdown +import com.hedvig.android.design.system.hedvig.HedvigMarkdownText import com.hedvig.android.design.system.hedvig.HedvigNotificationCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigTheme @@ -12,7 +12,6 @@ import com.hedvig.android.design.system.hedvig.NotificationDefaults import com.hedvig.android.design.system.hedvig.NotificationDefaults.InfoCardStyle.Default import com.hedvig.android.design.system.hedvig.NotificationDefaults.NotificationPriority.Info import com.hedvig.android.design.system.hedvig.ProvideTextStyle -import com.hedvig.android.design.system.hedvig.RichText import com.hedvig.android.design.system.hedvig.Surface import hedvig.resources.Res import hedvig.resources.general_close_button @@ -28,11 +27,7 @@ internal fun ChatBanner( ProvideTextStyle(HedvigTheme.typography.label.copy(color = HedvigTheme.colorScheme.signalBlueText)) { HedvigNotificationCard( content = { - RichText { - Markdown( - content = text, - ) - } + HedvigMarkdownText(content = text) }, priority = Info, modifier = modifier diff --git a/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/ui/TextWithClickableUrls.kt b/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/ui/TextWithClickableUrls.kt index a511d9b6ed..4edfc19aa3 100644 --- a/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/ui/TextWithClickableUrls.kt +++ b/app/feature/feature-chat/src/main/kotlin/com/hedvig/android/feature/chat/ui/TextWithClickableUrls.kt @@ -4,9 +4,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.text.TextStyle -import com.halilibo.richtext.commonmark.Markdown +import com.hedvig.android.design.system.hedvig.HedvigMarkdownText import com.hedvig.android.design.system.hedvig.ProvideTextStyle -import com.hedvig.android.design.system.hedvig.RichText @OptIn(ExperimentalTextApi::class) @Composable @@ -14,12 +13,9 @@ internal fun TextWithClickableUrls(text: String, modifier: Modifier = Modifier, ProvideTextStyle( style, ) { - RichText( + HedvigMarkdownText( + content = text, modifier = modifier, - ) { - Markdown( - content = text, - ) - } + ) } } diff --git a/app/feature/feature-delete-account/build.gradle.kts b/app/feature/feature-delete-account/build.gradle.kts index a4692a71ca..d6bcfe5229 100644 --- a/app/feature/feature-delete-account/build.gradle.kts +++ b/app/feature/feature-delete-account/build.gradle.kts @@ -17,8 +17,6 @@ dependencies { implementation(libs.androidx.navigation.compose) implementation(libs.apollo.normalizedCache) implementation(libs.arrow.core) - implementation(libs.compose.richtext) - implementation(libs.compose.richtextCommonmark) implementation(libs.coroutines.core) implementation(libs.jetbrains.lifecycle.runtime.compose) implementation(libs.koin.composeViewModel) diff --git a/app/feature/feature-delete-account/src/main/kotlin/com/hedvig/android/feature/deleteaccount/DeleteAccountDestination.kt b/app/feature/feature-delete-account/src/main/kotlin/com/hedvig/android/feature/deleteaccount/DeleteAccountDestination.kt index c425c35185..c7b8dad98e 100644 --- a/app/feature/feature-delete-account/src/main/kotlin/com/hedvig/android/feature/deleteaccount/DeleteAccountDestination.kt +++ b/app/feature/feature-delete-account/src/main/kotlin/com/hedvig/android/feature/deleteaccount/DeleteAccountDestination.kt @@ -12,16 +12,15 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.halilibo.richtext.commonmark.Markdown import com.hedvig.android.design.system.hedvig.ButtonDefaults import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigErrorSection import com.hedvig.android.design.system.hedvig.HedvigFullScreenCenterAlignedProgress +import com.hedvig.android.design.system.hedvig.HedvigMarkdownText import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigScaffold import com.hedvig.android.design.system.hedvig.HedvigText import com.hedvig.android.design.system.hedvig.HedvigTheme -import com.hedvig.android.design.system.hedvig.RichText import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.feature.chat.DeleteAccountViewModel import hedvig.resources.DELETE_ACCOUNT_DELETE_ACCOUNT_DESCRIPTION @@ -122,15 +121,12 @@ private fun DeleteScreenContents( .padding(horizontal = 16.dp), ) Spacer(Modifier.height(32.dp)) - RichText( + HedvigMarkdownText( + content = description, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), - ) { - Markdown( - content = description, - ) - } + ) Spacer(Modifier.height(16.dp)) Spacer(Modifier.weight(1f)) Spacer(Modifier.height(8.dp)) diff --git a/app/feature/feature-help-center/build.gradle.kts b/app/feature/feature-help-center/build.gradle.kts index 79aa961a86..4da8050c43 100644 --- a/app/feature/feature-help-center/build.gradle.kts +++ b/app/feature/feature-help-center/build.gradle.kts @@ -27,6 +27,7 @@ kotlin { implementation(libs.koin.composeViewModel) implementation(libs.koin.core) implementation(libs.kotlinx.serialization.core) + implementation(libs.mikepenz.markdown) implementation(projects.apolloCore) implementation(projects.apolloOctopusPublic) implementation(projects.composeUi) @@ -48,15 +49,8 @@ kotlin { } androidMain.dependencies { implementation(libs.bundles.kmpPreviewBugWorkaround) - implementation(libs.compose.richtext) - implementation(libs.compose.richtextCommonmark) } jvmMain.dependencies { - implementation(libs.compose.richtext) - implementation(libs.compose.richtextCommonmark) - } - nativeMain.dependencies { - implementation(libs.mikepenz.markdown) } androidInstrumentedTest.dependencies { implementation(libs.apollo.testingSupport) diff --git a/app/feature/feature-help-center/src/androidMain/kotlin/com/hedvig/android/feature/help/center/ui/MarkdownText.android.kt b/app/feature/feature-help-center/src/androidMain/kotlin/com/hedvig/android/feature/help/center/ui/MarkdownText.android.kt deleted file mode 100644 index 8af0c459e3..0000000000 --- a/app/feature/feature-help-center/src/androidMain/kotlin/com/hedvig/android/feature/help/center/ui/MarkdownText.android.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.hedvig.android.feature.help.center.ui - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.TextLinkStyles -import androidx.compose.ui.unit.sp -import androidx.compose.ui.text.style.TextDecoration -import com.halilibo.richtext.commonmark.Markdown -import com.halilibo.richtext.ui.RichTextStyle -import com.halilibo.richtext.ui.string.RichTextStringStyle -import com.hedvig.android.design.system.hedvig.HedvigTheme -import com.hedvig.android.design.system.hedvig.RichText - -@Composable -actual fun MarkdownText(markdown: String, modifier: Modifier, withArticleStyle: Boolean) { - val headingColor = HedvigTheme.colorScheme.textPrimary - val linkColor = HedvigTheme.colorScheme.link - val style = if (withArticleStyle) { - RichTextStyle( - paragraphSpacing = 12.sp, - headingStyle = { _, currentStyle -> - currentStyle.copy( - color = headingColor, - ) - }, - stringStyle = RichTextStringStyle( - boldStyle = SpanStyle( - color = headingColor, - ), - linkStyle = TextLinkStyles( - SpanStyle( - color = linkColor, - textDecoration = TextDecoration.Underline, - ), - ), - ), - ) - } else { - null - } - RichText( - modifier = modifier, - style = style, - ) { - Markdown(content = markdown) - } -} diff --git a/app/feature/feature-help-center/src/commonMain/kotlin/com/hedvig/android/feature/help/center/ui/MarkdownText.kt b/app/feature/feature-help-center/src/commonMain/kotlin/com/hedvig/android/feature/help/center/ui/MarkdownText.kt index 2d1a9cf403..ed1d849cbd 100644 --- a/app/feature/feature-help-center/src/commonMain/kotlin/com/hedvig/android/feature/help/center/ui/MarkdownText.kt +++ b/app/feature/feature-help-center/src/commonMain/kotlin/com/hedvig/android/feature/help/center/ui/MarkdownText.kt @@ -1,16 +1,139 @@ package com.hedvig.android.feature.help.center.ui +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextLinkStyles +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.hedvig.android.design.system.hedvig.HedvigTheme +import com.mikepenz.markdown.compose.Markdown +import com.mikepenz.markdown.model.MarkdownColors +import com.mikepenz.markdown.model.MarkdownPadding +import com.mikepenz.markdown.model.MarkdownTypography -/** - * Renders Markdown content. - * On Android: Uses RichText library for full Markdown rendering - * On iOS/other platforms: Uses com.mikepenz.markdown lib - */ @Composable -expect fun MarkdownText(markdown: String, modifier: Modifier = Modifier, withArticleStyle: Boolean = false) +fun MarkdownText(markdown: String, modifier: Modifier = Modifier, withArticleStyle: Boolean = false) { + val colors = HedvigTheme.colorScheme + val typography = HedvigTheme.typography + val headingColor = colors.textPrimary + + val markdownColors = object : MarkdownColors { + override val text: Color = colors.textSecondaryTranslucent + override val codeBackground: Color = colors.surfaceSecondary + override val inlineCodeBackground: Color = colors.surfaceSecondary + override val dividerColor: Color = colors.borderPrimary + override val tableBackground: Color = colors.surfaceSecondary + } + + val markdownTypography = if (withArticleStyle) { + object : MarkdownTypography { + override val h1: TextStyle = typography.bodySmall.copy( + color = headingColor, + fontWeight = FontWeight.Normal, + ) + override val h2: TextStyle = typography.bodySmall.copy( + color = headingColor, + fontWeight = FontWeight.Normal, + ) + override val h3: TextStyle = typography.bodySmall.copy( + color = headingColor, + fontWeight = FontWeight.Normal, + ) + override val h4: TextStyle = typography.bodySmall.copy( + color = headingColor, + fontWeight = FontWeight.Normal, + ) + override val h5: TextStyle = typography.bodySmall.copy( + color = headingColor, + fontWeight = FontWeight.Normal, + ) + override val h6: TextStyle = typography.bodySmall.copy( + color = headingColor, + fontWeight = FontWeight.Normal, + ) + override val text: TextStyle = typography.bodySmall.copy( + color = colors.textSecondaryTranslucent, + fontWeight = FontWeight.Normal, + ) + override val paragraph: TextStyle = typography.bodySmall.copy( + color = colors.textSecondaryTranslucent, + fontWeight = FontWeight.Normal, + ) + override val code: TextStyle = typography.label + override val inlineCode: TextStyle = typography.label + override val bullet: TextStyle = typography.bodySmall.copy( + color = colors.textSecondaryTranslucent, + ) + override val list: TextStyle = typography.bodySmall.copy( + color = colors.textSecondaryTranslucent, + ) + override val ordered: TextStyle = typography.bodySmall.copy( + color = colors.textSecondaryTranslucent, + ) + override val quote: TextStyle = typography.bodySmall.copy( + color = colors.textSecondaryTranslucent, + ) + override val table: TextStyle = typography.bodySmall.copy( + color = colors.textSecondaryTranslucent, + ) + override val textLink = TextLinkStyles( + style = SpanStyle(color = colors.link, textDecoration = TextDecoration.Underline), + focusedStyle = SpanStyle(color = colors.link, textDecoration = TextDecoration.Underline), + hoveredStyle = SpanStyle(color = colors.link, textDecoration = TextDecoration.Underline), + pressedStyle = SpanStyle(color = colors.link, textDecoration = TextDecoration.Underline), + ) + } + } else { + object : MarkdownTypography { + override val h1: TextStyle = typography.headlineLarge + override val h2: TextStyle = typography.headlineMedium + override val h3: TextStyle = typography.headlineSmall + override val h4: TextStyle = typography.displaySmall + override val h5: TextStyle = typography.bodyLarge + override val h6: TextStyle = typography.bodyMedium + override val text: TextStyle = typography.bodySmall + override val paragraph: TextStyle = typography.bodySmall + override val code: TextStyle = typography.label + override val inlineCode: TextStyle = typography.label + override val bullet: TextStyle = typography.bodySmall + override val list: TextStyle = typography.bodySmall + override val ordered: TextStyle = typography.bodySmall + override val quote: TextStyle = typography.bodySmall + override val table: TextStyle = typography.bodySmall + override val textLink = TextLinkStyles( + style = SpanStyle(color = colors.link, textDecoration = TextDecoration.Underline), + focusedStyle = SpanStyle(color = colors.link, textDecoration = TextDecoration.Underline), + hoveredStyle = SpanStyle(color = colors.link, textDecoration = TextDecoration.Underline), + pressedStyle = SpanStyle(color = colors.link, textDecoration = TextDecoration.Underline), + ) + } + } + + Markdown( + content = markdown, + modifier = modifier, + colors = markdownColors, + typography = markdownTypography, + padding = object : MarkdownPadding { + override val block: Dp = 6.dp + override val blockQuote: PaddingValues = PaddingValues(0.dp) + override val blockQuoteBar: PaddingValues.Absolute = PaddingValues.Absolute(0.dp) + override val blockQuoteText: PaddingValues = PaddingValues(0.dp) + override val codeBlock: PaddingValues = PaddingValues(0.dp) + override val list: Dp = 0.dp + override val listIndent: Dp = 0.dp + override val listItemBottom: Dp = 0.dp + override val listItemTop: Dp = 0.dp + }, + ) +} fun Int.toHapticFeedbackType(): HapticFeedbackType { return when (this) { diff --git a/app/feature/feature-help-center/src/jvmMain/kotlin/com/hedvig/android/feature/help/center/ui/MarkdownText.jvm.kt b/app/feature/feature-help-center/src/jvmMain/kotlin/com/hedvig/android/feature/help/center/ui/MarkdownText.jvm.kt deleted file mode 100644 index 79d6f17ebb..0000000000 --- a/app/feature/feature-help-center/src/jvmMain/kotlin/com/hedvig/android/feature/help/center/ui/MarkdownText.jvm.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.hedvig.android.feature.help.center.ui - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.halilibo.richtext.commonmark.Markdown -import com.hedvig.android.design.system.hedvig.RichText - -@Composable -actual fun MarkdownText(markdown: String, modifier: Modifier, withArticleStyle: Boolean) { - RichText(modifier = modifier) { - Markdown(content = markdown) - } -} diff --git a/app/feature/feature-help-center/src/nativeMain/kotlin/com/hedvig/android/feature/help/center/ui/MarkdownText.native.kt b/app/feature/feature-help-center/src/nativeMain/kotlin/com/hedvig/android/feature/help/center/ui/MarkdownText.native.kt deleted file mode 100644 index 3218aa53cb..0000000000 --- a/app/feature/feature-help-center/src/nativeMain/kotlin/com/hedvig/android/feature/help/center/ui/MarkdownText.native.kt +++ /dev/null @@ -1,133 +0,0 @@ -package com.hedvig.android.feature.help.center.ui - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.TextLinkStyles -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import com.hedvig.android.design.system.hedvig.HedvigTheme -import com.mikepenz.markdown.compose.Markdown -import com.mikepenz.markdown.model.MarkdownColors -import com.mikepenz.markdown.model.MarkdownPadding -import com.mikepenz.markdown.model.MarkdownTypography - -@Composable -actual fun MarkdownText(markdown: String, modifier: Modifier, withArticleStyle: Boolean) { - val colors = HedvigTheme.colorScheme - val typography = HedvigTheme.typography - val headingColor = colors.textPrimary - - val markdownColors = object : MarkdownColors { - override val text: Color = colors.textSecondaryTranslucent - override val codeBackground: Color = colors.surfaceSecondary - override val inlineCodeBackground: Color = colors.surfaceSecondary - override val dividerColor: Color = colors.borderPrimary - override val tableBackground: Color = colors.surfaceSecondary - } - - val markdownTypography = if (withArticleStyle) { - object : MarkdownTypography { - override val h1: TextStyle = typography.bodySmall.copy( - color = headingColor, - fontWeight = FontWeight.Normal, - ) - override val h2: TextStyle = typography.bodySmall.copy( - color = headingColor, - fontWeight = FontWeight.Normal, - ) - override val h3: TextStyle = typography.bodySmall.copy( - color = headingColor, - fontWeight = FontWeight.Normal, - ) - override val h4: TextStyle = typography.bodySmall.copy( - color = headingColor, - fontWeight = FontWeight.Normal, - ) - override val h5: TextStyle = typography.bodySmall.copy( - color = headingColor, - fontWeight = FontWeight.Normal, - ) - override val h6: TextStyle = typography.bodySmall.copy( - color = headingColor, - fontWeight = FontWeight.Normal, - ) - override val text: TextStyle = typography.bodySmall.copy( - color = colors.textSecondaryTranslucent, - fontWeight = FontWeight.Normal, - ) - override val paragraph: TextStyle = typography.bodySmall.copy( - color = colors.textSecondaryTranslucent, - fontWeight = FontWeight.Normal, - ) - override val code: TextStyle = typography.label - override val inlineCode: TextStyle = typography.label - override val bullet: TextStyle = typography.bodySmall.copy( - color = colors.textSecondaryTranslucent, - ) - override val list: TextStyle = typography.bodySmall.copy( - color = colors.textSecondaryTranslucent, - ) - override val ordered: TextStyle = typography.bodySmall.copy( - color = colors.textSecondaryTranslucent, - ) - override val quote: TextStyle = typography.bodySmall.copy( - color = colors.textSecondaryTranslucent, - ) - override val table: TextStyle = typography.bodySmall.copy( - color = colors.textSecondaryTranslucent, - ) - override val textLink = TextLinkStyles( - style = typography.bodySmall.copy(color = colors.link).toSpanStyle(), - focusedStyle = typography.bodySmall.copy(color = colors.link).toSpanStyle(), - hoveredStyle = typography.bodySmall.copy(color = colors.link).toSpanStyle(), - pressedStyle = typography.bodySmall.copy(color = colors.link).toSpanStyle(), - ) - } - } else { - object : MarkdownTypography { - override val h1: TextStyle = typography.headlineLarge - override val h2: TextStyle = typography.headlineMedium - override val h3: TextStyle = typography.headlineSmall - override val h4: TextStyle = typography.displaySmall - override val h5: TextStyle = typography.bodyLarge - override val h6: TextStyle = typography.bodyMedium - override val text: TextStyle = typography.bodySmall - override val paragraph: TextStyle = typography.bodySmall - override val code: TextStyle = typography.label - override val inlineCode: TextStyle = typography.label - override val bullet: TextStyle = typography.bodySmall - override val list: TextStyle = typography.bodySmall - override val ordered: TextStyle = typography.bodySmall - override val quote: TextStyle = typography.bodySmall - override val table: TextStyle = typography.bodySmall - override val textLink = TextLinkStyles( - style = typography.bodySmall.copy(color = colors.link).toSpanStyle(), - focusedStyle = typography.bodySmall.copy(color = colors.link).toSpanStyle(), - hoveredStyle = typography.bodySmall.copy(color = colors.link).toSpanStyle(), - pressedStyle = typography.bodySmall.copy(color = colors.link).toSpanStyle(), - ) - } - } - - Markdown( - content = markdown, - modifier = modifier, - colors = markdownColors, - typography = markdownTypography, - padding = object : MarkdownPadding { - override val block: Dp = 6.dp - override val blockQuote: PaddingValues = PaddingValues(0.dp) - override val blockQuoteBar: PaddingValues.Absolute = PaddingValues.Absolute(0.dp) - override val blockQuoteText: PaddingValues = PaddingValues(0.dp) - override val codeBlock: PaddingValues = PaddingValues(0.dp) - override val list: Dp = 0.dp - override val listIndent: Dp = 0.dp - override val listItemBottom: Dp = 0.dp - override val listItemTop: Dp = 0.dp - }, - ) -} diff --git a/app/feature/feature-movingflow/build.gradle.kts b/app/feature/feature-movingflow/build.gradle.kts index 6e69ae001b..4e737e559a 100644 --- a/app/feature/feature-movingflow/build.gradle.kts +++ b/app/feature/feature-movingflow/build.gradle.kts @@ -17,8 +17,6 @@ dependencies { implementation(libs.androidx.navigation.compose) implementation(libs.apollo.normalizedCache) implementation(libs.arrow.core) - implementation(libs.compose.richtext) - implementation(libs.compose.richtextCommonmark) implementation(libs.coroutines.core) implementation(libs.jetbrains.lifecycle.runtime.compose) implementation(libs.koin.composeViewModel) diff --git a/app/feature/feature-terminate-insurance/build.gradle.kts b/app/feature/feature-terminate-insurance/build.gradle.kts index 27eae28e29..9a6ae1c0f5 100644 --- a/app/feature/feature-terminate-insurance/build.gradle.kts +++ b/app/feature/feature-terminate-insurance/build.gradle.kts @@ -20,8 +20,6 @@ dependencies { implementation(libs.androidx.compose.material3.windowSizeClass) implementation(libs.androidx.navigation.compose) implementation(libs.arrow.core) - implementation(libs.compose.richtext) - implementation(libs.compose.richtextCommonmark) implementation(libs.coroutines.core) implementation(libs.jetbrains.lifecycle.runtime.compose) implementation(libs.jetbrains.lifecycle.viewmodel.compose) diff --git a/app/feature/feature-terminate-insurance/src/main/kotlin/com/hedvig/android/feature/terminateinsurance/step/survey/TerminationSurveyDestination.kt b/app/feature/feature-terminate-insurance/src/main/kotlin/com/hedvig/android/feature/terminateinsurance/step/survey/TerminationSurveyDestination.kt index b00b65f55a..342c955cdd 100644 --- a/app/feature/feature-terminate-insurance/src/main/kotlin/com/hedvig/android/feature/terminateinsurance/step/survey/TerminationSurveyDestination.kt +++ b/app/feature/feature-terminate-insurance/src/main/kotlin/com/hedvig/android/feature/terminateinsurance/step/survey/TerminationSurveyDestination.kt @@ -26,7 +26,6 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.dropUnlessResumed -import com.halilibo.richtext.commonmark.Markdown import com.hedvig.android.data.changetier.data.IntentOutput import com.hedvig.android.design.system.hedvig.ButtonDefaults.ButtonSize.Large import com.hedvig.android.design.system.hedvig.EmptyState @@ -35,6 +34,7 @@ import com.hedvig.android.design.system.hedvig.EmptyStateDefaults.EmptyStateIcon import com.hedvig.android.design.system.hedvig.EmptyStateDefaults.EmptyStateIconStyle.INFO import com.hedvig.android.design.system.hedvig.HedvigButton import com.hedvig.android.design.system.hedvig.HedvigDialog +import com.hedvig.android.design.system.hedvig.HedvigMarkdownText import com.hedvig.android.design.system.hedvig.HedvigNotificationCard import com.hedvig.android.design.system.hedvig.HedvigPreview import com.hedvig.android.design.system.hedvig.HedvigTextButton @@ -45,7 +45,6 @@ import com.hedvig.android.design.system.hedvig.ProvideTextStyle import com.hedvig.android.design.system.hedvig.RadioGroup import com.hedvig.android.design.system.hedvig.RadioOption import com.hedvig.android.design.system.hedvig.RadioOptionId -import com.hedvig.android.design.system.hedvig.RichText import com.hedvig.android.design.system.hedvig.Surface import com.hedvig.android.design.system.hedvig.a11y.FlowHeading import com.hedvig.android.design.system.hedvig.freetext.FreeTextDisplay @@ -300,11 +299,7 @@ private fun SelectedSurveyInfoBox( ProvideTextStyle( HedvigTheme.typography.label, ) { - RichText { - Markdown( - content = suggestion.description, - ) - } + HedvigMarkdownText(content = suggestion.description) } }, priority = when (suggestion.type) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fcf17b2b64..83104abf22 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -60,11 +60,11 @@ jetbrains-lifecycle = "2.10.0" jetbrains-material = "1.11.0-rc01" jetbrains-material3 = "1.11.0-alpha07" jetbrains-navigation = "2.9.2" +jetbrainsMarkdown = "0.7.3" arrow = "2.2.1.1" assertK = "0.28.1" atomicfu = "0.31.0" coil = "3.3.0" -composeRichtext = "1.0.0-alpha04" coreLibraryDesugaring = "2.1.5" coroutines = "1.10.2" datadog = "3.6.0" @@ -148,8 +148,6 @@ coil-compose = { module = "io.coil-kt.coil3:coil-compose-core", version.ref = "c coil-gif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coil" } coil-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" } coil-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil" } -compose-richtext = { module = "com.halilibo.compose-richtext:richtext-ui", version.ref = "composeRichtext" } -compose-richtextCommonmark = { module = "com.halilibo.compose-richtext:richtext-commonmark", version.ref = "composeRichtext" } coreLibraryDesugaring = { module = "com.android.tools:desugar_jdk_libs", version.ref = "coreLibraryDesugaring" } mikepenz-markdown = { module = "com.mikepenz:multiplatform-markdown-renderer", version.ref = "mikepenzMarkdown" } coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } @@ -183,6 +181,7 @@ jetbrains-compose-ui-tooling-preview = { module = "org.jetbrains.compose.ui:ui-t jetbrains-compose-ui-unit = { module = "org.jetbrains.compose.ui:ui-unit", version.ref = "jetbrains-compose" } jetbrains-compose-ui-util = { module = "org.jetbrains.compose.ui:ui-util", version.ref = "jetbrains-compose" } jetbrains-graphics-shapes = { module = "org.jetbrains.androidx.graphics:graphics-shapes", version.ref = "jetbrains-graphics" } +jetbrains-markdown = { module = "org.jetbrains:markdown", version.ref = "jetbrainsMarkdown" } jetbrains-lifecycle-common = { module = "org.jetbrains.androidx.lifecycle:lifecycle-common", version.ref = "jetbrains-lifecycle" } jetbrains-lifecycle-runtime = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime", version.ref = "jetbrains-lifecycle" } jetbrains-lifecycle-runtime-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "jetbrains-lifecycle" }