Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ package com.swmansion.enriched.spans

import com.swmansion.enriched.styles.HtmlStyle

data class BaseSpanConfig(val clazz: Class<*>)
data class ParagraphSpanConfig(val clazz: Class<*>, val isContinuous: Boolean)
data class ListSpanConfig(val clazz: Class<*>, val shortcut: String)
interface ISpanConfig {
val clazz: Class<*>
}

data class BaseSpanConfig(override val clazz: Class<*>): ISpanConfig
data class ParagraphSpanConfig(override val clazz: Class<*>, val isContinuous: Boolean): ISpanConfig
data class ListSpanConfig(override val clazz: Class<*>, val shortcut: String) : ISpanConfig

data class StylesMergingConfig(
// styles that should be removed when we apply specific style
Expand Down Expand Up @@ -64,6 +68,8 @@ object EnrichedSpans {
MENTION to BaseSpanConfig(EnrichedMentionSpan::class.java),
)

val allSpans: Map<String, ISpanConfig> = inlineSpans + paragraphSpans + listSpans + parametrizedStyles

fun getMergingConfigForStyle(style: String, htmlStyle: HtmlStyle): StylesMergingConfig? {
return when (style) {
BOLD -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import android.text.Spannable
import android.text.SpannableStringBuilder
import android.util.Log
import com.swmansion.enriched.EnrichedTextInputView
import com.swmansion.enriched.spans.EnrichedBlockQuoteSpan
import com.swmansion.enriched.spans.EnrichedCodeBlockSpan
import com.swmansion.enriched.spans.EnrichedSpans
import com.swmansion.enriched.spans.interfaces.EnrichedSpan
import com.swmansion.enriched.utils.getParagraphBounds
import com.swmansion.enriched.utils.getSafeSpanBoundaries

Expand Down Expand Up @@ -194,16 +193,84 @@ class ParagraphStyles(private val view: EnrichedTextInputView) {
s.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}

private fun handleConflictsDuringNewlineDeletion(s: Editable, style: String, paragraphStart: Int, paragraphEnd: Int): Boolean {
val spanState = view.spanState ?: return false
val mergingConfig = EnrichedSpans.getMergingConfigForStyle(style, view.htmlStyle) ?: return false
var isConflicting = false
val stylesToCheck = mergingConfig.blockingStyles + mergingConfig.conflictingStyles

for (styleToCheck in stylesToCheck) {
val conflictingType = EnrichedSpans.allSpans[styleToCheck]?.clazz ?: continue

val spans = s.getSpans(paragraphStart, paragraphEnd, conflictingType)
if (spans.isEmpty()) {
continue
}
isConflicting = true

val isParagraphStyle = EnrichedSpans.paragraphSpans[styleToCheck] != null
if (!isParagraphStyle) {
continue
}

for (span in spans) {
extendStyleOnWholeParagraph(s, span as EnrichedSpan, conflictingType, paragraphEnd)
}
}

if (isConflicting) {
val styleStart = spanState.getStart(style) ?: return false
spanState.setStart(style, null)
removeStyle(style, styleStart, paragraphEnd)
return true
}

return false
}


private fun deleteConflictingAndBlockingStyles(s: Editable, style: String, paragraphStart: Int, paragraphEnd: Int) {
val mergingConfig = EnrichedSpans.getMergingConfigForStyle(style, view.htmlStyle) ?: return
val stylesToCheck = mergingConfig.blockingStyles + mergingConfig.conflictingStyles

for (styleToCheck in stylesToCheck) {
val conflictingType = EnrichedSpans.allSpans[styleToCheck]?.clazz ?: continue

val spans = s.getSpans(paragraphStart, paragraphEnd, conflictingType)
for (span in spans) {
s.removeSpan(span)
}
}
}

private fun <T>extendStyleOnWholeParagraph(s: Editable, span: EnrichedSpan, type: Class<T>, paragraphEnd: Int) {
val currStyleStart = s.getSpanStart(span)
s.removeSpan(span)
val (safeStart, safeEnd) = s.getSafeSpanBoundaries(currStyleStart, paragraphEnd)
setSpan(s, type, safeStart, safeEnd)
}

fun afterTextChanged(s: Editable, endPosition: Int, previousTextLength: Int) {
var endCursorPosition = endPosition
val isBackspace = s.length < previousTextLength
val isNewLine = endCursorPosition == 0 || endCursorPosition > 0 && s[endCursorPosition - 1] == '\n'
val spanState = view.spanState ?: return

for ((style, config) in EnrichedSpans.paragraphSpans) {
val spanState = view.spanState ?: continue
val styleStart = spanState.getStart(style)

if (styleStart == null) {
if (isBackspace) {
val (start, end) = s.getParagraphBounds(endCursorPosition)
val spans = s.getSpans(start, end, config.clazz)

for (span in spans) {
// handle conflicts when entering paragraph with some paragraph style applied
deleteConflictingAndBlockingStyles(s, style, start, end)
extendStyleOnWholeParagraph(s, span as EnrichedSpan, config.clazz, end)
}
}

if (config.isContinuous) {
mergeAdjacentStyleSpans(s, endCursorPosition, config.clazz)
}
Expand All @@ -218,14 +285,23 @@ class ParagraphStyles(private val view: EnrichedTextInputView) {

if (isBackspace) {
endCursorPosition -= 1
view.spanState.setStart(style, null)
spanState.setStart(style, null)
} else {
s.insert(endCursorPosition, "\u200B")
endCursorPosition += 1
}
}

var (start, end) = s.getParagraphBounds(styleStart, endCursorPosition)

// handle conflicts when deleting newline from paragraph style (going back to previous line)
if (isBackspace && styleStart != start) {
val isConflicting = handleConflictsDuringNewlineDeletion(s, style, start, end)
if (isConflicting) {
continue
}
}

val isNotEndLineSpan = isSpanEnabledInNextLine(s, end, config.clazz)
val spans = s.getSpans(start, end, config.clazz)

Expand Down
Loading