Skip to content

Commit 41bd007

Browse files
committed
feat: add heading styles
1 parent 1ebb932 commit 41bd007

25 files changed

Lines changed: 711 additions & 402 deletions

android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt

Lines changed: 46 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,9 @@ class EnrichedTextInputView : AppCompatEditText {
475475
EnrichedSpans.H1 -> paragraphStyles?.toggleStyle(EnrichedSpans.H1)
476476
EnrichedSpans.H2 -> paragraphStyles?.toggleStyle(EnrichedSpans.H2)
477477
EnrichedSpans.H3 -> paragraphStyles?.toggleStyle(EnrichedSpans.H3)
478+
EnrichedSpans.H4 -> paragraphStyles?.toggleStyle(EnrichedSpans.H4)
479+
EnrichedSpans.H5 -> paragraphStyles?.toggleStyle(EnrichedSpans.H5)
480+
EnrichedSpans.H6 -> paragraphStyles?.toggleStyle(EnrichedSpans.H6)
478481
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.toggleStyle(EnrichedSpans.CODE_BLOCK)
479482
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.toggleStyle(EnrichedSpans.BLOCK_QUOTE)
480483
EnrichedSpans.ORDERED_LIST -> listStyles?.toggleStyle(EnrichedSpans.ORDERED_LIST)
@@ -485,54 +488,54 @@ class EnrichedTextInputView : AppCompatEditText {
485488
layoutManager.invalidateLayout()
486489
}
487490

488-
private fun removeStyle(
489-
name: String,
490-
start: Int,
491-
end: Int,
492-
): Boolean {
493-
val removed =
494-
when (name) {
495-
EnrichedSpans.BOLD -> inlineStyles?.removeStyle(EnrichedSpans.BOLD, start, end)
496-
EnrichedSpans.ITALIC -> inlineStyles?.removeStyle(EnrichedSpans.ITALIC, start, end)
497-
EnrichedSpans.UNDERLINE -> inlineStyles?.removeStyle(EnrichedSpans.UNDERLINE, start, end)
498-
EnrichedSpans.STRIKETHROUGH -> inlineStyles?.removeStyle(EnrichedSpans.STRIKETHROUGH, start, end)
499-
EnrichedSpans.INLINE_CODE -> inlineStyles?.removeStyle(EnrichedSpans.INLINE_CODE, start, end)
500-
EnrichedSpans.H1 -> paragraphStyles?.removeStyle(EnrichedSpans.H1, start, end)
501-
EnrichedSpans.H2 -> paragraphStyles?.removeStyle(EnrichedSpans.H2, start, end)
502-
EnrichedSpans.H3 -> paragraphStyles?.removeStyle(EnrichedSpans.H3, start, end)
503-
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.removeStyle(EnrichedSpans.CODE_BLOCK, start, end)
504-
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.removeStyle(EnrichedSpans.BLOCK_QUOTE, start, end)
505-
EnrichedSpans.ORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.ORDERED_LIST, start, end)
506-
EnrichedSpans.UNORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.UNORDERED_LIST, start, end)
507-
EnrichedSpans.LINK -> parametrizedStyles?.removeStyle(EnrichedSpans.LINK, start, end)
508-
EnrichedSpans.IMAGE -> parametrizedStyles?.removeStyle(EnrichedSpans.IMAGE, start, end)
509-
EnrichedSpans.MENTION -> parametrizedStyles?.removeStyle(EnrichedSpans.MENTION, start, end)
510-
else -> false
511-
}
491+
private fun removeStyle(name: String, start: Int, end: Int): Boolean {
492+
val removed = when (name) {
493+
EnrichedSpans.BOLD -> inlineStyles?.removeStyle(EnrichedSpans.BOLD, start, end)
494+
EnrichedSpans.ITALIC -> inlineStyles?.removeStyle(EnrichedSpans.ITALIC, start, end)
495+
EnrichedSpans.UNDERLINE -> inlineStyles?.removeStyle(EnrichedSpans.UNDERLINE, start, end)
496+
EnrichedSpans.STRIKETHROUGH -> inlineStyles?.removeStyle(EnrichedSpans.STRIKETHROUGH, start, end)
497+
EnrichedSpans.INLINE_CODE -> inlineStyles?.removeStyle(EnrichedSpans.INLINE_CODE, start, end)
498+
EnrichedSpans.H1 -> paragraphStyles?.removeStyle(EnrichedSpans.H1, start, end)
499+
EnrichedSpans.H2 -> paragraphStyles?.removeStyle(EnrichedSpans.H2, start, end)
500+
EnrichedSpans.H3 -> paragraphStyles?.removeStyle(EnrichedSpans.H3, start, end)
501+
EnrichedSpans.H4 -> paragraphStyles?.removeStyle(EnrichedSpans.H4, start, end)
502+
EnrichedSpans.H5 -> paragraphStyles?.removeStyle(EnrichedSpans.H5, start, end)
503+
EnrichedSpans.H6 -> paragraphStyles?.removeStyle(EnrichedSpans.H6, start, end)
504+
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.removeStyle(EnrichedSpans.CODE_BLOCK, start, end)
505+
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.removeStyle(EnrichedSpans.BLOCK_QUOTE, start, end)
506+
EnrichedSpans.ORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.ORDERED_LIST, start, end)
507+
EnrichedSpans.UNORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.UNORDERED_LIST, start, end)
508+
EnrichedSpans.LINK -> parametrizedStyles?.removeStyle(EnrichedSpans.LINK, start, end)
509+
EnrichedSpans.IMAGE -> parametrizedStyles?.removeStyle(EnrichedSpans.IMAGE, start, end)
510+
EnrichedSpans.MENTION -> parametrizedStyles?.removeStyle(EnrichedSpans.MENTION, start, end)
511+
else -> false
512+
}
512513

513514
return removed == true
514515
}
515516

516517
private fun getTargetRange(name: String): Pair<Int, Int> {
517-
val result =
518-
when (name) {
519-
EnrichedSpans.BOLD -> inlineStyles?.getStyleRange()
520-
EnrichedSpans.ITALIC -> inlineStyles?.getStyleRange()
521-
EnrichedSpans.UNDERLINE -> inlineStyles?.getStyleRange()
522-
EnrichedSpans.STRIKETHROUGH -> inlineStyles?.getStyleRange()
523-
EnrichedSpans.INLINE_CODE -> inlineStyles?.getStyleRange()
524-
EnrichedSpans.H1 -> paragraphStyles?.getStyleRange()
525-
EnrichedSpans.H2 -> paragraphStyles?.getStyleRange()
526-
EnrichedSpans.H3 -> paragraphStyles?.getStyleRange()
527-
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.getStyleRange()
528-
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.getStyleRange()
529-
EnrichedSpans.ORDERED_LIST -> listStyles?.getStyleRange()
530-
EnrichedSpans.UNORDERED_LIST -> listStyles?.getStyleRange()
531-
EnrichedSpans.LINK -> parametrizedStyles?.getStyleRange()
532-
EnrichedSpans.IMAGE -> parametrizedStyles?.getStyleRange()
533-
EnrichedSpans.MENTION -> parametrizedStyles?.getStyleRange()
534-
else -> Pair(0, 0)
535-
}
518+
val result = when (name) {
519+
EnrichedSpans.BOLD -> inlineStyles?.getStyleRange()
520+
EnrichedSpans.ITALIC -> inlineStyles?.getStyleRange()
521+
EnrichedSpans.UNDERLINE -> inlineStyles?.getStyleRange()
522+
EnrichedSpans.STRIKETHROUGH -> inlineStyles?.getStyleRange()
523+
EnrichedSpans.INLINE_CODE -> inlineStyles?.getStyleRange()
524+
EnrichedSpans.H1 -> paragraphStyles?.getStyleRange()
525+
EnrichedSpans.H2 -> paragraphStyles?.getStyleRange()
526+
EnrichedSpans.H3 -> paragraphStyles?.getStyleRange()
527+
EnrichedSpans.H4 -> paragraphStyles?.getStyleRange()
528+
EnrichedSpans.H5 -> paragraphStyles?.getStyleRange()
529+
EnrichedSpans.H6 -> paragraphStyles?.getStyleRange()
530+
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.getStyleRange()
531+
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.getStyleRange()
532+
EnrichedSpans.ORDERED_LIST -> listStyles?.getStyleRange()
533+
EnrichedSpans.UNORDERED_LIST -> listStyles?.getStyleRange()
534+
EnrichedSpans.LINK -> parametrizedStyles?.getStyleRange()
535+
EnrichedSpans.IMAGE -> parametrizedStyles?.getStyleRange()
536+
EnrichedSpans.MENTION -> parametrizedStyles?.getStyleRange()
537+
else -> Pair(0, 0)
538+
}
536539

537540
return result ?: Pair(0, 0)
538541
}

android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,18 @@ class EnrichedTextInputViewManager :
294294
view?.verifyAndToggleStyle(EnrichedSpans.H3)
295295
}
296296

297+
override fun toggleH4(view: EnrichedTextInputView?) {
298+
view?.verifyAndToggleStyle(EnrichedSpans.H4)
299+
}
300+
301+
override fun toggleH5(view: EnrichedTextInputView?) {
302+
view?.verifyAndToggleStyle(EnrichedSpans.H5)
303+
}
304+
305+
override fun toggleH6(view: EnrichedTextInputView?) {
306+
view?.verifyAndToggleStyle(EnrichedSpans.H6)
307+
}
308+
297309
override fun toggleCodeBlock(view: EnrichedTextInputView?) {
298310
view?.verifyAndToggleStyle(EnrichedSpans.CODE_BLOCK)
299311
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.swmansion.enriched.spans
2+
3+
import android.graphics.Typeface
4+
import android.text.TextPaint
5+
import android.text.style.AbsoluteSizeSpan
6+
import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan
7+
import com.swmansion.enriched.styles.HtmlStyle
8+
9+
class EnrichedH4Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlStyle.h4FontSize), EnrichedHeadingSpan {
10+
override fun updateDrawState(tp: TextPaint) {
11+
super.updateDrawState(tp)
12+
val bold = htmlStyle.h4Bold
13+
if (bold) {
14+
tp.typeface = Typeface.create(tp.typeface, Typeface.BOLD)
15+
}
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.swmansion.enriched.spans
2+
3+
import android.graphics.Typeface
4+
import android.text.TextPaint
5+
import android.text.style.AbsoluteSizeSpan
6+
import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan
7+
import com.swmansion.enriched.styles.HtmlStyle
8+
9+
class EnrichedH5Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlStyle.h5FontSize), EnrichedHeadingSpan {
10+
override fun updateDrawState(tp: TextPaint) {
11+
super.updateDrawState(tp)
12+
val bold = htmlStyle.h5Bold
13+
if (bold) {
14+
tp.typeface = Typeface.create(tp.typeface, Typeface.BOLD)
15+
}
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.swmansion.enriched.spans
2+
3+
import android.graphics.Typeface
4+
import android.text.TextPaint
5+
import android.text.style.AbsoluteSizeSpan
6+
import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan
7+
import com.swmansion.enriched.styles.HtmlStyle
8+
9+
class EnrichedH6Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlStyle.h6FontSize), EnrichedHeadingSpan {
10+
override fun updateDrawState(tp: TextPaint) {
11+
super.updateDrawState(tp)
12+
val bold = htmlStyle.h6Bold
13+
if (bold) {
14+
tp.typeface = Typeface.create(tp.typeface, Typeface.BOLD)
15+
}
16+
}
17+
}

android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt

Lines changed: 51 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ object EnrichedSpans {
3939
const val H1 = "h1"
4040
const val H2 = "h2"
4141
const val H3 = "h3"
42+
const val H4 = "h4"
43+
const val H5 = "h5"
44+
const val H6 = "h6"
4245
const val BLOCK_QUOTE = "block_quote"
4346
const val CODE_BLOCK = "code_block"
4447

@@ -60,14 +63,16 @@ object EnrichedSpans {
6063
INLINE_CODE to BaseSpanConfig(EnrichedInlineCodeSpan::class.java),
6164
)
6265

63-
val paragraphSpans: Map<String, ParagraphSpanConfig> =
64-
mapOf(
65-
H1 to ParagraphSpanConfig(EnrichedH1Span::class.java, false),
66-
H2 to ParagraphSpanConfig(EnrichedH2Span::class.java, false),
67-
H3 to ParagraphSpanConfig(EnrichedH3Span::class.java, false),
68-
BLOCK_QUOTE to ParagraphSpanConfig(EnrichedBlockQuoteSpan::class.java, true),
69-
CODE_BLOCK to ParagraphSpanConfig(EnrichedCodeBlockSpan::class.java, true),
70-
)
66+
val paragraphSpans: Map<String, ParagraphSpanConfig> = mapOf(
67+
H1 to ParagraphSpanConfig(EnrichedH1Span::class.java, false),
68+
H2 to ParagraphSpanConfig(EnrichedH2Span::class.java, false),
69+
H3 to ParagraphSpanConfig(EnrichedH3Span::class.java, false),
70+
H4 to ParagraphSpanConfig(EnrichedH4Span::class.java, false),
71+
H5 to ParagraphSpanConfig(EnrichedH5Span::class.java, false),
72+
H6 to ParagraphSpanConfig(EnrichedH6Span::class.java, false),
73+
BLOCK_QUOTE to ParagraphSpanConfig(EnrichedBlockQuoteSpan::class.java, true),
74+
CODE_BLOCK to ParagraphSpanConfig(EnrichedCodeBlockSpan::class.java, true),
75+
)
7176

7277
val listSpans: Map<String, ListSpanConfig> =
7378
mapOf(
@@ -94,6 +99,9 @@ object EnrichedSpans {
9499
if (htmlStyle.h1Bold) blockingStyles.add(H1)
95100
if (htmlStyle.h2Bold) blockingStyles.add(H2)
96101
if (htmlStyle.h3Bold) blockingStyles.add(H3)
102+
if (htmlStyle.h4Bold) blockingStyles.add(H4)
103+
if (htmlStyle.h5Bold) blockingStyles.add(H5)
104+
if (htmlStyle.h6Bold) blockingStyles.add(H6)
97105
StylesMergingConfig(blockingStyles = blockingStyles.toTypedArray())
98106
}
99107

@@ -123,79 +131,57 @@ object EnrichedSpans {
123131
}
124132

125133
H1 -> {
126-
val conflictingStyles = mutableListOf(H2, H3, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
134+
val conflictingStyles = mutableListOf(H2, H3, H4, H5, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
127135
if (htmlStyle.h1Bold) conflictingStyles.add(BOLD)
128136
StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray())
129137
}
130138

131139
H2 -> {
132-
val conflictingStyles = mutableListOf(H1, H3, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
140+
val conflictingStyles = mutableListOf(H1, H3, H4, H5, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
133141
if (htmlStyle.h2Bold) conflictingStyles.add(BOLD)
134142
StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray())
135143
}
136144

137145
H3 -> {
138-
val conflictingStyles = mutableListOf(H1, H2, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
146+
val conflictingStyles = mutableListOf(H1, H2, H4, H5, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
139147
if (htmlStyle.h3Bold) conflictingStyles.add(BOLD)
140148
StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray())
141149
}
142-
143-
BLOCK_QUOTE -> {
144-
StylesMergingConfig(
145-
conflictingStyles = arrayOf(H1, H2, H3, CODE_BLOCK, ORDERED_LIST, UNORDERED_LIST),
146-
)
147-
}
148-
149-
CODE_BLOCK -> {
150-
StylesMergingConfig(
151-
conflictingStyles =
152-
arrayOf(
153-
H1,
154-
H2,
155-
H3,
156-
BOLD,
157-
ITALIC,
158-
UNDERLINE,
159-
STRIKETHROUGH,
160-
UNORDERED_LIST,
161-
ORDERED_LIST,
162-
BLOCK_QUOTE,
163-
INLINE_CODE,
164-
),
165-
)
166-
}
167-
168-
UNORDERED_LIST -> {
169-
StylesMergingConfig(
170-
conflictingStyles = arrayOf(H1, H2, H3, ORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE),
171-
)
172-
}
173-
174-
ORDERED_LIST -> {
175-
StylesMergingConfig(
176-
conflictingStyles = arrayOf(H1, H2, H3, UNORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE),
177-
)
178-
}
179-
180-
LINK -> {
181-
StylesMergingConfig(
182-
blockingStyles = arrayOf(INLINE_CODE, CODE_BLOCK, MENTION),
183-
)
184-
}
185-
186-
IMAGE -> {
187-
StylesMergingConfig()
150+
H4 -> {
151+
val conflictingStyles = mutableListOf(H1, H2, H3, H5, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
152+
if (htmlStyle.h4Bold) conflictingStyles.add(BOLD)
153+
StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray())
188154
}
189-
190-
MENTION -> {
191-
StylesMergingConfig(
192-
blockingStyles = arrayOf(INLINE_CODE, CODE_BLOCK, LINK),
193-
)
155+
H5 -> {
156+
val conflictingStyles = mutableListOf(H1, H2, H3, H4, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
157+
if (htmlStyle.h5Bold) conflictingStyles.add(BOLD)
158+
StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray())
194159
}
195-
196-
else -> {
197-
null
160+
H6 -> {
161+
val conflictingStyles = mutableListOf(H1, H2, H3, H4, H5, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK)
162+
if (htmlStyle.h6Bold) conflictingStyles.add(BOLD)
163+
StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray())
198164
}
165+
BLOCK_QUOTE -> StylesMergingConfig(
166+
conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, CODE_BLOCK, ORDERED_LIST, UNORDERED_LIST)
167+
)
168+
CODE_BLOCK -> StylesMergingConfig(
169+
conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, BOLD, ITALIC, UNDERLINE, STRIKETHROUGH, UNORDERED_LIST, ORDERED_LIST, BLOCK_QUOTE, INLINE_CODE)
170+
)
171+
UNORDERED_LIST -> StylesMergingConfig(
172+
conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, ORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE)
173+
)
174+
ORDERED_LIST -> StylesMergingConfig(
175+
conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, UNORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE),
176+
)
177+
LINK -> StylesMergingConfig(
178+
blockingStyles = arrayOf(INLINE_CODE, CODE_BLOCK, MENTION)
179+
)
180+
IMAGE -> StylesMergingConfig()
181+
MENTION -> StylesMergingConfig(
182+
blockingStyles = arrayOf(INLINE_CODE, CODE_BLOCK, LINK)
183+
)
184+
else -> null
199185
}
200186

201187
fun isTypeContinuous(type: Class<*>): Boolean = paragraphSpans.values.find { it.clazz == type }?.isContinuous == true

android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ class HtmlStyle {
2828
var h3FontSize: Int = 56
2929
var h3Bold: Boolean = false
3030

31+
var h4FontSize: Int = 48
32+
var h4Bold: Boolean = false
33+
34+
var h5FontSize: Int = 40
35+
var h5Bold: Boolean = false
36+
37+
var h6FontSize: Int = 32
38+
var h6Bold: Boolean = false
39+
3140
var blockquoteColor: Int? = null
3241
var blockquoteBorderColor: Int = Color.BLACK
3342
var blockquoteStripeWidth: Int = 2
@@ -77,6 +86,18 @@ class HtmlStyle {
7786
h3FontSize = parseFloat(h3Style, "fontSize").toInt()
7887
h3Bold = h3Style?.getBoolean("bold") == true
7988

89+
val h4Style = style.getMap("h4")
90+
h4FontSize = parseFloat(h4Style, "fontSize").toInt()
91+
h4Bold = h4Style?.getBoolean("bold") == true
92+
93+
val h5Style = style.getMap("h5")
94+
h5FontSize = parseFloat(h5Style, "fontSize").toInt()
95+
h5Bold = h5Style?.getBoolean("bold") == true
96+
97+
val h6Style = style.getMap("h6")
98+
h6FontSize = parseFloat(h6Style, "fontSize").toInt()
99+
h6Bold = h6Style?.getBoolean("bold") == true
100+
80101
val blockquoteStyle = style.getMap("blockquote")
81102
blockquoteColor = parseOptionalColor(blockquoteStyle, "color")
82103
blockquoteBorderColor = parseColor(blockquoteStyle, "borderColor")

0 commit comments

Comments
 (0)