From 4257b4587806a74bd8541097dc0d5b4f3703ac3c Mon Sep 17 00:00:00 2001 From: IvanIhnatsiuk Date: Sun, 9 Nov 2025 18:54:49 +0100 Subject: [PATCH 1/5] feat: add android linters --- android/build.gradle | 5 + android/lint.gradle | 51 +++++ .../enriched/EnrichedTextInputView.kt | 154 +++++++++------ .../EnrichedTextInputViewLayoutManager.kt | 4 +- .../enriched/EnrichedTextInputViewManager.kt | 166 +++++++++++----- .../enriched/EnrichedTextInputViewPackage.kt | 4 +- .../swmansion/enriched/MeasurementStore.kt | 51 +++-- .../enriched/events/MentionHandler.kt | 30 ++- .../enriched/events/OnChangeHtmlEvent.kt | 17 +- .../enriched/events/OnChangeSelectionEvent.kt | 19 +- .../enriched/events/OnChangeStateEvent.kt | 21 +- .../enriched/events/OnChangeTextEvent.kt | 17 +- .../enriched/events/OnInputBlurEvent.kt | 16 +- .../enriched/events/OnInputFocusEvent.kt | 16 +- .../enriched/events/OnLinkDetectedEvent.kt | 24 +-- .../enriched/events/OnMentionDetectedEvent.kt | 19 +- .../enriched/events/OnMentionEvent.kt | 17 +- .../enriched/spans/EnrichedBlockQuoteSpan.kt | 25 ++- .../enriched/spans/EnrichedBoldSpan.kt | 6 +- .../enriched/spans/EnrichedCodeBlockSpan.kt | 8 +- .../enriched/spans/EnrichedH1Span.kt | 5 +- .../enriched/spans/EnrichedH2Span.kt | 5 +- .../enriched/spans/EnrichedH3Span.kt | 5 +- .../enriched/spans/EnrichedImageSpan.kt | 19 +- .../enriched/spans/EnrichedInlineCodeSpan.kt | 5 +- .../enriched/spans/EnrichedItalicSpan.kt | 6 +- .../enriched/spans/EnrichedLinkSpan.kt | 10 +- .../enriched/spans/EnrichedMentionSpan.kt | 21 +- .../enriched/spans/EnrichedOrderedListSpan.kt | 29 +-- .../swmansion/enriched/spans/EnrichedSpans.kt | 186 +++++++++++------- .../spans/EnrichedStrikeThroughSpan.kt | 6 +- .../enriched/spans/EnrichedUnderlineSpan.kt | 6 +- .../spans/EnrichedUnorderedListSpan.kt | 12 +- .../spans/interfaces/EnrichedBlockSpan.kt | 5 +- .../spans/interfaces/EnrichedHeadingSpan.kt | 3 +- .../spans/interfaces/EnrichedInlineSpan.kt | 3 +- .../spans/interfaces/EnrichedParagraphSpan.kt | 5 +- .../enriched/spans/interfaces/EnrichedSpan.kt | 3 +- .../interfaces/EnrichedZeroWidthSpaceSpan.kt | 3 +- .../swmansion/enriched/styles/HtmlStyle.kt | 65 ++++-- .../swmansion/enriched/styles/InlineStyles.kt | 33 +++- .../swmansion/enriched/styles/ListStyles.kt | 63 ++++-- .../enriched/styles/ParagraphStyles.kt | 47 ++++- .../enriched/styles/ParametrizedStyles.kt | 56 ++++-- .../enriched/utils/EnrichedParser.java | 162 ++++++++------- .../enriched/utils/EnrichedSelection.kt | 111 +++++++---- .../enriched/utils/EnrichedSpanState.kt | 67 ++++--- .../com/swmansion/enriched/utils/Utils.kt | 28 ++- .../enriched/watchers/EnrichedSpanWatcher.kt | 60 ++++-- .../enriched/watchers/EnrichedTextWatcher.kt | 32 ++- 50 files changed, 1106 insertions(+), 625 deletions(-) create mode 100644 android/lint.gradle diff --git a/android/build.gradle b/android/build.gradle index eae1bd08..3076d506 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -6,12 +6,15 @@ buildscript { repositories { google() mavenCentral() + gradlePluginPortal() } dependencies { classpath "com.android.tools.build:gradle:8.7.2" // noinspection DifferentKotlinGradleVersion classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}" + classpath "org.jlleitschuh.gradle:ktlint-gradle:13.1.0" + classpath 'com.diffplug.spotless:spotless-plugin-gradle:8.0.0' } } @@ -21,6 +24,8 @@ apply plugin: "kotlin-android" apply plugin: "com.facebook.react" +apply from: "lint.gradle" + def getExtOrIntegerDefault(name) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["EnrichedTextInput_" + name]).toInteger() } diff --git a/android/lint.gradle b/android/lint.gradle new file mode 100644 index 00000000..5df9bf91 --- /dev/null +++ b/android/lint.gradle @@ -0,0 +1,51 @@ +apply plugin: "org.jlleitschuh.gradle.ktlint" +apply plugin: 'com.diffplug.spotless' + +ktlint { + version = "1.7.1" + verbose = true + outputToConsole = true + coloredOutput = true + android = true + enableExperimentalRules = true + + reporters { + reporter "plain" + reporter "checkstyle" + reporter "html" + } + + filter { + exclude("**/generated/**") + include("**/kotlin/**") + } +} + +spotless { + java { + target 'src/**/*.java' + googleJavaFormat("1.23.0") + + removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() + leadingTabsToSpaces(2) + lineEndings 'UNIX' + } +} + + +tasks.register('lintFormat') { + group = "formatting" + description = "Auto-fix all code formatting issues" + dependsOn 'ktlintFormat', 'spotlessApply' +} + +tasks.register('lintVerify') { + group = "verification" + description = "Verify code is properly formatted" + dependsOn 'ktlintCheck', 'spotlessCheck' + doLast { + println "✓ All code formatting checks passed!" + } +} diff --git a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt index bae67e50..50cb1536 100644 --- a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +++ b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt @@ -30,11 +30,11 @@ import com.swmansion.enriched.events.MentionHandler import com.swmansion.enriched.events.OnInputBlurEvent import com.swmansion.enriched.events.OnInputFocusEvent import com.swmansion.enriched.spans.EnrichedSpans +import com.swmansion.enriched.styles.HtmlStyle import com.swmansion.enriched.styles.InlineStyles import com.swmansion.enriched.styles.ListStyles import com.swmansion.enriched.styles.ParagraphStyles import com.swmansion.enriched.styles.ParametrizedStyles -import com.swmansion.enriched.styles.HtmlStyle import com.swmansion.enriched.utils.EnrichedParser import com.swmansion.enriched.utils.EnrichedSelection import com.swmansion.enriched.utils.EnrichedSpanState @@ -43,7 +43,6 @@ import com.swmansion.enriched.watchers.EnrichedSpanWatcher import com.swmansion.enriched.watchers.EnrichedTextWatcher import kotlin.math.ceil - class EnrichedTextInputView : AppCompatEditText { var stateWrapper: StateWrapper? = null val selection: EnrichedSelection? = EnrichedSelection(this) @@ -54,6 +53,7 @@ class EnrichedTextInputView : AppCompatEditText { val parametrizedStyles: ParametrizedStyles? = ParametrizedStyles(this) var isDuringTransaction: Boolean = false var isRemovingMany: Boolean = false + var test = true val mentionHandler: MentionHandler? = MentionHandler(this) var htmlStyle: HtmlStyle = HtmlStyle(this, null) @@ -84,7 +84,7 @@ class EnrichedTextInputView : AppCompatEditText { constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( context, attrs, - defStyleAttr + defStyleAttr, ) { prepareComponent() } @@ -126,7 +126,8 @@ class EnrichedTextInputView : AppCompatEditText { if (!canScrollVertically(-1) && !canScrollVertically(1) && !canScrollHorizontally(-1) && - !canScrollHorizontally(1)) { + !canScrollHorizontally(1) + ) { // We cannot scroll, let parent views take care of these touches. this.parent.requestDisallowInterceptTouchEvent(false) } @@ -137,7 +138,10 @@ class EnrichedTextInputView : AppCompatEditText { return super.onTouchEvent(ev) } - override fun onSelectionChanged(selStart: Int, selEnd: Int) { + override fun onSelectionChanged( + selStart: Int, + selEnd: Int, + ) { super.onSelectionChanged(selStart, selEnd) selection?.onSelection(selStart, selEnd) } @@ -147,7 +151,11 @@ class EnrichedTextInputView : AppCompatEditText { inputMethodManager?.hideSoftInputFromWindow(windowToken, 0) } - override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) { + override fun onFocusChanged( + focused: Boolean, + direction: Int, + previouslyFocusedRect: Rect?, + ) { super.onFocusChanged(focused, direction, previouslyFocusedRect) val context = context as ReactContext val surfaceId = UIManagerHelper.getSurfaceId(context) @@ -335,19 +343,21 @@ class EnrichedTextInputView : AppCompatEditText { } fun setAutoCapitalize(flagName: String?) { - val flag = when (flagName) { - "none" -> InputType.TYPE_NULL - "sentences" -> InputType.TYPE_TEXT_FLAG_CAP_SENTENCES - "words" -> InputType.TYPE_TEXT_FLAG_CAP_WORDS - "characters" -> InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS - else -> InputType.TYPE_NULL - } + val flag = + when (flagName) { + "none" -> InputType.TYPE_NULL + "sentences" -> InputType.TYPE_TEXT_FLAG_CAP_SENTENCES + "words" -> InputType.TYPE_TEXT_FLAG_CAP_WORDS + "characters" -> InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + else -> InputType.TYPE_NULL + } - inputType = (inputType and - InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS.inv() and - InputType.TYPE_TEXT_FLAG_CAP_WORDS.inv() and - InputType.TYPE_TEXT_FLAG_CAP_SENTENCES.inv() - ) or if (flag == InputType.TYPE_NULL) 0 else flag + inputType = ( + inputType and + InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS.inv() and + InputType.TYPE_TEXT_FLAG_CAP_WORDS.inv() and + InputType.TYPE_TEXT_FLAG_CAP_SENTENCES.inv() + ) or if (flag == InputType.TYPE_NULL) 0 else flag } // https://github.com/facebook/react-native/blob/36df97f500aa0aa8031098caf7526db358b6ddc1/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt#L283C2-L284C1 @@ -356,9 +366,7 @@ class EnrichedTextInputView : AppCompatEditText { // next layout() to be called. However, we do not perform a layout() after a requestLayout(), so // we need to override isLayoutRequested to force EditText to scroll to the end of the new text // immediately. - override fun isLayoutRequested(): Boolean { - return false - } + override fun isLayoutRequested(): Boolean = false fun updateTypeface() { if (!typefaceDirty) return @@ -391,48 +399,54 @@ class EnrichedTextInputView : AppCompatEditText { layoutManager.invalidateLayout() } - private fun removeStyle(name: String, start: Int, end: Int): Boolean { - val removed = when (name) { - EnrichedSpans.BOLD -> inlineStyles?.removeStyle(EnrichedSpans.BOLD, start, end) - EnrichedSpans.ITALIC -> inlineStyles?.removeStyle(EnrichedSpans.ITALIC, start, end) - EnrichedSpans.UNDERLINE -> inlineStyles?.removeStyle(EnrichedSpans.UNDERLINE, start, end) - EnrichedSpans.STRIKETHROUGH -> inlineStyles?.removeStyle(EnrichedSpans.STRIKETHROUGH, start, end) - EnrichedSpans.INLINE_CODE -> inlineStyles?.removeStyle(EnrichedSpans.INLINE_CODE, start, end) - EnrichedSpans.H1 -> paragraphStyles?.removeStyle(EnrichedSpans.H1, start, end) - EnrichedSpans.H2 -> paragraphStyles?.removeStyle(EnrichedSpans.H2, start, end) - EnrichedSpans.H3 -> paragraphStyles?.removeStyle(EnrichedSpans.H3, start, end) - EnrichedSpans.CODE_BLOCK -> paragraphStyles?.removeStyle(EnrichedSpans.CODE_BLOCK, start, end) - EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.removeStyle(EnrichedSpans.BLOCK_QUOTE, start, end) - EnrichedSpans.ORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.ORDERED_LIST, start, end) - EnrichedSpans.UNORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.UNORDERED_LIST, start, end) - EnrichedSpans.LINK -> parametrizedStyles?.removeStyle(EnrichedSpans.LINK, start, end) - EnrichedSpans.IMAGE -> parametrizedStyles?.removeStyle(EnrichedSpans.IMAGE, start, end) - EnrichedSpans.MENTION -> parametrizedStyles?.removeStyle(EnrichedSpans.MENTION, start, end) - else -> false - } + private fun removeStyle( + name: String, + start: Int, + end: Int, + ): Boolean { + val removed = + when (name) { + EnrichedSpans.BOLD -> inlineStyles?.removeStyle(EnrichedSpans.BOLD, start, end) + EnrichedSpans.ITALIC -> inlineStyles?.removeStyle(EnrichedSpans.ITALIC, start, end) + EnrichedSpans.UNDERLINE -> inlineStyles?.removeStyle(EnrichedSpans.UNDERLINE, start, end) + EnrichedSpans.STRIKETHROUGH -> inlineStyles?.removeStyle(EnrichedSpans.STRIKETHROUGH, start, end) + EnrichedSpans.INLINE_CODE -> inlineStyles?.removeStyle(EnrichedSpans.INLINE_CODE, start, end) + EnrichedSpans.H1 -> paragraphStyles?.removeStyle(EnrichedSpans.H1, start, end) + EnrichedSpans.H2 -> paragraphStyles?.removeStyle(EnrichedSpans.H2, start, end) + EnrichedSpans.H3 -> paragraphStyles?.removeStyle(EnrichedSpans.H3, start, end) + EnrichedSpans.CODE_BLOCK -> paragraphStyles?.removeStyle(EnrichedSpans.CODE_BLOCK, start, end) + EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.removeStyle(EnrichedSpans.BLOCK_QUOTE, start, end) + EnrichedSpans.ORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.ORDERED_LIST, start, end) + EnrichedSpans.UNORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.UNORDERED_LIST, start, end) + EnrichedSpans.LINK -> parametrizedStyles?.removeStyle(EnrichedSpans.LINK, start, end) + EnrichedSpans.IMAGE -> parametrizedStyles?.removeStyle(EnrichedSpans.IMAGE, start, end) + EnrichedSpans.MENTION -> parametrizedStyles?.removeStyle(EnrichedSpans.MENTION, start, end) + else -> false + } return removed == true } private fun getTargetRange(name: String): Pair { - val result = when (name) { - EnrichedSpans.BOLD -> inlineStyles?.getStyleRange() - EnrichedSpans.ITALIC -> inlineStyles?.getStyleRange() - EnrichedSpans.UNDERLINE -> inlineStyles?.getStyleRange() - EnrichedSpans.STRIKETHROUGH -> inlineStyles?.getStyleRange() - EnrichedSpans.INLINE_CODE -> inlineStyles?.getStyleRange() - EnrichedSpans.H1 -> paragraphStyles?.getStyleRange() - EnrichedSpans.H2 -> paragraphStyles?.getStyleRange() - EnrichedSpans.H3 -> paragraphStyles?.getStyleRange() - EnrichedSpans.CODE_BLOCK -> paragraphStyles?.getStyleRange() - EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.getStyleRange() - EnrichedSpans.ORDERED_LIST -> listStyles?.getStyleRange() - EnrichedSpans.UNORDERED_LIST -> listStyles?.getStyleRange() - EnrichedSpans.LINK -> parametrizedStyles?.getStyleRange() - EnrichedSpans.IMAGE -> parametrizedStyles?.getStyleRange() - EnrichedSpans.MENTION -> parametrizedStyles?.getStyleRange() - else -> Pair(0, 0) - } + val result = + when (name) { + EnrichedSpans.BOLD -> inlineStyles?.getStyleRange() + EnrichedSpans.ITALIC -> inlineStyles?.getStyleRange() + EnrichedSpans.UNDERLINE -> inlineStyles?.getStyleRange() + EnrichedSpans.STRIKETHROUGH -> inlineStyles?.getStyleRange() + EnrichedSpans.INLINE_CODE -> inlineStyles?.getStyleRange() + EnrichedSpans.H1 -> paragraphStyles?.getStyleRange() + EnrichedSpans.H2 -> paragraphStyles?.getStyleRange() + EnrichedSpans.H3 -> paragraphStyles?.getStyleRange() + EnrichedSpans.CODE_BLOCK -> paragraphStyles?.getStyleRange() + EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.getStyleRange() + EnrichedSpans.ORDERED_LIST -> listStyles?.getStyleRange() + EnrichedSpans.UNORDERED_LIST -> listStyles?.getStyleRange() + EnrichedSpans.LINK -> parametrizedStyles?.getStyleRange() + EnrichedSpans.IMAGE -> parametrizedStyles?.getStyleRange() + EnrichedSpans.MENTION -> parametrizedStyles?.getStyleRange() + else -> Pair(0, 0) + } return result ?: Pair(0, 0) } @@ -466,11 +480,12 @@ class EnrichedTextInputView : AppCompatEditText { val lengthAfter = text?.length ?: 0 val charactersRemoved = lengthBefore - lengthAfter - val finalEnd = if (charactersRemoved > 0) { - (end - charactersRemoved).coerceAtLeast(0) - } else { - end - } + val finalEnd = + if (charactersRemoved > 0) { + (end - charactersRemoved).coerceAtLeast(0) + } else { + end + } val finalStart = start.coerceAtLeast(0).coerceAtMost(finalEnd) selection?.onSelection(finalStart, finalEnd) @@ -492,7 +507,12 @@ class EnrichedTextInputView : AppCompatEditText { toggleStyle(name) } - fun addLink(start: Int, end: Int, text: String, url: String) { + fun addLink( + start: Int, + end: Int, + text: String, + url: String, + ) { val isValid = verifyStyle(EnrichedSpans.LINK) if (!isValid) return @@ -514,7 +534,11 @@ class EnrichedTextInputView : AppCompatEditText { parametrizedStyles?.startMention(indicator) } - fun addMention(indicator: String, text: String, attributes: Map) { + fun addMention( + indicator: String, + text: String, + attributes: Map, + ) { val isValid = verifyStyle(EnrichedSpans.MENTION) if (!isValid) return diff --git a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewLayoutManager.kt b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewLayoutManager.kt index ad7673cd..6403ce55 100644 --- a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewLayoutManager.kt +++ b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewLayoutManager.kt @@ -2,7 +2,9 @@ package com.swmansion.enriched import com.facebook.react.bridge.Arguments -class EnrichedTextInputViewLayoutManager(private val view: EnrichedTextInputView) { +class EnrichedTextInputViewLayoutManager( + private val view: EnrichedTextInputView, +) { private var forceHeightRecalculationCounter: Int = 0 fun invalidateLayout() { diff --git a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt index 418c3fb2..1a5a1826 100644 --- a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +++ b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt @@ -1,7 +1,6 @@ package com.swmansion.enriched import android.content.Context -import android.util.Log import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import com.facebook.react.module.annotations.ReactModule @@ -16,11 +15,11 @@ import com.facebook.react.uimanager.annotations.ReactProp import com.facebook.react.viewmanagers.EnrichedTextInputViewManagerDelegate import com.facebook.react.viewmanagers.EnrichedTextInputViewManagerInterface import com.facebook.yoga.YogaMeasureMode -import com.swmansion.enriched.events.OnInputBlurEvent import com.swmansion.enriched.events.OnChangeHtmlEvent import com.swmansion.enriched.events.OnChangeSelectionEvent import com.swmansion.enriched.events.OnChangeStateEvent import com.swmansion.enriched.events.OnChangeTextEvent +import com.swmansion.enriched.events.OnInputBlurEvent import com.swmansion.enriched.events.OnInputFocusEvent import com.swmansion.enriched.events.OnLinkDetectedEvent import com.swmansion.enriched.events.OnMentionDetectedEvent @@ -30,22 +29,17 @@ import com.swmansion.enriched.styles.HtmlStyle import com.swmansion.enriched.utils.jsonStringToStringMap @ReactModule(name = EnrichedTextInputViewManager.NAME) -class EnrichedTextInputViewManager : SimpleViewManager(), +class EnrichedTextInputViewManager : + SimpleViewManager(), EnrichedTextInputViewManagerInterface { private val mDelegate: ViewManagerDelegate = EnrichedTextInputViewManagerDelegate(this) - override fun getDelegate(): ViewManagerDelegate? { - return mDelegate - } + override fun getDelegate(): ViewManagerDelegate? = mDelegate - override fun getName(): String { - return NAME - } + override fun getName(): String = NAME - public override fun createViewInstance(context: ThemedReactContext): EnrichedTextInputView { - return EnrichedTextInputView(context) - } + public override fun createViewInstance(context: ThemedReactContext): EnrichedTextInputView = EnrichedTextInputView(context) override fun onDropViewInstance(view: EnrichedTextInputView) { super.onDropViewInstance(view) @@ -55,64 +49,88 @@ class EnrichedTextInputViewManager : SimpleViewManager(), override fun updateState( view: EnrichedTextInputView, props: ReactStylesDiffMap?, - stateWrapper: StateWrapper? + stateWrapper: StateWrapper?, ): Any? { view.stateWrapper = stateWrapper return super.updateState(view, props, stateWrapper) } - override fun getExportedCustomDirectEventTypeConstants(): MutableMap { - val map = mutableMapOf() - map.put(OnInputFocusEvent.EVENT_NAME, mapOf("registrationName" to OnInputFocusEvent.EVENT_NAME)) - map.put(OnInputBlurEvent.EVENT_NAME, mapOf("registrationName" to OnInputBlurEvent.EVENT_NAME)) - map.put(OnChangeTextEvent.EVENT_NAME, mapOf("registrationName" to OnChangeTextEvent.EVENT_NAME)) - map.put(OnChangeHtmlEvent.EVENT_NAME, mapOf("registrationName" to OnChangeHtmlEvent.EVENT_NAME)) - map.put(OnChangeStateEvent.EVENT_NAME, mapOf("registrationName" to OnChangeStateEvent.EVENT_NAME)) - map.put(OnLinkDetectedEvent.EVENT_NAME, mapOf("registrationName" to OnLinkDetectedEvent.EVENT_NAME)) - map.put(OnMentionDetectedEvent.EVENT_NAME, mapOf("registrationName" to OnMentionDetectedEvent.EVENT_NAME)) - map.put(OnMentionEvent.EVENT_NAME, mapOf("registrationName" to OnMentionEvent.EVENT_NAME)) - map.put(OnChangeSelectionEvent.EVENT_NAME, mapOf("registrationName" to OnChangeSelectionEvent.EVENT_NAME)) + override fun getExportedCustomDirectEventTypeConstants(): MutableMap { + val map = mutableMapOf() + map.put(OnInputFocusEvent.EVENT_NAME, mapOf("registrationName" to OnInputFocusEvent.EVENT_NAME)) + map.put(OnInputBlurEvent.EVENT_NAME, mapOf("registrationName" to OnInputBlurEvent.EVENT_NAME)) + map.put(OnChangeTextEvent.EVENT_NAME, mapOf("registrationName" to OnChangeTextEvent.EVENT_NAME)) + map.put(OnChangeHtmlEvent.EVENT_NAME, mapOf("registrationName" to OnChangeHtmlEvent.EVENT_NAME)) + map.put(OnChangeStateEvent.EVENT_NAME, mapOf("registrationName" to OnChangeStateEvent.EVENT_NAME)) + map.put(OnLinkDetectedEvent.EVENT_NAME, mapOf("registrationName" to OnLinkDetectedEvent.EVENT_NAME)) + map.put(OnMentionDetectedEvent.EVENT_NAME, mapOf("registrationName" to OnMentionDetectedEvent.EVENT_NAME)) + map.put(OnMentionEvent.EVENT_NAME, mapOf("registrationName" to OnMentionEvent.EVENT_NAME)) + map.put(OnChangeSelectionEvent.EVENT_NAME, mapOf("registrationName" to OnChangeSelectionEvent.EVENT_NAME)) - return map - } + return map + } @ReactProp(name = "defaultValue") - override fun setDefaultValue(view: EnrichedTextInputView?, value: String?) { + override fun setDefaultValue( + view: EnrichedTextInputView?, + value: String?, + ) { view?.setValue(value) } @ReactProp(name = "placeholder") - override fun setPlaceholder(view: EnrichedTextInputView?, value: String?) { + override fun setPlaceholder( + view: EnrichedTextInputView?, + value: String?, + ) { view?.setPlaceholder(value) } @ReactProp(name = "placeholderTextColor", customType = "Color") - override fun setPlaceholderTextColor(view: EnrichedTextInputView?, color: Int?) { + override fun setPlaceholderTextColor( + view: EnrichedTextInputView?, + color: Int?, + ) { view?.setPlaceholderTextColor(color) } @ReactProp(name = "cursorColor", customType = "Color") - override fun setCursorColor(view: EnrichedTextInputView?, color: Int?) { + override fun setCursorColor( + view: EnrichedTextInputView?, + color: Int?, + ) { view?.setCursorColor(color) } @ReactProp(name = "selectionColor", customType = "Color") - override fun setSelectionColor(view: EnrichedTextInputView?, color: Int?) { + override fun setSelectionColor( + view: EnrichedTextInputView?, + color: Int?, + ) { view?.setSelectionColor(color) } @ReactProp(name = "autoFocus", defaultBoolean = false) - override fun setAutoFocus(view: EnrichedTextInputView?, autoFocus: Boolean) { + override fun setAutoFocus( + view: EnrichedTextInputView?, + autoFocus: Boolean, + ) { view?.setAutoFocus(autoFocus) } @ReactProp(name = "editable", defaultBoolean = true) - override fun setEditable(view: EnrichedTextInputView?, editable: Boolean) { + override fun setEditable( + view: EnrichedTextInputView?, + editable: Boolean, + ) { view?.isEnabled = editable } @ReactProp(name = "mentionIndicators") - override fun setMentionIndicators(view: EnrichedTextInputView?, indicators: ReadableArray?) { + override fun setMentionIndicators( + view: EnrichedTextInputView?, + indicators: ReadableArray?, + ) { if (indicators == null) return val indicatorsList = mutableListOf() @@ -126,32 +144,50 @@ class EnrichedTextInputViewManager : SimpleViewManager(), } @ReactProp(name = "htmlStyle") - override fun setHtmlStyle(view: EnrichedTextInputView?, style: ReadableMap?) { + override fun setHtmlStyle( + view: EnrichedTextInputView?, + style: ReadableMap?, + ) { view?.htmlStyle = HtmlStyle(view, style) } @ReactProp(name = ViewProps.COLOR, customType = "Color") - override fun setColor(view: EnrichedTextInputView?, color: Int?) { + override fun setColor( + view: EnrichedTextInputView?, + color: Int?, + ) { view?.setColor(color) } @ReactProp(name = "fontSize", defaultFloat = ViewDefaults.FONT_SIZE_SP) - override fun setFontSize(view: EnrichedTextInputView?, size: Float) { + override fun setFontSize( + view: EnrichedTextInputView?, + size: Float, + ) { view?.setFontSize(size) } @ReactProp(name = "fontFamily") - override fun setFontFamily(view: EnrichedTextInputView?, family: String?) { + override fun setFontFamily( + view: EnrichedTextInputView?, + family: String?, + ) { view?.setFontFamily(family) } @ReactProp(name = "fontWeight") - override fun setFontWeight(view: EnrichedTextInputView?, weight: String?) { + override fun setFontWeight( + view: EnrichedTextInputView?, + weight: String?, + ) { view?.setFontWeight(weight) } @ReactProp(name = "fontStyle") - override fun setFontStyle(view: EnrichedTextInputView?, style: String?) { + override fun setFontStyle( + view: EnrichedTextInputView?, + style: String?, + ) { view?.setFontStyle(style) } @@ -165,24 +201,30 @@ class EnrichedTextInputViewManager : SimpleViewManager(), left: Int, top: Int, right: Int, - bottom: Int + bottom: Int, ) { super.setPadding(view, left, top, right, bottom) view?.setPadding(left, top, right, bottom) } - override fun setIsOnChangeHtmlSet(view: EnrichedTextInputView?, value: Boolean) { + override fun setIsOnChangeHtmlSet( + view: EnrichedTextInputView?, + value: Boolean, + ) { // this prop isn't used on Android as of now, but the setter must be present } - override fun setAutoCapitalize(view: EnrichedTextInputView?, flag: String?) { + override fun setAutoCapitalize( + view: EnrichedTextInputView?, + flag: String?, + ) { view?.setAutoCapitalize(flag) } override fun setAndroidExperimentalSynchronousEvents( view: EnrichedTextInputView?, - value: Boolean + value: Boolean, ) { view?.experimentalSynchronousEvents = value } @@ -195,7 +237,10 @@ class EnrichedTextInputViewManager : SimpleViewManager(), view?.clearFocus() } - override fun setValue(view: EnrichedTextInputView?, text: String) { + override fun setValue( + view: EnrichedTextInputView?, + text: String, + ) { view?.setValue(text) } @@ -247,19 +292,36 @@ class EnrichedTextInputViewManager : SimpleViewManager(), view?.verifyAndToggleStyle(EnrichedSpans.UNORDERED_LIST) } - override fun addLink(view: EnrichedTextInputView?, start: Int, end: Int, text: String, url: String) { + override fun addLink( + view: EnrichedTextInputView?, + start: Int, + end: Int, + text: String, + url: String, + ) { view?.addLink(start, end, text, url) } - override fun addImage(view: EnrichedTextInputView?, src: String) { + override fun addImage( + view: EnrichedTextInputView?, + src: String, + ) { view?.addImage(src) } - override fun startMention(view: EnrichedTextInputView?, indicator: String) { + override fun startMention( + view: EnrichedTextInputView?, + indicator: String, + ) { view?.startMention(indicator) } - override fun addMention(view: EnrichedTextInputView?, indicator: String, text: String, payload: String) { + override fun addMention( + view: EnrichedTextInputView?, + indicator: String, + text: String, + payload: String, + ) { val attributes = jsonStringToStringMap(payload) view?.addMention(text, indicator, attributes) } @@ -273,10 +335,8 @@ class EnrichedTextInputViewManager : SimpleViewManager(), widthMode: YogaMeasureMode?, height: Float, heightMode: YogaMeasureMode?, - attachmentsPositions: FloatArray? - ): Long { - return MeasurementStore.getMeasureById(localData?.getInt("viewTag"), width) - } + attachmentsPositions: FloatArray?, + ): Long = MeasurementStore.getMeasureById(localData?.getInt("viewTag"), width) companion object { const val NAME = "EnrichedTextInputView" diff --git a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt index 3b93c129..dec42fbf 100644 --- a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt +++ b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt @@ -13,7 +13,5 @@ class EnrichedTextInputViewPackage : ReactPackage { return viewManagers } - override fun createNativeModules(reactContext: ReactApplicationContext): List { - return emptyList() - } + override fun createNativeModules(reactContext: ReactApplicationContext): List = emptyList() } diff --git a/android/src/main/java/com/swmansion/enriched/MeasurementStore.kt b/android/src/main/java/com/swmansion/enriched/MeasurementStore.kt index ed4df3b3..65898076 100644 --- a/android/src/main/java/com/swmansion/enriched/MeasurementStore.kt +++ b/android/src/main/java/com/swmansion/enriched/MeasurementStore.kt @@ -19,14 +19,17 @@ object MeasurementStore { data class MeasurementParams( val cachedWidth: Float, val cachedSize: Long, - val spannable: Spannable?, val paintParams: PaintParams, ) private val data = ConcurrentHashMap() - fun store(id: Int, spannable: Spannable?, paint: TextPaint): Boolean { + fun store( + id: Int, + spannable: Spannable?, + paint: TextPaint, + ): Boolean { val cachedWidth = data[id]?.cachedWidth ?: 0f val cachedSize = data[id]?.cachedSize ?: 0L val size = measure(cachedWidth, spannable, paint) @@ -40,22 +43,32 @@ object MeasurementStore { data.remove(id) } - fun measure(maxWidth: Float, spannable: Spannable?, paintParams: PaintParams): Long { - val paint = TextPaint().apply { - typeface = paintParams.typeface - textSize = paintParams.fontSize - } + fun measure( + maxWidth: Float, + spannable: Spannable?, + paintParams: PaintParams, + ): Long { + val paint = + TextPaint().apply { + typeface = paintParams.typeface + textSize = paintParams.fontSize + } return measure(maxWidth, spannable, paint) } - fun measure(maxWidth: Float, spannable: Spannable?, paint: TextPaint): Long { + fun measure( + maxWidth: Float, + spannable: Spannable?, + paint: TextPaint, + ): Long { val text = spannable ?: "" val textLength = text.length - val builder = StaticLayout.Builder - .obtain(text, 0, textLength, paint, maxWidth.toInt()) - .setIncludePad(true) - .setLineSpacing(0f, 1f) + val builder = + StaticLayout.Builder + .obtain(text, 0, textLength, paint, maxWidth.toInt()) + .setIncludePad(true) + .setLineSpacing(0f, 1f) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { builder.setBreakStrategy(LineBreaker.BREAK_STRATEGY_HIGH_QUALITY) @@ -71,7 +84,10 @@ object MeasurementStore { return YogaMeasureOutput.make(widthInSP, heightInSP) } - fun getMeasureById(id: Int?, width: Float): Long { + fun getMeasureById( + id: Int?, + width: Float, + ): Long { val id = id ?: return YogaMeasureOutput.make(0, 0) val value = data[id] ?: return YogaMeasureOutput.make(0, 0) @@ -79,10 +95,11 @@ object MeasurementStore { return value.cachedSize } - val paint = TextPaint().apply { - typeface = value.paintParams.typeface - textSize = value.paintParams.fontSize - } + val paint = + TextPaint().apply { + typeface = value.paintParams.typeface + textSize = value.paintParams.fontSize + } val size = measure(width, value.spannable, paint) data[id] = MeasurementParams(width, size, value.spannable, value.paintParams) return size diff --git a/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt b/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt index 874b3126..b3507a2e 100644 --- a/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt +++ b/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt @@ -4,7 +4,9 @@ import com.facebook.react.bridge.ReactContext import com.facebook.react.uimanager.UIManagerHelper import com.swmansion.enriched.EnrichedTextInputView -class MentionHandler(private val view: EnrichedTextInputView) { +class MentionHandler( + private val view: EnrichedTextInputView, +) { private var previousText: String? = null private var previousIndicator: String? = null @@ -21,12 +23,18 @@ class MentionHandler(private val view: EnrichedTextInputView) { previousIndicator = null } - fun onMention(indicator: String, text: String?) { + fun onMention( + indicator: String, + text: String?, + ) { emitEvent(indicator, text) previousIndicator = indicator } - private fun emitEvent(indicator: String, text: String?) { + private fun emitEvent( + indicator: String, + text: String?, + ) { // Do not emit events too often if (previousText == text) return @@ -34,12 +42,14 @@ class MentionHandler(private val view: EnrichedTextInputView) { val context = view.context as ReactContext val surfaceId = UIManagerHelper.getSurfaceId(context) val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id) - dispatcher?.dispatchEvent(OnMentionEvent( - surfaceId, - view.id, - indicator, - text, - view.experimentalSynchronousEvents, - )) + dispatcher?.dispatchEvent( + OnMentionEvent( + surfaceId, + view.id, + indicator, + text, + view.experimentalSynchronousEvents, + ), + ) } } diff --git a/android/src/main/java/com/swmansion/enriched/events/OnChangeHtmlEvent.kt b/android/src/main/java/com/swmansion/enriched/events/OnChangeHtmlEvent.kt index 69bdb72d..5d108d22 100644 --- a/android/src/main/java/com/swmansion/enriched/events/OnChangeHtmlEvent.kt +++ b/android/src/main/java/com/swmansion/enriched/events/OnChangeHtmlEvent.kt @@ -4,12 +4,13 @@ import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.events.Event -class OnChangeHtmlEvent(surfaceId: Int, viewId: Int, private val html: String, private val experimentalSynchronousEvents: Boolean) : - Event(surfaceId, viewId) { - - override fun getEventName(): String { - return EVENT_NAME - } +class OnChangeHtmlEvent( + surfaceId: Int, + viewId: Int, + private val html: String, + private val experimentalSynchronousEvents: Boolean, +) : Event(surfaceId, viewId) { + override fun getEventName(): String = EVENT_NAME override fun getEventData(): WritableMap { val eventData: WritableMap = Arguments.createMap() @@ -18,9 +19,7 @@ class OnChangeHtmlEvent(surfaceId: Int, viewId: Int, private val html: String, p return eventData } - override fun experimental_isSynchronous(): Boolean { - return experimentalSynchronousEvents - } + override fun experimental_isSynchronous(): Boolean = experimentalSynchronousEvents companion object { const val EVENT_NAME: String = "onChangeHtml" diff --git a/android/src/main/java/com/swmansion/enriched/events/OnChangeSelectionEvent.kt b/android/src/main/java/com/swmansion/enriched/events/OnChangeSelectionEvent.kt index 9c3ad086..37f17259 100644 --- a/android/src/main/java/com/swmansion/enriched/events/OnChangeSelectionEvent.kt +++ b/android/src/main/java/com/swmansion/enriched/events/OnChangeSelectionEvent.kt @@ -4,12 +4,15 @@ import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.events.Event -class OnChangeSelectionEvent(surfaceId: Int, viewId: Int, private val text: String, private val start: Int, private val end: Int, private val experimentalSynchronousEvents: Boolean) : - Event(surfaceId, viewId) { - - override fun getEventName(): String { - return EVENT_NAME - } +class OnChangeSelectionEvent( + surfaceId: Int, + viewId: Int, + private val text: String, + private val start: Int, + private val end: Int, + private val experimentalSynchronousEvents: Boolean, +) : Event(surfaceId, viewId) { + override fun getEventName(): String = EVENT_NAME override fun getEventData(): WritableMap { val eventData: WritableMap = Arguments.createMap() @@ -19,9 +22,7 @@ class OnChangeSelectionEvent(surfaceId: Int, viewId: Int, private val text: Stri return eventData } - override fun experimental_isSynchronous(): Boolean { - return experimentalSynchronousEvents - } + override fun experimental_isSynchronous(): Boolean = experimentalSynchronousEvents companion object { const val EVENT_NAME: String = "onChangeSelection" diff --git a/android/src/main/java/com/swmansion/enriched/events/OnChangeStateEvent.kt b/android/src/main/java/com/swmansion/enriched/events/OnChangeStateEvent.kt index 1c19e068..3de8a075 100644 --- a/android/src/main/java/com/swmansion/enriched/events/OnChangeStateEvent.kt +++ b/android/src/main/java/com/swmansion/enriched/events/OnChangeStateEvent.kt @@ -3,20 +3,17 @@ package com.swmansion.enriched.events import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.events.Event -class OnChangeStateEvent(surfaceId: Int, viewId: Int, private val state: WritableMap, private val experimentalSynchronousEvents: Boolean) : - Event(surfaceId, viewId) { +class OnChangeStateEvent( + surfaceId: Int, + viewId: Int, + private val state: WritableMap, + private val experimentalSynchronousEvents: Boolean, +) : Event(surfaceId, viewId) { + override fun getEventName(): String = EVENT_NAME - override fun getEventName(): String { - return EVENT_NAME - } - - override fun getEventData(): WritableMap { - return state - } + override fun getEventData(): WritableMap = state - override fun experimental_isSynchronous(): Boolean { - return experimentalSynchronousEvents - } + override fun experimental_isSynchronous(): Boolean = experimentalSynchronousEvents companion object { const val EVENT_NAME: String = "onChangeState" diff --git a/android/src/main/java/com/swmansion/enriched/events/OnChangeTextEvent.kt b/android/src/main/java/com/swmansion/enriched/events/OnChangeTextEvent.kt index a3194652..5406025f 100644 --- a/android/src/main/java/com/swmansion/enriched/events/OnChangeTextEvent.kt +++ b/android/src/main/java/com/swmansion/enriched/events/OnChangeTextEvent.kt @@ -5,12 +5,13 @@ import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.events.Event -class OnChangeTextEvent(surfaceId: Int, viewId: Int, private val editable: Editable, private val experimentalSynchronousEvents: Boolean) : - Event(surfaceId, viewId) { - - override fun getEventName(): String { - return EVENT_NAME - } +class OnChangeTextEvent( + surfaceId: Int, + viewId: Int, + private val editable: Editable, + private val experimentalSynchronousEvents: Boolean, +) : Event(surfaceId, viewId) { + override fun getEventName(): String = EVENT_NAME override fun getEventData(): WritableMap { val eventData: WritableMap = Arguments.createMap() @@ -20,9 +21,7 @@ class OnChangeTextEvent(surfaceId: Int, viewId: Int, private val editable: Edita return eventData } - override fun experimental_isSynchronous(): Boolean { - return experimentalSynchronousEvents - } + override fun experimental_isSynchronous(): Boolean = experimentalSynchronousEvents companion object { const val EVENT_NAME: String = "onChangeText" diff --git a/android/src/main/java/com/swmansion/enriched/events/OnInputBlurEvent.kt b/android/src/main/java/com/swmansion/enriched/events/OnInputBlurEvent.kt index 3cccbde6..57418e90 100644 --- a/android/src/main/java/com/swmansion/enriched/events/OnInputBlurEvent.kt +++ b/android/src/main/java/com/swmansion/enriched/events/OnInputBlurEvent.kt @@ -4,12 +4,12 @@ import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.events.Event -class OnInputBlurEvent(surfaceId: Int, viewId: Int, private val experimentalSynchronousEvents: Boolean) : - Event(surfaceId, viewId) { - - override fun getEventName(): String { - return EVENT_NAME - } +class OnInputBlurEvent( + surfaceId: Int, + viewId: Int, + private val experimentalSynchronousEvents: Boolean, +) : Event(surfaceId, viewId) { + override fun getEventName(): String = EVENT_NAME override fun getEventData(): WritableMap { val eventData: WritableMap = Arguments.createMap() @@ -17,9 +17,7 @@ class OnInputBlurEvent(surfaceId: Int, viewId: Int, private val experimentalSync return eventData } - override fun experimental_isSynchronous(): Boolean { - return experimentalSynchronousEvents - } + override fun experimental_isSynchronous(): Boolean = experimentalSynchronousEvents companion object { const val EVENT_NAME: String = "onInputBlur" diff --git a/android/src/main/java/com/swmansion/enriched/events/OnInputFocusEvent.kt b/android/src/main/java/com/swmansion/enriched/events/OnInputFocusEvent.kt index c70443c7..f9a716fe 100644 --- a/android/src/main/java/com/swmansion/enriched/events/OnInputFocusEvent.kt +++ b/android/src/main/java/com/swmansion/enriched/events/OnInputFocusEvent.kt @@ -4,12 +4,12 @@ import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.events.Event -class OnInputFocusEvent(surfaceId: Int, viewId: Int, private val experimentalSynchronousEvents: Boolean) : - Event(surfaceId, viewId) { - - override fun getEventName(): String { - return EVENT_NAME - } +class OnInputFocusEvent( + surfaceId: Int, + viewId: Int, + private val experimentalSynchronousEvents: Boolean, +) : Event(surfaceId, viewId) { + override fun getEventName(): String = EVENT_NAME override fun getEventData(): WritableMap { val eventData: WritableMap = Arguments.createMap() @@ -17,9 +17,7 @@ class OnInputFocusEvent(surfaceId: Int, viewId: Int, private val experimentalSyn return eventData } - override fun experimental_isSynchronous(): Boolean { - return experimentalSynchronousEvents - } + override fun experimental_isSynchronous(): Boolean = experimentalSynchronousEvents companion object { const val EVENT_NAME: String = "onInputFocus" diff --git a/android/src/main/java/com/swmansion/enriched/events/OnLinkDetectedEvent.kt b/android/src/main/java/com/swmansion/enriched/events/OnLinkDetectedEvent.kt index edc35e41..31b33f98 100644 --- a/android/src/main/java/com/swmansion/enriched/events/OnLinkDetectedEvent.kt +++ b/android/src/main/java/com/swmansion/enriched/events/OnLinkDetectedEvent.kt @@ -4,25 +4,27 @@ import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.events.Event -class OnLinkDetectedEvent(surfaceId: Int, viewId: Int, private val text: String, private val url: String, private val start: Int, private val end: Int, private val experimentalSynchronousEvents: Boolean) : - Event(surfaceId, viewId) { - - override fun getEventName(): String { - return EVENT_NAME - } +class OnLinkDetectedEvent( + surfaceId: Int, + viewId: Int, + private val text: String, + private val url: String, + private val start: Int, + private val end: Int, + private val experimentalSynchronousEvents: Boolean, +) : Event(surfaceId, viewId) { + override fun getEventName(): String = EVENT_NAME override fun getEventData(): WritableMap { val eventData: WritableMap = Arguments.createMap() eventData.putString("text", text) eventData.putString("url", url) - eventData.putInt("start", start); - eventData.putInt("end", end); + eventData.putInt("start", start) + eventData.putInt("end", end) return eventData } - override fun experimental_isSynchronous(): Boolean { - return experimentalSynchronousEvents - } + override fun experimental_isSynchronous(): Boolean = experimentalSynchronousEvents companion object { const val EVENT_NAME: String = "onLinkDetected" diff --git a/android/src/main/java/com/swmansion/enriched/events/OnMentionDetectedEvent.kt b/android/src/main/java/com/swmansion/enriched/events/OnMentionDetectedEvent.kt index 70922b15..a86094c5 100644 --- a/android/src/main/java/com/swmansion/enriched/events/OnMentionDetectedEvent.kt +++ b/android/src/main/java/com/swmansion/enriched/events/OnMentionDetectedEvent.kt @@ -4,12 +4,15 @@ import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.events.Event -class OnMentionDetectedEvent(surfaceId: Int, viewId: Int, private val text: String, private val indicator: String, private val payload: String, private val experimentalSynchronousEvents: Boolean) : - Event(surfaceId, viewId) { - - override fun getEventName(): String { - return EVENT_NAME - } +class OnMentionDetectedEvent( + surfaceId: Int, + viewId: Int, + private val text: String, + private val indicator: String, + private val payload: String, + private val experimentalSynchronousEvents: Boolean, +) : Event(surfaceId, viewId) { + override fun getEventName(): String = EVENT_NAME override fun getEventData(): WritableMap { val eventData: WritableMap = Arguments.createMap() @@ -19,9 +22,7 @@ class OnMentionDetectedEvent(surfaceId: Int, viewId: Int, private val text: Stri return eventData } - override fun experimental_isSynchronous(): Boolean { - return experimentalSynchronousEvents - } + override fun experimental_isSynchronous(): Boolean = experimentalSynchronousEvents companion object { const val EVENT_NAME: String = "onMentionDetected" diff --git a/android/src/main/java/com/swmansion/enriched/events/OnMentionEvent.kt b/android/src/main/java/com/swmansion/enriched/events/OnMentionEvent.kt index 62c1237a..8eb842cd 100644 --- a/android/src/main/java/com/swmansion/enriched/events/OnMentionEvent.kt +++ b/android/src/main/java/com/swmansion/enriched/events/OnMentionEvent.kt @@ -4,11 +4,14 @@ import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.WritableMap import com.facebook.react.uimanager.events.Event -class OnMentionEvent(surfaceId: Int, viewId: Int, private val indicator: String, private val text: String?, private val experimentalSynchronousEvents: Boolean) : Event(surfaceId, viewId) { - - override fun getEventName(): String { - return EVENT_NAME - } +class OnMentionEvent( + surfaceId: Int, + viewId: Int, + private val indicator: String, + private val text: String?, + private val experimentalSynchronousEvents: Boolean, +) : Event(surfaceId, viewId) { + override fun getEventName(): String = EVENT_NAME override fun getEventData(): WritableMap? { val eventData: WritableMap = Arguments.createMap() @@ -23,9 +26,7 @@ class OnMentionEvent(surfaceId: Int, viewId: Int, private val indicator: String, return eventData } - override fun experimental_isSynchronous(): Boolean { - return experimentalSynchronousEvents - } + override fun experimental_isSynchronous(): Boolean = experimentalSynchronousEvents companion object { const val EVENT_NAME: String = "onMention" diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedBlockQuoteSpan.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedBlockQuoteSpan.kt index 405f16e7..bb91f52d 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedBlockQuoteSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedBlockQuoteSpan.kt @@ -10,16 +10,31 @@ import com.swmansion.enriched.spans.interfaces.EnrichedBlockSpan import com.swmansion.enriched.styles.HtmlStyle // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/text/style/QuoteSpan.java -class EnrichedBlockQuoteSpan(private val htmlStyle: HtmlStyle) : MetricAffectingSpan(), LeadingMarginSpan, EnrichedBlockSpan { +class EnrichedBlockQuoteSpan( + private val htmlStyle: HtmlStyle, +) : MetricAffectingSpan(), + LeadingMarginSpan, + EnrichedBlockSpan { override fun updateMeasureState(p0: TextPaint) { // Do nothing, but inform layout that this span affects text metrics } - override fun getLeadingMargin(p0: Boolean): Int { - return htmlStyle.blockquoteStripeWidth + htmlStyle.blockquoteGapWidth - } + override fun getLeadingMargin(p0: Boolean): Int = htmlStyle.blockquoteStripeWidth + htmlStyle.blockquoteGapWidth - override fun drawLeadingMargin(c: Canvas, p: Paint, x: Int, dir: Int, top: Int, baseline: Int, bottom: Int, text: CharSequence?, start: Int, end: Int, first: Boolean, layout: Layout?) { + override fun drawLeadingMargin( + c: Canvas, + p: Paint, + x: Int, + dir: Int, + top: Int, + baseline: Int, + bottom: Int, + text: CharSequence?, + start: Int, + end: Int, + first: Boolean, + layout: Layout?, + ) { val style = p.style val color = p.color p.style = Paint.Style.FILL diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedBoldSpan.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedBoldSpan.kt index 72a5d4e7..734d6533 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedBoldSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedBoldSpan.kt @@ -6,5 +6,7 @@ import com.swmansion.enriched.spans.interfaces.EnrichedInlineSpan import com.swmansion.enriched.styles.HtmlStyle @Suppress("UNUSED_PARAMETER") -class EnrichedBoldSpan(htmlStyle: HtmlStyle) : StyleSpan(Typeface.BOLD), EnrichedInlineSpan { -} +class EnrichedBoldSpan( + htmlStyle: HtmlStyle, +) : StyleSpan(Typeface.BOLD), + EnrichedInlineSpan diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt index 2ff064ba..af50190a 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt @@ -10,7 +10,11 @@ import android.text.style.MetricAffectingSpan import com.swmansion.enriched.spans.interfaces.EnrichedBlockSpan import com.swmansion.enriched.styles.HtmlStyle -class EnrichedCodeBlockSpan(private val htmlStyle: HtmlStyle) : MetricAffectingSpan(), LineBackgroundSpan, EnrichedBlockSpan { +class EnrichedCodeBlockSpan( + private val htmlStyle: HtmlStyle, +) : MetricAffectingSpan(), + LineBackgroundSpan, + EnrichedBlockSpan { override fun updateDrawState(paint: TextPaint) { paint.typeface = Typeface.MONOSPACE paint.color = htmlStyle.codeBlockColor @@ -31,7 +35,7 @@ class EnrichedCodeBlockSpan(private val htmlStyle: HtmlStyle) : MetricAffectingS text: CharSequence, start: Int, end: Int, - lineNum: Int + lineNum: Int, ) { val previousColor = p.color p.color = htmlStyle.codeBlockBackgroundColor diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH1Span.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH1Span.kt index c2b28e3f..0518a2c6 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH1Span.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH1Span.kt @@ -6,7 +6,10 @@ import android.text.style.AbsoluteSizeSpan import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan import com.swmansion.enriched.styles.HtmlStyle -class EnrichedH1Span(private val style: HtmlStyle) : AbsoluteSizeSpan(style.h1FontSize), EnrichedHeadingSpan { +class EnrichedH1Span( + private val style: HtmlStyle, +) : AbsoluteSizeSpan(style.h1FontSize), + EnrichedHeadingSpan { override fun updateDrawState(tp: TextPaint) { super.updateDrawState(tp) val bold = style.h1Bold diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH2Span.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH2Span.kt index 736fe08c..9b58760b 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH2Span.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH2Span.kt @@ -6,7 +6,10 @@ import android.text.style.AbsoluteSizeSpan import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan import com.swmansion.enriched.styles.HtmlStyle -class EnrichedH2Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlStyle.h2FontSize), EnrichedHeadingSpan { +class EnrichedH2Span( + private val htmlStyle: HtmlStyle, +) : AbsoluteSizeSpan(htmlStyle.h2FontSize), + EnrichedHeadingSpan { override fun updateDrawState(tp: TextPaint) { super.updateDrawState(tp) val bold = htmlStyle.h2Bold diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH3Span.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH3Span.kt index 151acf65..b949ad4b 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH3Span.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH3Span.kt @@ -6,7 +6,10 @@ import android.text.style.AbsoluteSizeSpan import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan import com.swmansion.enriched.styles.HtmlStyle -class EnrichedH3Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlStyle.h3FontSize), EnrichedHeadingSpan { +class EnrichedH3Span( + private val htmlStyle: HtmlStyle, +) : AbsoluteSizeSpan(htmlStyle.h3FontSize), + EnrichedHeadingSpan { override fun updateDrawState(tp: TextPaint) { super.updateDrawState(tp) val bold = htmlStyle.h3Bold diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt index 708050d6..e2090df3 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt @@ -10,10 +10,12 @@ import androidx.core.graphics.withSave import com.swmansion.enriched.spans.interfaces.EnrichedInlineSpan import com.swmansion.enriched.styles.HtmlStyle -class EnrichedImageSpan : ImageSpan, EnrichedInlineSpan { +class EnrichedImageSpan : + ImageSpan, + EnrichedInlineSpan { private var htmlStyle: HtmlStyle? = null - constructor(context: Context, uri: Uri, htmlStyle: HtmlStyle, ) : super(context, uri, ALIGN_BASELINE) { + constructor(context: Context, uri: Uri, htmlStyle: HtmlStyle) : super(context, uri, ALIGN_BASELINE) { this.htmlStyle = htmlStyle } @@ -22,11 +24,18 @@ class EnrichedImageSpan : ImageSpan, EnrichedInlineSpan { } override fun draw( - canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, - top: Int, y: Int, bottom: Int, paint: Paint + canvas: Canvas, + text: CharSequence?, + start: Int, + end: Int, + x: Float, + top: Int, + y: Int, + bottom: Int, + paint: Paint, ) { val drawable = drawable - canvas.withSave() { + canvas.withSave { val transY = bottom - drawable.bounds.bottom - paint.fontMetricsInt.descent translate(x, transY.toFloat()) drawable.draw(this) diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedInlineCodeSpan.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedInlineCodeSpan.kt index 8c9717e8..602d968a 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedInlineCodeSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedInlineCodeSpan.kt @@ -6,7 +6,10 @@ import android.text.style.MetricAffectingSpan import com.swmansion.enriched.spans.interfaces.EnrichedInlineSpan import com.swmansion.enriched.styles.HtmlStyle -class EnrichedInlineCodeSpan(private val htmlStyle: HtmlStyle) : MetricAffectingSpan(), EnrichedInlineSpan { +class EnrichedInlineCodeSpan( + private val htmlStyle: HtmlStyle, +) : MetricAffectingSpan(), + EnrichedInlineSpan { override fun updateDrawState(textPaint: TextPaint) { val typeface = Typeface.create(Typeface.MONOSPACE, Typeface.NORMAL) textPaint.typeface = typeface diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedItalicSpan.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedItalicSpan.kt index 3d44f9e9..d548977a 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedItalicSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedItalicSpan.kt @@ -6,5 +6,7 @@ import com.swmansion.enriched.spans.interfaces.EnrichedInlineSpan import com.swmansion.enriched.styles.HtmlStyle @Suppress("UNUSED_PARAMETER") -class EnrichedItalicSpan(private val htmlStyle: HtmlStyle) : StyleSpan(Typeface.ITALIC), EnrichedInlineSpan { -} +class EnrichedItalicSpan( + private val htmlStyle: HtmlStyle, +) : StyleSpan(Typeface.ITALIC), + EnrichedInlineSpan diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedLinkSpan.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedLinkSpan.kt index b8a96369..d5636c13 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedLinkSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedLinkSpan.kt @@ -6,7 +6,11 @@ import android.view.View import com.swmansion.enriched.spans.interfaces.EnrichedInlineSpan import com.swmansion.enriched.styles.HtmlStyle -class EnrichedLinkSpan(private val url: String, private val htmlStyle: HtmlStyle) : ClickableSpan(), EnrichedInlineSpan { +class EnrichedLinkSpan( + private val url: String, + private val htmlStyle: HtmlStyle, +) : ClickableSpan(), + EnrichedInlineSpan { override fun onClick(view: View) { // Do nothing, links inside the input are not clickable. // We are using `ClickableSpan` to allow the text to be styled as a link. @@ -18,7 +22,5 @@ class EnrichedLinkSpan(private val url: String, private val htmlStyle: HtmlStyle textPaint.isUnderlineText = htmlStyle.aUnderline } - fun getUrl(): String { - return url - } + fun getUrl(): String = url } diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedMentionSpan.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedMentionSpan.kt index f28925b5..93f63730 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedMentionSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedMentionSpan.kt @@ -6,8 +6,13 @@ import android.view.View import com.swmansion.enriched.spans.interfaces.EnrichedInlineSpan import com.swmansion.enriched.styles.HtmlStyle -class EnrichedMentionSpan(private val text: String, private val indicator: String, private val attributes: Map, private val htmlStyle: HtmlStyle) : - ClickableSpan(), EnrichedInlineSpan { +class EnrichedMentionSpan( + private val text: String, + private val indicator: String, + private val attributes: Map, + private val htmlStyle: HtmlStyle, +) : ClickableSpan(), + EnrichedInlineSpan { override fun onClick(view: View) { // Do nothing. Mentions inside the input are not clickable. // We are using `ClickableSpan` to allow the text to be styled as a clickable element. @@ -22,15 +27,9 @@ class EnrichedMentionSpan(private val text: String, private val indicator: Strin textPaint.isUnderlineText = mentionsStyle.underline } - fun getAttributes(): Map { - return attributes - } + fun getAttributes(): Map = attributes - fun getText(): String { - return text - } + fun getText(): String = text - fun getIndicator(): String { - return indicator - } + fun getIndicator(): String = indicator } diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedOrderedListSpan.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedOrderedListSpan.kt index 55b91678..fdfbc21a 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedOrderedListSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedOrderedListSpan.kt @@ -10,7 +10,12 @@ import android.text.style.MetricAffectingSpan import com.swmansion.enriched.spans.interfaces.EnrichedParagraphSpan import com.swmansion.enriched.styles.HtmlStyle -class EnrichedOrderedListSpan(private var index: Int, private val htmlStyle: HtmlStyle) : MetricAffectingSpan(), LeadingMarginSpan, EnrichedParagraphSpan { +class EnrichedOrderedListSpan( + private var index: Int, + private val htmlStyle: HtmlStyle, +) : MetricAffectingSpan(), + LeadingMarginSpan, + EnrichedParagraphSpan { override fun updateMeasureState(p0: TextPaint) { // Do nothing, but inform layout that this span affects text metrics } @@ -19,9 +24,7 @@ class EnrichedOrderedListSpan(private var index: Int, private val htmlStyle: Htm // Do nothing, but inform layout that this span affects text metrics } - override fun getLeadingMargin(first: Boolean): Int { - return htmlStyle.olMarginLeft + htmlStyle.olGapWidth - } + override fun getLeadingMargin(first: Boolean): Int = htmlStyle.olMarginLeft + htmlStyle.olGapWidth override fun drawLeadingMargin( canvas: Canvas, @@ -35,7 +38,7 @@ class EnrichedOrderedListSpan(private var index: Int, private val htmlStyle: Htm start: Int, end: Int, first: Boolean, - layout: Layout? + layout: Layout?, ) { if (first) { val text = "$index." @@ -56,8 +59,11 @@ class EnrichedOrderedListSpan(private var index: Int, private val htmlStyle: Htm } } - private fun getTypeface(fontWeight: Int?, originalTypeface: Typeface): Typeface { - return if (fontWeight == null) { + private fun getTypeface( + fontWeight: Int?, + originalTypeface: Typeface, + ): Typeface = + if (fontWeight == null) { originalTypeface } else if (android.os.Build.VERSION.SDK_INT >= 28) { Typeface.create(originalTypeface, fontWeight, false) @@ -69,13 +75,10 @@ class EnrichedOrderedListSpan(private var index: Int, private val htmlStyle: Htm Typeface.create(originalTypeface, Typeface.NORMAL) } } - } - fun getIndex(): Int { - return index - } + fun getIndex(): Int = index - fun setIndex(i: Int) { - index = i + fun setIndex(newIndex: Int) { + index = newIndex } } diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt index 85e47b28..8eb38775 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt @@ -1,8 +1,18 @@ package com.swmansion.enriched.spans -data class BaseSpanConfig(val clazz: Class<*>) -data class ParagraphSpanConfig(val clazz: Class<*>, val isContinuous: Boolean) -data class ListSpanConfig(val clazz: Class<*>, val shortcut: String) +data class BaseSpanConfig( + val clazz: Class<*>, +) + +data class ParagraphSpanConfig( + val clazz: Class<*>, + val isContinuous: Boolean, +) + +data class ListSpanConfig( + val clazz: Class<*>, + val shortcut: String, +) data class StylesMergingConfig( // styles that should be removed when we apply specific style @@ -35,77 +45,109 @@ object EnrichedSpans { const val IMAGE = "image" const val MENTION = "mention" - val inlineSpans: Map = mapOf( - BOLD to BaseSpanConfig(EnrichedBoldSpan::class.java), - ITALIC to BaseSpanConfig(EnrichedItalicSpan::class.java), - UNDERLINE to BaseSpanConfig(EnrichedUnderlineSpan::class.java), - STRIKETHROUGH to BaseSpanConfig(EnrichedStrikeThroughSpan::class.java), - INLINE_CODE to BaseSpanConfig(EnrichedInlineCodeSpan::class.java), - ) + val inlineSpans: Map = + mapOf( + BOLD to BaseSpanConfig(EnrichedBoldSpan::class.java), + ITALIC to BaseSpanConfig(EnrichedItalicSpan::class.java), + UNDERLINE to BaseSpanConfig(EnrichedUnderlineSpan::class.java), + STRIKETHROUGH to BaseSpanConfig(EnrichedStrikeThroughSpan::class.java), + INLINE_CODE to BaseSpanConfig(EnrichedInlineCodeSpan::class.java), + ) - val paragraphSpans: Map = mapOf( - H1 to ParagraphSpanConfig(EnrichedH1Span::class.java, false), - H2 to ParagraphSpanConfig(EnrichedH2Span::class.java, false), - H3 to ParagraphSpanConfig(EnrichedH3Span::class.java, false), - BLOCK_QUOTE to ParagraphSpanConfig(EnrichedBlockQuoteSpan::class.java, true), - CODE_BLOCK to ParagraphSpanConfig(EnrichedCodeBlockSpan::class.java, true), - ) + val paragraphSpans: Map = + mapOf( + H1 to ParagraphSpanConfig(EnrichedH1Span::class.java, false), + H2 to ParagraphSpanConfig(EnrichedH2Span::class.java, false), + H3 to ParagraphSpanConfig(EnrichedH3Span::class.java, false), + BLOCK_QUOTE to ParagraphSpanConfig(EnrichedBlockQuoteSpan::class.java, true), + CODE_BLOCK to ParagraphSpanConfig(EnrichedCodeBlockSpan::class.java, true), + ) - val listSpans: Map = mapOf( - UNORDERED_LIST to ListSpanConfig(EnrichedUnorderedListSpan::class.java, "- "), - ORDERED_LIST to ListSpanConfig(EnrichedOrderedListSpan::class.java, "1. "), - ) + val listSpans: Map = + mapOf( + UNORDERED_LIST to ListSpanConfig(EnrichedUnorderedListSpan::class.java, "- "), + ORDERED_LIST to ListSpanConfig(EnrichedOrderedListSpan::class.java, "1. "), + ) - val parametrizedStyles: Map = mapOf( - LINK to BaseSpanConfig(EnrichedLinkSpan::class.java), - IMAGE to BaseSpanConfig(EnrichedImageSpan::class.java), - MENTION to BaseSpanConfig(EnrichedMentionSpan::class.java), - ) + val parametrizedStyles: Map = + mapOf( + LINK to BaseSpanConfig(EnrichedLinkSpan::class.java), + IMAGE to BaseSpanConfig(EnrichedImageSpan::class.java), + MENTION to BaseSpanConfig(EnrichedMentionSpan::class.java), + ) - val mergingConfig: Map = mapOf( - BOLD to StylesMergingConfig( - blockingStyles = arrayOf(CODE_BLOCK) - ), - ITALIC to StylesMergingConfig( - blockingStyles = arrayOf(CODE_BLOCK) - ), - UNDERLINE to StylesMergingConfig( - blockingStyles = arrayOf(CODE_BLOCK) - ), - STRIKETHROUGH to StylesMergingConfig( - blockingStyles = arrayOf(CODE_BLOCK) - ), - INLINE_CODE to StylesMergingConfig( - conflictingStyles = arrayOf(MENTION, LINK), - blockingStyles = arrayOf(CODE_BLOCK) - ), - H1 to StylesMergingConfig( - conflictingStyles = arrayOf(H2, H3, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK), - ), - H2 to StylesMergingConfig( - conflictingStyles = arrayOf(H1, H3, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK), - ), - H3 to StylesMergingConfig( - conflictingStyles = arrayOf(H1, H2, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK), - ), - BLOCK_QUOTE to StylesMergingConfig( - conflictingStyles = arrayOf(H1, H2, H3, CODE_BLOCK, ORDERED_LIST, UNORDERED_LIST), - ), - CODE_BLOCK to StylesMergingConfig( - conflictingStyles = arrayOf(H1, H2, H3, BOLD, ITALIC, UNDERLINE, STRIKETHROUGH, UNORDERED_LIST, ORDERED_LIST, BLOCK_QUOTE, INLINE_CODE), - ), - UNORDERED_LIST to StylesMergingConfig( - conflictingStyles = arrayOf(H1, H2, H3, ORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE), - ), - ORDERED_LIST to StylesMergingConfig( - conflictingStyles = arrayOf(H1, H2, H3, UNORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE), - ), - LINK to StylesMergingConfig( - blockingStyles = arrayOf(INLINE_CODE, CODE_BLOCK, MENTION) - ), - IMAGE to StylesMergingConfig(), - MENTION to StylesMergingConfig( - blockingStyles = arrayOf(INLINE_CODE, CODE_BLOCK, LINK) - ), - ) + val mergingConfig: Map = + mapOf( + BOLD to + StylesMergingConfig( + blockingStyles = arrayOf(CODE_BLOCK), + ), + ITALIC to + StylesMergingConfig( + blockingStyles = arrayOf(CODE_BLOCK), + ), + UNDERLINE to + StylesMergingConfig( + blockingStyles = arrayOf(CODE_BLOCK), + ), + STRIKETHROUGH to + StylesMergingConfig( + blockingStyles = arrayOf(CODE_BLOCK), + ), + INLINE_CODE to + StylesMergingConfig( + conflictingStyles = arrayOf(MENTION, LINK), + blockingStyles = arrayOf(CODE_BLOCK), + ), + H1 to + StylesMergingConfig( + conflictingStyles = arrayOf(H2, H3, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK), + ), + H2 to + StylesMergingConfig( + conflictingStyles = arrayOf(H1, H3, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK), + ), + H3 to + StylesMergingConfig( + conflictingStyles = arrayOf(H1, H2, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK), + ), + BLOCK_QUOTE to + StylesMergingConfig( + conflictingStyles = arrayOf(H1, H2, H3, CODE_BLOCK, ORDERED_LIST, UNORDERED_LIST), + ), + CODE_BLOCK to + StylesMergingConfig( + conflictingStyles = + arrayOf( + H1, + H2, + H3, + BOLD, + ITALIC, + UNDERLINE, + STRIKETHROUGH, + UNORDERED_LIST, + ORDERED_LIST, + BLOCK_QUOTE, + INLINE_CODE, + ), + ), + UNORDERED_LIST to + StylesMergingConfig( + conflictingStyles = arrayOf(H1, H2, H3, ORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE), + ), + ORDERED_LIST to + StylesMergingConfig( + conflictingStyles = arrayOf(H1, H2, H3, UNORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE), + ), + LINK to + StylesMergingConfig( + blockingStyles = arrayOf(INLINE_CODE, CODE_BLOCK, MENTION), + ), + IMAGE to StylesMergingConfig(), + MENTION to + StylesMergingConfig( + blockingStyles = arrayOf(INLINE_CODE, CODE_BLOCK, LINK), + ), + ) } diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedStrikeThroughSpan.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedStrikeThroughSpan.kt index 46ad5fce..7bb8835c 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedStrikeThroughSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedStrikeThroughSpan.kt @@ -5,5 +5,7 @@ import com.swmansion.enriched.spans.interfaces.EnrichedInlineSpan import com.swmansion.enriched.styles.HtmlStyle @Suppress("UNUSED_PARAMETER") -class EnrichedStrikeThroughSpan(private val htmlStyle: HtmlStyle) : StrikethroughSpan(), EnrichedInlineSpan { -} +class EnrichedStrikeThroughSpan( + private val htmlStyle: HtmlStyle, +) : StrikethroughSpan(), + EnrichedInlineSpan diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnderlineSpan.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnderlineSpan.kt index 977388c4..2619bfb6 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnderlineSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnderlineSpan.kt @@ -5,5 +5,7 @@ import com.swmansion.enriched.spans.interfaces.EnrichedInlineSpan import com.swmansion.enriched.styles.HtmlStyle @Suppress("UNUSED_PARAMETER") -class EnrichedUnderlineSpan(private val htmlStyle: HtmlStyle) : UnderlineSpan(), EnrichedInlineSpan { -} +class EnrichedUnderlineSpan( + private val htmlStyle: HtmlStyle, +) : UnderlineSpan(), + EnrichedInlineSpan diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnorderedListSpan.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnorderedListSpan.kt index 9235a8ba..27c1f5cb 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnorderedListSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnorderedListSpan.kt @@ -11,7 +11,11 @@ import com.swmansion.enriched.spans.interfaces.EnrichedParagraphSpan import com.swmansion.enriched.styles.HtmlStyle // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/text/style/BulletSpan.java -class EnrichedUnorderedListSpan(private val htmlStyle: HtmlStyle) : MetricAffectingSpan(), LeadingMarginSpan, EnrichedParagraphSpan { +class EnrichedUnorderedListSpan( + private val htmlStyle: HtmlStyle, +) : MetricAffectingSpan(), + LeadingMarginSpan, + EnrichedParagraphSpan { override fun updateMeasureState(p0: TextPaint) { // Do nothing, but inform layout that this span affects text metrics } @@ -20,9 +24,7 @@ class EnrichedUnorderedListSpan(private val htmlStyle: HtmlStyle) : MetricAffect // Do nothing, but inform layout that this span affects text metrics } - override fun getLeadingMargin(p0: Boolean): Int { - return htmlStyle.ulBulletSize + htmlStyle.ulGapWidth + htmlStyle.ulMarginLeft - } + override fun getLeadingMargin(p0: Boolean): Int = htmlStyle.ulBulletSize + htmlStyle.ulGapWidth + htmlStyle.ulMarginLeft override fun drawLeadingMargin( canvas: Canvas, @@ -36,7 +38,7 @@ class EnrichedUnorderedListSpan(private val htmlStyle: HtmlStyle) : MetricAffect start: Int, end: Int, first: Boolean, - layout: Layout? + layout: Layout?, ) { val spannedText = text as Spanned diff --git a/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedBlockSpan.kt b/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedBlockSpan.kt index 0d18ff5b..b57bff3c 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedBlockSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedBlockSpan.kt @@ -1,4 +1,5 @@ package com.swmansion.enriched.spans.interfaces -interface EnrichedBlockSpan : EnrichedSpan, EnrichedZeroWidthSpaceSpan { -} +interface EnrichedBlockSpan : + EnrichedSpan, + EnrichedZeroWidthSpaceSpan diff --git a/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedHeadingSpan.kt b/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedHeadingSpan.kt index 5d13f53c..49d2a53c 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedHeadingSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedHeadingSpan.kt @@ -1,4 +1,3 @@ package com.swmansion.enriched.spans.interfaces -interface EnrichedHeadingSpan : EnrichedParagraphSpan { -} +interface EnrichedHeadingSpan : EnrichedParagraphSpan diff --git a/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedInlineSpan.kt b/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedInlineSpan.kt index 559e70ac..ee04f60d 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedInlineSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedInlineSpan.kt @@ -1,4 +1,3 @@ package com.swmansion.enriched.spans.interfaces -interface EnrichedInlineSpan : EnrichedSpan { -} +interface EnrichedInlineSpan : EnrichedSpan diff --git a/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedParagraphSpan.kt b/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedParagraphSpan.kt index 72f6d4e4..785d9aa8 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedParagraphSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedParagraphSpan.kt @@ -1,4 +1,5 @@ package com.swmansion.enriched.spans.interfaces -interface EnrichedParagraphSpan : EnrichedSpan, EnrichedZeroWidthSpaceSpan { -} +interface EnrichedParagraphSpan : + EnrichedSpan, + EnrichedZeroWidthSpaceSpan diff --git a/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedSpan.kt b/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedSpan.kt index 5a5de532..8dcc0f63 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedSpan.kt @@ -1,4 +1,3 @@ package com.swmansion.enriched.spans.interfaces -interface EnrichedSpan { -} +interface EnrichedSpan diff --git a/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedZeroWidthSpaceSpan.kt b/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedZeroWidthSpaceSpan.kt index 470c0a1a..21f4c9a3 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedZeroWidthSpaceSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedZeroWidthSpaceSpan.kt @@ -1,5 +1,4 @@ package com.swmansion.enriched.spans.interfaces // Spans that are prefixed with zero-width space (\u200B) to ensure that they are visible even when empty. -interface EnrichedZeroWidthSpaceSpan { -} +interface EnrichedZeroWidthSpaceSpan diff --git a/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt b/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt index 8a4b9088..1e696f9a 100644 --- a/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +++ b/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt @@ -121,19 +121,29 @@ class HtmlStyle { mentionsStyle = parseMentionsStyle(mentionStyle) } - private fun parseFloat(map: ReadableMap?, key: String): Float { + private fun parseFloat( + map: ReadableMap?, + key: String, + ): Float { val safeMap = ensureValueIsSet(map, key) val fontSize = safeMap.getDouble(key) return ceil(PixelUtil.toPixelFromSP(fontSize)) } - private fun parseColorWithOpacity(map: ReadableMap?, key: String, opacity: Int): Int { + private fun parseColorWithOpacity( + map: ReadableMap?, + key: String, + opacity: Int, + ): Int { val color = parseColor(map, key) return withOpacity(color, opacity) } - private fun parseOptionalColor(map: ReadableMap?, key: String): Int? { + private fun parseOptionalColor( + map: ReadableMap?, + key: String, + ): Int? { if (map == null) return null if (!map.hasKey(key)) return null if (map.isNull(key)) return null @@ -141,7 +151,10 @@ class HtmlStyle { return parseColor(map, key) } - private fun parseColor(map: ReadableMap?, key: String): Int { + private fun parseColor( + map: ReadableMap?, + key: String, + ): Int { val safeMap = ensureValueIsSet(map, key) val color = safeMap.getDouble(key) @@ -153,7 +166,10 @@ class HtmlStyle { return parsedColor } - private fun withOpacity(color: Int, alpha: Int): Int { + private fun withOpacity( + color: Int, + alpha: Int, + ): Int { // Do not apply opacity to transparent color if (Color.alpha(color) == 0) return color val a = alpha.coerceIn(0, 255) @@ -171,14 +187,20 @@ class HtmlStyle { throw Error("Specified textDecorationLine value is not supported: $underline. Supported values are 'underline' and 'none'.") } - private fun calculateOlMarginLeft(view: EnrichedTextInputView?, userMargin: Int): Int { + private fun calculateOlMarginLeft( + view: EnrichedTextInputView?, + userMargin: Int, + ): Int { val fontSize = view?.fontSize?.toInt() ?: 0 val leadMargin = fontSize / 2 return leadMargin + userMargin } - private fun ensureValueIsSet(map: ReadableMap?, key: String): ReadableMap { + private fun ensureValueIsSet( + map: ReadableMap?, + key: String, + ): ReadableMap { if (map == null) throw Error("Style map cannot be null") if (!map.hasKey(key)) throw Error("Style map must contain key: $key") @@ -193,24 +215,27 @@ class HtmlStyle { val parsedMentionsStyle: MutableMap = mutableMapOf() - val iterator = mentionsStyle.keySetIterator() - while (iterator.hasNextKey()) { - val key = iterator.nextKey() - val value = mentionsStyle.getMap(key) + val iterator = mentionsStyle.keySetIterator() + while (iterator.hasNextKey()) { + val key = iterator.nextKey() + val value = mentionsStyle.getMap(key) - if (value == null) throw Error("Mention style for key '$key' cannot be null") + if (value == null) throw Error("Mention style for key '$key' cannot be null") - val color = parseColor(value, "color") - val backgroundColor = parseColorWithOpacity(value, "backgroundColor", 80) - val isUnderline = parseIsUnderline(value) - val parsedStyle = MentionStyle(color, backgroundColor, isUnderline) - parsedMentionsStyle.put(key, parsedStyle) - } + val color = parseColor(value, "color") + val backgroundColor = parseColorWithOpacity(value, "backgroundColor", 80) + val isUnderline = parseIsUnderline(value) + val parsedStyle = MentionStyle(color, backgroundColor, isUnderline) + parsedMentionsStyle.put(key, parsedStyle) + } return parsedMentionsStyle } - private fun parseOptionalFontWeight(map: ReadableMap?, key: String): Int? { + private fun parseOptionalFontWeight( + map: ReadableMap?, + key: String, + ): Int? { if (map == null) return null if (!map.hasKey(key)) return null if (map.isNull(key)) return null @@ -223,7 +248,7 @@ class HtmlStyle { data class MentionStyle( val color: Int, val backgroundColor: Int, - val underline: Boolean + val underline: Boolean, ) } } diff --git a/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt b/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt index 41581c0f..c9d4b88c 100644 --- a/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt +++ b/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt @@ -6,8 +6,15 @@ import com.swmansion.enriched.EnrichedTextInputView import com.swmansion.enriched.spans.EnrichedSpans import com.swmansion.enriched.utils.getSafeSpanBoundaries -class InlineStyles(private val view: EnrichedTextInputView) { - private fun setSpan(spannable: Spannable, type: Class, start: Int, end: Int) { +class InlineStyles( + private val view: EnrichedTextInputView, +) { + private fun setSpan( + spannable: Spannable, + type: Class, + start: Int, + end: Int, + ) { val previousSpanStart = (start - 1).coerceAtLeast(0) val previousSpanEnd = previousSpanStart + 1 val nextSpanStart = (end + 1).coerceAtMost(spannable.length) @@ -37,7 +44,12 @@ class InlineStyles(private val view: EnrichedTextInputView) { spannable.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } - private fun setAndMergeSpans(spannable: Spannable, type: Class, start: Int, end: Int) { + private fun setAndMergeSpans( + spannable: Spannable, + type: Class, + start: Int, + end: Int, + ) { val spans = spannable.getSpans(start, end, type) // No spans setup for current selection, means we just need to assign new span @@ -88,7 +100,10 @@ class InlineStyles(private val view: EnrichedTextInputView) { } } - fun afterTextChanged(s: Editable, endCursorPosition: Int) { + fun afterTextChanged( + s: Editable, + endCursorPosition: Int, + ) { for ((style, config) in EnrichedSpans.inlineSpans) { val start = view.spanState?.getStart(style) ?: continue var end = endCursorPosition @@ -127,7 +142,11 @@ class InlineStyles(private val view: EnrichedTextInputView) { view.selection.validateStyles() } - fun removeStyle(name: String, start: Int, end: Int): Boolean { + fun removeStyle( + name: String, + start: Int, + end: Int, + ): Boolean { val config = EnrichedSpans.inlineSpans[name] ?: return false val spannable = view.text as Spannable val spans = spannable.getSpans(start, end, config.clazz) @@ -140,7 +159,5 @@ class InlineStyles(private val view: EnrichedTextInputView) { return true } - fun getStyleRange(): Pair { - return view.selection?.getInlineSelection() ?: Pair(0, 0) - } + fun getStyleRange(): Pair = view.selection?.getInlineSelection() ?: Pair(0, 0) } diff --git a/android/src/main/java/com/swmansion/enriched/styles/ListStyles.kt b/android/src/main/java/com/swmansion/enriched/styles/ListStyles.kt index c419955b..93cfe8d9 100644 --- a/android/src/main/java/com/swmansion/enriched/styles/ListStyles.kt +++ b/android/src/main/java/com/swmansion/enriched/styles/ListStyles.kt @@ -11,8 +11,14 @@ import com.swmansion.enriched.spans.EnrichedUnorderedListSpan import com.swmansion.enriched.utils.getParagraphBounds import com.swmansion.enriched.utils.getSafeSpanBoundaries -class ListStyles(private val view: EnrichedTextInputView) { - private fun getPreviousParagraphSpan(spannable: Spannable, s: Int, type: Class): T? { +class ListStyles( + private val view: EnrichedTextInputView, +) { + private fun getPreviousParagraphSpan( + spannable: Spannable, + s: Int, + type: Class, + ): T? { if (s <= 0) return null val (previousParagraphStart, previousParagraphEnd) = spannable.getParagraphBounds(s - 1) @@ -25,19 +31,31 @@ class ListStyles(private val view: EnrichedTextInputView) { return null } - private fun isPreviousParagraphList(spannable: Spannable, s: Int, type: Class): Boolean { + private fun isPreviousParagraphList( + spannable: Spannable, + s: Int, + type: Class, + ): Boolean { val previousSpan = getPreviousParagraphSpan(spannable, s, type) return previousSpan != null } - private fun getOrderedListIndex(spannable: Spannable, s: Int): Int { + private fun getOrderedListIndex( + spannable: Spannable, + s: Int, + ): Int { val span = getPreviousParagraphSpan(spannable, s, EnrichedOrderedListSpan::class.java) val index = span?.getIndex() ?: 0 return index + 1 } - private fun setSpan(spannable: Spannable, name: String, start: Int, end: Int) { + private fun setSpan( + spannable: Spannable, + name: String, + start: Int, + end: Int, + ) { val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, end) if (name == EnrichedSpans.UNORDERED_LIST) { @@ -53,7 +71,12 @@ class ListStyles(private val view: EnrichedTextInputView) { } } - private fun removeSpansForRange(spannable: Spannable, start: Int, end: Int, clazz: Class): Boolean { + private fun removeSpansForRange( + spannable: Spannable, + start: Int, + end: Int, + clazz: Class, + ): Boolean { val ssb = spannable as SpannableStringBuilder val spans = ssb.getSpans(start, end, clazz) if (spans.isEmpty()) return false @@ -66,7 +89,10 @@ class ListStyles(private val view: EnrichedTextInputView) { return true } - fun updateOrderedListIndexes(text: Spannable, position: Int) { + fun updateOrderedListIndexes( + text: Spannable, + position: Int, + ) { val spans = text.getSpans(position + 1, text.length, EnrichedOrderedListSpan::class.java) for (span in spans) { val spanStart = text.getSpanStart(span) @@ -114,7 +140,12 @@ class ListStyles(private val view: EnrichedTextInputView) { view.spanState?.setStart(name, currentStart) } - private fun handleAfterTextChanged(s: Editable, name: String, endCursorPosition: Int, previousTextLength: Int) { + private fun handleAfterTextChanged( + s: Editable, + name: String, + endCursorPosition: Int, + previousTextLength: Int, + ) { val config = EnrichedSpans.listSpans[name] ?: return val cursorPosition = endCursorPosition.coerceAtMost(s.length) val (start, end) = s.getParagraphBounds(cursorPosition) @@ -155,16 +186,22 @@ class ListStyles(private val view: EnrichedTextInputView) { } } - fun afterTextChanged(s: Editable, endCursorPosition: Int, previousTextLength: Int) { + fun afterTextChanged( + s: Editable, + endCursorPosition: Int, + previousTextLength: Int, + ) { handleAfterTextChanged(s, EnrichedSpans.ORDERED_LIST, endCursorPosition, previousTextLength) handleAfterTextChanged(s, EnrichedSpans.UNORDERED_LIST, endCursorPosition, previousTextLength) } - fun getStyleRange(): Pair { - return view.selection?.getParagraphSelection() ?: Pair(0, 0) - } + fun getStyleRange(): Pair = view.selection?.getParagraphSelection() ?: Pair(0, 0) - fun removeStyle(name: String, start: Int, end: Int): Boolean { + fun removeStyle( + name: String, + start: Int, + end: Int, + ): Boolean { val config = EnrichedSpans.listSpans[name] ?: return false val spannable = view.text as Spannable return removeSpansForRange(spannable, start, end, config.clazz) diff --git a/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt b/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt index 2d3d0b80..a8f19b9f 100644 --- a/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt +++ b/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt @@ -8,14 +8,26 @@ import com.swmansion.enriched.spans.EnrichedSpans import com.swmansion.enriched.utils.getParagraphBounds import com.swmansion.enriched.utils.getSafeSpanBoundaries -class ParagraphStyles(private val view: EnrichedTextInputView) { - private fun setSpan(spannable: Spannable, type: Class, start: Int, end: Int) { +class ParagraphStyles( + private val view: EnrichedTextInputView, +) { + private fun setSpan( + spannable: Spannable, + type: Class, + start: Int, + end: Int, + ) { val span = type.getDeclaredConstructor(HtmlStyle::class.java).newInstance(view.htmlStyle) val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, end) spannable.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } - private fun removeSpansForRange(spannable: Spannable, start: Int, end: Int, clazz: Class): Boolean { + private fun removeSpansForRange( + spannable: Spannable, + start: Int, + end: Int, + clazz: Class, + ): Boolean { val ssb = spannable as SpannableStringBuilder var finalStart = start var finalEnd = end @@ -34,7 +46,12 @@ class ParagraphStyles(private val view: EnrichedTextInputView) { return true } - private fun setAndMergeSpans(spannable: Spannable, type: Class, start: Int, end: Int) { + private fun setAndMergeSpans( + spannable: Spannable, + type: Class, + start: Int, + end: Int, + ) { val spans = spannable.getSpans(start, end, type) // No spans setup for current selection, means we just need to assign new span @@ -85,7 +102,11 @@ class ParagraphStyles(private val view: EnrichedTextInputView) { } } - private fun isSpanEnabledInNextLine(spannable: Spannable, index: Int, type: Class): Boolean { + private fun isSpanEnabledInNextLine( + spannable: Spannable, + index: Int, + type: Class, + ): Boolean { val selection = view.selection ?: return false if (index + 1 >= spannable.length) return false val (start, end) = selection.getParagraphSelection() @@ -94,7 +115,11 @@ class ParagraphStyles(private val view: EnrichedTextInputView) { return spans.isNotEmpty() } - fun afterTextChanged(s: Editable, endPosition: Int, previousTextLength: Int) { + 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' @@ -174,11 +199,13 @@ class ParagraphStyles(private val view: EnrichedTextInputView) { setAndMergeSpans(spannable, type, start, currentEnd) } - fun getStyleRange(): Pair { - return view.selection?.getParagraphSelection() ?: Pair(0, 0) - } + fun getStyleRange(): Pair = view.selection?.getParagraphSelection() ?: Pair(0, 0) - fun removeStyle(name: String, start: Int, end: Int): Boolean { + fun removeStyle( + name: String, + start: Int, + end: Int, + ): Boolean { val config = EnrichedSpans.paragraphSpans[name] ?: return false val spannable = view.text as Spannable return removeSpansForRange(spannable, start, end, config.clazz) diff --git a/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt b/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt index cdadbcad..18d12a4b 100644 --- a/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +++ b/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt @@ -13,13 +13,20 @@ import com.swmansion.enriched.spans.EnrichedSpans import com.swmansion.enriched.utils.getSafeSpanBoundaries import java.io.File -class ParametrizedStyles(private val view: EnrichedTextInputView) { +class ParametrizedStyles( + private val view: EnrichedTextInputView, +) { private var mentionStart: Int? = null private var isSettingLinkSpan = false var mentionIndicators: Array = emptyArray() - fun removeSpansForRange(spannable: Spannable, start: Int, end: Int, clazz: Class): Boolean { + fun removeSpansForRange( + spannable: Spannable, + start: Int, + end: Int, + clazz: Class, + ): Boolean { val ssb = spannable as SpannableStringBuilder val spans = ssb.getSpans(start, end, clazz) if (spans.isEmpty()) return false @@ -33,7 +40,12 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) { return true } - fun setLinkSpan(start: Int, end: Int, text: String, url: String) { + fun setLinkSpan( + start: Int, + end: Int, + text: String, + url: String, + ) { isSettingLinkSpan = true val spannable = view.text as SpannableStringBuilder @@ -57,7 +69,10 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) { isSettingLinkSpan = false } - fun afterTextChanged(s: Editable, endCursorPosition: Int) { + fun afterTextChanged( + s: Editable, + endCursorPosition: Int, + ) { val result = getWordAtIndex(s, endCursorPosition) ?: return afterTextChangedLinks(result) @@ -68,7 +83,9 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) { val spannable = view.text as Spannable // TODO: Consider using more reliable regex, this one matches almost anything - val urlPattern = android.util.Patterns.WEB_URL.matcher(spannable) + val urlPattern = + android.util.Patterns.WEB_URL + .matcher(spannable) val spans = spannable.getSpans(0, spannable.length, EnrichedLinkSpan::class.java) for (span in spans) { @@ -85,8 +102,11 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) { } } - private fun getWordAtIndex(s: Editable, index: Int): Triple? { - if (index < 0 ) return null + private fun getWordAtIndex( + s: Editable, + index: Int, + ): Triple? { + if (index < 0) return null var start = index var end = index @@ -111,7 +131,9 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) { val (word, start, end) = result // TODO: Consider using more reliable regex, this one matches almost anything - val urlPattern = android.util.Patterns.WEB_URL.matcher(word) + val urlPattern = + android.util.Patterns.WEB_URL + .matcher(word) val spans = spannable.getSpans(start, end, EnrichedLinkSpan::class.java) for (span in spans) { @@ -132,7 +154,7 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) { val indicatorsPattern = mentionIndicators.joinToString("|") { Regex.escape(it) } val mentionIndicatorRegex = Regex("^($indicatorsPattern)") - val mentionRegex= Regex("^($indicatorsPattern)\\w*") + val mentionRegex = Regex("^($indicatorsPattern)\\w*") val spans = spannable.getSpans(start, end, EnrichedMentionSpan::class.java) for (span in spans) { @@ -189,7 +211,11 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) { } } - fun setMentionSpan(indicator: String, text: String, attributes: Map) { + fun setMentionSpan( + indicator: String, + text: String, + attributes: Map, + ) { val selection = view.selection ?: return val spannable = view.text as SpannableStringBuilder @@ -220,11 +246,13 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) { view.selection.validateStyles() } - fun getStyleRange(): Pair { - return view.selection?.getInlineSelection() ?: Pair(0, 0) - } + fun getStyleRange(): Pair = view.selection?.getInlineSelection() ?: Pair(0, 0) - fun removeStyle(name: String, start: Int, end: Int): Boolean { + fun removeStyle( + name: String, + start: Int, + end: Int, + ): Boolean { val config = EnrichedSpans.parametrizedStyles[name] ?: return false val spannable = view.text as Spannable return removeSpansForRange(spannable, start, end, config.clazz) diff --git a/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java b/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java index 550774c9..d1f198ff 100644 --- a/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +++ b/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java @@ -9,7 +9,6 @@ import android.text.TextUtils; import android.text.style.AlignmentSpan; import android.text.style.ParagraphStyle; - import com.swmansion.enriched.spans.EnrichedBlockQuoteSpan; import com.swmansion.enriched.spans.EnrichedBoldSpan; import com.swmansion.enriched.spans.EnrichedCodeBlockSpan; @@ -26,11 +25,14 @@ import com.swmansion.enriched.spans.EnrichedUnderlineSpan; import com.swmansion.enriched.spans.EnrichedUnorderedListSpan; import com.swmansion.enriched.spans.interfaces.EnrichedBlockSpan; -import com.swmansion.enriched.spans.interfaces.EnrichedParagraphSpan; import com.swmansion.enriched.spans.interfaces.EnrichedInlineSpan; +import com.swmansion.enriched.spans.interfaces.EnrichedParagraphSpan; import com.swmansion.enriched.spans.interfaces.EnrichedZeroWidthSpaceSpan; import com.swmansion.enriched.styles.HtmlStyle; - +import java.io.IOException; +import java.io.StringReader; +import java.util.HashMap; +import java.util.Map; import org.ccil.cowan.tagsoup.HTMLSchema; import org.ccil.cowan.tagsoup.Parser; import org.xml.sax.Attributes; @@ -42,46 +44,38 @@ import org.xml.sax.SAXNotSupportedException; import org.xml.sax.XMLReader; -import java.io.IOException; -import java.io.StringReader; -import java.util.HashMap; -import java.util.Map; - /** * Most of the code in this file is copied from the Android source code and adjusted to our needs. - * For the reference see docs + * For the reference see docs */ public class EnrichedParser { - /** - * Retrieves images for HTML <img> tags. - */ + /** Retrieves images for HTML <img> tags. */ public interface ImageGetter { /** - * This method is called when the HTML parser encounters an - * <img> tag. The source argument is the - * string from the "src" attribute; the return value should be - * a Drawable representation of the image or null - * for a generic replacement image. Make sure you call - * setBounds() on your Drawable if it doesn't already have - * its bounds set. + * This method is called when the HTML parser encounters an <img> tag. The source + * argument is the string from the "src" attribute; the return value should be a + * Drawable representation of the image or null for a generic replacement image. + * Make sure you call setBounds() on your Drawable if it doesn't already have its bounds set. */ Drawable getDrawable(String source); } - private EnrichedParser() { } + private EnrichedParser() {} + /** - * Lazy initialization holder for HTML parser. This class will - * a) be preloaded by the zygote, or b) not loaded until absolutely - * necessary. + * Lazy initialization holder for HTML parser. This class will a) be preloaded by the zygote, or + * b) not loaded until absolutely necessary. */ private static class HtmlParser { private static final HTMLSchema schema = new HTMLSchema(); } + /** - * Returns displayable styled text from the provided HTML string. Any <img> tags in the - * HTML will use the specified ImageGetter to request a representation of the image (use null - * if you don't want this) and the specified TagHandler to handle unknown tags (specify null if - * you don't want this). + * Returns displayable styled text from the provided HTML string. Any <img> tags in the HTML + * will use the specified ImageGetter to request a representation of the image (use null if you + * don't want this) and the specified TagHandler to handle unknown tags (specify null if you don't + * want this). * *

This uses TagSoup to handle real HTML, including all of the brokenness found in the wild. */ @@ -93,36 +87,40 @@ public static Spanned fromHtml(String source, HtmlStyle style, ImageGetter image // Should not happen. throw new RuntimeException(e); } - HtmlToSpannedConverter converter = new HtmlToSpannedConverter(source, style, imageGetter, parser); + HtmlToSpannedConverter converter = + new HtmlToSpannedConverter(source, style, imageGetter, parser); return converter.convert(); } + public static String toHtml(Spanned text) { StringBuilder out = new StringBuilder(); withinHtml(out, text); String outString = out.toString(); // Codeblocks and blockquotes appends a newline character by default, so we have to remove it String normalizedCodeBlock = outString.replaceAll("\\n
", ""); - String normalizedBlockQuote = normalizedCodeBlock.replaceAll("\\n
", ""); + String normalizedBlockQuote = + normalizedCodeBlock.replaceAll("\\n
", ""); return "\n" + normalizedBlockQuote + ""; } - /** - * Returns an HTML escaped representation of the given plain text. - */ + + /** Returns an HTML escaped representation of the given plain text. */ public static String escapeHtml(CharSequence text) { StringBuilder out = new StringBuilder(); withinStyle(out, text, 0, text.length()); return out.toString(); } + private static void withinHtml(StringBuilder out, Spanned text) { withinDiv(out, text, 0, text.length()); } + private static void withinDiv(StringBuilder out, Spanned text, int start, int end) { int next; for (int i = start; i < end; i = next) { next = text.nextSpanTransition(i, end, EnrichedBlockSpan.class); EnrichedBlockSpan[] blocks = text.getSpans(i, next, EnrichedBlockSpan.class); String tag = "unknown"; - if (blocks.length > 0){ + if (blocks.length > 0) { tag = blocks[0] instanceof EnrichedCodeBlockSpan ? "codeblock" : "blockquote"; } @@ -141,6 +139,7 @@ private static void withinDiv(StringBuilder out, Spanned text, int start, int en } } } + private static String getBlockTag(EnrichedParagraphSpan[] spans) { for (EnrichedParagraphSpan span : spans) { if (span instanceof EnrichedUnorderedListSpan) { @@ -158,6 +157,7 @@ private static String getBlockTag(EnrichedParagraphSpan[] spans) { return "p"; } + private static void withinBlock(StringBuilder out, Spanned text, int start, int end) { boolean isInUlList = false; boolean isInOlList = false; @@ -179,7 +179,8 @@ private static void withinBlock(StringBuilder out, Spanned text, int start, int } out.append("
\n"); } else { - EnrichedParagraphSpan[] paragraphStyles = text.getSpans(i, next, EnrichedParagraphSpan.class); + EnrichedParagraphSpan[] paragraphStyles = + text.getSpans(i, next, EnrichedParagraphSpan.class); String tag = getBlockTag(paragraphStyles); boolean isUlListItem = tag.equals("ul"); boolean isOlListItem = tag.equals("ol"); @@ -226,6 +227,7 @@ private static void withinBlock(StringBuilder out, Spanned text, int start, int next++; } } + private static void withinParagraph(StringBuilder out, Spanned text, int start, int end) { int next; for (int i = start; i < end; i = next) { @@ -306,8 +308,8 @@ private static void withinParagraph(StringBuilder out, Spanned text, int start, } } } - private static void withinStyle(StringBuilder out, CharSequence text, - int start, int end) { + + private static void withinStyle(StringBuilder out, CharSequence text, int start, int end) { for (int i = start; i < end; i++) { char c = text.charAt(i); if (c == '\u200B') { @@ -342,6 +344,7 @@ private static void withinStyle(StringBuilder out, CharSequence text, } } } + class HtmlToSpannedConverter implements ContentHandler { private final HtmlStyle mStyle; private final String mSource; @@ -351,7 +354,8 @@ class HtmlToSpannedConverter implements ContentHandler { private static Integer currentOrderedListItemIndex = 0; private static Boolean isInOrderedList = false; - public HtmlToSpannedConverter(String source, HtmlStyle style, EnrichedParser.ImageGetter imageGetter, Parser parser) { + public HtmlToSpannedConverter( + String source, HtmlStyle style, EnrichedParser.ImageGetter imageGetter, Parser parser) { mStyle = style; mSource = source; mSpannableStringBuilder = new SpannableStringBuilder(); @@ -371,14 +375,15 @@ public Spanned convert() { throw new RuntimeException(e); } // Fix flags and range for paragraph-type markup. - Object[] obj = mSpannableStringBuilder.getSpans(0, mSpannableStringBuilder.length(), ParagraphStyle.class); + Object[] obj = + mSpannableStringBuilder.getSpans(0, mSpannableStringBuilder.length(), ParagraphStyle.class); for (int i = 0; i < obj.length; i++) { int start = mSpannableStringBuilder.getSpanStart(obj[i]); int end = mSpannableStringBuilder.getSpanEnd(obj[i]); // If the last line of the range is blank, back off by one. if (end - 2 >= 0) { - if (mSpannableStringBuilder.charAt(end - 1) == '\n' && - mSpannableStringBuilder.charAt(end - 2) == '\n') { + if (mSpannableStringBuilder.charAt(end - 1) == '\n' + && mSpannableStringBuilder.charAt(end - 2) == '\n') { end--; } } @@ -386,19 +391,23 @@ public Spanned convert() { mSpannableStringBuilder.removeSpan(obj[i]); } else { // TODO: verify if Spannable.SPAN_EXCLUSIVE_EXCLUSIVE does not break anything. - // Previously it was SPAN_PARAGRAPH. I've changed that in order to fix ranges for list items. + // Previously it was SPAN_PARAGRAPH. I've changed that in order to fix ranges for list + // items. mSpannableStringBuilder.setSpan(obj[i], start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } // Assign zero-width space character to the proper spans. - EnrichedZeroWidthSpaceSpan[] zeroWidthSpaceSpans = mSpannableStringBuilder.getSpans(0, mSpannableStringBuilder.length(), EnrichedZeroWidthSpaceSpan.class); + EnrichedZeroWidthSpaceSpan[] zeroWidthSpaceSpans = + mSpannableStringBuilder.getSpans( + 0, mSpannableStringBuilder.length(), EnrichedZeroWidthSpaceSpan.class); for (EnrichedZeroWidthSpaceSpan zeroWidthSpaceSpan : zeroWidthSpaceSpans) { int start = mSpannableStringBuilder.getSpanStart(zeroWidthSpaceSpan); int end = mSpannableStringBuilder.getSpanEnd(zeroWidthSpaceSpan); mSpannableStringBuilder.insert(start, "\u200B"); mSpannableStringBuilder.removeSpan(zeroWidthSpaceSpan); - mSpannableStringBuilder.setSpan(zeroWidthSpaceSpan, start, end + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + mSpannableStringBuilder.setSpan( + zeroWidthSpaceSpan, start, end + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } return mSpannableStringBuilder; @@ -501,8 +510,8 @@ private static void appendNewlines(Editable text, int minNewline) { } private static void startBlockElement(Editable text) { - appendNewlines(text, 1); - start(text, new Newline(1)); + appendNewlines(text, 1); + start(text, new Newline(1)); } private static void endBlockElement(Editable text) { @@ -661,7 +670,8 @@ private static void end(Editable text, Class kind, Object repl) { } } - private static void startImg(Editable text, Attributes attributes, EnrichedParser.ImageGetter img, HtmlStyle style) { + private static void startImg( + Editable text, Attributes attributes, EnrichedParser.ImageGetter img, HtmlStyle style) { String src = attributes.getValue("", "src"); Drawable d = null; if (img != null) { @@ -674,7 +684,11 @@ private static void startImg(Editable text, Attributes attributes, EnrichedParse int len = text.length(); text.append(""); - text.setSpan(new EnrichedImageSpan(d, src, style), len, text.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan( + new EnrichedImageSpan(d, src, style), + len, + text.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } private static void startA(Editable text, Attributes attributes) { @@ -716,20 +730,15 @@ private void endMention(Editable text, HtmlStyle style) { setSpanFromMark(text, m, new EnrichedMentionSpan(m.mText, m.mIndicator, m.mAttributes, style)); } - public void setDocumentLocator(Locator locator) { - } + public void setDocumentLocator(Locator locator) {} - public void startDocument() { - } + public void startDocument() {} - public void endDocument() { - } + public void endDocument() {} - public void startPrefixMapping(String prefix, String uri) { - } + public void startPrefixMapping(String prefix, String uri) {} - public void endPrefixMapping(String prefix) { - } + public void endPrefixMapping(String prefix) {} public void startElement(String uri, String localName, String qName, Attributes attributes) { handleStartTag(localName, attributes); @@ -770,44 +779,31 @@ public void characters(char[] ch, int start, int length) { mSpannableStringBuilder.append(sb); } - public void ignorableWhitespace(char[] ch, int start, int length) { - } + public void ignorableWhitespace(char[] ch, int start, int length) {} - public void processingInstruction(String target, String data) { - } + public void processingInstruction(String target, String data) {} - public void skippedEntity(String name) { - } + public void skippedEntity(String name) {} - private static class H1 { - } + private static class H1 {} - private static class H2 { - } + private static class H2 {} - private static class H3 { - } + private static class H3 {} - private static class Bold { - } + private static class Bold {} - private static class Italic { - } + private static class Italic {} - private static class Underline { - } + private static class Underline {} - private static class Code { - } + private static class Code {} - private static class CodeBlock { - } + private static class CodeBlock {} - private static class Strikethrough { - } + private static class Strikethrough {} - private static class Blockquote { - } + private static class Blockquote {} private static class List { public int mIndex; diff --git a/android/src/main/java/com/swmansion/enriched/utils/EnrichedSelection.kt b/android/src/main/java/com/swmansion/enriched/utils/EnrichedSelection.kt index 774a4e34..4d36459a 100644 --- a/android/src/main/java/com/swmansion/enriched/utils/EnrichedSelection.kt +++ b/android/src/main/java/com/swmansion/enriched/utils/EnrichedSelection.kt @@ -13,14 +13,19 @@ import com.swmansion.enriched.spans.EnrichedMentionSpan import com.swmansion.enriched.spans.EnrichedSpans import org.json.JSONObject -class EnrichedSelection(private val view: EnrichedTextInputView) { +class EnrichedSelection( + private val view: EnrichedTextInputView, +) { var start: Int = 0 var end: Int = 0 private var previousLinkDetectedEvent: MutableMap = mutableMapOf("text" to "", "url" to "") private var previousMentionDetectedEvent: MutableMap = mutableMapOf("text" to "", "payload" to "") - fun onSelection(selStart: Int, selEnd: Int) { + fun onSelection( + selStart: Int, + selEnd: Int, + ) { var shouldValidateStyles = false var newStart = start var newEnd = end @@ -52,19 +57,23 @@ class EnrichedSelection(private val view: EnrichedTextInputView) { emitSelectionChangeEvent(view.text, finalStart, finalEnd) } - private fun isZeroWidthSelection(start: Int, end: Int): Boolean { + private fun isZeroWidthSelection( + start: Int, + end: Int, + ): Boolean { val text = view.text ?: return false if (start != end) { return text.substring(start, end) == "\u200B" } - val isNewLine = if (start > 0 ) text.substring(start - 1, start) == "\n" else true - val isNextCharacterZeroWidth = if (start < text.length) { - text.substring(start, start + 1) == "\u200B" - } else { - false - } + val isNewLine = if (start > 0) text.substring(start - 1, start) == "\n" else true + val isNextCharacterZeroWidth = + if (start < text.length) { + text.substring(start, start + 1) == "\u200B" + } else { + false + } return isNewLine && isNextCharacterZeroWidth } @@ -103,7 +112,7 @@ class EnrichedSelection(private val view: EnrichedTextInputView) { return Pair(finalStart, finalEnd) } - private fun getInlineStyleStart(type: Class): Int? { + private fun getInlineStyleStart(type: Class): Int? { val (start, end) = getInlineSelection() val spannable = view.text as Spannable val spans = spannable.getSpans(start, end, type) @@ -129,7 +138,7 @@ class EnrichedSelection(private val view: EnrichedTextInputView) { return spannable.getParagraphBounds(currentStart, currentEnd) } - private fun getParagraphStyleStart(type: Class): Int? { + private fun getParagraphStyleStart(type: Class): Int? { val (start, end) = getParagraphSelection() val spannable = view.text as Spannable val spans = spannable.getSpans(start, end, type) @@ -148,7 +157,7 @@ class EnrichedSelection(private val view: EnrichedTextInputView) { return styleStart } - private fun getListStyleStart(type: Class): Int? { + private fun getListStyleStart(type: Class): Int? { val (start, end) = getParagraphSelection() val spannable = view.text as Spannable var styleStart: Int? = null @@ -177,7 +186,7 @@ class EnrichedSelection(private val view: EnrichedTextInputView) { return styleStart } - private fun getParametrizedStyleStart(type: Class): Int? { + private fun getParametrizedStyleStart(type: Class): Int? { val (start, end) = getInlineSelection() val spannable = view.text as Spannable val spans = spannable.getSpans(start, end, type) @@ -212,7 +221,11 @@ class EnrichedSelection(private val view: EnrichedTextInputView) { return null } - private fun emitSelectionChangeEvent(editable: Editable?, start: Int, end: Int) { + private fun emitSelectionChangeEvent( + editable: Editable?, + start: Int, + end: Int, + ) { if (editable == null) return val context = view.context as ReactContext @@ -220,17 +233,24 @@ class EnrichedSelection(private val view: EnrichedTextInputView) { val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id) val text = editable.substring(start, end) - dispatcher?.dispatchEvent(OnChangeSelectionEvent( - surfaceId, - view.id, - text, - start , - end, - view.experimentalSynchronousEvents, - )) + dispatcher?.dispatchEvent( + OnChangeSelectionEvent( + surfaceId, + view.id, + text, + start, + end, + view.experimentalSynchronousEvents, + ), + ) } - private fun emitLinkDetectedEvent(spannable: Spannable, span: EnrichedLinkSpan?, start: Int, end: Int) { + private fun emitLinkDetectedEvent( + spannable: Spannable, + span: EnrichedLinkSpan?, + start: Int, + end: Int, + ) { val text = spannable.substring(start, end) val url = span?.getUrl() ?: "" @@ -243,18 +263,25 @@ class EnrichedSelection(private val view: EnrichedTextInputView) { val context = view.context as ReactContext val surfaceId = UIManagerHelper.getSurfaceId(context) val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id) - dispatcher?.dispatchEvent(OnLinkDetectedEvent( - surfaceId, - view.id, - text, - url, - start, - end, - view.experimentalSynchronousEvents, - )) + dispatcher?.dispatchEvent( + OnLinkDetectedEvent( + surfaceId, + view.id, + text, + url, + start, + end, + view.experimentalSynchronousEvents, + ), + ) } - private fun emitMentionDetectedEvent(spannable: Spannable, span: EnrichedMentionSpan?, start: Int, end: Int) { + private fun emitMentionDetectedEvent( + spannable: Spannable, + span: EnrichedMentionSpan?, + start: Int, + end: Int, + ) { val text = spannable.substring(start, end) val attributes = span?.getAttributes() ?: emptyMap() val indicator = span?.getIndicator() ?: "" @@ -273,13 +300,15 @@ class EnrichedSelection(private val view: EnrichedTextInputView) { val context = view.context as ReactContext val surfaceId = UIManagerHelper.getSurfaceId(context) val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id) - dispatcher?.dispatchEvent(OnMentionDetectedEvent( - surfaceId, - view.id, - text, - indicator, - payload, - view.experimentalSynchronousEvents, - )) + dispatcher?.dispatchEvent( + OnMentionDetectedEvent( + surfaceId, + view.id, + text, + indicator, + payload, + view.experimentalSynchronousEvents, + ), + ) } } diff --git a/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt b/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt index ff733dd4..e1871455 100644 --- a/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt +++ b/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt @@ -8,7 +8,9 @@ import com.swmansion.enriched.EnrichedTextInputView import com.swmansion.enriched.events.OnChangeStateEvent import com.swmansion.enriched.spans.EnrichedSpans -class EnrichedSpanState(private val view: EnrichedTextInputView) { +class EnrichedSpanState( + private val view: EnrichedTextInputView, +) { private var previousPayload: WritableMap? = null var boldStart: Int? = null @@ -118,29 +120,33 @@ class EnrichedSpanState(private val view: EnrichedTextInputView) { } fun getStart(name: String): Int? { - val start = when (name) { - EnrichedSpans.BOLD -> boldStart - EnrichedSpans.ITALIC -> italicStart - EnrichedSpans.UNDERLINE -> underlineStart - EnrichedSpans.STRIKETHROUGH -> strikethroughStart - EnrichedSpans.INLINE_CODE -> inlineCodeStart - EnrichedSpans.H1 -> h1Start - EnrichedSpans.H2 -> h2Start - EnrichedSpans.H3 -> h3Start - EnrichedSpans.CODE_BLOCK -> codeBlockStart - EnrichedSpans.BLOCK_QUOTE -> blockQuoteStart - EnrichedSpans.ORDERED_LIST -> orderedListStart - EnrichedSpans.UNORDERED_LIST -> unorderedListStart - EnrichedSpans.LINK -> linkStart - EnrichedSpans.IMAGE -> imageStart - EnrichedSpans.MENTION -> mentionStart - else -> null - } + val start = + when (name) { + EnrichedSpans.BOLD -> boldStart + EnrichedSpans.ITALIC -> italicStart + EnrichedSpans.UNDERLINE -> underlineStart + EnrichedSpans.STRIKETHROUGH -> strikethroughStart + EnrichedSpans.INLINE_CODE -> inlineCodeStart + EnrichedSpans.H1 -> h1Start + EnrichedSpans.H2 -> h2Start + EnrichedSpans.H3 -> h3Start + EnrichedSpans.CODE_BLOCK -> codeBlockStart + EnrichedSpans.BLOCK_QUOTE -> blockQuoteStart + EnrichedSpans.ORDERED_LIST -> orderedListStart + EnrichedSpans.UNORDERED_LIST -> unorderedListStart + EnrichedSpans.LINK -> linkStart + EnrichedSpans.IMAGE -> imageStart + EnrichedSpans.MENTION -> mentionStart + else -> null + } return start } - fun setStart(name: String, start: Int?) { + fun setStart( + name: String, + start: Int?, + ) { when (name) { EnrichedSpans.BOLD -> setBoldStart(start) EnrichedSpans.ITALIC -> setItalicStart(start) @@ -183,19 +189,22 @@ class EnrichedSpanState(private val view: EnrichedTextInputView) { return } - previousPayload = Arguments.createMap().apply { - merge(payload) - } + previousPayload = + Arguments.createMap().apply { + merge(payload) + } val context = view.context as ReactContext val surfaceId = UIManagerHelper.getSurfaceId(context) val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id) - dispatcher?.dispatchEvent(OnChangeStateEvent( - surfaceId, - view.id, - payload, - view.experimentalSynchronousEvents, - )) + dispatcher?.dispatchEvent( + OnChangeStateEvent( + surfaceId, + view.id, + payload, + view.experimentalSynchronousEvents, + ), + ) } companion object { diff --git a/android/src/main/java/com/swmansion/enriched/utils/Utils.kt b/android/src/main/java/com/swmansion/enriched/utils/Utils.kt index cd7c9c8c..9814ae32 100644 --- a/android/src/main/java/com/swmansion/enriched/utils/Utils.kt +++ b/android/src/main/java/com/swmansion/enriched/utils/Utils.kt @@ -25,14 +25,20 @@ fun jsonStringToStringMap(json: String): Map { return result } -fun Spannable.getSafeSpanBoundaries(start: Int, end: Int): Pair { +fun Spannable.getSafeSpanBoundaries( + start: Int, + end: Int, +): Pair { val safeStart = start.coerceAtMost(end).coerceAtLeast(0) val safeEnd = end.coerceAtLeast(start).coerceAtMost(this.length) return Pair(safeStart, safeEnd) } -fun Spannable.getParagraphBounds(start: Int, end: Int): Pair { +fun Spannable.getParagraphBounds( + start: Int, + end: Int, +): Pair { var startPosition = start.coerceAtLeast(0).coerceAtMost(this.length) var endPosition = end.coerceAtLeast(0).coerceAtMost(this.length) @@ -54,15 +60,19 @@ fun Spannable.getParagraphBounds(start: Int, end: Int): Pair { return Pair(startPosition, endPosition) } -fun Spannable.getParagraphBounds(index: Int): Pair { - return this.getParagraphBounds(index, index) -} +fun Spannable.getParagraphBounds(index: Int): Pair = this.getParagraphBounds(index, index) -fun Spannable.mergeSpannables(start: Int, end: Int, string: String): Spannable { - return this.mergeSpannables(start, end, SpannableString(string)) -} +fun Spannable.mergeSpannables( + start: Int, + end: Int, + string: String, +): Spannable = this.mergeSpannables(start, end, SpannableString(string)) -fun Spannable.mergeSpannables(start: Int, end: Int, spannable: Spannable): Spannable { +fun Spannable.mergeSpannables( + start: Int, + end: Int, + spannable: Spannable, +): Spannable { var finalStart = start var finalEnd = end diff --git a/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt b/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt index 16b2511e..527805c0 100644 --- a/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt +++ b/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt @@ -13,26 +13,49 @@ import com.swmansion.enriched.spans.interfaces.EnrichedSpan import com.swmansion.enriched.utils.EnrichedParser import com.swmansion.enriched.utils.getSafeSpanBoundaries -class EnrichedSpanWatcher(private val view: EnrichedTextInputView) : SpanWatcher { +class EnrichedSpanWatcher( + private val view: EnrichedTextInputView, +) : SpanWatcher { private var previousHtml: String? = null - override fun onSpanAdded(text: Spannable, what: Any, start: Int, end: Int) { + override fun onSpanAdded( + text: Spannable, + what: Any, + start: Int, + end: Int, + ) { updateNextLineLayout(what, text, end) updateUnorderedListSpans(what, text, end) emitEvent(text, what) } - override fun onSpanRemoved(text: Spannable, what: Any, start: Int, end: Int) { + override fun onSpanRemoved( + text: Spannable, + what: Any, + start: Int, + end: Int, + ) { updateNextLineLayout(what, text, end) updateUnorderedListSpans(what, text, end) emitEvent(text, what) } - override fun onSpanChanged(text: Spannable, what: Any, ostart: Int, oend: Int, nstart: Int, nend: Int) { + override fun onSpanChanged( + text: Spannable, + what: Any, + ostart: Int, + oend: Int, + nstart: Int, + nend: Int, + ) { // Do nothing for now } - private fun updateUnorderedListSpans(what: Any, text: Spannable, end: Int) { + private fun updateUnorderedListSpans( + what: Any, + text: Spannable, + end: Int, + ) { if (what is EnrichedOrderedListSpan) { view.listStyles?.updateOrderedListIndexes(text, end) } @@ -40,8 +63,12 @@ class EnrichedSpanWatcher(private val view: EnrichedTextInputView) : SpanWatcher // After adding/removing heading span, we have to manually set empty paragraph span to the following text // This allows us to update the layout (as it's not updated automatically - looks like an Android issue) - private fun updateNextLineLayout(what: Any, text: Spannable, end: Int) { - class EmptySpan : ParagraphStyle {} + private fun updateNextLineLayout( + what: Any, + text: Spannable, + end: Int, + ) { + class EmptySpan : ParagraphStyle if (what is EnrichedHeadingSpan) { val finalStart = (end + 1) @@ -51,7 +78,10 @@ class EnrichedSpanWatcher(private val view: EnrichedTextInputView) : SpanWatcher } } - fun emitEvent(s: Spannable, what: Any?) { + fun emitEvent( + s: Spannable, + what: Any?, + ) { // Emit event only if we change one of ours spans if (what != null && what !is EnrichedSpan) return @@ -62,11 +92,13 @@ class EnrichedSpanWatcher(private val view: EnrichedTextInputView) : SpanWatcher val context = view.context as ReactContext val surfaceId = UIManagerHelper.getSurfaceId(context) val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id) - dispatcher?.dispatchEvent(OnChangeHtmlEvent( - surfaceId, - view.id, - html, - view.experimentalSynchronousEvents, - )) + dispatcher?.dispatchEvent( + OnChangeHtmlEvent( + surfaceId, + view.id, + html, + view.experimentalSynchronousEvents, + ), + ) } } diff --git a/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt b/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt index decd332e..826083a9 100644 --- a/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt +++ b/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt @@ -7,15 +7,27 @@ import com.facebook.react.uimanager.UIManagerHelper import com.swmansion.enriched.EnrichedTextInputView import com.swmansion.enriched.events.OnChangeTextEvent -class EnrichedTextWatcher(private val view: EnrichedTextInputView) : TextWatcher { +class EnrichedTextWatcher( + private val view: EnrichedTextInputView, +) : TextWatcher { private var endCursorPosition: Int = 0 private var previousTextLength: Int = 0 - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int, + ) { previousTextLength = s?.length ?: 0 } - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + override fun onTextChanged( + s: CharSequence?, + start: Int, + before: Int, + count: Int, + ) { endCursorPosition = start + count view.layoutManager.invalidateLayout() view.isRemovingMany = !view.isDuringTransaction && before > count + 1 @@ -40,12 +52,14 @@ class EnrichedTextWatcher(private val view: EnrichedTextInputView) : TextWatcher val context = view.context as ReactContext val surfaceId = UIManagerHelper.getSurfaceId(context) val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id) - dispatcher?.dispatchEvent(OnChangeTextEvent( - surfaceId, - view.id, - s, - view.experimentalSynchronousEvents, - )) + dispatcher?.dispatchEvent( + OnChangeTextEvent( + surfaceId, + view.id, + s, + view.experimentalSynchronousEvents, + ), + ) view.spanWatcher?.emitEvent(s, null) } } From e58fbb0481d1e5f947cefaa52873269a037cfb71 Mon Sep 17 00:00:00 2001 From: IvanIhnatsiuk Date: Sun, 9 Nov 2025 19:02:43 +0100 Subject: [PATCH 2/5] feat: add GH action --- .github/workflows/android-verify.yml | 45 +++++++++++++++++++ .../enriched/EnrichedTextInputView.kt | 1 - package.json | 2 + 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/android-verify.yml diff --git a/.github/workflows/android-verify.yml b/.github/workflows/android-verify.yml new file mode 100644 index 00000000..1be4c214 --- /dev/null +++ b/.github/workflows/android-verify.yml @@ -0,0 +1,45 @@ +name: 🤖 Android Lint Check + +on: + push: + branches: + - main + pull_request: + branches: + - main + merge_group: + types: + - checks_requested + +jobs: + lint: + name: Run Gradle lint verification + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: ☕ Set up JDK + if: env.turbo_cache_hit != 1 + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + + - name: Cache Gradle + if: env.turbo_cache_hit != 1 + uses: actions/cache@v4 + with: + path: | + ~/.gradle/wrapper + ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('example/android/gradle/wrapper/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Grant execute permission for Gradle wrapper + run: chmod +x ./gradlew + + - name: Run lint verification + run: ./gradlew lintVerify --no-daemon --stacktrace diff --git a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt index 50cb1536..e09a269d 100644 --- a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +++ b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt @@ -53,7 +53,6 @@ class EnrichedTextInputView : AppCompatEditText { val parametrizedStyles: ParametrizedStyles? = ParametrizedStyles(this) var isDuringTransaction: Boolean = false var isRemovingMany: Boolean = false - var test = true val mentionHandler: MentionHandler? = MentionHandler(this) var htmlStyle: HtmlStyle = HtmlStyle(this, null) diff --git a/package.json b/package.json index adbd1ee3..3b4c5c66 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,8 @@ "test": "jest --passWithNoTests", "typecheck": "tsc", "lint": "eslint \"**/*.{js,ts,tsx}\"", + "lint:android": "cd android && ./gradlew lintVerify", + "lint:android:fix": "cd android && ./gradlew lintFormat", "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib", "prepare": "bob build", "release": "release-it", From 8f2a77412b2872915e9ec828d3bac7b0070cadda Mon Sep 17 00:00:00 2001 From: IvanIhnatsiuk Date: Sun, 9 Nov 2025 19:30:05 +0100 Subject: [PATCH 3/5] feat: comment merge_group to test action on CI --- .github/workflows/android-verify.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/android-verify.yml b/.github/workflows/android-verify.yml index 1be4c214..80b11f77 100644 --- a/.github/workflows/android-verify.yml +++ b/.github/workflows/android-verify.yml @@ -7,9 +7,9 @@ on: pull_request: branches: - main - merge_group: - types: - - checks_requested + # merge_group: + # types: + # - checks_requested jobs: lint: From fccce711408b90ad96365210b98880410eb5c24d Mon Sep 17 00:00:00 2001 From: IvanIhnatsiuk Date: Sun, 9 Nov 2025 19:30:55 +0100 Subject: [PATCH 4/5] fix: remove commented code in GH action --- .github/workflows/android-verify.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/android-verify.yml b/.github/workflows/android-verify.yml index 80b11f77..1be4c214 100644 --- a/.github/workflows/android-verify.yml +++ b/.github/workflows/android-verify.yml @@ -7,9 +7,9 @@ on: pull_request: branches: - main - # merge_group: - # types: - # - checks_requested + merge_group: + types: + - checks_requested jobs: lint: From 5219fc20aaed4482d33d59b6a576c2822ed62956 Mon Sep 17 00:00:00 2001 From: IvanIhnatsiuk Date: Sun, 9 Nov 2025 19:37:46 +0100 Subject: [PATCH 5/5] fix: use correct directory --- .github/workflows/android-verify.yml | 3 +++ package.json | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/android-verify.yml b/.github/workflows/android-verify.yml index 1be4c214..aa0eb46a 100644 --- a/.github/workflows/android-verify.yml +++ b/.github/workflows/android-verify.yml @@ -15,6 +15,9 @@ jobs: lint: name: Run Gradle lint verification runs-on: ubuntu-latest + defaults: + run: + working-directory: example/android steps: - name: Checkout repository diff --git a/package.json b/package.json index 3b4c5c66..85d68cc0 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,8 @@ "test": "jest --passWithNoTests", "typecheck": "tsc", "lint": "eslint \"**/*.{js,ts,tsx}\"", - "lint:android": "cd android && ./gradlew lintVerify", - "lint:android:fix": "cd android && ./gradlew lintFormat", + "lint:android": "cd example/android && ./gradlew lintVerify", + "lint:android:fix": "cd example/android && ./gradlew lintFormat", "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib", "prepare": "bob build", "release": "release-it",