Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom block rendering #149

Merged
merged 7 commits into from
Dec 5, 2024
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
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
Loading