Skip to content

Commit 0ba72a4

Browse files
author
Gerardo
committed
Merge branch 'trunk' into fix/gutenberg-backspace-styles
2 parents d845fbf + bf01d44 commit 0ba72a4

File tree

6 files changed

+163
-14
lines changed

6 files changed

+163
-14
lines changed

aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1348,6 +1348,7 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
13481348
AztecTextFormat.FORMAT_CODE -> inlineFormatter.toggle(textFormat)
13491349
AztecTextFormat.FORMAT_BOLD,
13501350
AztecTextFormat.FORMAT_STRONG -> inlineFormatter.toggleAny(ToolbarAction.BOLD.textFormats)
1351+
AztecTextFormat.FORMAT_MARK -> inlineFormatter.toggle(textFormat)
13511352
AztecTextFormat.FORMAT_UNORDERED_LIST -> blockFormatter.toggleUnorderedList()
13521353
AztecTextFormat.FORMAT_TASK_LIST -> blockFormatter.toggleTaskList()
13531354
AztecTextFormat.FORMAT_ORDERED_LIST -> blockFormatter.toggleOrderedList()

aztec/src/main/kotlin/org/wordpress/aztec/formatting/InlineFormatter.kt

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import org.wordpress.aztec.watchers.TextChangedEvent
3737
class InlineFormatter(editor: AztecText, val codeStyle: CodeStyle, private val highlightStyle: HighlightStyle) : AztecFormatter(editor) {
3838

3939
var backgroundSpanColor: Int? = null
40+
var markStyleColor: String? = null
4041

4142
data class CodeStyle(val codeBackground: Int, val codeBackgroundAlpha: Float, val codeColor: Int)
4243
data class HighlightStyle(@ColorRes val color: Int)
@@ -107,12 +108,8 @@ class InlineFormatter(editor: AztecText, val codeStyle: CodeStyle, private val h
107108
applyInlineStyle(item, textChangedEvent.inputStart, textChangedEvent.inputEnd)
108109
}
109110
AztecTextFormat.FORMAT_MARK -> {
110-
// For cases of an empty mark tag, either at the beginning of the text or in between
111-
if (textChangedEvent.inputStart == 0 && textChangedEvent.inputEnd == 1) {
112-
applyMarkInlineStyle(textChangedEvent.inputStart, textChangedEvent.inputEnd)
113-
} else {
114-
applyInlineStyle(item, textChangedEvent.inputStart, textChangedEvent.inputEnd)
115-
}
111+
applyInlineStyle(item, textChangedEvent.inputStart, textChangedEvent.inputEnd)
112+
applyAfterMarkInlineStyle(textChangedEvent.inputStart, textChangedEvent.inputEnd)
116113
}
117114
else -> {
118115
// do nothing
@@ -128,6 +125,16 @@ class InlineFormatter(editor: AztecText, val codeStyle: CodeStyle, private val h
128125
val newStart = if (start > end) end else start
129126
// if there is END_OF_BUFFER_MARKER at the end of or range, extend the range to include it
130127

128+
// Clear Mark formatting styles
129+
if (!editor.selectedStyles.contains(AztecTextFormat.FORMAT_MARK) && start >= 1 && end > 1 ) {
130+
val previousMarkSpan = editableText.getSpans(start - 1, start, MarkSpan::class.java)
131+
val markSpan = editableText.getSpans(start, end, MarkSpan::class.java)
132+
if (markSpan.isNotEmpty() || previousMarkSpan.isNotEmpty()) {
133+
removeInlineCssStyle(start, end)
134+
return
135+
}
136+
}
137+
131138
// remove lingering empty spans when removing characters
132139
if (start > end) {
133140
editableText.getSpans(newStart, end, IAztecInlineSpan::class.java)
@@ -250,10 +257,40 @@ class InlineFormatter(editor: AztecText, val codeStyle: CodeStyle, private val h
250257
}
251258
}
252259

253-
private fun applyMarkInlineStyle(start: Int = selectionStart, end: Int = selectionEnd) {
254-
val previousSpans = editableText.getSpans(start, end, MarkSpan::class.java)
255-
previousSpans.forEach {
256-
it.applyInlineStyleAttributes(editableText, start, end)
260+
private fun applyAfterMarkInlineStyle(start: Int = selectionStart, end: Int = selectionEnd) {
261+
// If there's no new mark style color to update, it skips applying the style updates.
262+
if (markStyleColor == null) {
263+
return
264+
}
265+
266+
val spans = editableText.getSpans(start, end, MarkSpan::class.java)
267+
spans.forEach { span ->
268+
if (span != null) {
269+
val color = span.getTextColor()
270+
val currentSpanStart = editableText.getSpanStart(span)
271+
val currentSpanEnd = editableText.getSpanEnd(span)
272+
273+
if (end < currentSpanEnd) {
274+
markStyleColor = null
275+
return
276+
}
277+
278+
if (!color.equals(markStyleColor, ignoreCase = true)) {
279+
editableText.removeSpan(span)
280+
editableText.setSpan(
281+
MarkSpan(AztecAttributes(), color),
282+
currentSpanStart,
283+
start,
284+
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
285+
)
286+
editableText.setSpan(
287+
MarkSpan(AztecAttributes(), markStyleColor),
288+
start,
289+
end,
290+
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
291+
)
292+
}
293+
}
257294
}
258295
}
259296

@@ -444,7 +481,7 @@ class InlineFormatter(editor: AztecText, val codeStyle: CodeStyle, private val h
444481
AztecTextFormat.FORMAT_HIGHLIGHT -> {
445482
HighlightSpan.create(context = editor.context, defaultStyle = highlightStyle)
446483
}
447-
AztecTextFormat.FORMAT_MARK -> MarkSpan()
484+
AztecTextFormat.FORMAT_MARK -> MarkSpan(AztecAttributes(), markStyleColor)
448485
else -> AztecStyleSpan(Typeface.NORMAL)
449486
}
450487
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.wordpress.aztec.plugins
2+
3+
import android.text.SpannableStringBuilder
4+
import org.wordpress.aztec.plugins.visual2html.ISpanPreprocessor
5+
import org.wordpress.aztec.source.CssStyleFormatter
6+
import org.wordpress.aztec.spans.MarkSpan
7+
8+
class MarkPlugin : ISpanPreprocessor {
9+
10+
override fun beforeSpansProcessed(spannable: SpannableStringBuilder) {
11+
spannable.getSpans(0, spannable.length, MarkSpan::class.java).forEach {
12+
if (!CssStyleFormatter.containsStyleAttribute(it.attributes, CssStyleFormatter.CSS_BACKGROUND_COLOR_ATTRIBUTE)) {
13+
CssStyleFormatter.addStyleAttribute(it.attributes, CssStyleFormatter.CSS_BACKGROUND_COLOR_ATTRIBUTE, "rgba(0, 0, 0, 0)")
14+
}
15+
16+
if (!CssStyleFormatter.containsStyleAttribute(it.attributes, CssStyleFormatter.CSS_COLOR_ATTRIBUTE)) {
17+
CssStyleFormatter.addStyleAttribute(it.attributes, CssStyleFormatter.CSS_COLOR_ATTRIBUTE, it.getTextColor())
18+
}
19+
20+
if (!it.attributes.hasAttribute("class")) {
21+
it.attributes.setValue("class", "has-inline-color")
22+
}
23+
}
24+
}
25+
}
Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,48 @@
11
package org.wordpress.aztec.spans
22

3+
import android.graphics.Color
34
import android.text.TextPaint
45
import android.text.style.CharacterStyle
56
import org.wordpress.aztec.AztecAttributes
7+
import org.wordpress.aztec.source.CssStyleFormatter
68

7-
class MarkSpan(override var attributes: AztecAttributes = AztecAttributes()) : CharacterStyle(), IAztecInlineSpan {
9+
class MarkSpan : CharacterStyle, IAztecInlineSpan {
810
override var TAG = "mark"
911

10-
override fun updateDrawState(tp: TextPaint?) {
12+
override var attributes: AztecAttributes = AztecAttributes()
13+
private val textColorValue: Int?
14+
15+
constructor(attributes: AztecAttributes = AztecAttributes()) : super() {
16+
this.attributes = attributes
17+
18+
val color = CssStyleFormatter.getStyleAttribute(attributes,
19+
CssStyleFormatter.CSS_COLOR_ATTRIBUTE)
20+
textColorValue = safelyParseColor(color)
21+
}
22+
23+
constructor(attributes: AztecAttributes = AztecAttributes(), colorString: String?) : super() {
24+
this.attributes = attributes
25+
textColorValue = safelyParseColor(colorString)
26+
}
27+
28+
private fun safelyParseColor(colorString: String?): Int? {
29+
if (colorString.isNullOrBlank()) {
30+
return null
31+
}
32+
return try {
33+
Color.parseColor(colorString)
34+
} catch (e: IllegalArgumentException) {
35+
// Unknown color
36+
null
37+
}
38+
}
39+
40+
override fun updateDrawState(tp: TextPaint) {
41+
textColorValue?.let { tp.color = it }
42+
}
43+
44+
fun getTextColor(): String {
45+
val currentColor = textColorValue ?: 0
46+
return String.format("#%06X", 0xFFFFFF and currentColor)
1147
}
1248
}

aztec/src/main/kotlin/org/wordpress/aztec/watchers/SuggestionWatcher.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,18 @@ class SuggestionWatcher(aztecText: AztecText) : TextWatcher {
143143
private fun reapplyCarriedOverInlineSpans(editableText: Spannable) {
144144
carryOverSpans.forEach {
145145
if (it.start >= 0 && it.end <= editableText.length && it.start < it.end) {
146-
editableText.setSpan(it.span, it.start, it.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
146+
try {
147+
editableText.setSpan(
148+
it.span,
149+
it.start,
150+
it.end,
151+
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
152+
)
153+
} catch (e: IndexOutOfBoundsException) {
154+
// This is a workaround for a possible bug in the Android framework
155+
// https://github.com/wordpress-mobile/WordPress-Android/issues/20481
156+
e.printStackTrace()
157+
}
147158
}
148159
}
149160
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.wordpress.aztec
2+
3+
import org.junit.Assert
4+
import org.junit.Test
5+
import org.wordpress.aztec.spans.MarkSpan
6+
7+
class MarkSpanTest {
8+
/**
9+
* Test used to confirm two crashes related are fixed.
10+
*
11+
* https://github.com/wordpress-mobile/WordPress-Android/issues/20738
12+
*/
13+
@Test
14+
fun `Calling MarkSpan#safelyParseColor with empty string should not cause a crash`() {
15+
var error = false
16+
try {
17+
MarkSpan(colorString = "")
18+
} catch (e: Exception) {
19+
error = true
20+
}
21+
Assert.assertFalse(error)
22+
}
23+
24+
/**
25+
* Test used to confirm two crashes related are fixed.
26+
*
27+
* https://github.com/wordpress-mobile/WordPress-Android/issues/20694
28+
*/
29+
@Test
30+
fun `Calling MarkSpan#safelyParseColor with null string should not cause a crash`() {
31+
var error = false
32+
try {
33+
MarkSpan(colorString = null)
34+
} catch (e: Exception) {
35+
error = true
36+
}
37+
Assert.assertFalse(error)
38+
}
39+
}

0 commit comments

Comments
 (0)