Skip to content

Commit 812ed45

Browse files
authored
Merge pull request #149 from halilozercan/halilozercan/custom-block-rendering
Add custom block rendering
2 parents 1dd0e77 + 78a59a3 commit 812ed45

45 files changed

Lines changed: 729 additions & 476 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0)
55

66
> **Warning**
7-
> compose-richtext library and all its modules are very experimental and undermaintained. The roadmap is unclear at the moment. Thanks for your patience. Fork option is available as always.
7+
> compose-richtext library and all its modules are very experimental. The roadmap is unclear at the moment. Thanks for your patience. Fork option is available as always.
88
99
A collection of Compose libraries for working with rich text formatting and documents.
1010

11-
`richtext-ui`, `richtext-commonmark`, and `richtext-material-ui` are Kotlin Multiplatform Compose Libraries.
11+
Aside from `printing`, and `slideshow`, all modules are Kotlin Multiplatform Compose Libraries.
1212

1313
This repo is currently very experimental and really just proofs-of-concept: there are no tests and some things
1414
might be broken or very non-performant.

android-sample/build.gradle.kts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ plugins {
22
id("com.android.application")
33
kotlin("android")
44
id("org.jetbrains.compose") version Compose.desktopVersion
5+
id("org.jetbrains.kotlin.plugin.compose") version Kotlin.version
56
}
67

78
android {
@@ -24,10 +25,6 @@ android {
2425
kotlinOptions {
2526
jvmTarget = "11"
2627
}
27-
28-
composeOptions {
29-
kotlinCompilerExtensionVersion = Compose.compilerVersion
30-
}
3128
}
3229

3330
dependencies {

android-sample/src/main/java/com/zachklipp/richtext/sample/LazyMarkdownSample.kt

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import androidx.compose.material3.Text
1818
import androidx.compose.material3.darkColorScheme
1919
import androidx.compose.material3.lightColorScheme
2020
import androidx.compose.runtime.Composable
21+
import androidx.compose.runtime.CompositionLocalProvider
2122
import androidx.compose.runtime.LaunchedEffect
2223
import androidx.compose.runtime.getValue
2324
import androidx.compose.runtime.mutableStateOf
@@ -27,10 +28,12 @@ import androidx.compose.ui.Alignment
2728
import androidx.compose.ui.Modifier
2829
import androidx.compose.ui.platform.LocalContext
2930
import androidx.compose.ui.platform.LocalDensity
31+
import androidx.compose.ui.platform.LocalUriHandler
32+
import androidx.compose.ui.platform.UriHandler
3033
import androidx.compose.ui.tooling.preview.Preview
3134
import androidx.compose.ui.unit.dp
35+
import com.halilibo.richtext.commonmark.CommonMarkdownParseOptions
3236
import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser
33-
import com.halilibo.richtext.commonmark.MarkdownParseOptions
3437
import com.halilibo.richtext.markdown.BasicMarkdown
3538
import com.halilibo.richtext.markdown.node.AstDocument
3639
import com.halilibo.richtext.markdown.node.AstNode
@@ -50,7 +53,7 @@ import com.halilibo.richtext.ui.resolveDefaults
5053
var richTextStyle by remember { mutableStateOf(RichTextStyle().resolveDefaults()) }
5154
var isDarkModeEnabled by remember { mutableStateOf(false) }
5255
var isWordWrapEnabled by remember { mutableStateOf(true) }
53-
var markdownParseOptions by remember { mutableStateOf(MarkdownParseOptions.Default) }
56+
var markdownParseOptions by remember { mutableStateOf(CommonMarkdownParseOptions.Default) }
5457
var isAutolinkEnabled by remember { mutableStateOf(true) }
5558

5659
LaunchedEffect(isWordWrapEnabled) {
@@ -115,14 +118,13 @@ import com.halilibo.richtext.ui.resolveDefaults
115118
parser.parse(sampleMarkdown)
116119
}
117120

118-
RichText(
119-
style = richTextStyle,
120-
linkClickHandler = {
121-
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
122-
},
123-
modifier = Modifier.padding(8.dp),
124-
) {
125-
LazyMarkdown(astNode)
121+
ProvideToastUriHandler(context) {
122+
RichText(
123+
style = richTextStyle,
124+
modifier = Modifier.padding(8.dp),
125+
) {
126+
LazyMarkdown(astNode)
127+
}
126128
}
127129
}
128130
}

android-sample/src/main/java/com/zachklipp/richtext/sample/MarkdownSample.kt

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,16 @@ import androidx.compose.ui.platform.LocalLayoutDirection
3232
import androidx.compose.ui.tooling.preview.Preview
3333
import androidx.compose.ui.unit.LayoutDirection
3434
import androidx.compose.ui.unit.dp
35+
import androidx.compose.ui.unit.sp
36+
import com.halilibo.richtext.commonmark.CommonMarkdownParseOptions
3537
import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser
36-
import com.halilibo.richtext.commonmark.MarkdownParseOptions
38+
import com.halilibo.richtext.markdown.AstBlockNodeComposer
3739
import com.halilibo.richtext.markdown.BasicMarkdown
40+
import com.halilibo.richtext.markdown.node.AstBlockNodeType
41+
import com.halilibo.richtext.markdown.node.AstHeading
42+
import com.halilibo.richtext.markdown.node.AstNode
43+
import com.halilibo.richtext.ui.Heading
44+
import com.halilibo.richtext.ui.RichTextScope
3845
import com.halilibo.richtext.ui.RichTextStyle
3946
import com.halilibo.richtext.ui.material3.RichText
4047
import com.halilibo.richtext.ui.resolveDefaults
@@ -49,7 +56,7 @@ import com.halilibo.richtext.ui.resolveDefaults
4956
var richTextStyle by remember { mutableStateOf(RichTextStyle().resolveDefaults()) }
5057
var isDarkModeEnabled by remember { mutableStateOf(false) }
5158
var isWordWrapEnabled by remember { mutableStateOf(true) }
52-
var markdownParseOptions by remember { mutableStateOf(MarkdownParseOptions.Default) }
59+
var markdownParseOptions by remember { mutableStateOf(CommonMarkdownParseOptions.Default) }
5360
var isAutolinkEnabled by remember { mutableStateOf(true) }
5461
var isRtl by remember { mutableStateOf(false) }
5562

@@ -126,14 +133,13 @@ import com.halilibo.richtext.ui.resolveDefaults
126133
parser.parse(sampleMarkdown)
127134
}
128135

129-
RichText(
130-
style = richTextStyle,
131-
linkClickHandler = {
132-
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
133-
},
134-
modifier = Modifier.padding(8.dp),
135-
) {
136-
BasicMarkdown(astNode)
136+
ProvideToastUriHandler(context) {
137+
RichText(
138+
style = richTextStyle,
139+
modifier = Modifier.padding(8.dp),
140+
) {
141+
BasicMarkdown(astNode, HeadingAstBlockNodeComposer)
142+
}
137143
}
138144
}
139145
}
@@ -143,6 +149,25 @@ import com.halilibo.richtext.ui.resolveDefaults
143149
}
144150
}
145151

152+
val HeadingAstBlockNodeComposer = object : AstBlockNodeComposer {
153+
override fun predicate(astBlockNodeType: AstBlockNodeType): Boolean {
154+
return astBlockNodeType is AstHeading
155+
}
156+
157+
@Composable override fun RichTextScope.Compose(
158+
astNode: AstNode,
159+
visitChildren: @Composable (AstNode) -> Unit
160+
) {
161+
val headingNode = astNode.type as? AstHeading ?: return
162+
Column {
163+
Heading(level = headingNode.level) {
164+
visitChildren(astNode)
165+
}
166+
Text("Custom rendering is used for this heading!", fontSize = 8.sp)
167+
}
168+
}
169+
}
170+
146171
@Composable
147172
private fun CheckboxPreference(
148173
onClick: () -> Unit,

android-sample/src/main/java/com/zachklipp/richtext/sample/RichTextSample.kt

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.zachklipp.richtext.sample
22

3+
import androidx.annotation.IntRange
34
import androidx.compose.foundation.clickable
5+
import androidx.compose.foundation.interaction.MutableInteractionSource
46
import androidx.compose.foundation.layout.Arrangement
57
import androidx.compose.foundation.layout.Column
68
import androidx.compose.foundation.layout.Row
@@ -11,7 +13,10 @@ import androidx.compose.foundation.verticalScroll
1113
import androidx.compose.material3.Card
1214
import androidx.compose.material3.CardDefaults
1315
import androidx.compose.material3.Checkbox
16+
import androidx.compose.material3.ExperimentalMaterial3Api
1417
import androidx.compose.material3.Slider
18+
import androidx.compose.material3.SliderColors
19+
import androidx.compose.material3.SliderDefaults
1520
import androidx.compose.material3.Surface
1621
import androidx.compose.material3.Text
1722
import androidx.compose.material3.darkColorScheme
@@ -24,6 +29,7 @@ import androidx.compose.runtime.setValue
2429
import androidx.compose.ui.Alignment
2530
import androidx.compose.ui.Modifier
2631
import androidx.compose.ui.tooling.preview.Preview
32+
import androidx.compose.ui.unit.DpSize
2733
import androidx.compose.ui.unit.dp
2834
import androidx.compose.ui.unit.sp
2935
import com.halilibo.richtext.ui.RichTextStyle
@@ -83,7 +89,7 @@ fun RichTextStyleConfig(
8389
onChanged: (RichTextStyle) -> Unit
8490
) {
8591
Text("Paragraph spacing: ${richTextStyle.paragraphSpacing}")
86-
Slider(
92+
SliderForHumans(
8793
value = richTextStyle.paragraphSpacing!!.value,
8894
valueRange = 0f..20f,
8995
onValueChange = {
@@ -92,7 +98,7 @@ fun RichTextStyleConfig(
9298
)
9399

94100
Text("Table cell padding: ${richTextStyle.tableStyle!!.cellPadding}")
95-
Slider(
101+
SliderForHumans(
96102
value = richTextStyle.tableStyle!!.cellPadding!!.value,
97103
valueRange = 0f..20f,
98104
onValueChange = {
@@ -107,7 +113,7 @@ fun RichTextStyleConfig(
107113
)
108114

109115
Text("Table border width padding: ${richTextStyle.tableStyle!!.borderStrokeWidth!!}")
110-
Slider(
116+
SliderForHumans(
111117
value = richTextStyle.tableStyle!!.borderStrokeWidth!!,
112118
valueRange = 0f..20f,
113119
onValueChange = {
@@ -121,3 +127,35 @@ fun RichTextStyleConfig(
121127
}
122128
)
123129
}
130+
131+
@OptIn(ExperimentalMaterial3Api::class)
132+
@Composable
133+
fun SliderForHumans(
134+
value: Float,
135+
onValueChange: (Float) -> Unit,
136+
modifier: Modifier = Modifier,
137+
enabled: Boolean = true,
138+
valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
139+
@IntRange(from = 0) steps: Int = 0,
140+
onValueChangeFinished: (() -> Unit)? = null,
141+
colors: SliderColors = SliderDefaults.colors(),
142+
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
143+
) {
144+
Slider(
145+
value = value,
146+
onValueChange = onValueChange,
147+
modifier = modifier,
148+
enabled = enabled,
149+
valueRange = valueRange,
150+
steps = steps,
151+
onValueChangeFinished = onValueChangeFinished,
152+
colors = colors,
153+
interactionSource = interactionSource,
154+
thumb = {
155+
SliderDefaults.Thumb(
156+
interactionSource = interactionSource,
157+
thumbSize = DpSize(4.dp, 20.dp)
158+
)
159+
}
160+
)
161+
}

android-sample/src/main/java/com/zachklipp/richtext/sample/SampleLauncher.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import androidx.compose.foundation.clickable
66
import androidx.compose.foundation.layout.aspectRatio
77
import androidx.compose.foundation.layout.height
88
import androidx.compose.foundation.layout.padding
9+
import androidx.compose.foundation.layout.size
10+
import androidx.compose.foundation.layout.width
911
import androidx.compose.foundation.lazy.LazyColumn
1012
import androidx.compose.foundation.lazy.itemsIndexed
1113
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -78,7 +80,7 @@ private val Samples = listOf<Pair<String, @Composable () -> Unit>>(
7880
@Composable private fun SamplePreview(content: @Composable () -> Unit) {
7981
ScreenPreview(
8082
Modifier
81-
.height(50.dp)
83+
.size(50.dp)
8284
.aspectRatio(1f)
8385
.clipToBounds()
8486
// "Zoom in" to the top-start corner to make the preview more legible.

android-sample/src/main/java/com/zachklipp/richtext/sample/TextDemo.kt

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.zachklipp.richtext.sample
22

3+
import android.content.Context
4+
import android.widget.Toast
35
import androidx.compose.animation.Animatable
46
import androidx.compose.animation.core.Animatable
57
import androidx.compose.animation.core.LinearEasing
@@ -14,6 +16,7 @@ import androidx.compose.foundation.layout.size
1416
import androidx.compose.foundation.layout.wrapContentSize
1517
import androidx.compose.material3.Text
1618
import androidx.compose.runtime.Composable
19+
import androidx.compose.runtime.CompositionLocalProvider
1720
import androidx.compose.runtime.LaunchedEffect
1821
import androidx.compose.runtime.getValue
1922
import androidx.compose.runtime.mutableStateOf
@@ -28,6 +31,8 @@ import androidx.compose.ui.graphics.StrokeCap.Companion.Round
2831
import androidx.compose.ui.graphics.drawscope.withTransform
2932
import androidx.compose.ui.graphics.graphicsLayer
3033
import androidx.compose.ui.platform.LocalContext
34+
import androidx.compose.ui.platform.LocalUriHandler
35+
import androidx.compose.ui.platform.UriHandler
3136
import androidx.compose.ui.text.TextStyle
3237
import androidx.compose.ui.tooling.preview.Preview
3338
import androidx.compose.ui.unit.dp
@@ -65,7 +70,7 @@ import kotlinx.coroutines.launch
6570
appendPreviewSentence(Superscript)
6671
appendPreviewSentence(Code)
6772
appendPreviewSentence(
68-
Link(""),
73+
Link("") { toggleLink = !toggleLink },
6974
if (toggleLink) "clicked link" else "link"
7075
)
7176
append("Here, ")
@@ -96,7 +101,7 @@ import kotlinx.coroutines.launch
96101
}
97102
}
98103
}
99-
RichText(linkClickHandler = { toggleLink = !toggleLink }) {
104+
RichText {
100105
Text(text)
101106
}
102107
}
@@ -213,3 +218,16 @@ private fun Builder.appendPreviewSentence(
213218
}
214219
append(" text. ")
215220
}
221+
222+
@Composable
223+
fun ProvideToastUriHandler(context: Context, content: @Composable () -> Unit) {
224+
val uriHandler = remember(context) {
225+
object : UriHandler {
226+
override fun openUri(uri: String) {
227+
Toast.makeText(context, uri, Toast.LENGTH_SHORT).show()
228+
}
229+
}
230+
}
231+
232+
CompositionLocalProvider(LocalUriHandler provides uriHandler, content)
233+
}

build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ subprojects {
133133
extensions.findByType<PublishingExtension>()?.apply {
134134
repositories {
135135
maven {
136-
val localProperties = gradleLocalProperties(rootProject.rootDir)
136+
val localProperties = gradleLocalProperties(rootProject.rootDir, providers)
137137

138138
val sonatypeUsername =
139139
localProperties.getProperty("SONATYPE_USERNAME") ?: System.getenv("SONATYPE_USERNAME")
@@ -165,7 +165,7 @@ subprojects {
165165
}
166166

167167
extensions.findByType<SigningExtension>()?.apply {
168-
val localProperties = gradleLocalProperties(rootProject.rootDir)
168+
val localProperties = gradleLocalProperties(rootProject.rootDir, providers)
169169

170170
val gpgPrivateKey =
171171
localProperties.getProperty("GPG_PRIVATE_KEY")

buildSrc/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ plugins {
1010

1111
dependencies {
1212
// keep in sync with Dependencies.BuildPlugins.androidGradlePlugin
13-
implementation("com.android.tools.build:gradle:8.2.0")
13+
implementation("com.android.tools.build:gradle:8.7.0")
1414
// keep in sync with Dependencies.Kotlin.gradlePlugin
15-
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20")
15+
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21")
1616
implementation(kotlin("script-runtime"))
1717
}

0 commit comments

Comments
 (0)