@@ -3,6 +3,7 @@ package com.swmansion.enriched.styles
33import android.text.Editable
44import android.text.Spannable
55import com.swmansion.enriched.EnrichedTextInputView
6+ import com.swmansion.enriched.spans.EnrichedColoredSpan
67import com.swmansion.enriched.spans.EnrichedSpans
78import com.swmansion.enriched.utils.getSafeSpanBoundaries
89
@@ -101,21 +102,203 @@ class InlineStyles(
101102 }
102103 }
103104
105+ private fun applyColorSpan (
106+ spannable : Spannable ,
107+ start : Int ,
108+ end : Int ,
109+ color : Int ,
110+ ) {
111+ val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, end)
112+ spannable.setSpan(
113+ EnrichedColoredSpan (view.htmlStyle, color),
114+ safeStart,
115+ safeEnd,
116+ Spannable .SPAN_EXCLUSIVE_EXCLUSIVE ,
117+ )
118+ }
119+
120+ private fun splitExistingColorSpans (
121+ spannable : Spannable ,
122+ start : Int ,
123+ end : Int ,
124+ onRemain : (s: Int , e: Int , color: Int ) -> Unit ,
125+ ) {
126+ val spans = spannable.getSpans(start, end, EnrichedColoredSpan ::class .java)
127+ for (span in spans) {
128+ val spanStart = spannable.getSpanStart(span)
129+ val spanEnd = spannable.getSpanEnd(span)
130+ val color = span.color
131+
132+ spannable.removeSpan(span)
133+
134+ if (spanStart < start) {
135+ onRemain(spanStart, start, color)
136+ }
137+
138+ if (spanEnd > end) {
139+ onRemain(end, spanEnd, color)
140+ }
141+ }
142+ }
143+
144+ private fun mergeAdjacentColors (spannable : Spannable ) {
145+ val colorSpans =
146+ spannable
147+ .getSpans(0 , spannable.length, EnrichedColoredSpan ::class .java)
148+ .sortedBy { spannable.getSpanStart(it) }
149+
150+ var index = 0
151+ while (index < colorSpans.size - 1 ) {
152+ val currentSpan = colorSpans[index]
153+ val nextSpan = colorSpans[index + 1 ]
154+
155+ val currentStart = spannable.getSpanStart(currentSpan)
156+ val currentEnd = spannable.getSpanEnd(currentSpan)
157+ val nextStart = spannable.getSpanStart(nextSpan)
158+ val nextEnd = spannable.getSpanEnd(nextSpan)
159+
160+ if (currentEnd == nextStart && currentSpan.color == nextSpan.color) {
161+ spannable.removeSpan(currentSpan)
162+ spannable.removeSpan(nextSpan)
163+
164+ applyColorSpan(spannable, currentStart, nextEnd, currentSpan.color)
165+
166+ return mergeAdjacentColors(spannable)
167+ }
168+
169+ index++
170+ }
171+ }
172+
173+ private fun isFullyColoredWith (
174+ spannable : Spannable ,
175+ start : Int ,
176+ end : Int ,
177+ color : Int ,
178+ ): Boolean {
179+ val spans = spannable.getSpans(start, end, EnrichedColoredSpan ::class .java)
180+ if (spans.isEmpty()) return false
181+
182+ val allSame = spans.all { it.color == color }
183+
184+ if (! allSame) {
185+ return false
186+ }
187+
188+ val minStart = spans.minOf { spannable.getSpanStart(it) }
189+ val maxEnd = spans.maxOf { spannable.getSpanEnd(it) }
190+
191+ return minStart <= start && maxEnd >= end
192+ }
193+
194+ fun setColorStyle (color : Int ) {
195+ val (start, end) = view.selection?.getInlineSelection() ? : return
196+ val spannable = view.text as Spannable
197+
198+ if (start == end) {
199+ val spanState = view.spanState
200+ if (spanState?.colorStart != null && spanState.typingColor == color) {
201+ view.spanState.setColorStart(null , null )
202+ } else {
203+ view.spanState?.setColorStart(start, color)
204+ }
205+ return
206+ }
207+
208+ if (isFullyColoredWith(spannable, start, end, color)) {
209+ removeColorRange(start, end)
210+ view.spanState?.setColorStart(null , null )
211+ view.selection.validateStyles()
212+ return
213+ }
214+
215+ splitExistingColorSpans(spannable, start, end) { spanStart, spanEnd, existingColor ->
216+ applyColorSpan(spannable, spanStart, spanEnd, existingColor)
217+ }
218+
219+ applyColorSpan(spannable, start, end, color)
220+
221+ mergeAdjacentColors(spannable)
222+
223+ view.spanState?.setColorStart(null , null )
224+ view.selection.validateStyles()
225+ }
226+
227+ private fun removeColorRange (
228+ start : Int ,
229+ end : Int ,
230+ ) {
231+ val spannable = view.text as Spannable
232+
233+ splitExistingColorSpans(spannable, start, end) { spanStart, spanEnd, color ->
234+ if (spanStart < start) applyColorSpan(spannable, spanStart, start, color)
235+ if (spanEnd > end) applyColorSpan(spannable, end, spanEnd, color)
236+ }
237+ }
238+
239+ fun removeColorSpan () {
240+ val (start, end) = view.selection?.getInlineSelection() ? : return
241+
242+ if (start == end) {
243+ view.spanState?.setColorStart(null , null )
244+ return
245+ }
246+
247+ removeColorRange(start, end)
248+
249+ view.spanState?.setColorStart(null , null )
250+ view.selection.validateStyles()
251+ }
252+
253+ private fun applyTypingColorIfActive (
254+ spannable : Spannable ,
255+ cursor : Int ,
256+ ) {
257+ val state = view.spanState ? : return
258+ val colorStart = state.colorStart ? : return
259+ val color = state.typingColor ? : return
260+
261+ val existing =
262+ spannable
263+ .getSpans(colorStart, colorStart, EnrichedColoredSpan ::class .java)
264+ .firstOrNull { it.color == color }
265+
266+ if (existing != null ) {
267+ val spanStart = spannable.getSpanStart(existing)
268+ val spanEnd = spannable.getSpanEnd(existing)
269+
270+ if (cursor > spanEnd) {
271+ spannable.removeSpan(existing)
272+ applyColorSpan(spannable, spanStart, cursor, color)
273+ }
274+
275+ view.spanState.setColorStart(cursor, color)
276+ return
277+ }
278+
279+ applyColorSpan(spannable, colorStart, cursor, color)
280+ view.spanState.setColorStart(cursor, color)
281+ }
282+
104283 fun afterTextChanged (
105- s : Editable ,
284+ editable : Editable ,
106285 endCursorPosition : Int ,
107286 ) {
108287 for ((style, config) in EnrichedSpans .inlineSpans) {
109288 val start = view.spanState?.getStart(style) ? : continue
110289 var end = endCursorPosition
111- val spans = s.getSpans(start, end, config.clazz)
290+ if (config.clazz == EnrichedColoredSpan ::class .java) {
291+ applyTypingColorIfActive(editable, end)
292+ continue
293+ }
294+ val spans = editable.getSpans(start, end, config.clazz)
112295
113296 for (span in spans) {
114- end = s .getSpanEnd(span).coerceAtLeast(end)
115- s .removeSpan(span)
297+ end = editable .getSpanEnd(span).coerceAtLeast(end)
298+ editable .removeSpan(span)
116299 }
117300
118- setSpan(s , config.clazz, start, end)
301+ setSpan(editable , config.clazz, start, end)
119302 }
120303 }
121304
0 commit comments