Skip to content

Commit

Permalink
Merge pull request #273 from MohamedRejeb/1.x
Browse files Browse the repository at this point in the history
Fix losing RichSpanStyle on applying SpanStyle and improve tree simplification
  • Loading branch information
MohamedRejeb authored May 12, 2024
2 parents 0ab93af + 623c519 commit 05bde68
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ interface RichSpanStyle {
}
}

object Default : RichSpanStyle {
data object Default : RichSpanStyle {
override val spanStyle: (RichTextConfig) -> SpanStyle =
{ SpanStyle() }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,12 @@ class RichTextState internal constructor(
/**
* Returns the selected link text.
*/
val selectedLinkText: String? get() =
if (isLink)
getRichSpanByTextIndex(textIndex = selection.min - 1)?.text
else
null
val selectedLinkText: String?
get() =
if (isLink)
getRichSpanByTextIndex(textIndex = selection.min - 1)?.text
else
null

/**
* Returns the selected link URL.
Expand Down Expand Up @@ -128,7 +129,7 @@ class RichTextState internal constructor(
fun isRichSpan(spanStyle: RichSpanStyle): Boolean =
isRichSpan(spanStyle::class)

inline fun <reified T: RichSpanStyle> isRichSpan(): Boolean =
inline fun <reified T : RichSpanStyle> isRichSpan(): Boolean =
isRichSpan(T::class)

fun isRichSpan(kClass: KClass<out RichSpanStyle>): Boolean {
Expand Down Expand Up @@ -444,7 +445,7 @@ class RichTextState internal constructor(
}

fun addRichSpan(spanStyle: RichSpanStyle) {
if (toRemoveRichSpanStyle::class == spanStyle)
if (toRemoveRichSpanStyle == spanStyle::class)
toRemoveRichSpanStyle = RichSpanStyle.Default::class
toAddRichSpanStyle = spanStyle

Expand Down Expand Up @@ -926,7 +927,7 @@ class RichTextState internal constructor(

if (
(toAddSpanStyle == SpanStyle() && toRemoveSpanStyle == SpanStyle() &&
toAddRichSpanStyle is RichSpanStyle.Default && toRemoveRichSpanStyle::class != activeRichSpan.style::class) ||
toAddRichSpanStyle is RichSpanStyle.Default && toRemoveRichSpanStyle::class != activeRichSpan.style::class) ||
(newSpanStyle == activeRichSpanFullSpanStyle && newRichSpanStyle::class == activeRichSpan.style::class)
) {
activeRichSpan.text = beforeText + typedText + afterText
Expand Down Expand Up @@ -1501,36 +1502,77 @@ class RichTextState internal constructor(
richSpan.spanStyle = richSpan.spanStyle
.copy(textDecoration = fullSpanStyle.textDecoration)
.customMerge(toAddSpanStyle)
richSpan.style = toAddRichSpanStyle
richSpan.style =
if (toAddRichSpanStyle !is RichSpanStyle.Default)
toAddRichSpanStyle
else
richSpan.style

return
}

richSpan.text = beforeText
val newRichSpan = RichSpan(
paragraph = richSpan.paragraph,
parent = richSpan,
text = middleText,
textRange =
TextRange(
startIndex,
startIndex + middleText.length
),
spanStyle =
SpanStyle(textDecoration = fullSpanStyle.textDecoration)
.customMerge(toAddSpanStyle),
style = toAddRichSpanStyle,
)
val newRichSpan =
RichSpan(
paragraph = richSpan.paragraph,
parent = richSpan,
text = middleText,
textRange = TextRange(
startIndex,
startIndex + middleText.length
),
spanStyle = SpanStyle(textDecoration = fullSpanStyle.textDecoration).customMerge(toAddSpanStyle),
style =
if (toAddRichSpanStyle !is RichSpanStyle.Default)
toAddRichSpanStyle
else
richSpan.style,
)

val parent = richSpan.parent
val index =
parent?.children?.indexOf(richSpan) ?: richSpan.paragraph.children.indexOf(richSpan)
var isRemoved = false

val isRichSpanStylingEmpty = richSpan.spanStyle == SpanStyle() && richSpan.style is RichSpanStyle.Default

if (middleText.isNotEmpty()) {
richSpan.children.add(
0,
newRichSpan
)
if (
(isRichSpanStylingEmpty || richSpan.text.isEmpty()) &&
index != -1 &&
richSpan.children.isEmpty()
) {
newRichSpan.parent = richSpan.parent

if (!isRichSpanStylingEmpty) {
newRichSpan.spanStyle = richSpan.spanStyle.customMerge(newRichSpan.spanStyle)
if (richSpan.style !is RichSpanStyle.Default && newRichSpan.style is RichSpanStyle.Default)
newRichSpan.style = richSpan.style
}

if (parent != null) {
parent.children.add(index + 1, newRichSpan)

if (richSpan.text.isEmpty()) {
parent.children.removeAt(index)
isRemoved = true
}
} else {
richSpan.paragraph.children.add(index + 1, newRichSpan)

if (richSpan.text.isEmpty()) {
richSpan.paragraph.children.removeAt(index)
isRemoved = true
}
}
} else {
richSpan.children.add(0, newRichSpan)
newRichSpan.parent = richSpan
}
}

if (afterText.isNotEmpty()) {
richSpan.children.add(
1,
val afterRichSpan =
RichSpan(
paragraph = richSpan.paragraph,
parent = richSpan,
Expand All @@ -1540,15 +1582,50 @@ class RichTextState internal constructor(
startIndex + middleText.length + afterText.length
),
)
)

if (
(isRichSpanStylingEmpty || richSpan.text.isEmpty()) &&
index != -1 &&
richSpan.children.isEmpty()
) {
afterRichSpan.parent = richSpan.parent

if (!isRichSpanStylingEmpty) {
afterRichSpan.spanStyle = richSpan.spanStyle.customMerge(afterRichSpan.spanStyle)
if (richSpan.style !is RichSpanStyle.Default && afterRichSpan.style is RichSpanStyle.Default)
afterRichSpan.style = richSpan.style
}

val addIndex =
if (isRemoved || middleText.isEmpty())
index + 1
else
index + 2

if (parent != null) {
parent.children.add(addIndex, afterRichSpan)

if (richSpan.text.isEmpty() && !isRemoved)
parent.children.removeAt(index)
} else {
richSpan.paragraph.children.add(addIndex, afterRichSpan)

if (richSpan.text.isEmpty() && !isRemoved)
richSpan.paragraph.children.removeAt(index)
}
} else {
richSpan.children.add(1, afterRichSpan)
afterRichSpan.parent = richSpan
}
} else {
val firstRichSpan = richSpan.children.firstOrNull()
val secondRichSpan = richSpan.children.getOrNull(1)

if (
firstRichSpan != null &&
secondRichSpan != null &&
firstRichSpan.spanStyle == secondRichSpan.spanStyle
firstRichSpan.spanStyle == secondRichSpan.spanStyle &&
firstRichSpan.style == secondRichSpan.style
) {
firstRichSpan.text += secondRichSpan.text
firstRichSpan.children.addAll(secondRichSpan.children)
Expand Down Expand Up @@ -2302,7 +2379,7 @@ class RichTextState internal constructor(

val isCursorInParagraph =
searchTextRange.min in paragraphStartIndex..result.first ||
searchTextRange.max in paragraphStartIndex..result.first
searchTextRange.max in paragraphStartIndex..result.first

if (result.second.isNotEmpty() || isCursorInParagraph)
richParagraphList.add(richParagraphStyle)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.mohamedrejeb.richeditor.model

import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.font.FontWeight
import com.mohamedrejeb.richeditor.annotation.ExperimentalRichTextApi
import com.mohamedrejeb.richeditor.paragraph.RichParagraph
import org.junit.Test
import kotlin.test.assertTrue

class RichTextStateTest {

@OptIn(ExperimentalRichTextApi::class)
@Test
fun testApplyStyleToLink() {
val richTextState = RichTextState(
initialRichParagraphList = listOf(
RichParagraph(
key = 1,
).also {
it.children.add(
RichSpan(
text = "Before Link After",
paragraph = it,
),
)
}
)
)

richTextState.selection = TextRange(6, 9)
richTextState.addLinkToSelection("https://www.google.com")

richTextState.selection = TextRange(1, 12)
richTextState.addSpanStyle(SpanStyle(fontWeight = FontWeight.Bold))

richTextState.selection = TextRange(7)
assertTrue(richTextState.isLink)
}

}

0 comments on commit 05bde68

Please sign in to comment.