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

Apply ordered and unordered list to all selection #263

Merged
merged 15 commits into from
May 1, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ data class RichTextConfig(
val codeColor: Color = Color.Unspecified,
val codeBackgroundColor: Color = Color.Transparent,
val codeStrokeColor: Color = Color.LightGray,
)
val listIndent: Int = 38
)

internal const val DefaultListIndent = 38
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,15 @@ class RichTextState internal constructor(
codeColor: Color = Color.Unspecified,
codeBackgroundColor: Color = Color.Unspecified,
codeStrokeColor: Color = Color.Unspecified,
listIndent: Int = -1
) {
richTextConfig = RichTextConfig(
linkColor = if (linkColor.isSpecified) linkColor else richTextConfig.linkColor,
linkTextDecoration = linkTextDecoration ?: richTextConfig.linkTextDecoration,
codeColor = if (codeColor.isSpecified) codeColor else richTextConfig.codeColor,
codeBackgroundColor = if (codeBackgroundColor.isSpecified) codeBackgroundColor else richTextConfig.codeBackgroundColor,
codeStrokeColor = if (codeStrokeColor.isSpecified) codeStrokeColor else richTextConfig.codeStrokeColor,
listIndent = if (listIndent > -1) listIndent else richTextConfig.listIndent
)

updateTextFieldValue(textFieldValue)
Expand Down Expand Up @@ -551,17 +553,32 @@ class RichTextState internal constructor(
}

fun toggleUnorderedList() {
val paragraph = getRichParagraphByTextIndex(selection.min - 1) ?: return
if (paragraph.type is UnorderedList) removeUnorderedList()
else addUnorderedList()
val paragraphs = getRichParagraphListByTextRange(selection)
if (paragraphs.isEmpty()) return
val removeUnorderedList = paragraphs.first().type is UnorderedList
paragraphs.forEach { paragraph ->
if (removeUnorderedList) {
removeUnorderedList(paragraph)
} else {
addUnorderedList(paragraph)
}
}
}

fun addUnorderedList() {
val paragraph = getRichParagraphByTextIndex(selection.min - 1) ?: return
val paragraphs = getRichParagraphListByTextRange(selection)

paragraphs.forEach { paragraph ->
addUnorderedList(paragraph)
}
}

private fun addUnorderedList(paragraph: RichParagraph) {
if (paragraph.type is UnorderedList) return

val newType = UnorderedList()
val newType = UnorderedList(
initialIndent = richTextConfig.listIndent
)

updateParagraphType(
paragraph = paragraph,
Expand All @@ -570,20 +587,41 @@ class RichTextState internal constructor(
}

fun removeUnorderedList() {
val paragraph = getRichParagraphByTextIndex(selection.min - 1) ?: return
val paragraphs = getRichParagraphListByTextRange(selection)

paragraphs.forEach { paragraph ->
removeUnorderedList(paragraph)
}
}

private fun removeUnorderedList(paragraph: RichParagraph) {
if (paragraph.type !is UnorderedList) return

resetParagraphType(paragraph = paragraph)
}

fun toggleOrderedList() {
val paragraph = getRichParagraphByTextIndex(selection.min - 1) ?: return
if (paragraph.type is OrderedList) removeOrderedList()
else addOrderedList()
val paragraphs = getRichParagraphListByTextRange(selection)
if (paragraphs.isEmpty()) return
val removeOrderedList = paragraphs.first().type is OrderedList
paragraphs.forEach { paragraph ->
if (removeOrderedList) {
removeOrderedList(paragraph)
} else {
addOrderedList(paragraph)
}
}
}

fun addOrderedList() {
val paragraph = getRichParagraphByTextIndex(selection.min - 1) ?: return
val paragraphs = getRichParagraphListByTextRange(selection)

paragraphs.forEach { paragraph ->
addOrderedList(paragraph)
}
}

private fun addOrderedList(paragraph: RichParagraph) {
if (paragraph.type is OrderedList) return
val index = richParagraphList.indexOf(paragraph)
if (index == -1) return
Expand All @@ -603,6 +641,7 @@ class RichTextState internal constructor(

val newType = OrderedList(
number = orderedListNumber,
initialIndent = richTextConfig.listIndent,
startTextSpanStyle = firstRichSpan?.spanStyle ?: SpanStyle(),
startTextWidth = 0.sp
)
Expand All @@ -616,7 +655,14 @@ class RichTextState internal constructor(
}

fun removeOrderedList() {
val paragraph = getRichParagraphByTextIndex(selection.min - 1) ?: return
val paragraphs = getRichParagraphListByTextRange(selection)

paragraphs.forEach { paragraph ->
removeOrderedList(paragraph)
}
}

private fun removeOrderedList(paragraph: RichParagraph) {
if (paragraph.type !is OrderedList) return
val index = richParagraphList.indexOf(paragraph)
if (index == -1) return
Expand Down Expand Up @@ -769,7 +815,7 @@ class RichTextState internal constructor(
* @see [annotatedString]
*/
private fun updateAnnotatedString(newTextFieldValue: TextFieldValue = textFieldValue) {
var newText =
val newText =
if (singleParagraphMode)
newTextFieldValue.text
else
Expand All @@ -784,7 +830,7 @@ class RichTextState internal constructor(
return@fastForEachIndexed
}

withStyle(richParagraph.paragraphStyle.merge(richParagraph.type.style)) {
withStyle(richParagraph.paragraphStyle.merge(richParagraph.type.getStyle(richTextConfig))) {
append(richParagraph.type.startText)
val richParagraphStartTextLength = richParagraph.type.startText.length
richParagraph.type.startRichSpan.textRange = TextRange(index, index + richParagraphStartTextLength)
Expand Down Expand Up @@ -1133,11 +1179,16 @@ class RichTextState internal constructor(
return

if (richSpan.text == "- " || richSpan.text == "* ") {
richSpan.paragraph.type = UnorderedList()
richSpan.paragraph.type = UnorderedList(
initialIndent = richTextConfig.listIndent,
)
richSpan.text = ""
} else if (richSpan.text.matches(Regex("^\\d+\\. "))) {
val number = richSpan.text.first().digitToIntOrNull() ?: 1
richSpan.paragraph.type = OrderedList(number)
richSpan.paragraph.type = OrderedList(
number = number,
initialIndent = richTextConfig.listIndent,
)
richSpan.text = ""
}
}
Expand All @@ -1158,6 +1209,7 @@ class RichTextState internal constructor(
paragraph = currentParagraph,
newType = OrderedList(
number = number,
initialIndent = richTextConfig.listIndent,
startTextSpanStyle = currentParagraphType.startTextSpanStyle,
startTextWidth = currentParagraphType.startTextWidth
),
Expand Down Expand Up @@ -1188,6 +1240,7 @@ class RichTextState internal constructor(
paragraph = currentParagraph,
newType = OrderedList(
number = number,
initialIndent = richTextConfig.listIndent,
startTextSpanStyle = currentParagraphType.startTextSpanStyle,
startTextWidth = currentParagraphType.startTextWidth
),
Expand Down Expand Up @@ -2231,14 +2284,29 @@ class RichTextState internal constructor(
var index = 0
val richParagraphList = mutableListOf<RichParagraph>()
this.richParagraphList.fastForEachIndexed { paragraphIndex, richParagraphStyle ->
// TODO: Improve this, we don't need to get all rich span list, we can stop when we find the first one.
val result = richParagraphStyle.getRichSpanListByTextRange(
paragraphIndex = paragraphIndex,
searchTextRange = searchTextRange,
offset = index,
)
if (result.second.isNotEmpty())

val paragraphStartIndex =
if (paragraphIndex == 0)
0
else if (searchTextRange.collapsed)
index + 1
// If the search text range is not collapsed, we need to ignore the first index of the paragraph.
// Because the first index of the paragraph is the last index of the previous paragraph.
else
index + 2

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

if (result.second.isNotEmpty() || isCursorInParagraph)
richParagraphList.add(richParagraphStyle)

index = result.first
}
return richParagraphList
Expand Down Expand Up @@ -2383,7 +2451,7 @@ class RichTextState internal constructor(
annotatedString = buildAnnotatedString {
var index = 0
richParagraphList.fastForEachIndexed { i, richParagraphStyle ->
withStyle(richParagraphStyle.paragraphStyle.merge(richParagraphStyle.type.style)) {
withStyle(richParagraphStyle.paragraphStyle.merge(richParagraphStyle.type.getStyle(richTextConfig))) {
append(richParagraphStyle.type.startText)
val richParagraphStartTextLength = richParagraphStyle.type.startText.length
richParagraphStyle.type.startRichSpan.textRange =
Expand Down Expand Up @@ -2504,4 +2572,4 @@ class RichTextState internal constructor(
}
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ package com.mohamedrejeb.richeditor.paragraph.type
import androidx.compose.ui.text.ParagraphStyle
import com.mohamedrejeb.richeditor.annotation.ExperimentalRichTextApi
import com.mohamedrejeb.richeditor.model.RichSpan
import com.mohamedrejeb.richeditor.model.RichTextConfig
import com.mohamedrejeb.richeditor.paragraph.RichParagraph

internal class DefaultParagraph : ParagraphType {
override val style: ParagraphStyle =
private val style: ParagraphStyle =
ParagraphStyle()

override fun getStyle(config: RichTextConfig): ParagraphStyle {
return style
}

@OptIn(ExperimentalRichTextApi::class)
override val startRichSpan: RichSpan =
RichSpan(paragraph = RichParagraph(type = this))
Expand All @@ -18,4 +23,15 @@ internal class DefaultParagraph : ParagraphType {

override fun copy(): ParagraphType =
DefaultParagraph()

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is DefaultParagraph) return false

return true
}

override fun hashCode(): Int {
return 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import androidx.compose.ui.text.style.TextIndent
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.sp
import com.mohamedrejeb.richeditor.annotation.ExperimentalRichTextApi
import com.mohamedrejeb.richeditor.model.DefaultListIndent
import com.mohamedrejeb.richeditor.model.RichSpan
import com.mohamedrejeb.richeditor.model.RichTextConfig
import com.mohamedrejeb.richeditor.paragraph.RichParagraph

internal class OrderedList(
number: Int,
initialIndent: Int = DefaultListIndent,
startTextSpanStyle: SpanStyle = SpanStyle(),
startTextWidth: TextUnit = 0.sp
) : ParagraphType {
Expand All @@ -33,14 +36,25 @@ internal class OrderedList(
style = getNewParagraphStyle()
}

override var style: ParagraphStyle =
private var indent = initialIndent

private var style: ParagraphStyle =
getNewParagraphStyle()

override fun getStyle(config: RichTextConfig): ParagraphStyle {
if (config.listIndent != indent) {
indent = config.listIndent
style = getNewParagraphStyle()
}

return style
}

private fun getNewParagraphStyle() =
ParagraphStyle(
textIndent = TextIndent(
firstLine = (38 - startTextWidth.value).sp,
restLine = 38.sp
firstLine = (indent - startTextWidth.value).sp,
restLine = indent.sp
)
)

Expand All @@ -58,14 +72,29 @@ internal class OrderedList(
override fun getNextParagraphType(): ParagraphType =
OrderedList(
number = number + 1,
initialIndent = indent,
startTextSpanStyle = startTextSpanStyle,
startTextWidth = startTextWidth
)

override fun copy(): ParagraphType =
OrderedList(
number = number,
initialIndent = indent,
startTextSpanStyle = startTextSpanStyle,
startTextWidth = startTextWidth
)
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is OrderedList) return false

if (indent != other.indent) return false

return true
}

override fun hashCode(): Int {
return indent
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package com.mohamedrejeb.richeditor.paragraph.type

import androidx.compose.ui.text.ParagraphStyle
import com.mohamedrejeb.richeditor.model.RichSpan
import com.mohamedrejeb.richeditor.model.RichTextConfig

internal interface ParagraphType {

val style: ParagraphStyle
fun getStyle(config: RichTextConfig): ParagraphStyle

val startRichSpan: RichSpan

Expand Down
Loading
Loading