Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions app/design-system/design-system-hedvig/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <R : Any> 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 <R : Any> 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()
}
}

This file was deleted.

3 changes: 1 addition & 2 deletions app/feature/feature-chat/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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(' ')
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ 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
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
Expand All @@ -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
Expand Down
Loading
Loading