Skip to content

Commit 29c5438

Browse files
committed
Re-organized into a spellcheck module
1 parent f7301f0 commit 29c5438

File tree

23 files changed

+246
-49
lines changed

23 files changed

+246
-49
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
2+
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
3+
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
4+
5+
plugins {
6+
alias(libs.plugins.kotlinMultiplatform)
7+
alias(libs.plugins.compose.compiler)
8+
alias(libs.plugins.composeMultiplatform)
9+
alias(libs.plugins.androidLibrary)
10+
alias(libs.plugins.bcv)
11+
id("module.publication")
12+
}
13+
14+
kotlin {
15+
explicitApi()
16+
applyDefaultHierarchyTemplate()
17+
18+
androidTarget {
19+
publishLibraryVariants("release")
20+
@OptIn(ExperimentalKotlinGradlePluginApi::class)
21+
compilerOptions {
22+
jvmTarget.set(JvmTarget.JVM_1_8)
23+
}
24+
}
25+
26+
jvm("desktop") {
27+
@OptIn(ExperimentalKotlinGradlePluginApi::class)
28+
compilerOptions {
29+
jvmTarget.set(JvmTarget.JVM_11)
30+
}
31+
}
32+
33+
js(IR) {
34+
browser()
35+
}
36+
@OptIn(ExperimentalWasmDsl::class)
37+
wasmJs {
38+
browser {
39+
testTask {
40+
enabled = false
41+
}
42+
}
43+
}
44+
45+
iosX64()
46+
iosArm64()
47+
iosSimulatorArm64()
48+
49+
sourceSets.commonMain.dependencies {
50+
implementation(projects.richeditorCompose)
51+
52+
implementation(compose.ui)
53+
implementation(compose.foundation)
54+
implementation(compose.material3)
55+
56+
// Spell Check
57+
implementation(libs.symspellkt)
58+
}
59+
60+
sourceSets.commonTest.dependencies {
61+
implementation(kotlin("test"))
62+
}
63+
}
64+
65+
android {
66+
namespace = "com.mohamedrejeb.richeditor.compose.spellcheck"
67+
compileSdk = libs.versions.android.compileSdk.get().toInt()
68+
69+
defaultConfig {
70+
minSdk = libs.versions.android.minSdk.get().toInt()
71+
}
72+
73+
compileOptions {
74+
sourceCompatibility = JavaVersion.VERSION_1_8
75+
targetCompatibility = JavaVersion.VERSION_1_8
76+
}
77+
}
78+
79+
apiValidation {
80+
@OptIn(kotlinx.validation.ExperimentalBCVApi::class)
81+
klib {
82+
enabled = true
83+
}
84+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.mohamedrejeb.richeditor.sample.common.richeditor
1+
package com.mohamedrejeb.richeditor.compose.spellcheck
22

33
import androidx.compose.ui.graphics.Color
44
import androidx.compose.ui.graphics.Path
@@ -15,7 +15,7 @@ import com.mohamedrejeb.richeditor.model.RichTextConfig
1515
import com.mohamedrejeb.richeditor.utils.getBoundingBoxes
1616

1717
@OptIn(ExperimentalRichTextApi::class)
18-
object SpellCheck: RichSpanStyle {
18+
public object SpellCheck: RichSpanStyle {
1919
override val spanStyle: (RichTextConfig) -> SpanStyle = {
2020
SpanStyle()
2121
}

sample/common/src/commonMain/kotlin/com/mohamedrejeb/richeditor/sample/common/spellcheck/SpellCheckState.kt renamed to richeditor-compose-spellcheck/src/commonMain/kotlin/com/mohamedrejeb/richeditor/compose/spellcheck/SpellCheckState.kt

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
1-
package com.mohamedrejeb.richeditor.sample.common.spellcheck
1+
package com.mohamedrejeb.richeditor.compose.spellcheck
22

33
import androidx.compose.ui.geometry.Offset
44
import androidx.compose.ui.text.TextRange
55
import androidx.compose.ui.util.fastForEach
66
import com.darkrockstudios.symspellkt.api.SpellChecker
77
import com.darkrockstudios.symspellkt.common.SuggestionItem
88
import com.darkrockstudios.symspellkt.common.Verbosity
9+
import com.mohamedrejeb.richeditor.compose.spellcheck.utils.applyCapitalizationStrategy
10+
import com.mohamedrejeb.richeditor.compose.spellcheck.utils.isSpelledCorrectly
11+
import com.mohamedrejeb.richeditor.compose.spellcheck.utils.spellingIsCorrect
912
import com.mohamedrejeb.richeditor.model.RichSpanStyle
1013
import com.mohamedrejeb.richeditor.model.RichTextState
11-
import com.mohamedrejeb.richeditor.sample.common.richeditor.SpellCheck
1214
import com.mohamedrejeb.richeditor.utils.WordSegment
1315

14-
class SpellCheckState(val richTextState: RichTextState, var spellChecker: SpellChecker?) {
16+
public class SpellCheckState(
17+
public val richTextState: RichTextState,
18+
public var spellChecker: SpellChecker?
19+
) {
1520
private var lastTextHash = -1
1621
private val misspelledWords = mutableListOf<WordSegment>()
1722

18-
fun handleSpanClick(span: RichSpanStyle, range: TextRange, click: Offset): WordSegment? {
23+
public fun handleSpanClick(span: RichSpanStyle, range: TextRange, click: Offset): WordSegment? {
1924
return if (span is SpellCheck) {
2025
findWordSegmentContainingRange(
2126
misspelledWords,
@@ -26,7 +31,7 @@ class SpellCheckState(val richTextState: RichTextState, var spellChecker: SpellC
2631
}
2732
}
2833

29-
fun correctSpelling(segment: WordSegment, correction: String) {
34+
public fun correctSpelling(segment: WordSegment, correction: String) {
3035
val currentStyle = richTextState.getSpanStyle(segment.range)
3136
richTextState.replaceTextRange(segment.range, correction)
3237

@@ -39,7 +44,7 @@ class SpellCheckState(val richTextState: RichTextState, var spellChecker: SpellC
3944
* This is a very naive algorithm that just removes all spell check spans and
4045
* reruns the entire spell check again.
4146
*/
42-
fun runSpellCheck() {
47+
public fun runSpellCheck() {
4348
val sp = spellChecker ?: return
4449

4550
richTextState.apply {
@@ -66,7 +71,7 @@ class SpellCheckState(val richTextState: RichTextState, var spellChecker: SpellC
6671
}
6772
}
6873

69-
fun onTextChange(richTextState: RichTextState) {
74+
public fun onTextChange(richTextState: RichTextState) {
7075
val newTextHash = richTextState.annotatedString.hashCode()
7176
if (lastTextHash != newTextHash) {
7277
runSpellCheck()
@@ -84,7 +89,7 @@ class SpellCheckState(val richTextState: RichTextState, var spellChecker: SpellC
8489
}
8590
}
8691

87-
fun getSuggestions(word: String): List<SuggestionItem> {
92+
public fun getSuggestions(word: String): List<SuggestionItem> {
8893
val sp = spellChecker ?: return emptyList()
8994

9095
val suggestions = sp.lookup(word, verbosity = Verbosity.All)
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,32 @@
1-
package com.mohamedrejeb.richeditor.spellcheck
1+
package com.mohamedrejeb.richeditor.compose.spellcheck
22

33
import androidx.compose.runtime.Composable
44
import androidx.compose.runtime.MutableState
55
import androidx.compose.runtime.mutableStateOf
66
import androidx.compose.ui.Modifier
77
import androidx.compose.ui.geometry.Offset
8-
import com.mohamedrejeb.richeditor.sample.common.spellcheck.SpellCheckState
98
import com.mohamedrejeb.richeditor.utils.WordSegment
109

1110
@Composable
12-
expect fun SpellCheckTextContextMenuProvider(
11+
public expect fun SpellCheckTextContextMenuProvider(
1312
modifier: Modifier,
1413
spellCheckMenuState: SpellCheckMenuState,
1514
content: @Composable () -> Unit
1615
)
1716

18-
data class SpellCheckMenuState(
17+
public data class SpellCheckMenuState(
1918
val spellCheckState: SpellCheckState,
2019
) {
2120
val missSpelling: MutableState<MissSpelling?> = mutableStateOf(null)
2221

23-
fun clearSpellCheck() {
22+
public fun clearSpellCheck() {
2423
missSpelling.value = null
2524
}
2625

27-
fun performCorrection(toReplace: WordSegment, correction: String) {
26+
public fun performCorrection(toReplace: WordSegment, correction: String) {
2827
spellCheckState.correctSpelling(toReplace, correction)
2928
clearSpellCheck()
3029
}
3130

32-
data class MissSpelling(val wordSegment: WordSegment, val menuPosition: Offset)
31+
public data class MissSpelling(val wordSegment: WordSegment, val menuPosition: Offset)
3332
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
package com.mohamedrejeb.richeditor.sample.common.spellcheck
1+
package com.mohamedrejeb.richeditor.compose.spellcheck
22

33
import androidx.compose.runtime.Composable
44
import androidx.compose.runtime.LaunchedEffect
55
import androidx.compose.runtime.remember
66
import androidx.compose.runtime.rememberCoroutineScope
77
import com.darkrockstudios.symspellkt.api.SpellChecker
8+
import com.mohamedrejeb.richeditor.compose.spellcheck.utils.debounceUntilQuiescent
89
import com.mohamedrejeb.richeditor.model.rememberRichTextState
910
import kotlinx.coroutines.launch
1011
import kotlin.time.Duration.Companion.seconds
1112

1213
@Composable
13-
fun rememberSpellCheckState(spellChecker: SpellChecker?): SpellCheckState {
14+
public fun rememberSpellCheckState(spellChecker: SpellChecker?): SpellCheckState {
1415
val richTextState = rememberRichTextState()
1516
val state = remember { SpellCheckState(richTextState, spellChecker) }
1617

sample/common/src/commonMain/kotlin/com/mohamedrejeb/richeditor/sample/common/spellcheck/SpellCheckDropdown.kt renamed to richeditor-compose-spellcheck/src/commonMain/kotlin/com/mohamedrejeb/richeditor/compose/spellcheck/ui/SpellCheckDropdown.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.mohamedrejeb.richeditor.sample.common.spellcheck
1+
package com.mohamedrejeb.richeditor.compose.spellcheck.ui
22

33
import androidx.compose.foundation.layout.Box
44
import androidx.compose.foundation.layout.offset
@@ -15,11 +15,12 @@ import androidx.compose.ui.Modifier
1515
import androidx.compose.ui.geometry.Offset
1616
import androidx.compose.ui.unit.IntOffset
1717
import com.darkrockstudios.symspellkt.common.SuggestionItem
18+
import com.mohamedrejeb.richeditor.compose.spellcheck.SpellCheckState
1819
import com.mohamedrejeb.richeditor.utils.WordSegment
1920
import kotlin.math.roundToInt
2021

2122
@Composable
22-
fun SpellCheckDropdown(
23+
internal fun SpellCheckDropdown(
2324
word: WordSegment?,
2425
position: Offset,
2526
spellCheckState: SpellCheckState,
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.mohamedrejeb.richeditor.sample.common.spellcheck
1+
package com.mohamedrejeb.richeditor.compose.spellcheck.ui
22

33
import androidx.compose.foundation.interaction.MutableInteractionSource
44
import androidx.compose.foundation.layout.fillMaxSize
@@ -14,14 +14,16 @@ import androidx.compose.ui.graphics.Color
1414
import androidx.compose.ui.graphics.SolidColor
1515
import androidx.compose.ui.text.TextLayoutResult
1616
import androidx.compose.ui.text.TextStyle
17-
import com.mohamedrejeb.richeditor.spellcheck.SpellCheckMenuState
18-
import com.mohamedrejeb.richeditor.spellcheck.SpellCheckTextContextMenuProvider
17+
import com.mohamedrejeb.richeditor.compose.spellcheck.SpellCheckMenuState
18+
import com.mohamedrejeb.richeditor.compose.spellcheck.SpellCheckState
19+
import com.mohamedrejeb.richeditor.compose.spellcheck.SpellCheckTextContextMenuProvider
20+
import com.mohamedrejeb.richeditor.compose.spellcheck.rememberSpellCheckState
1921
import com.mohamedrejeb.richeditor.ui.BasicRichTextEditor
2022
import com.mohamedrejeb.richeditor.ui.InteractionType
2123
import com.mohamedrejeb.richeditor.ui.RichSpanClickListener
2224

2325
@Composable
24-
fun SpellCheckedRichTextEditor(
26+
public fun SpellCheckedRichTextEditor(
2527
modifier: Modifier = Modifier,
2628
enabled: Boolean = true,
2729
textStyle: TextStyle = TextStyle.Default,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.mohamedrejeb.richeditor.compose.spellcheck.utils
2+
3+
import com.darkrockstudios.symspellkt.common.SuggestionItem
4+
5+
public fun List<SuggestionItem>.spellingIsCorrect(word: String): Boolean =
6+
(size == 1 && get(0).term.equals(word, ignoreCase = true))
7+
8+
public fun String.isSpelledCorrectly(suggestions: List<SuggestionItem>): Boolean =
9+
(suggestions.size == 1 && suggestions[0].term.equals(this, ignoreCase = true))
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.mohamedrejeb.richeditor.sample.common.spellcheck
1+
package com.mohamedrejeb.richeditor.compose.spellcheck.utils
22

33
internal fun applyCapitalizationStrategy(source: String, target: String): String {
44
fun isAllUpperCase(str: String): Boolean = str.all { it.isUpperCase() || !it.isLetter() }

sample/common/src/commonMain/kotlin/com/mohamedrejeb/richeditor/sample/common/spellcheck/debounceUntilQuiescent.kt renamed to richeditor-compose-spellcheck/src/commonMain/kotlin/com/mohamedrejeb/richeditor/compose/spellcheck/utils/debounceUntilQuiescent.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.mohamedrejeb.richeditor.sample.common.spellcheck
1+
package com.mohamedrejeb.richeditor.compose.spellcheck.utils
22

33
import kotlinx.coroutines.Job
44
import kotlinx.coroutines.delay

sample/common/src/desktopMain/kotlin/LocalSpellCheckingTextContextMenu.kt renamed to richeditor-compose-spellcheck/src/desktopMain/kotlin/com/mohamedrejeb/richeditor/compose/spellcheck/SpellCheckTextContextMenuProvider.desktop.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.mohamedrejeb.richeditor.spellcheck
1+
package com.mohamedrejeb.richeditor.compose.spellcheck
22

33
import androidx.compose.foundation.ContextMenuData
44
import androidx.compose.foundation.ContextMenuItem
@@ -10,7 +10,7 @@ import androidx.compose.runtime.remember
1010
import androidx.compose.ui.Modifier
1111

1212
@Composable
13-
actual fun SpellCheckTextContextMenuProvider(
13+
public actual fun SpellCheckTextContextMenuProvider(
1414
modifier: Modifier,
1515
spellCheckMenuState: SpellCheckMenuState,
1616
content: @Composable () -> Unit

sample/common/src/androidMain/kotlin/LocalSpellCheckingTextContextMenu.kt renamed to richeditor-compose-spellcheck/src/iosMain/kotlin/com/mohamedrejeb/richeditor/compose/spellcheck/SpellCheckTextContextMenuProvider.ios.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
package com.mohamedrejeb.richeditor.spellcheck
1+
package com.mohamedrejeb.richeditor.compose.spellcheck
22

33
import androidx.compose.foundation.layout.Box
44
import androidx.compose.runtime.Composable
55
import androidx.compose.ui.Modifier
6-
import com.mohamedrejeb.richeditor.sample.common.spellcheck.SpellCheckDropdown
6+
import com.mohamedrejeb.richeditor.compose.spellcheck.ui.SpellCheckDropdown
77

88
@Composable
9-
actual fun SpellCheckTextContextMenuProvider(
9+
public actual fun SpellCheckTextContextMenuProvider(
1010
modifier: Modifier,
1111
spellCheckMenuState: SpellCheckMenuState,
1212
content: @Composable () -> Unit

sample/common/src/iosMain/kotlin/com/mohamedrejeb/richeditor/spellcheck/LocalSpellCheckingTextContextMenu.kt renamed to richeditor-compose-spellcheck/src/jsMain/kotlin/com/mohamedrejeb/richeditor/compose/spellcheck/SpellCheckTextContextMenuProvider.js.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
package com.mohamedrejeb.richeditor.spellcheck
1+
package com.mohamedrejeb.richeditor.compose.spellcheck
22

33
import androidx.compose.foundation.layout.Box
44
import androidx.compose.runtime.Composable
55
import androidx.compose.ui.Modifier
6-
import com.mohamedrejeb.richeditor.sample.common.spellcheck.SpellCheckDropdown
6+
import com.mohamedrejeb.richeditor.compose.spellcheck.ui.SpellCheckDropdown
77

88
@Composable
9-
actual fun SpellCheckTextContextMenuProvider(
9+
public actual fun SpellCheckTextContextMenuProvider(
1010
modifier: Modifier,
1111
spellCheckMenuState: SpellCheckMenuState,
1212
content: @Composable () -> Unit
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.mohamedrejeb.richeditor.compose.spellcheck
2+
3+
import androidx.compose.foundation.layout.Box
4+
import androidx.compose.runtime.Composable
5+
import androidx.compose.ui.Modifier
6+
import com.mohamedrejeb.richeditor.compose.spellcheck.ui.SpellCheckDropdown
7+
8+
@Composable
9+
public actual fun SpellCheckTextContextMenuProvider(
10+
modifier: Modifier,
11+
spellCheckMenuState: SpellCheckMenuState,
12+
content: @Composable () -> Unit
13+
) {
14+
Box(modifier = modifier) {
15+
content()
16+
17+
spellCheckMenuState.missSpelling.value?.apply {
18+
SpellCheckDropdown(
19+
wordSegment,
20+
menuPosition,
21+
spellCheckMenuState.spellCheckState,
22+
dismiss = spellCheckMenuState::clearSpellCheck,
23+
correctSpelling = spellCheckMenuState::performCorrection
24+
)
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)