react-native-enriched 0.0.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +875 -0
- package/ReactNativeEnriched.podspec +27 -0
- package/android/build.gradle +101 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerDelegate.java +146 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerInterface.java +55 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ComponentDescriptors.cpp +22 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ComponentDescriptors.h +24 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.cpp +118 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.h +95 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.cpp +128 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.h +577 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ShadowNodes.cpp +17 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ShadowNodes.h +23 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/States.cpp +16 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/States.h +20 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +535 -0
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewLayoutManager.kt +64 -0
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +292 -0
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt +19 -0
- package/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt +40 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnChangeHtmlEvent.kt +28 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnChangeSelectionEvent.kt +29 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnChangeStateEvent.kt +24 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnChangeTextEvent.kt +30 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnInputBlurEvent.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnInputFocusEvent.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnLinkDetectedEvent.kt +30 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnMentionDetectedEvent.kt +29 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnMentionEvent.kt +33 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBlockQuoteSpan.kt +34 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBoldSpan.kt +10 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt +38 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH1Span.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH2Span.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH3Span.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt +41 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedInlineCodeSpan.kt +16 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedItalicSpan.kt +10 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedLinkSpan.kt +24 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedMentionSpan.kt +36 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedOrderedListSpan.kt +71 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +111 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedStrikeThroughSpan.kt +9 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnderlineSpan.kt +9 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnorderedListSpan.kt +49 -0
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedBlockSpan.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedHeadingSpan.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedInlineSpan.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedParagraphSpan.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedSpan.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedZeroWidthSpaceSpan.kt +5 -0
- package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +227 -0
- package/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt +146 -0
- package/android/src/main/java/com/swmansion/enriched/styles/ListStyles.kt +173 -0
- package/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt +186 -0
- package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +223 -0
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +857 -0
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSelection.kt +285 -0
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt +204 -0
- package/android/src/main/java/com/swmansion/enriched/utils/Utils.kt +91 -0
- package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt +73 -0
- package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt +51 -0
- package/android/src/main/new_arch/CMakeLists.txt +56 -0
- package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.cpp +22 -0
- package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.h +26 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputComponentDescriptor.h +35 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.cpp +51 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.h +26 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.cpp +34 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.h +54 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.cpp +9 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.h +25 -0
- package/ios/EnrichedTextInputView.h +33 -0
- package/ios/EnrichedTextInputView.mm +1190 -0
- package/ios/EnrichedTextInputViewManager.mm +13 -0
- package/ios/config/InputConfig.h +67 -0
- package/ios/config/InputConfig.mm +382 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/ComponentDescriptors.cpp +22 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/ComponentDescriptors.h +24 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.cpp +118 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.h +95 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/Props.cpp +128 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/Props.h +577 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/RCTComponentViewHelpers.h +384 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/ShadowNodes.cpp +17 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/ShadowNodes.h +23 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/States.cpp +16 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/States.h +20 -0
- package/ios/inputParser/InputParser.h +11 -0
- package/ios/inputParser/InputParser.mm +659 -0
- package/ios/inputTextView/InputTextView.h +6 -0
- package/ios/inputTextView/InputTextView.mm +115 -0
- package/ios/internals/EnrichedTextInputViewComponentDescriptor.h +17 -0
- package/ios/internals/EnrichedTextInputViewShadowNode.h +40 -0
- package/ios/internals/EnrichedTextInputViewShadowNode.mm +83 -0
- package/ios/internals/EnrichedTextInputViewState.cpp +10 -0
- package/ios/internals/EnrichedTextInputViewState.h +20 -0
- package/ios/styles/BlockQuoteStyle.mm +248 -0
- package/ios/styles/BoldStyle.mm +122 -0
- package/ios/styles/H1Style.mm +10 -0
- package/ios/styles/H2Style.mm +10 -0
- package/ios/styles/H3Style.mm +10 -0
- package/ios/styles/HeadingStyleBase.mm +144 -0
- package/ios/styles/InlineCodeStyle.mm +163 -0
- package/ios/styles/ItalicStyle.mm +110 -0
- package/ios/styles/LinkStyle.mm +463 -0
- package/ios/styles/MentionStyle.mm +476 -0
- package/ios/styles/OrderedListStyle.mm +225 -0
- package/ios/styles/StrikethroughStyle.mm +80 -0
- package/ios/styles/UnderlineStyle.mm +112 -0
- package/ios/styles/UnorderedListStyle.mm +225 -0
- package/ios/utils/BaseStyleProtocol.h +16 -0
- package/ios/utils/ColorExtension.h +6 -0
- package/ios/utils/ColorExtension.mm +27 -0
- package/ios/utils/FontExtension.h +13 -0
- package/ios/utils/FontExtension.mm +91 -0
- package/ios/utils/LayoutManagerExtension.h +6 -0
- package/ios/utils/LayoutManagerExtension.mm +171 -0
- package/ios/utils/LinkData.h +9 -0
- package/ios/utils/LinkData.mm +4 -0
- package/ios/utils/MentionParams.h +9 -0
- package/ios/utils/MentionParams.mm +4 -0
- package/ios/utils/MentionStyleProps.h +13 -0
- package/ios/utils/MentionStyleProps.mm +56 -0
- package/ios/utils/OccurenceUtils.h +37 -0
- package/ios/utils/OccurenceUtils.mm +124 -0
- package/ios/utils/ParagraphsUtils.h +7 -0
- package/ios/utils/ParagraphsUtils.mm +54 -0
- package/ios/utils/StringExtension.h +15 -0
- package/ios/utils/StringExtension.mm +57 -0
- package/ios/utils/StyleHeaders.h +74 -0
- package/ios/utils/StylePair.h +9 -0
- package/ios/utils/StylePair.mm +4 -0
- package/ios/utils/StyleTypeEnum.h +22 -0
- package/ios/utils/TextDecorationLineEnum.h +6 -0
- package/ios/utils/TextDecorationLineEnum.mm +4 -0
- package/ios/utils/TextInsertionUtils.h +6 -0
- package/ios/utils/TextInsertionUtils.mm +48 -0
- package/ios/utils/WordsUtils.h +6 -0
- package/ios/utils/WordsUtils.mm +88 -0
- package/ios/utils/ZeroWidthSpaceUtils.h +7 -0
- package/ios/utils/ZeroWidthSpaceUtils.mm +164 -0
- package/lib/module/EnrichedTextInput.js +191 -0
- package/lib/module/EnrichedTextInput.js.map +1 -0
- package/lib/module/EnrichedTextInputNativeComponent.ts +235 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/normalizeHtmlStyle.js +141 -0
- package/lib/module/normalizeHtmlStyle.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/EnrichedTextInput.d.ts +113 -0
- package/lib/typescript/src/EnrichedTextInput.d.ts.map +1 -0
- package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts +160 -0
- package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +3 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/normalizeHtmlStyle.d.ts +4 -0
- package/lib/typescript/src/normalizeHtmlStyle.d.ts.map +1 -0
- package/package.json +172 -1
- package/react-native.config.js +13 -0
- package/src/EnrichedTextInput.tsx +358 -0
- package/src/EnrichedTextInputNativeComponent.ts +235 -0
- package/src/index.tsx +9 -0
- package/src/normalizeHtmlStyle.ts +188 -0
package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ShadowNodes.cpp
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
|
|
4
|
+
*
|
|
5
|
+
* Do not edit this file as changes may cause incorrect behavior and will be lost
|
|
6
|
+
* once the code is regenerated.
|
|
7
|
+
*
|
|
8
|
+
* @generated by codegen project: GenerateShadowNodeCpp.js
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#include "ShadowNodes.h"
|
|
12
|
+
|
|
13
|
+
namespace facebook::react {
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
} // namespace facebook::react
|
package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ShadowNodes.h
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
|
|
4
|
+
*
|
|
5
|
+
* Do not edit this file as changes may cause incorrect behavior and will be lost
|
|
6
|
+
* once the code is regenerated.
|
|
7
|
+
*
|
|
8
|
+
* @generated by codegen project: GenerateShadowNodeH.js
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#pragma once
|
|
12
|
+
|
|
13
|
+
#include "EventEmitters.h"
|
|
14
|
+
#include "Props.h"
|
|
15
|
+
#include "States.h"
|
|
16
|
+
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
|
|
17
|
+
#include <jsi/jsi.h>
|
|
18
|
+
|
|
19
|
+
namespace facebook::react {
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
} // namespace facebook::react
|
package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/States.cpp
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
|
|
4
|
+
*
|
|
5
|
+
* Do not edit this file as changes may cause incorrect behavior and will be lost
|
|
6
|
+
* once the code is regenerated.
|
|
7
|
+
*
|
|
8
|
+
* @generated by codegen project: GenerateStateCpp.js
|
|
9
|
+
*/
|
|
10
|
+
#include "States.h"
|
|
11
|
+
|
|
12
|
+
namespace facebook::react {
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
|
|
3
|
+
*
|
|
4
|
+
* Do not edit this file as changes may cause incorrect behavior and will be lost
|
|
5
|
+
* once the code is regenerated.
|
|
6
|
+
*
|
|
7
|
+
* @generated by codegen project: GenerateStateH.js
|
|
8
|
+
*/
|
|
9
|
+
#pragma once
|
|
10
|
+
|
|
11
|
+
#include <react/renderer/core/StateData.h>
|
|
12
|
+
#ifdef RN_SERIALIZABLE_STATE
|
|
13
|
+
#include <folly/dynamic.h>
|
|
14
|
+
#endif
|
|
15
|
+
|
|
16
|
+
namespace facebook::react {
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
package com.swmansion.enriched
|
|
2
|
+
|
|
3
|
+
import android.content.ClipData
|
|
4
|
+
import android.content.ClipboardManager
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.graphics.BlendMode
|
|
7
|
+
import android.graphics.BlendModeColorFilter
|
|
8
|
+
import android.graphics.Color
|
|
9
|
+
import android.graphics.Rect
|
|
10
|
+
import android.os.Build
|
|
11
|
+
import android.text.InputType
|
|
12
|
+
import android.text.Spannable
|
|
13
|
+
import android.util.AttributeSet
|
|
14
|
+
import android.util.Log
|
|
15
|
+
import android.util.TypedValue
|
|
16
|
+
import android.view.Gravity
|
|
17
|
+
import android.view.MotionEvent
|
|
18
|
+
import android.view.inputmethod.InputMethodManager
|
|
19
|
+
import androidx.appcompat.widget.AppCompatEditText
|
|
20
|
+
import com.facebook.react.bridge.ReactContext
|
|
21
|
+
import com.facebook.react.common.ReactConstants
|
|
22
|
+
import com.facebook.react.uimanager.PixelUtil
|
|
23
|
+
import com.facebook.react.uimanager.StateWrapper
|
|
24
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
25
|
+
import com.facebook.react.views.text.ReactTypefaceUtils.applyStyles
|
|
26
|
+
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontStyle
|
|
27
|
+
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontWeight
|
|
28
|
+
import com.swmansion.enriched.events.MentionHandler
|
|
29
|
+
import com.swmansion.enriched.events.OnInputBlurEvent
|
|
30
|
+
import com.swmansion.enriched.events.OnInputFocusEvent
|
|
31
|
+
import com.swmansion.enriched.spans.EnrichedSpans
|
|
32
|
+
import com.swmansion.enriched.styles.InlineStyles
|
|
33
|
+
import com.swmansion.enriched.styles.ListStyles
|
|
34
|
+
import com.swmansion.enriched.styles.ParagraphStyles
|
|
35
|
+
import com.swmansion.enriched.styles.ParametrizedStyles
|
|
36
|
+
import com.swmansion.enriched.styles.HtmlStyle
|
|
37
|
+
import com.swmansion.enriched.utils.EnrichedParser
|
|
38
|
+
import com.swmansion.enriched.utils.EnrichedSelection
|
|
39
|
+
import com.swmansion.enriched.utils.EnrichedSpanState
|
|
40
|
+
import com.swmansion.enriched.utils.mergeSpannables
|
|
41
|
+
import com.swmansion.enriched.watchers.EnrichedSpanWatcher
|
|
42
|
+
import com.swmansion.enriched.watchers.EnrichedTextWatcher
|
|
43
|
+
import kotlin.math.ceil
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class EnrichedTextInputView : AppCompatEditText {
|
|
47
|
+
var stateWrapper: StateWrapper? = null
|
|
48
|
+
val selection: EnrichedSelection? = EnrichedSelection(this)
|
|
49
|
+
val spanState: EnrichedSpanState? = EnrichedSpanState(this)
|
|
50
|
+
val inlineStyles: InlineStyles? = InlineStyles(this)
|
|
51
|
+
val paragraphStyles: ParagraphStyles? = ParagraphStyles(this)
|
|
52
|
+
val listStyles: ListStyles? = ListStyles(this)
|
|
53
|
+
val parametrizedStyles: ParametrizedStyles? = ParametrizedStyles(this)
|
|
54
|
+
var isSettingValue: Boolean = false
|
|
55
|
+
var isRemovingMany: Boolean = false
|
|
56
|
+
|
|
57
|
+
val mentionHandler: MentionHandler? = MentionHandler(this)
|
|
58
|
+
var htmlStyle: HtmlStyle = HtmlStyle(this, null)
|
|
59
|
+
var spanWatcher: EnrichedSpanWatcher? = null
|
|
60
|
+
var layoutManager: EnrichedTextInputViewLayoutManager = EnrichedTextInputViewLayoutManager(this)
|
|
61
|
+
|
|
62
|
+
var experimentalSynchronousEvents: Boolean = false
|
|
63
|
+
|
|
64
|
+
var fontSize: Float? = null
|
|
65
|
+
private var autoFocus = false
|
|
66
|
+
private var typefaceDirty = false
|
|
67
|
+
private var didAttachToWindow = false
|
|
68
|
+
private var detectScrollMovement = false
|
|
69
|
+
private var fontFamily: String? = null
|
|
70
|
+
private var fontStyle: Int = ReactConstants.UNSET
|
|
71
|
+
private var fontWeight: Int = ReactConstants.UNSET
|
|
72
|
+
|
|
73
|
+
private var inputMethodManager: InputMethodManager? = null
|
|
74
|
+
|
|
75
|
+
constructor(context: Context) : super(context) {
|
|
76
|
+
prepareComponent()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
|
80
|
+
prepareComponent()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
|
|
84
|
+
context,
|
|
85
|
+
attrs,
|
|
86
|
+
defStyleAttr
|
|
87
|
+
) {
|
|
88
|
+
prepareComponent()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
init {
|
|
92
|
+
inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private fun prepareComponent() {
|
|
96
|
+
isSingleLine = false
|
|
97
|
+
isHorizontalScrollBarEnabled = false
|
|
98
|
+
isVerticalScrollBarEnabled = true
|
|
99
|
+
gravity = Gravity.TOP or Gravity.START
|
|
100
|
+
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
|
101
|
+
|
|
102
|
+
setPadding(0, 0, 0, 0)
|
|
103
|
+
setBackgroundColor(Color.TRANSPARENT)
|
|
104
|
+
|
|
105
|
+
addSpanWatcher(EnrichedSpanWatcher(this))
|
|
106
|
+
addTextChangedListener(EnrichedTextWatcher(this))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// https://github.com/facebook/react-native/blob/36df97f500aa0aa8031098caf7526db358b6ddc1/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt#L295C1-L296C1
|
|
110
|
+
override fun onTouchEvent(ev: MotionEvent): Boolean {
|
|
111
|
+
when (ev.action) {
|
|
112
|
+
MotionEvent.ACTION_DOWN -> {
|
|
113
|
+
detectScrollMovement = true
|
|
114
|
+
// Disallow parent views to intercept touch events, until we can detect if we should be
|
|
115
|
+
// capturing these touches or not.
|
|
116
|
+
this.parent.requestDisallowInterceptTouchEvent(true)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
MotionEvent.ACTION_MOVE ->
|
|
120
|
+
if (detectScrollMovement) {
|
|
121
|
+
if (!canScrollVertically(-1) &&
|
|
122
|
+
!canScrollVertically(1) &&
|
|
123
|
+
!canScrollHorizontally(-1) &&
|
|
124
|
+
!canScrollHorizontally(1)) {
|
|
125
|
+
// We cannot scroll, let parent views take care of these touches.
|
|
126
|
+
this.parent.requestDisallowInterceptTouchEvent(false)
|
|
127
|
+
}
|
|
128
|
+
detectScrollMovement = false
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return super.onTouchEvent(ev)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
override fun onSelectionChanged(selStart: Int, selEnd: Int) {
|
|
136
|
+
super.onSelectionChanged(selStart, selEnd)
|
|
137
|
+
selection?.onSelection(selStart, selEnd)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
override fun clearFocus() {
|
|
141
|
+
super.clearFocus()
|
|
142
|
+
inputMethodManager?.hideSoftInputFromWindow(windowToken, 0)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
|
|
146
|
+
super.onFocusChanged(focused, direction, previouslyFocusedRect)
|
|
147
|
+
val context = context as ReactContext
|
|
148
|
+
val surfaceId = UIManagerHelper.getSurfaceId(context)
|
|
149
|
+
val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, id)
|
|
150
|
+
|
|
151
|
+
if (focused) {
|
|
152
|
+
dispatcher?.dispatchEvent(OnInputFocusEvent(surfaceId, id, experimentalSynchronousEvents))
|
|
153
|
+
} else {
|
|
154
|
+
dispatcher?.dispatchEvent(OnInputBlurEvent(surfaceId, id, experimentalSynchronousEvents))
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
override fun onTextContextMenuItem(id: Int): Boolean {
|
|
159
|
+
when (id) {
|
|
160
|
+
android.R.id.copy -> {
|
|
161
|
+
handleCustomCopy()
|
|
162
|
+
return true
|
|
163
|
+
}
|
|
164
|
+
android.R.id.paste -> {
|
|
165
|
+
handleCustomPaste()
|
|
166
|
+
return true
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return super.onTextContextMenuItem(id)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private fun handleCustomCopy() {
|
|
173
|
+
val start = selectionStart
|
|
174
|
+
val end = selectionEnd
|
|
175
|
+
val spannable = text as Spannable
|
|
176
|
+
|
|
177
|
+
if (start < end) {
|
|
178
|
+
val selectedText = spannable.subSequence(start, end) as Spannable
|
|
179
|
+
val selectedHtml = EnrichedParser.toHtml(selectedText)
|
|
180
|
+
|
|
181
|
+
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
182
|
+
val clip = ClipData.newHtmlText(CLIPBOARD_TAG, selectedText, selectedHtml)
|
|
183
|
+
clipboard.setPrimaryClip(clip)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private fun handleCustomPaste() {
|
|
188
|
+
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
189
|
+
if (!clipboard.hasPrimaryClip()) return
|
|
190
|
+
|
|
191
|
+
val clip = clipboard.primaryClip
|
|
192
|
+
val item = clip?.getItemAt(0)
|
|
193
|
+
val htmlText = item?.htmlText
|
|
194
|
+
val currentText = text as Spannable
|
|
195
|
+
val start = selection?.start ?: 0
|
|
196
|
+
val end = selection?.end ?: 0
|
|
197
|
+
|
|
198
|
+
if (htmlText != null) {
|
|
199
|
+
val parsedText = parseText(htmlText)
|
|
200
|
+
if (parsedText is Spannable) {
|
|
201
|
+
val finalText = currentText.mergeSpannables(start, end, parsedText)
|
|
202
|
+
setValue(finalText)
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
val finalText = currentText.mergeSpannables(start, end, item?.text.toString())
|
|
208
|
+
setValue(finalText)
|
|
209
|
+
parametrizedStyles?.detectAllLinks()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
fun requestFocusProgrammatically() {
|
|
213
|
+
requestFocus()
|
|
214
|
+
inputMethodManager?.showSoftInput(this, 0)
|
|
215
|
+
setSelection(selection?.start ?: text?.length ?: 0)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private fun parseText(text: CharSequence): CharSequence {
|
|
219
|
+
val isHtml = text.startsWith("<html>") && text.endsWith("</html>")
|
|
220
|
+
if (!isHtml) return text
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
val parsed = EnrichedParser.fromHtml(text.toString(), htmlStyle, null)
|
|
224
|
+
val withoutLastNewLine = parsed.trimEnd('\n')
|
|
225
|
+
return withoutLastNewLine
|
|
226
|
+
} catch (e: Exception) {
|
|
227
|
+
Log.e("EnrichedTextInputView", "Error parsing HTML: ${e.message}")
|
|
228
|
+
return text
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
fun setValue(value: CharSequence?) {
|
|
233
|
+
if (value == null) return
|
|
234
|
+
isSettingValue = true
|
|
235
|
+
|
|
236
|
+
val newText = parseText(value)
|
|
237
|
+
setText(newText)
|
|
238
|
+
|
|
239
|
+
// Assign SpanWatcher one more time as our previous spannable has been replaced
|
|
240
|
+
addSpanWatcher(EnrichedSpanWatcher(this))
|
|
241
|
+
|
|
242
|
+
// Scroll to the last line of text
|
|
243
|
+
setSelection(text?.length ?: 0)
|
|
244
|
+
|
|
245
|
+
isSettingValue = false
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
fun setAutoFocus(autoFocus: Boolean) {
|
|
249
|
+
this.autoFocus = autoFocus
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
fun setPlaceholder(placeholder: String?) {
|
|
253
|
+
if (placeholder == null) return
|
|
254
|
+
|
|
255
|
+
hint = placeholder
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
fun setPlaceholderTextColor(colorInt: Int?) {
|
|
259
|
+
if (colorInt == null) return
|
|
260
|
+
|
|
261
|
+
setHintTextColor(colorInt)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
fun setSelectionColor(colorInt: Int?) {
|
|
265
|
+
if (colorInt == null) return
|
|
266
|
+
|
|
267
|
+
highlightColor = colorInt
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
fun setCursorColor(colorInt: Int?) {
|
|
271
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
272
|
+
val cursorDrawable = textCursorDrawable
|
|
273
|
+
if (cursorDrawable == null) return
|
|
274
|
+
|
|
275
|
+
if (colorInt != null) {
|
|
276
|
+
cursorDrawable.colorFilter = BlendModeColorFilter(colorInt, BlendMode.SRC_IN)
|
|
277
|
+
} else {
|
|
278
|
+
cursorDrawable.clearColorFilter()
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
textCursorDrawable = cursorDrawable
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
fun setColor(colorInt: Int?) {
|
|
286
|
+
if (colorInt == null) {
|
|
287
|
+
setTextColor(Color.BLACK)
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
setTextColor(colorInt)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
fun setFontSize(size: Float) {
|
|
295
|
+
if (size == 0f) return
|
|
296
|
+
|
|
297
|
+
val sizeInt = ceil(PixelUtil.toPixelFromSP(size))
|
|
298
|
+
fontSize = sizeInt
|
|
299
|
+
setTextSize(TypedValue.COMPLEX_UNIT_PX, sizeInt)
|
|
300
|
+
|
|
301
|
+
// This ensured that newly created spans will take the new font size into account
|
|
302
|
+
htmlStyle.invalidateStyles()
|
|
303
|
+
layoutManager.invalidateLayout(text)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
fun setFontFamily(family: String?) {
|
|
307
|
+
if (family != fontFamily) {
|
|
308
|
+
fontFamily = family
|
|
309
|
+
typefaceDirty = true
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
fun setFontWeight(weight: String?) {
|
|
314
|
+
val fontWeight = parseFontWeight(weight)
|
|
315
|
+
|
|
316
|
+
if (fontWeight != fontStyle) {
|
|
317
|
+
this.fontWeight = fontWeight
|
|
318
|
+
typefaceDirty = true
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
fun setFontStyle(style: String?) {
|
|
323
|
+
val fontStyle = parseFontStyle(style)
|
|
324
|
+
|
|
325
|
+
if (fontStyle != this.fontStyle) {
|
|
326
|
+
this.fontStyle = fontStyle
|
|
327
|
+
typefaceDirty = true
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
fun setAutoCapitalize(flagName: String?) {
|
|
332
|
+
val flag = when (flagName) {
|
|
333
|
+
"none" -> InputType.TYPE_NULL
|
|
334
|
+
"sentences" -> InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
|
335
|
+
"words" -> InputType.TYPE_TEXT_FLAG_CAP_WORDS
|
|
336
|
+
"characters" -> InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
|
|
337
|
+
else -> InputType.TYPE_NULL
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
inputType = (inputType and
|
|
341
|
+
InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS.inv() and
|
|
342
|
+
InputType.TYPE_TEXT_FLAG_CAP_WORDS.inv() and
|
|
343
|
+
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES.inv()
|
|
344
|
+
) or if (flag == InputType.TYPE_NULL) 0 else flag
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// https://github.com/facebook/react-native/blob/36df97f500aa0aa8031098caf7526db358b6ddc1/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt#L283C2-L284C1
|
|
348
|
+
// After the text changes inside an EditText, TextView checks if a layout() has been requested.
|
|
349
|
+
// If it has, it will not scroll the text to the end of the new text inserted, but wait for the
|
|
350
|
+
// next layout() to be called. However, we do not perform a layout() after a requestLayout(), so
|
|
351
|
+
// we need to override isLayoutRequested to force EditText to scroll to the end of the new text
|
|
352
|
+
// immediately.
|
|
353
|
+
override fun isLayoutRequested(): Boolean {
|
|
354
|
+
return false
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
fun updateTypeface() {
|
|
358
|
+
if (!typefaceDirty) return
|
|
359
|
+
typefaceDirty = false
|
|
360
|
+
|
|
361
|
+
val newTypeface = applyStyles(typeface, fontStyle, fontWeight, fontFamily, context.assets)
|
|
362
|
+
typeface = newTypeface
|
|
363
|
+
paint.typeface = newTypeface
|
|
364
|
+
|
|
365
|
+
layoutManager.invalidateLayout(text)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private fun toggleStyle(name: String) {
|
|
369
|
+
when (name) {
|
|
370
|
+
EnrichedSpans.BOLD -> inlineStyles?.toggleStyle(EnrichedSpans.BOLD)
|
|
371
|
+
EnrichedSpans.ITALIC -> inlineStyles?.toggleStyle(EnrichedSpans.ITALIC)
|
|
372
|
+
EnrichedSpans.UNDERLINE -> inlineStyles?.toggleStyle(EnrichedSpans.UNDERLINE)
|
|
373
|
+
EnrichedSpans.STRIKETHROUGH -> inlineStyles?.toggleStyle(EnrichedSpans.STRIKETHROUGH)
|
|
374
|
+
EnrichedSpans.INLINE_CODE -> inlineStyles?.toggleStyle(EnrichedSpans.INLINE_CODE)
|
|
375
|
+
EnrichedSpans.H1 -> paragraphStyles?.toggleStyle(EnrichedSpans.H1)
|
|
376
|
+
EnrichedSpans.H2 -> paragraphStyles?.toggleStyle(EnrichedSpans.H2)
|
|
377
|
+
EnrichedSpans.H3 -> paragraphStyles?.toggleStyle(EnrichedSpans.H3)
|
|
378
|
+
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.toggleStyle(EnrichedSpans.CODE_BLOCK)
|
|
379
|
+
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.toggleStyle(EnrichedSpans.BLOCK_QUOTE)
|
|
380
|
+
EnrichedSpans.ORDERED_LIST -> listStyles?.toggleStyle(EnrichedSpans.ORDERED_LIST)
|
|
381
|
+
EnrichedSpans.UNORDERED_LIST -> listStyles?.toggleStyle(EnrichedSpans.UNORDERED_LIST)
|
|
382
|
+
else -> Log.w("EnrichedTextInputView", "Unknown style: $name")
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
layoutManager.invalidateLayout(text)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private fun removeStyle(name: String, start: Int, end: Int): Boolean {
|
|
389
|
+
val removed = when (name) {
|
|
390
|
+
EnrichedSpans.BOLD -> inlineStyles?.removeStyle(EnrichedSpans.BOLD, start, end)
|
|
391
|
+
EnrichedSpans.ITALIC -> inlineStyles?.removeStyle(EnrichedSpans.ITALIC, start, end)
|
|
392
|
+
EnrichedSpans.UNDERLINE -> inlineStyles?.removeStyle(EnrichedSpans.UNDERLINE, start, end)
|
|
393
|
+
EnrichedSpans.STRIKETHROUGH -> inlineStyles?.removeStyle(EnrichedSpans.STRIKETHROUGH, start, end)
|
|
394
|
+
EnrichedSpans.INLINE_CODE -> inlineStyles?.removeStyle(EnrichedSpans.INLINE_CODE, start, end)
|
|
395
|
+
EnrichedSpans.H1 -> paragraphStyles?.removeStyle(EnrichedSpans.H1, start, end)
|
|
396
|
+
EnrichedSpans.H2 -> paragraphStyles?.removeStyle(EnrichedSpans.H2, start, end)
|
|
397
|
+
EnrichedSpans.H3 -> paragraphStyles?.removeStyle(EnrichedSpans.H3, start, end)
|
|
398
|
+
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.removeStyle(EnrichedSpans.CODE_BLOCK, start, end)
|
|
399
|
+
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.removeStyle(EnrichedSpans.BLOCK_QUOTE, start, end)
|
|
400
|
+
EnrichedSpans.ORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.ORDERED_LIST, start, end)
|
|
401
|
+
EnrichedSpans.UNORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.UNORDERED_LIST, start, end)
|
|
402
|
+
EnrichedSpans.LINK -> parametrizedStyles?.removeStyle(EnrichedSpans.LINK, start, end)
|
|
403
|
+
EnrichedSpans.IMAGE -> parametrizedStyles?.removeStyle(EnrichedSpans.IMAGE, start, end)
|
|
404
|
+
EnrichedSpans.MENTION -> parametrizedStyles?.removeStyle(EnrichedSpans.MENTION, start, end)
|
|
405
|
+
else -> false
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return removed == true
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private fun getTargetRange(name: String): Pair<Int, Int> {
|
|
412
|
+
val result = when (name) {
|
|
413
|
+
EnrichedSpans.BOLD -> inlineStyles?.getStyleRange()
|
|
414
|
+
EnrichedSpans.ITALIC -> inlineStyles?.getStyleRange()
|
|
415
|
+
EnrichedSpans.UNDERLINE -> inlineStyles?.getStyleRange()
|
|
416
|
+
EnrichedSpans.STRIKETHROUGH -> inlineStyles?.getStyleRange()
|
|
417
|
+
EnrichedSpans.INLINE_CODE -> inlineStyles?.getStyleRange()
|
|
418
|
+
EnrichedSpans.H1 -> paragraphStyles?.getStyleRange()
|
|
419
|
+
EnrichedSpans.H2 -> paragraphStyles?.getStyleRange()
|
|
420
|
+
EnrichedSpans.H3 -> paragraphStyles?.getStyleRange()
|
|
421
|
+
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.getStyleRange()
|
|
422
|
+
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.getStyleRange()
|
|
423
|
+
EnrichedSpans.ORDERED_LIST -> listStyles?.getStyleRange()
|
|
424
|
+
EnrichedSpans.UNORDERED_LIST -> listStyles?.getStyleRange()
|
|
425
|
+
EnrichedSpans.LINK -> parametrizedStyles?.getStyleRange()
|
|
426
|
+
EnrichedSpans.IMAGE -> parametrizedStyles?.getStyleRange()
|
|
427
|
+
EnrichedSpans.MENTION -> parametrizedStyles?.getStyleRange()
|
|
428
|
+
else -> Pair(0, 0)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return result ?: Pair(0, 0)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
private fun verifyStyle(name: String): Boolean {
|
|
435
|
+
val mergingConfig = EnrichedSpans.mergingConfig[name] ?: return true
|
|
436
|
+
val conflictingStyles = mergingConfig.conflictingStyles
|
|
437
|
+
val blockingStyles = mergingConfig.blockingStyles
|
|
438
|
+
val isEnabling = spanState?.getStart(name) == null
|
|
439
|
+
if (!isEnabling) return true
|
|
440
|
+
|
|
441
|
+
for (style in blockingStyles) {
|
|
442
|
+
if (spanState?.getStart(style) != null) {
|
|
443
|
+
spanState.setStart(name, null)
|
|
444
|
+
return false
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
for (style in conflictingStyles) {
|
|
449
|
+
val start = selection?.start ?: 0
|
|
450
|
+
val end = selection?.end ?: 0
|
|
451
|
+
val lengthBefore = text?.length ?: 0
|
|
452
|
+
|
|
453
|
+
isSettingValue = true
|
|
454
|
+
val targetRange = getTargetRange(name)
|
|
455
|
+
val removed = removeStyle(style, targetRange.first, targetRange.second)
|
|
456
|
+
if (removed) {
|
|
457
|
+
spanState?.setStart(style, null)
|
|
458
|
+
}
|
|
459
|
+
isSettingValue = false
|
|
460
|
+
|
|
461
|
+
val lengthAfter = text?.length ?: 0
|
|
462
|
+
val charactersRemoved = lengthBefore - lengthAfter
|
|
463
|
+
val finalEnd = if (charactersRemoved > 0) {
|
|
464
|
+
(end - charactersRemoved).coerceAtLeast(0)
|
|
465
|
+
} else {
|
|
466
|
+
end
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
val finalStart = start.coerceAtLeast(0).coerceAtMost(finalEnd)
|
|
470
|
+
selection?.onSelection(finalStart, finalEnd)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return true
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
private fun addSpanWatcher(watcher: EnrichedSpanWatcher) {
|
|
477
|
+
val spannable = text as Spannable
|
|
478
|
+
spannable.setSpan(watcher, 0, spannable.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
|
|
479
|
+
spanWatcher = watcher
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
fun verifyAndToggleStyle(name: String) {
|
|
483
|
+
val isValid = verifyStyle(name)
|
|
484
|
+
if (!isValid) return
|
|
485
|
+
|
|
486
|
+
toggleStyle(name)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
fun addLink(start: Int, end: Int, text: String, url: String) {
|
|
490
|
+
val isValid = verifyStyle(EnrichedSpans.LINK)
|
|
491
|
+
if (!isValid) return
|
|
492
|
+
|
|
493
|
+
parametrizedStyles?.setLinkSpan(start, end, text, url)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
fun addImage(src: String) {
|
|
497
|
+
val isValid = verifyStyle(EnrichedSpans.IMAGE)
|
|
498
|
+
if (!isValid) return
|
|
499
|
+
|
|
500
|
+
parametrizedStyles?.setImageSpan(src)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
fun startMention(indicator: String) {
|
|
504
|
+
val isValid = verifyStyle(EnrichedSpans.MENTION)
|
|
505
|
+
if (!isValid) return
|
|
506
|
+
|
|
507
|
+
parametrizedStyles?.startMention(indicator)
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
fun addMention(indicator: String, text: String, attributes: Map<String, String>) {
|
|
511
|
+
val isValid = verifyStyle(EnrichedSpans.MENTION)
|
|
512
|
+
if (!isValid) return
|
|
513
|
+
|
|
514
|
+
parametrizedStyles?.setMentionSpan(text, indicator, attributes)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
override fun onAttachedToWindow() {
|
|
518
|
+
super.onAttachedToWindow()
|
|
519
|
+
|
|
520
|
+
if (autoFocus && !didAttachToWindow) {
|
|
521
|
+
requestFocusProgrammatically()
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
didAttachToWindow = true
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
override fun onDetachedFromWindow() {
|
|
528
|
+
layoutManager.cleanup()
|
|
529
|
+
super.onDetachedFromWindow()
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
companion object {
|
|
533
|
+
const val CLIPBOARD_TAG = "react-native-enriched-clipboard"
|
|
534
|
+
}
|
|
535
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
package com.swmansion.enriched
|
|
2
|
+
|
|
3
|
+
import android.text.Editable
|
|
4
|
+
import android.text.StaticLayout
|
|
5
|
+
import com.facebook.react.bridge.Arguments
|
|
6
|
+
import com.facebook.react.uimanager.PixelUtil
|
|
7
|
+
|
|
8
|
+
class EnrichedTextInputViewLayoutManager(private val view: EnrichedTextInputView) {
|
|
9
|
+
private var cachedSize: Pair<Float, Float> = Pair(0f, 0f)
|
|
10
|
+
private var cachedYogaWidth: Float = 0f
|
|
11
|
+
private var forceHeightRecalculationCounter: Int = 0
|
|
12
|
+
|
|
13
|
+
fun cleanup() {
|
|
14
|
+
forceHeightRecalculationCounter = 0
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Update shadow node's state in order to recalculate layout
|
|
18
|
+
fun invalidateLayout(text: Editable?) {
|
|
19
|
+
measureSize(text ?: "")
|
|
20
|
+
|
|
21
|
+
val counter = forceHeightRecalculationCounter
|
|
22
|
+
forceHeightRecalculationCounter++
|
|
23
|
+
val state = Arguments.createMap()
|
|
24
|
+
state.putInt("forceHeightRecalculationCounter", counter)
|
|
25
|
+
view.stateWrapper?.updateState(state)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fun getMeasuredSize(maxWidth: Float): Pair<Float, Float> {
|
|
29
|
+
if (maxWidth == cachedYogaWidth) {
|
|
30
|
+
return cachedSize
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
val text = view.text ?: ""
|
|
34
|
+
val result = measureAndCacheSize(text, maxWidth)
|
|
35
|
+
cachedYogaWidth = maxWidth
|
|
36
|
+
return result
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fun measureSize(text: CharSequence): Pair<Float, Float> {
|
|
40
|
+
return measureAndCacheSize(text, cachedYogaWidth)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private fun measureAndCacheSize(text: CharSequence, maxWidth: Float): Pair<Float, Float> {
|
|
44
|
+
val result = measureSize(text, maxWidth)
|
|
45
|
+
cachedSize = result
|
|
46
|
+
return result
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private fun measureSize(text: CharSequence, maxWidth: Float): Pair<Float, Float> {
|
|
50
|
+
val paint = view.paint
|
|
51
|
+
val textLength = text.length
|
|
52
|
+
|
|
53
|
+
val staticLayout = StaticLayout.Builder
|
|
54
|
+
.obtain(text, 0, textLength, paint, maxWidth.toInt())
|
|
55
|
+
.setIncludePad(true)
|
|
56
|
+
.setLineSpacing(0f, 1f)
|
|
57
|
+
.build()
|
|
58
|
+
|
|
59
|
+
val heightInSP = PixelUtil.toDIPFromPixel(staticLayout.height.toFloat())
|
|
60
|
+
val widthInSP = PixelUtil.toDIPFromPixel(maxWidth)
|
|
61
|
+
|
|
62
|
+
return Pair(widthInSP, heightInSP)
|
|
63
|
+
}
|
|
64
|
+
}
|