Skip to content

Commit

Permalink
Merge pull request #149 from halilozercan/halilozercan/custom-block-r…
Browse files Browse the repository at this point in the history
…endering

Add custom block rendering
  • Loading branch information
halilozercan authored Dec 5, 2024
2 parents 1dd0e77 + 78a59a3 commit 812ed45
Show file tree
Hide file tree
Showing 45 changed files with 729 additions and 476 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0)

> **Warning**
> 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.
> 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.
A collection of Compose libraries for working with rich text formatting and documents.

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

This repo is currently very experimental and really just proofs-of-concept: there are no tests and some things
might be broken or very non-performant.
Expand Down
5 changes: 1 addition & 4 deletions android-sample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
id("com.android.application")
kotlin("android")
id("org.jetbrains.compose") version Compose.desktopVersion
id("org.jetbrains.kotlin.plugin.compose") version Kotlin.version
}

android {
Expand All @@ -24,10 +25,6 @@ android {
kotlinOptions {
jvmTarget = "11"
}

composeOptions {
kotlinCompilerExtensionVersion = Compose.compilerVersion
}
}

dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
Expand All @@ -27,10 +28,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.platform.UriHandler
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.halilibo.richtext.commonmark.CommonMarkdownParseOptions
import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser
import com.halilibo.richtext.commonmark.MarkdownParseOptions
import com.halilibo.richtext.markdown.BasicMarkdown
import com.halilibo.richtext.markdown.node.AstDocument
import com.halilibo.richtext.markdown.node.AstNode
Expand All @@ -50,7 +53,7 @@ import com.halilibo.richtext.ui.resolveDefaults
var richTextStyle by remember { mutableStateOf(RichTextStyle().resolveDefaults()) }
var isDarkModeEnabled by remember { mutableStateOf(false) }
var isWordWrapEnabled by remember { mutableStateOf(true) }
var markdownParseOptions by remember { mutableStateOf(MarkdownParseOptions.Default) }
var markdownParseOptions by remember { mutableStateOf(CommonMarkdownParseOptions.Default) }
var isAutolinkEnabled by remember { mutableStateOf(true) }

LaunchedEffect(isWordWrapEnabled) {
Expand Down Expand Up @@ -115,14 +118,13 @@ import com.halilibo.richtext.ui.resolveDefaults
parser.parse(sampleMarkdown)
}

RichText(
style = richTextStyle,
linkClickHandler = {
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
},
modifier = Modifier.padding(8.dp),
) {
LazyMarkdown(astNode)
ProvideToastUriHandler(context) {
RichText(
style = richTextStyle,
modifier = Modifier.padding(8.dp),
) {
LazyMarkdown(astNode)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,16 @@ import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.halilibo.richtext.commonmark.CommonMarkdownParseOptions
import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser
import com.halilibo.richtext.commonmark.MarkdownParseOptions
import com.halilibo.richtext.markdown.AstBlockNodeComposer
import com.halilibo.richtext.markdown.BasicMarkdown
import com.halilibo.richtext.markdown.node.AstBlockNodeType
import com.halilibo.richtext.markdown.node.AstHeading
import com.halilibo.richtext.markdown.node.AstNode
import com.halilibo.richtext.ui.Heading
import com.halilibo.richtext.ui.RichTextScope
import com.halilibo.richtext.ui.RichTextStyle
import com.halilibo.richtext.ui.material3.RichText
import com.halilibo.richtext.ui.resolveDefaults
Expand All @@ -49,7 +56,7 @@ import com.halilibo.richtext.ui.resolveDefaults
var richTextStyle by remember { mutableStateOf(RichTextStyle().resolveDefaults()) }
var isDarkModeEnabled by remember { mutableStateOf(false) }
var isWordWrapEnabled by remember { mutableStateOf(true) }
var markdownParseOptions by remember { mutableStateOf(MarkdownParseOptions.Default) }
var markdownParseOptions by remember { mutableStateOf(CommonMarkdownParseOptions.Default) }
var isAutolinkEnabled by remember { mutableStateOf(true) }
var isRtl by remember { mutableStateOf(false) }

Expand Down Expand Up @@ -126,14 +133,13 @@ import com.halilibo.richtext.ui.resolveDefaults
parser.parse(sampleMarkdown)
}

RichText(
style = richTextStyle,
linkClickHandler = {
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
},
modifier = Modifier.padding(8.dp),
) {
BasicMarkdown(astNode)
ProvideToastUriHandler(context) {
RichText(
style = richTextStyle,
modifier = Modifier.padding(8.dp),
) {
BasicMarkdown(astNode, HeadingAstBlockNodeComposer)
}
}
}
}
Expand All @@ -143,6 +149,25 @@ import com.halilibo.richtext.ui.resolveDefaults
}
}

val HeadingAstBlockNodeComposer = object : AstBlockNodeComposer {
override fun predicate(astBlockNodeType: AstBlockNodeType): Boolean {
return astBlockNodeType is AstHeading
}

@Composable override fun RichTextScope.Compose(
astNode: AstNode,
visitChildren: @Composable (AstNode) -> Unit
) {
val headingNode = astNode.type as? AstHeading ?: return
Column {
Heading(level = headingNode.level) {
visitChildren(astNode)
}
Text("Custom rendering is used for this heading!", fontSize = 8.sp)
}
}
}

@Composable
private fun CheckboxPreference(
onClick: () -> Unit,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.zachklipp.richtext.sample

import androidx.annotation.IntRange
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
Expand All @@ -11,7 +13,10 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderColors
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.darkColorScheme
Expand All @@ -24,6 +29,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.halilibo.richtext.ui.RichTextStyle
Expand Down Expand Up @@ -83,7 +89,7 @@ fun RichTextStyleConfig(
onChanged: (RichTextStyle) -> Unit
) {
Text("Paragraph spacing: ${richTextStyle.paragraphSpacing}")
Slider(
SliderForHumans(
value = richTextStyle.paragraphSpacing!!.value,
valueRange = 0f..20f,
onValueChange = {
Expand All @@ -92,7 +98,7 @@ fun RichTextStyleConfig(
)

Text("Table cell padding: ${richTextStyle.tableStyle!!.cellPadding}")
Slider(
SliderForHumans(
value = richTextStyle.tableStyle!!.cellPadding!!.value,
valueRange = 0f..20f,
onValueChange = {
Expand All @@ -107,7 +113,7 @@ fun RichTextStyleConfig(
)

Text("Table border width padding: ${richTextStyle.tableStyle!!.borderStrokeWidth!!}")
Slider(
SliderForHumans(
value = richTextStyle.tableStyle!!.borderStrokeWidth!!,
valueRange = 0f..20f,
onValueChange = {
Expand All @@ -121,3 +127,35 @@ fun RichTextStyleConfig(
}
)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SliderForHumans(
value: Float,
onValueChange: (Float) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
@IntRange(from = 0) steps: Int = 0,
onValueChangeFinished: (() -> Unit)? = null,
colors: SliderColors = SliderDefaults.colors(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) {
Slider(
value = value,
onValueChange = onValueChange,
modifier = modifier,
enabled = enabled,
valueRange = valueRange,
steps = steps,
onValueChangeFinished = onValueChangeFinished,
colors = colors,
interactionSource = interactionSource,
thumb = {
SliderDefaults.Thumb(
interactionSource = interactionSource,
thumbSize = DpSize(4.dp, 20.dp)
)
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.ExperimentalMaterial3Api
Expand Down Expand Up @@ -78,7 +80,7 @@ private val Samples = listOf<Pair<String, @Composable () -> Unit>>(
@Composable private fun SamplePreview(content: @Composable () -> Unit) {
ScreenPreview(
Modifier
.height(50.dp)
.size(50.dp)
.aspectRatio(1f)
.clipToBounds()
// "Zoom in" to the top-start corner to make the preview more legible.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.zachklipp.richtext.sample

import android.content.Context
import android.widget.Toast
import androidx.compose.animation.Animatable
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.LinearEasing
Expand All @@ -14,6 +16,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
Expand All @@ -28,6 +31,8 @@ import androidx.compose.ui.graphics.StrokeCap.Companion.Round
import androidx.compose.ui.graphics.drawscope.withTransform
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.platform.UriHandler
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
Expand Down Expand Up @@ -65,7 +70,7 @@ import kotlinx.coroutines.launch
appendPreviewSentence(Superscript)
appendPreviewSentence(Code)
appendPreviewSentence(
Link(""),
Link("") { toggleLink = !toggleLink },
if (toggleLink) "clicked link" else "link"
)
append("Here, ")
Expand Down Expand Up @@ -96,7 +101,7 @@ import kotlinx.coroutines.launch
}
}
}
RichText(linkClickHandler = { toggleLink = !toggleLink }) {
RichText {
Text(text)
}
}
Expand Down Expand Up @@ -213,3 +218,16 @@ private fun Builder.appendPreviewSentence(
}
append(" text. ")
}

@Composable
fun ProvideToastUriHandler(context: Context, content: @Composable () -> Unit) {
val uriHandler = remember(context) {
object : UriHandler {
override fun openUri(uri: String) {
Toast.makeText(context, uri, Toast.LENGTH_SHORT).show()
}
}
}

CompositionLocalProvider(LocalUriHandler provides uriHandler, content)
}
4 changes: 2 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ subprojects {
extensions.findByType<PublishingExtension>()?.apply {
repositories {
maven {
val localProperties = gradleLocalProperties(rootProject.rootDir)
val localProperties = gradleLocalProperties(rootProject.rootDir, providers)

val sonatypeUsername =
localProperties.getProperty("SONATYPE_USERNAME") ?: System.getenv("SONATYPE_USERNAME")
Expand Down Expand Up @@ -165,7 +165,7 @@ subprojects {
}

extensions.findByType<SigningExtension>()?.apply {
val localProperties = gradleLocalProperties(rootProject.rootDir)
val localProperties = gradleLocalProperties(rootProject.rootDir, providers)

val gpgPrivateKey =
localProperties.getProperty("GPG_PRIVATE_KEY")
Expand Down
4 changes: 2 additions & 2 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ plugins {

dependencies {
// keep in sync with Dependencies.BuildPlugins.androidGradlePlugin
implementation("com.android.tools.build:gradle:8.2.0")
implementation("com.android.tools.build:gradle:8.7.0")
// keep in sync with Dependencies.Kotlin.gradlePlugin
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21")
implementation(kotlin("script-runtime"))
}
Loading

0 comments on commit 812ed45

Please sign in to comment.