From 49aa7a70fd18ec2780d927c3c22637c98ab13ff4 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 24 Sep 2025 19:14:46 -0400 Subject: [PATCH 1/4] Updates Coil and required dependencies to 3.0 --- android-sample/build.gradle.kts | 9 ++- buildSrc/build.gradle.kts | 4 +- buildSrc/src/main/kotlin/Dependencies.kt | 13 ++-- .../richtext-android-library.gradle.kts | 8 +- .../kotlin/richtext-kmp-library.gradle.kts | 9 ++- gradle/wrapper/gradle-wrapper.properties | 2 +- richtext-markdown/build.gradle.kts | 1 + .../richtext/markdown/MarkdownImage.kt | 78 ++++++++++--------- .../com/halilibo/richtext/ui/FormattedList.kt | 2 +- 9 files changed, 73 insertions(+), 53 deletions(-) diff --git a/android-sample/build.gradle.kts b/android-sample/build.gradle.kts index 0f5729bb..75bea54f 100644 --- a/android-sample/build.gradle.kts +++ b/android-sample/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { id("com.android.application") kotlin("android") @@ -22,8 +24,11 @@ android { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } - kotlinOptions { - jvmTarget = "11" +} + +kotlin { + compilerOptions { + jvmTarget = JvmTarget.JVM_11 } } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index aaf2e76d..a47ed4c1 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -10,8 +10,8 @@ plugins { dependencies { // keep in sync with Dependencies.BuildPlugins.androidGradlePlugin - implementation("com.android.tools.build:gradle:8.7.0") + implementation("com.android.tools.build:gradle:8.13.0") // keep in sync with Dependencies.Kotlin.gradlePlugin - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21") + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.20") implementation(kotlin("script-runtime")) } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 9ff3451e..e260117e 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -1,10 +1,10 @@ object BuildPlugins { // keep in sync with buildSrc/build.gradle.kts - val androidGradlePlugin = "com.android.tools.build:gradle:8.7.0" + val androidGradlePlugin = "com.android.tools.build:gradle:8.13.0" } object AndroidX { - val appcompat = "androidx.appcompat:appcompat:1.3.0" + val appcompat = "androidx.appcompat:appcompat:1.7.1" } object Network { @@ -13,7 +13,7 @@ object Network { object Kotlin { // keep in sync with buildSrc/build.gradle.kts - val version = "2.0.21" + val version = "2.2.20" val binaryCompatibilityValidatorPlugin = "org.jetbrains.kotlinx:binary-compatibility-validator:0.9.0" val gradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$version" @@ -30,11 +30,12 @@ object Compose { val desktopVersion = "1.8.2" val activity = "androidx.activity:activity-compose:1.8.2" val toolingData = "androidx.compose.ui:ui-tooling-data:1.6.0" - val coil = "io.coil-kt:coil-compose:2.5.0" + val coil = "io.coil-kt.coil3:coil-compose:3.3.0" + val coilHttp = "io.coil-kt.coil3:coil-network-okhttp:3.3.0" } object Commonmark { - private val version = "0.25.0" + private val version = "0.26.0" val core = "org.commonmark:commonmark:$version" val tables = "org.commonmark:commonmark-ext-gfm-tables:$version" val strikethrough = "org.commonmark:commonmark-ext-gfm-strikethrough:$version" @@ -43,6 +44,6 @@ object Commonmark { object AndroidConfiguration { val minSdk = 23 - val targetSdk = 35 + val targetSdk = 36 val compileSdk = targetSdk } diff --git a/buildSrc/src/main/kotlin/richtext-android-library.gradle.kts b/buildSrc/src/main/kotlin/richtext-android-library.gradle.kts index 0d72f74b..2517ac98 100644 --- a/buildSrc/src/main/kotlin/richtext-android-library.gradle.kts +++ b/buildSrc/src/main/kotlin/richtext-android-library.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { id("com.android.library") kotlin("android") @@ -5,6 +7,9 @@ plugins { kotlin { explicitApi() + compilerOptions { + jvmTarget = JvmTarget.JVM_11 + } } android { @@ -19,9 +24,6 @@ android { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } - kotlinOptions { - jvmTarget = "11" - } buildFeatures { compose = true diff --git a/buildSrc/src/main/kotlin/richtext-kmp-library.gradle.kts b/buildSrc/src/main/kotlin/richtext-kmp-library.gradle.kts index 5dc38896..f2cdcbec 100644 --- a/buildSrc/src/main/kotlin/richtext-kmp-library.gradle.kts +++ b/buildSrc/src/main/kotlin/richtext-kmp-library.gradle.kts @@ -1,3 +1,8 @@ +import AndroidConfiguration.compileSdk +import AndroidConfiguration.minSdk +import AndroidConfiguration.targetSdk +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { id("com.android.library") kotlin("multiplatform") @@ -14,8 +19,8 @@ kotlin { jvm() androidTarget { publishLibraryVariants("release") - compilations.all { - kotlinOptions.jvmTarget = "11" + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) } } explicitApi() diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 09523c0e..37f853b1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/richtext-markdown/build.gradle.kts b/richtext-markdown/build.gradle.kts index 264c41d6..3fef99db 100644 --- a/richtext-markdown/build.gradle.kts +++ b/richtext-markdown/build.gradle.kts @@ -27,6 +27,7 @@ kotlin { val androidMain by getting { dependencies { implementation(Compose.coil) + implementation(Compose.coilHttp) } } diff --git a/richtext-markdown/src/androidMain/kotlin/com/halilibo/richtext/markdown/MarkdownImage.kt b/richtext-markdown/src/androidMain/kotlin/com/halilibo/richtext/markdown/MarkdownImage.kt index 64d8a883..d0c6db10 100644 --- a/richtext-markdown/src/androidMain/kotlin/com/halilibo/richtext/markdown/MarkdownImage.kt +++ b/richtext-markdown/src/androidMain/kotlin/com/halilibo/richtext/markdown/MarkdownImage.kt @@ -4,11 +4,11 @@ import android.annotation.SuppressLint import android.util.Base64 import androidx.compose.foundation.Image import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.BoxWithConstraintsScope import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.isSpecified @@ -16,9 +16,10 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp -import coil.compose.rememberAsyncImagePainter -import coil.request.ImageRequest -import coil.size.Size +import coil3.compose.rememberAsyncImagePainter +import coil3.request.ImageRequest +import coil3.request.crossfade +import coil3.size.Size private val DEFAULT_IMAGE_SIZE = 64.dp @@ -46,39 +47,10 @@ internal actual fun MarkdownImage( .build() ) - val density = LocalDensity.current - @SuppressLint("UnusedBoxWithConstraintsScope") BoxWithConstraints(modifier, contentAlignment = Alignment.Center) { - val sizeModifier by remember(density, painter) { - derivedStateOf { - val painterIntrinsicSize = painter.state.painter?.intrinsicSize - if (painterIntrinsicSize != null && - painterIntrinsicSize.isSpecified && - painterIntrinsicSize.width != Float.POSITIVE_INFINITY && - painterIntrinsicSize.height != Float.POSITIVE_INFINITY - ) { - val width = painterIntrinsicSize.width - val height = painterIntrinsicSize.height - val scale = if (width > constraints.maxWidth) { - constraints.maxWidth.toFloat() / width - } else { - 1f - } - - with(density) { - Modifier.size( - (width * scale).toDp(), - (height * scale).toDp() - ) - } - } else { - // if size is not defined at all, Coil fails to render the image - // here, we give a default size for images until they are loaded. - Modifier.size(DEFAULT_IMAGE_SIZE) - } - } - } + val painterState by painter.state.collectAsState() + val sizeModifier = renderInSize(painterState.painter?.intrinsicSize) Image( painter = painter, @@ -88,3 +60,37 @@ internal actual fun MarkdownImage( ) } } + +@Composable +public fun BoxWithConstraintsScope.renderInSize( + painterIntrinsicSize: androidx.compose.ui.geometry.Size?, +): Modifier { + val density = LocalDensity.current + + val sizeModifier = if (painterIntrinsicSize != null && + painterIntrinsicSize.isSpecified && + painterIntrinsicSize.width != Float.POSITIVE_INFINITY && + painterIntrinsicSize.height != Float.POSITIVE_INFINITY + ) { + val width = painterIntrinsicSize.width + val height = painterIntrinsicSize.height + val scale = if (width > constraints.maxWidth) { + constraints.maxWidth.toFloat() / width + } else { + 1f + } + + with(density) { + Modifier.size( + (width * scale).toDp(), + (height * scale).toDp() + ) + } + } else { + // if size is not defined at all, Coil fails to render the image + // here, we give a default size for images until they are loaded. + Modifier.size(DEFAULT_IMAGE_SIZE) + } + + return sizeModifier +} diff --git a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/FormattedList.kt b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/FormattedList.kt index 466e1138..37aa637b 100644 --- a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/FormattedList.kt +++ b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/FormattedList.kt @@ -100,7 +100,7 @@ public interface UnorderedMarkers { * Creates an [UnorderedMarkers] that will cycle through the values in [markers] for each * indentation level. */ -public fun @Composable RichTextScope.textUnorderedMarkers( +public fun RichTextScope.textUnorderedMarkers( vararg markers: String ): UnorderedMarkers = UnorderedMarkers { Text(markers[it % markers.size]) From 500828e117448307a6281a804799f76779c70305 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 24 Sep 2025 19:30:09 -0400 Subject: [PATCH 2/4] Updates CI to match new JDK version --- .github/workflows/android.yml | 4 ++-- .github/workflows/docs.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 5fc4870c..b6eaec83 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -11,10 +11,10 @@ jobs: steps: - uses: actions/checkout@v2 - uses: gradle/wrapper-validation-action@v1 - - name: set up JDK 17 + - name: set up JDK 21 uses: actions/setup-java@v1 with: - java-version: 17 + java-version: 21 - uses: actions/cache@v4 with: path: ~/.gradle/caches diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c8ae0e70..8fb09994 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: - java-version: 17 + java-version: 21 - uses: actions/setup-python@v2 with: python-version: 3.x From dd0ee2f877342f84a21ad4a2909ca810997a29be Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 Sep 2025 10:17:39 -0400 Subject: [PATCH 3/4] Adds a copy function since this is not a data class anymore --- .../richtext/ui/string/RichTextString.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/string/RichTextString.kt b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/string/RichTextString.kt index 0c913d60..31e64d31 100644 --- a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/string/RichTextString.kt +++ b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/string/RichTextString.kt @@ -50,6 +50,26 @@ public class RichTextStringStyle( public val codeStyle: SpanStyle? = null, public val linkStyle: TextLinkStyles? = null ) { + public fun copy( + boldStyle: SpanStyle? = null, + italicStyle: SpanStyle? = null, + underlineStyle: SpanStyle? = null, + strikethroughStyle: SpanStyle? = null, + subscriptStyle: SpanStyle? = null, + superscriptStyle: SpanStyle? = null, + codeStyle: SpanStyle? = null, + linkStyle: TextLinkStyles? = null + ): RichTextStringStyle = RichTextStringStyle( + boldStyle = boldStyle ?: this.boldStyle, + italicStyle = italicStyle ?: this.italicStyle, + underlineStyle = underlineStyle ?: this.underlineStyle, + strikethroughStyle = strikethroughStyle ?: this.strikethroughStyle, + subscriptStyle = subscriptStyle ?: this.subscriptStyle, + superscriptStyle = superscriptStyle ?: this.superscriptStyle, + codeStyle = codeStyle ?: this.codeStyle, + linkStyle = linkStyle ?: this.linkStyle + ) + internal fun merge(otherStyle: RichTextStringStyle?): RichTextStringStyle { if (otherStyle == null) return this return RichTextStringStyle( From 2ce9ae0b5608e1d98d1e3479983d45259b9c72c9 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 25 Sep 2025 20:11:44 -0400 Subject: [PATCH 4/4] Fixes the lack of null behavior in the copy function --- .../richtext/ui/string/RichTextString.kt | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/string/RichTextString.kt b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/string/RichTextString.kt index 31e64d31..5a32426b 100644 --- a/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/string/RichTextString.kt +++ b/richtext-ui/src/commonMain/kotlin/com/halilibo/richtext/ui/string/RichTextString.kt @@ -51,23 +51,23 @@ public class RichTextStringStyle( public val linkStyle: TextLinkStyles? = null ) { public fun copy( - boldStyle: SpanStyle? = null, - italicStyle: SpanStyle? = null, - underlineStyle: SpanStyle? = null, - strikethroughStyle: SpanStyle? = null, - subscriptStyle: SpanStyle? = null, - superscriptStyle: SpanStyle? = null, - codeStyle: SpanStyle? = null, - linkStyle: TextLinkStyles? = null + boldStyle: SpanStyle? = this.boldStyle, + italicStyle: SpanStyle? = this.italicStyle, + underlineStyle: SpanStyle? = this.underlineStyle, + strikethroughStyle: SpanStyle? = this.strikethroughStyle, + subscriptStyle: SpanStyle? = this.subscriptStyle, + superscriptStyle: SpanStyle? = this.superscriptStyle, + codeStyle: SpanStyle? = this.codeStyle, + linkStyle: TextLinkStyles? = this.linkStyle ): RichTextStringStyle = RichTextStringStyle( - boldStyle = boldStyle ?: this.boldStyle, - italicStyle = italicStyle ?: this.italicStyle, - underlineStyle = underlineStyle ?: this.underlineStyle, - strikethroughStyle = strikethroughStyle ?: this.strikethroughStyle, - subscriptStyle = subscriptStyle ?: this.subscriptStyle, - superscriptStyle = superscriptStyle ?: this.superscriptStyle, - codeStyle = codeStyle ?: this.codeStyle, - linkStyle = linkStyle ?: this.linkStyle + boldStyle = boldStyle, + italicStyle = italicStyle, + underlineStyle = underlineStyle, + strikethroughStyle = strikethroughStyle, + subscriptStyle = subscriptStyle, + superscriptStyle = superscriptStyle, + codeStyle = codeStyle, + linkStyle = linkStyle ) internal fun merge(otherStyle: RichTextStringStyle?): RichTextStringStyle {