Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
cp-megh-l committed Feb 5, 2024
1 parent c4d2002 commit e77bcb8
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 70 deletions.
83 changes: 44 additions & 39 deletions app/src/main/assets/android-quill-sample.json
Original file line number Diff line number Diff line change
@@ -1,96 +1,101 @@
{
"spans": [
{
"insert": "Android Quill",
"attributes": {
"header": 1,
"bold": true
},
"insert": "Android Quill"
"bold": true,
"header": 1
}
},
{
"insert": "\n"
},
{
"insert": "\nRich",
"attributes": {
"header": 2,
"bold": true
},
"insert": "\nRich text editor for Android"
"bold": true,
"header": 2
}
},
{
"insert": " text ",
"attributes": {
"header": 2
}
},
{
"insert": "editor for Android",
"attributes": {
"bold": true,
"header": 2
}
},
{
"insert": "\n"
},
{
"insert": "Quill component for Android\n",
"attributes": {
"header": 3,
"italic": true
},
"insert": "Quill component for Android"
}
},
{
"insert": "Bullet Journal",
"attributes": {
"color": "rgba(0, 0, 0, 0.847)"
},
"insert": " and "
"bold": true
}
},
{
"insert": ":\nTrack personal and group journals (ToDo, Note, Ledger) from multiple views with timely reminders",
"attributes": {
"bold": true
},
"insert": "Bullet Journal"
},
{
"insert": ":\nTrack personal and group journals (ToDo, Note, Ledger) from multiple views with timely reminders"
}
},
{
"attributes": {
"bold": true
},
"insert": "\n"
},
{
"insert": "Share your tasks and notes with teammates, and see changes as they happen in real-time, across all devices"
"insert": "Share your tasks and notes with teammates, and see changes as they happen in real-time, across all devices",
"attributes": {}
},
{
"attributes": {
"list": "ordered"
},
"insert": "\n"
},
{
"insert": "Check out what you and your teammates are working on each day"
},
{
"insert": "Splitting bills with friends can never be easier.",
"attributes": {
"list": "ordered"
},
"insert": "\n"
"list": "bullet"
}
},
{
"insert": "\nSplitting bills with friends can never be easier."
"insert": "\n"
},
{
"insert": "Testing span addition to the editor.",
"attributes": {
"list": "bullet"
},
"insert": "\n"
}
},
{
"insert": "Start creating a group and invite your friends to join."
"insert": "\n"
},
{
"insert": "Start creating a group and invite your friends to join.",
"attributes": {
"list": "bullet"
},
"insert": "\n"
}
},
{
"insert": "Create a BuJo of Ledger type to see expense or balance summary."
"insert": "\n"
},
{
"insert": "Create a BuJo of Ledger type to see expense or balance summary.",
"attributes": {
"list": "bullet"
},
}
},
{
"insert": "\n"
}
]
Expand Down
7 changes: 6 additions & 1 deletion app/src/main/java/com/example/texteditor/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ import androidx.compose.ui.window.PopupProperties
import com.canopas.editor.ui.data.QuillEditorState
import com.canopas.editor.ui.ui.RichEditor
import com.canopas.editor.ui.utils.TextSpanStyle
import com.example.texteditor.parser.JsonEditorParser
import com.example.texteditor.parser.QuillJsonEditorParser
import com.example.texteditor.ui.theme.TextEditorTheme

Expand Down Expand Up @@ -124,6 +123,12 @@ fun StyleContainer(
value = state,
)

StyleButton(
icon = R.drawable.baseline_format_list_bulleted_24,
style = TextSpanStyle.BulletStyle,
value = state,
)

IconButton(
modifier = Modifier
.padding(2.dp)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#000000" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M4,10.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM4,4.5c-0.83,0 -1.5,0.67 -1.5,1.5S3.17,7.5 4,7.5 5.5,6.83 5.5,6 4.83,4.5 4,4.5zM4,16.5c-0.83,0 -1.5,0.68 -1.5,1.5s0.68,1.5 1.5,1.5 1.5,-0.68 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM7,19h14v-2L7,17v2zM7,13h14v-2L7,11v2zM7,5v2h14L21,5L7,5z"/>
</vector>
137 changes: 109 additions & 28 deletions editor/src/main/java/com/canopas/editor/ui/data/QuillTextManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package com.canopas.editor.ui.data

import android.text.Editable
import android.text.Spannable
import android.text.style.BulletSpan
import android.text.style.RelativeSizeSpan
import android.text.style.StyleSpan
import android.text.style.UnderlineSpan
import android.util.Log
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.ui.text.TextRange
import com.canopas.editor.ui.model.Attributes
import com.canopas.editor.ui.model.ListType
import com.canopas.editor.ui.model.QuillSpan
import com.canopas.editor.ui.model.RichTextSpan
import com.canopas.editor.ui.model.Span
Expand Down Expand Up @@ -56,6 +57,10 @@ class QuillTextManager(quillSpan: QuillSpan) {
style.add(TextSpanStyle.UnderlineStyle)
}

if (it.list == ListType.bullet) {
style.add(TextSpanStyle.BulletStyle)
}

style.forEach {
spans.add(
RichTextSpan(
Expand All @@ -65,37 +70,89 @@ class QuillTextManager(quillSpan: QuillSpan) {
)
)
}
} ?: run {
spans.add(
RichTextSpan(
from = fromIndex,
to = endIndex,
style = TextSpanStyle.Default
)
)
}
}
}


private val editableText: String get() = editable.toString()

private var selection = TextRange(0, 0)
private val currentStyles = mutableStateListOf<TextSpanStyle>()
private var rawText: String = editableText

internal val richText: QuillSpan
get() = QuillSpan(spans = spans.map { span ->
val attributes = when (span.style) {
TextSpanStyle.BoldStyle -> Attributes(bold = true)
TextSpanStyle.ItalicStyle -> Attributes(italic = true)
TextSpanStyle.UnderlineStyle -> Attributes(underline = true)
TextSpanStyle.H1Style -> Attributes(header = 1)
TextSpanStyle.H2Style -> Attributes(header = 2)
TextSpanStyle.H3Style -> Attributes(header = 3)
TextSpanStyle.H4Style -> Attributes(header = 4)
TextSpanStyle.H5Style -> Attributes(header = 5)
TextSpanStyle.H6Style -> Attributes(header = 6)
else -> null
get() {
val groupedSpans = mutableListOf<Span>()
var currentInsert = ""
var currentAttributes: Attributes? = null

spans.forEach { span ->
val insert = editableText.substring(span.from, span.to + 1)
val attributes = Attributes(
header = if (span.style.isHeaderStyle()) span.style.headerLevel() else null,
bold = if (span.style == TextSpanStyle.BoldStyle) true else null,
italic = if (span.style == TextSpanStyle.ItalicStyle) true else null,
underline = if (span.style == TextSpanStyle.UnderlineStyle) true else null,
list = if (span.style == TextSpanStyle.BulletStyle) ListType.bullet else null
)

if (insert == currentInsert && attributes == currentAttributes) {
// Same insert and attributes, continue to the next span
return@forEach
}

if (insert == currentInsert) {
// Same insert but different attributes, update the currentAttributes
currentAttributes = mergeAttributes(currentAttributes, attributes)
} else {
// Different insert, add the currentInsert with currentAttributes to the list
if (currentInsert.isNotEmpty()) {
groupedSpans.add(Span(currentInsert, currentAttributes))
}

// Update currentInsert and currentAttributes for the new insert
currentInsert = insert
currentAttributes = attributes
}
}
Span(
insert = editableText.substring(span.from, span.to + 1),
attributes = attributes
)
})

// Add the last insert with attributes to the list
if (currentInsert.isNotEmpty()) {
groupedSpans.add(Span(currentInsert, currentAttributes))
}

return QuillSpan(groupedSpans)
}

private fun mergeAttributes(currentAttributes: Attributes?, newAttributes: Attributes): Attributes {
return Attributes(
header = newAttributes.header ?: currentAttributes?.header,
bold = newAttributes.bold ?: currentAttributes?.bold,
italic = newAttributes.italic ?: currentAttributes?.italic,
underline = newAttributes.underline ?: currentAttributes?.underline,
list = newAttributes.list ?: currentAttributes?.list
)
}

private fun TextSpanStyle.headerLevel(): Int? {
return when(this) {
TextSpanStyle.H1Style -> 1
TextSpanStyle.H2Style -> 2
TextSpanStyle.H3Style -> 3
TextSpanStyle.H4Style -> 4
TextSpanStyle.H5Style -> 5
TextSpanStyle.H6Style -> 6
else -> null
}
}

internal fun setEditable(editable: Editable) {
editable.append(editableText)
Expand All @@ -107,6 +164,7 @@ class QuillTextManager(quillSpan: QuillSpan) {
editable.removeSpans<RelativeSizeSpan>()
editable.removeSpans<StyleSpan>()
editable.removeSpans<UnderlineSpan>()
editable.removeSpans<BulletSpan>()

spans.forEach {
editable.setSpan(
Expand All @@ -130,7 +188,30 @@ class QuillTextManager(quillSpan: QuillSpan) {
getRichSpanListByTextRange(selection).distinct()
}

this.currentStyles.addAll(currentStyles)
val currentSpan =
spans.findLast {
it.from <= selection.min - 2 && it.to >= selection.min - 2 && it.style == TextSpanStyle.BulletStyle
}

if (currentSpan != null) {
if (
currentSpan.style == TextSpanStyle.BulletStyle &&
editable[selection.min - 1] == '\n' &&
editable[selection.min - 2] != '\n'
) {
addStyle(TextSpanStyle.BulletStyle)
} else if (
currentSpan.style == TextSpanStyle.BulletStyle &&
editable[selection.min - 1] == '\n' &&
editable[selection.min - 2] == '\n'
) {
removeStyle(TextSpanStyle.BulletStyle)
} else {
this.currentStyles.addAll(currentStyles)
}
} else {
this.currentStyles.addAll(currentStyles)
}
}

private fun getRichSpanByTextIndex(textIndex: Int): List<TextSpanStyle> {
Expand Down Expand Up @@ -327,16 +408,16 @@ class QuillTextManager(quillSpan: QuillSpan) {
}

private fun handleAddingCharacters(newValue: Editable) {
val typedChars = newValue.length - rawText.length
val startTypeIndex = selection.min - typedChars
val typedCharsCount = newValue.length - rawText.length
val startTypeIndex = selection.min - typedCharsCount

if (newValue.getOrNull(startTypeIndex) == '\n' && currentStyles.any { it.isHeaderStyle() }) {
if (newValue.getOrNull(startTypeIndex) == '\n' && currentStyles.any { it.isHeaderStyle() || it == TextSpanStyle.BulletStyle }) {
currentStyles.clear()
}

val selectedStyles = currentStyles.toMutableList()

moveSpans(startTypeIndex, typedChars)
moveSpans(startTypeIndex, typedCharsCount)

val startParts = spans.filter { startTypeIndex - 1 in it.from..it.to }
val endParts = spans.filter { startTypeIndex in it.from..it.to }
Expand All @@ -346,21 +427,21 @@ class QuillTextManager(quillSpan: QuillSpan) {
.forEach {
if (selectedStyles.contains(it.style)) {
val index = spans.indexOf(it)
spans[index] = it.copy(to = it.to + typedChars)
spans[index] = it.copy(to = it.to + typedCharsCount)
selectedStyles.remove(it.style)
}
}

endParts.filter { it !in commonParts }
.forEach { processSpan(it, typedChars, startTypeIndex, selectedStyles, true) }
.forEach { processSpan(it, typedCharsCount, startTypeIndex, selectedStyles, true) }

commonParts.forEach { processSpan(it, typedChars, startTypeIndex, selectedStyles) }
commonParts.forEach { processSpan(it, typedCharsCount, startTypeIndex, selectedStyles) }

selectedStyles.forEach {
spans.add(
RichTextSpan(
from = startTypeIndex,
to = startTypeIndex + typedChars - 1,
to = startTypeIndex + typedCharsCount - 1,
style = it
)
)
Expand Down
Loading

0 comments on commit e77bcb8

Please sign in to comment.