@momo-kits/calculator-keyboard 0.150.2-beta.2 → 0.150.2-beta.20-sp.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/README.md +45 -3
- package/android/src/main/java/com/calculatorkeyboard/CalculatorKeyboardPackage.kt +1 -1
- package/android/src/main/java/com/calculatorkeyboard/CustomKeyboardView.kt +117 -46
- package/android/src/main/java/com/calculatorkeyboard/KeyboardOverplayHost.kt +232 -0
- package/android/src/main/java/com/calculatorkeyboard/RCTInputCalculator.kt +115 -104
- package/ios/CalculatorKeyboardView.h +30 -0
- package/ios/CalculatorKeyboardView.mm +231 -0
- package/ios/NativeInputCalculator.h +11 -0
- package/ios/NativeInputCalculator.mm +200 -0
- package/package.json +75 -183
- package/react-native-calculator-keyboard.podspec +4 -3
- package/src/NativeInputCalculatorNativeComponent.ts +56 -0
- package/src/index.tsx +71 -35
- package/ios/CalculatorKeyboardView.swift +0 -115
- package/ios/InputCalculator-Bridging-Header.h +0 -23
- package/ios/InputCalculator.m +0 -79
- package/ios/InputCalculator.swift +0 -138
- package/ios/extension.swift +0 -23
- package/lib/commonjs/index.js +0 -48
- package/lib/commonjs/index.js.map +0 -1
- package/lib/module/index.js +0 -44
- package/lib/module/index.js.map +0 -1
- package/lib/typescript/commonjs/package.json +0 -1
- package/lib/typescript/commonjs/src/index.d.ts +0 -13
- package/lib/typescript/commonjs/src/index.d.ts.map +0 -1
- package/lib/typescript/module/package.json +0 -1
- package/lib/typescript/module/src/index.d.ts +0 -13
- package/lib/typescript/module/src/index.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -10,15 +10,57 @@ npm install react-native-calculator-keyboard
|
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
|
-
|
|
14
13
|
```js
|
|
15
|
-
import
|
|
14
|
+
import InputCalculator from '@momo-kits/calculator-keyboard';
|
|
16
15
|
|
|
17
16
|
// ...
|
|
18
17
|
|
|
19
|
-
<
|
|
18
|
+
<InputCalculator
|
|
19
|
+
mode="NumDefault"
|
|
20
|
+
customKeyText="Next"
|
|
21
|
+
onCustomKeyEvent={() => console.log('Custom key pressed')}
|
|
22
|
+
/>;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Requirements
|
|
26
|
+
|
|
27
|
+
**React Native 0.80+** with **Fabric (New Architecture) enabled**.
|
|
28
|
+
|
|
29
|
+
This library is **pure Fabric** implementation with:
|
|
30
|
+
|
|
31
|
+
- ✅ Zero RCTBridge dependencies
|
|
32
|
+
- ✅ Native C++ ComponentView on iOS
|
|
33
|
+
- ✅ Fabric ViewManager with codegen delegates on Android
|
|
34
|
+
- ✅ All Props, Events, Commands auto-generated by codegen
|
|
35
|
+
- ❌ No Paper (old architecture) support
|
|
36
|
+
|
|
37
|
+
### Android Setup
|
|
38
|
+
|
|
39
|
+
Add to your `gradle.properties`:
|
|
40
|
+
|
|
41
|
+
```properties
|
|
42
|
+
newArchEnabled=true
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Then rebuild:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
cd android && ./gradlew clean && cd ..
|
|
49
|
+
npx react-native run-android
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### iOS Setup
|
|
53
|
+
|
|
54
|
+
**Required**: Set the environment variable before installing pods:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
cd ios
|
|
58
|
+
RCT_NEW_ARCH_ENABLED=1 pod install
|
|
59
|
+
cd ..
|
|
60
|
+
npx react-native run-ios
|
|
20
61
|
```
|
|
21
62
|
|
|
63
|
+
**Note**: This library uses Fabric ComponentView (`.mm` files) and will not work without `RCT_NEW_ARCH_ENABLED=1`.
|
|
22
64
|
|
|
23
65
|
## Contributing
|
|
24
66
|
|
|
@@ -7,55 +7,71 @@ import android.graphics.drawable.GradientDrawable
|
|
|
7
7
|
import android.view.Gravity
|
|
8
8
|
import android.widget.Button
|
|
9
9
|
import android.widget.ImageButton
|
|
10
|
-
import androidx.appcompat.app.AppCompatActivity
|
|
11
10
|
import androidx.constraintlayout.widget.ConstraintLayout
|
|
12
|
-
import androidx.core.graphics.ColorUtils
|
|
13
11
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
14
12
|
import org.mariuszgromada.math.mxparser.Expression
|
|
15
13
|
import androidx.core.graphics.toColorInt
|
|
16
|
-
import com.
|
|
17
|
-
|
|
14
|
+
import com.calculatorkeyboard.RCTInputCalculator.Companion.calculatorHeight
|
|
15
|
+
import com.facebook.react.bridge.Arguments
|
|
16
|
+
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
18
17
|
|
|
19
18
|
@SuppressLint("SetTextI18n", "ViewConstructor")
|
|
20
19
|
class CustomKeyboardView(
|
|
21
20
|
context: ThemedReactContext,
|
|
22
21
|
private val editText: CalculatorEditText
|
|
23
22
|
) : ConstraintLayout(context) {
|
|
24
|
-
private val
|
|
25
|
-
listOf("
|
|
26
|
-
listOf("
|
|
27
|
-
listOf("
|
|
28
|
-
listOf("
|
|
29
|
-
|
|
23
|
+
private val numWithCTAKeys = listOf(
|
|
24
|
+
listOf("1", "2", "3", "÷", "back"),
|
|
25
|
+
listOf("4", "5", "6", "×", "="),
|
|
26
|
+
listOf("7", "8", "9", "-", "Tiếp"),
|
|
27
|
+
listOf("000", "0", "+")
|
|
28
|
+
)
|
|
29
|
+
private val defaultKeys = listOf(
|
|
30
|
+
listOf("1", "2", "3", "÷", "AC"),
|
|
31
|
+
listOf("4", "5", "6", "×", "back"),
|
|
32
|
+
listOf("7", "8", "9", "-", "="),
|
|
33
|
+
listOf("000", "0", "+")
|
|
30
34
|
)
|
|
31
|
-
private val specialKeys = listOf("=", "-", "×", "÷", "
|
|
35
|
+
private val specialKeys = listOf("=", "-", "×", "÷", "back", "+", "AC")
|
|
32
36
|
private val separatorWidth = 8f
|
|
33
37
|
private var specialButtonColor: Int = "#D8D8D8".toColorInt()
|
|
34
38
|
|
|
35
|
-
|
|
36
|
-
val activity = context.currentActivity as? AppCompatActivity
|
|
37
|
-
if (activity != null) {
|
|
38
|
-
val displayMetrics = resources.displayMetrics
|
|
39
|
-
val widthButton = (displayMetrics.widthPixels - separatorWidth * 2 - 3 * separatorWidth) / 4f
|
|
40
|
-
val heightButton = (290.dpToPx() - separatorWidth * 2 - 4 * separatorWidth) / 5
|
|
39
|
+
private var keyboardMode: String = "NumDefault"
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
private var customKeyButton: Button? = null
|
|
42
|
+
private var customKeyButtonBackground: Int = "#D8D8D8".toColorInt()
|
|
43
|
+
private var customKeyButtonTextColor: Int = Color.BLACK
|
|
44
|
+
private var customKeyButtonState: String = "default"
|
|
44
45
|
|
|
46
|
+
init {
|
|
47
|
+
isClickable = false
|
|
48
|
+
isFocusable = false
|
|
49
|
+
isFocusableInTouchMode = false
|
|
50
|
+
clipToPadding = false
|
|
51
|
+
clipChildren = false
|
|
45
52
|
}
|
|
46
53
|
|
|
47
|
-
private fun renderUI(
|
|
54
|
+
private fun renderUI() {
|
|
55
|
+
val displayMetrics = resources.displayMetrics
|
|
56
|
+
val buttonWidth = (displayMetrics.widthPixels - separatorWidth * 2 - 4 * separatorWidth) / 5f
|
|
57
|
+
val buttonHeight = (calculatorHeight - separatorWidth * 2 - 3 * separatorWidth) / 4
|
|
58
|
+
|
|
48
59
|
var yOffset = separatorWidth
|
|
49
|
-
|
|
60
|
+
val keys = if (keyboardMode == "NumWithCTA") numWithCTAKeys else defaultKeys
|
|
61
|
+
for ((rowIndex, row) in keys.withIndex()) {
|
|
50
62
|
var xOffset = separatorWidth
|
|
51
|
-
for ((
|
|
63
|
+
for ((colIndex, key) in row.withIndex()) {
|
|
64
|
+
val isMainKey = rowIndex == 2 && colIndex == 4
|
|
65
|
+
val isMainCTAKey = isMainKey && keyboardMode == "NumWithCTA"
|
|
52
66
|
val width = if (key == "000") buttonWidth * 2 + separatorWidth else buttonWidth
|
|
53
|
-
val height = if (
|
|
67
|
+
val height = if (isMainKey) buttonHeight * 2 + separatorWidth else buttonHeight
|
|
54
68
|
|
|
55
69
|
val button = if (key == "back") {
|
|
56
70
|
createImageButton(key, xOffset, yOffset, buttonWidth.toInt(), buttonHeight.toInt())
|
|
57
71
|
} else {
|
|
58
|
-
createButton(key, xOffset, yOffset, width.toInt(), height.toInt())
|
|
72
|
+
createButton(key, xOffset, yOffset, width.toInt(), height.toInt(), isMainKey, isMainCTAKey).also { b ->
|
|
73
|
+
if (isMainCTAKey) customKeyButton = b
|
|
74
|
+
}
|
|
59
75
|
}
|
|
60
76
|
|
|
61
77
|
addView(button)
|
|
@@ -72,8 +88,9 @@ class CustomKeyboardView(
|
|
|
72
88
|
yOffset: Float,
|
|
73
89
|
buttonWidth: Int,
|
|
74
90
|
buttonHeight: Int,
|
|
91
|
+
isMainKey: Boolean,
|
|
92
|
+
isMainCTAKey: Boolean
|
|
75
93
|
): Button {
|
|
76
|
-
val specialKeys = listOf("=", "-", "×", "÷", "AC", "back", "+")
|
|
77
94
|
return Button(context).apply {
|
|
78
95
|
val shapeInit = GradientDrawable().apply {
|
|
79
96
|
shape = GradientDrawable.RECTANGLE
|
|
@@ -85,9 +102,11 @@ class CustomKeyboardView(
|
|
|
85
102
|
background = shapeInit
|
|
86
103
|
text = key
|
|
87
104
|
setTypeface(typeface)
|
|
88
|
-
textSize = 24.toFloat()
|
|
105
|
+
textSize = (if (isMainCTAKey) 18 else 24).toFloat()
|
|
89
106
|
setTextColor(Color.BLACK)
|
|
90
107
|
stateListAnimator = null
|
|
108
|
+
maxLines = 1
|
|
109
|
+
isAllCaps = false
|
|
91
110
|
layoutParams = LayoutParams(
|
|
92
111
|
buttonWidth,
|
|
93
112
|
buttonHeight
|
|
@@ -95,7 +114,7 @@ class CustomKeyboardView(
|
|
|
95
114
|
constrainedWidth = false
|
|
96
115
|
}
|
|
97
116
|
|
|
98
|
-
if (specialKeys.contains(key)) {
|
|
117
|
+
if (specialKeys.contains(key) || isMainKey) {
|
|
99
118
|
background = GradientDrawable().apply {
|
|
100
119
|
shape = GradientDrawable.RECTANGLE
|
|
101
120
|
cornerRadius = 24f
|
|
@@ -104,10 +123,13 @@ class CustomKeyboardView(
|
|
|
104
123
|
setTextColor(Color.BLACK)
|
|
105
124
|
}
|
|
106
125
|
|
|
126
|
+
isClickable = true
|
|
127
|
+
isFocusable = false
|
|
128
|
+
isFocusableInTouchMode = false
|
|
107
129
|
|
|
108
130
|
translationX = xOffset.toInt().toFloat()
|
|
109
131
|
translationY = yOffset.toInt().toFloat()
|
|
110
|
-
setOnClickListener { onKeyPress(key) }
|
|
132
|
+
setOnClickListener { onKeyPress(key, isMainCTAKey) }
|
|
111
133
|
}
|
|
112
134
|
}
|
|
113
135
|
|
|
@@ -132,11 +154,16 @@ class CustomKeyboardView(
|
|
|
132
154
|
).apply {
|
|
133
155
|
constrainedWidth = false
|
|
134
156
|
}
|
|
157
|
+
|
|
158
|
+
isClickable = true
|
|
159
|
+
isFocusable = false
|
|
160
|
+
isFocusableInTouchMode = false
|
|
161
|
+
|
|
135
162
|
translationX = xOffset
|
|
136
163
|
translationY = yOffset
|
|
137
164
|
setImageResource(android.R.drawable.ic_input_delete)
|
|
138
165
|
setImageTintList(ColorStateList.valueOf(Color.BLACK))
|
|
139
|
-
setOnClickListener { onKeyPress(key) }
|
|
166
|
+
setOnClickListener { onKeyPress(key, false) }
|
|
140
167
|
}
|
|
141
168
|
}
|
|
142
169
|
|
|
@@ -172,25 +199,19 @@ class CustomKeyboardView(
|
|
|
172
199
|
}
|
|
173
200
|
}
|
|
174
201
|
|
|
175
|
-
private fun onKeyPress(key: String) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
"back" -> {
|
|
182
|
-
onBackSpace()
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
"=" -> {
|
|
186
|
-
calculateResult()
|
|
187
|
-
}
|
|
202
|
+
private fun onKeyPress(key: String, isMainCTAKey: Boolean) {
|
|
203
|
+
if (isMainCTAKey) {
|
|
204
|
+
emitCustomKey()
|
|
205
|
+
return
|
|
206
|
+
}
|
|
188
207
|
|
|
208
|
+
emitKeyPress(key)
|
|
209
|
+
when (key) {
|
|
210
|
+
"AC" -> clearText()
|
|
211
|
+
"back" -> onBackSpace()
|
|
212
|
+
"=" -> calculateResult()
|
|
189
213
|
"×", "+", "-", "÷" -> keyDidPress(" $key ")
|
|
190
|
-
|
|
191
|
-
else -> {
|
|
192
|
-
editText.text?.insert(editText.selectionStart, key)
|
|
193
|
-
}
|
|
214
|
+
else -> editText.text?.insert(editText.selectionStart, key)
|
|
194
215
|
}
|
|
195
216
|
}
|
|
196
217
|
|
|
@@ -239,4 +260,54 @@ class CustomKeyboardView(
|
|
|
239
260
|
}
|
|
240
261
|
}
|
|
241
262
|
|
|
263
|
+
private fun emitKeyPress(key: String) {
|
|
264
|
+
val reactContext = context as ThemedReactContext
|
|
265
|
+
val params = Arguments.createMap().apply {
|
|
266
|
+
putString("key", key)
|
|
267
|
+
}
|
|
268
|
+
reactContext.getJSModule(RCTEventEmitter::class.java)
|
|
269
|
+
.receiveEvent(editText.id, "onKeyPress", params)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private fun emitCustomKey() {
|
|
273
|
+
val reactContext = context as ThemedReactContext
|
|
274
|
+
reactContext.getJSModule(RCTEventEmitter::class.java)
|
|
275
|
+
.receiveEvent(editText.id, "onCustomKeyEvent", null)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
fun setCustomKeyText(text: String) {
|
|
279
|
+
customKeyButton?.text = text
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
fun setMode(mode: String) {
|
|
283
|
+
keyboardMode = mode
|
|
284
|
+
renderUI()
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
fun setCustomKeyBackground(background: Int) {
|
|
288
|
+
customKeyButtonBackground = background
|
|
289
|
+
updateCustomKeyUI(background, customKeyButtonTextColor)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
fun setCustomKeyTextColor(textColor: Int) {
|
|
293
|
+
customKeyButtonTextColor = textColor
|
|
294
|
+
updateCustomKeyUI(customKeyButtonBackground, textColor)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
fun setCustomKeyState(state: String) {
|
|
299
|
+
customKeyButtonState = state
|
|
300
|
+
customKeyButton?.isEnabled = state != "disable"
|
|
301
|
+
updateCustomKeyUI(customKeyButtonBackground, customKeyButtonTextColor)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private fun updateCustomKeyUI(background: Int, textColor: Int){
|
|
305
|
+
customKeyButton?.background = GradientDrawable().apply {
|
|
306
|
+
shape = GradientDrawable.RECTANGLE
|
|
307
|
+
cornerRadius = 24f
|
|
308
|
+
setColor(background)
|
|
309
|
+
}
|
|
310
|
+
customKeyButton?.setTextColor(textColor)
|
|
311
|
+
}
|
|
312
|
+
|
|
242
313
|
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
package com.calculatorkeyboard
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.app.Activity
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.content.ContextWrapper
|
|
7
|
+
import android.util.Log
|
|
8
|
+
import android.view.Gravity
|
|
9
|
+
import android.view.MotionEvent
|
|
10
|
+
import android.view.View
|
|
11
|
+
import android.view.ViewGroup
|
|
12
|
+
import android.widget.FrameLayout
|
|
13
|
+
import androidx.core.view.ViewCompat
|
|
14
|
+
import androidx.core.view.WindowInsetsCompat
|
|
15
|
+
import androidx.core.view.doOnLayout
|
|
16
|
+
import androidx.core.view.updatePadding
|
|
17
|
+
import java.lang.ref.WeakReference
|
|
18
|
+
import androidx.core.view.isNotEmpty
|
|
19
|
+
|
|
20
|
+
internal class KeyboardOverlayHost {
|
|
21
|
+
|
|
22
|
+
private val tag = "KeyboardOverlayHost"
|
|
23
|
+
|
|
24
|
+
private var localRootRef: WeakReference<ViewGroup>? = null
|
|
25
|
+
private var containerRef: WeakReference<FrameLayout>? = null
|
|
26
|
+
private var paddingTargetRef: WeakReference<View>? = null
|
|
27
|
+
|
|
28
|
+
private var originalBottomPadding: Int = 0
|
|
29
|
+
private var isShowing = false
|
|
30
|
+
|
|
31
|
+
private class OverlayContainer(ctx: Context) : FrameLayout(ctx) {
|
|
32
|
+
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean = false
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fun show(anchorView: View, keyboardView: View, heightPx: Int) {
|
|
36
|
+
val localRoot = findLocalRoot(anchorView) ?: run {
|
|
37
|
+
Log.w(tag, "show: cannot find local root from anchorView")
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
val container = ensureContainer(localRoot, anchorView.context) ?: return
|
|
41
|
+
|
|
42
|
+
val paddingTarget = findPaddingTargetWithin(anchorView, localRoot)
|
|
43
|
+
paddingTargetRef = WeakReference(paddingTarget)
|
|
44
|
+
localRootRef = WeakReference(localRoot)
|
|
45
|
+
|
|
46
|
+
val bottomInset = (ViewCompat.getRootWindowInsets(container)
|
|
47
|
+
?.getInsets(WindowInsetsCompat.Type.systemBars())?.bottom) ?: 0
|
|
48
|
+
|
|
49
|
+
val lp = FrameLayout.LayoutParams(
|
|
50
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
51
|
+
heightPx + bottomInset
|
|
52
|
+
).apply { gravity = Gravity.BOTTOM }
|
|
53
|
+
|
|
54
|
+
if (keyboardView.parent !== container) {
|
|
55
|
+
container.removeAllViews()
|
|
56
|
+
container.addView(keyboardView, lp)
|
|
57
|
+
} else {
|
|
58
|
+
keyboardView.layoutParams = lp
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
keyboardView.isClickable = true
|
|
62
|
+
keyboardView.isFocusable = false
|
|
63
|
+
keyboardView.isFocusableInTouchMode = false
|
|
64
|
+
keyboardView.visibility = View.VISIBLE
|
|
65
|
+
|
|
66
|
+
if (!isShowing) originalBottomPadding = paddingTarget.paddingBottom
|
|
67
|
+
paddingTarget.updatePadding(bottom = heightPx)
|
|
68
|
+
|
|
69
|
+
container.visibility = View.VISIBLE
|
|
70
|
+
container.bringToFront()
|
|
71
|
+
container.translationZ = 10000f
|
|
72
|
+
container.elevation = 10000f
|
|
73
|
+
localRoot.post { container.bringToFront() }
|
|
74
|
+
|
|
75
|
+
keyboardView.animate().cancel()
|
|
76
|
+
keyboardView.post {
|
|
77
|
+
container.bringToFront()
|
|
78
|
+
keyboardView.doOnLayout { child ->
|
|
79
|
+
val h = child.height.takeIf { it > 0 } ?: heightPx
|
|
80
|
+
child.translationY = h.toFloat()
|
|
81
|
+
child.animate()
|
|
82
|
+
.translationY(0f)
|
|
83
|
+
.setDuration(250L)
|
|
84
|
+
.withStartAction {
|
|
85
|
+
isShowing = true
|
|
86
|
+
child.setLayerType(View.LAYER_TYPE_HARDWARE, null)
|
|
87
|
+
container.bringToFront()
|
|
88
|
+
}
|
|
89
|
+
.withEndAction {
|
|
90
|
+
child.setLayerType(View.LAYER_TYPE_NONE, null)
|
|
91
|
+
}
|
|
92
|
+
.start()
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fun hide() {
|
|
98
|
+
val container = containerRef?.get() ?: return
|
|
99
|
+
val localRoot = localRootRef?.get() ?: return
|
|
100
|
+
val paddingTarget = paddingTargetRef?.get() ?: localRoot
|
|
101
|
+
|
|
102
|
+
val child = if (container.isNotEmpty())
|
|
103
|
+
container.getChildAt(container.childCount - 1) else null
|
|
104
|
+
|
|
105
|
+
if (child == null) {
|
|
106
|
+
paddingTarget.updatePadding(bottom = originalBottomPadding)
|
|
107
|
+
isShowing = false
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
child.animate().cancel()
|
|
112
|
+
val h = child.height.takeIf { it > 0 } ?: (child.measuredHeight.takeIf { it > 0 } ?: 0)
|
|
113
|
+
if (h == 0) {
|
|
114
|
+
container.removeAllViews()
|
|
115
|
+
paddingTarget.updatePadding(bottom = originalBottomPadding)
|
|
116
|
+
isShowing = false
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
child.animate()
|
|
121
|
+
.translationY(h.toFloat())
|
|
122
|
+
.setDuration(250L)
|
|
123
|
+
.withEndAction {
|
|
124
|
+
container.removeAllViews()
|
|
125
|
+
paddingTarget.updatePadding(bottom = originalBottomPadding)
|
|
126
|
+
isShowing = false
|
|
127
|
+
}
|
|
128
|
+
.start()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
fun detach() {
|
|
132
|
+
containerRef?.get()?.let { (it.parent as? ViewGroup)?.removeView(it) }
|
|
133
|
+
containerRef = null
|
|
134
|
+
localRootRef = null
|
|
135
|
+
paddingTargetRef = null
|
|
136
|
+
isShowing = false
|
|
137
|
+
originalBottomPadding = 0
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private fun findLocalRoot(anchor: View): ViewGroup? {
|
|
141
|
+
var cur: View? = anchor
|
|
142
|
+
|
|
143
|
+
while (cur != null) {
|
|
144
|
+
if (cur is ViewGroup && isReactRoot(cur)) {
|
|
145
|
+
return cur
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
val parent = cur.parent
|
|
149
|
+
if (parent is ViewGroup) {
|
|
150
|
+
val parentName = parent::class.java.simpleName
|
|
151
|
+
if (parentName.contains("FragmentContainerView")) {
|
|
152
|
+
return (cur as? ViewGroup) ?: parent
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
cur = (cur.parent as? View)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return (anchor.rootView as? ViewGroup)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private fun isReactRoot(v: View): Boolean {
|
|
163
|
+
val n = v::class.java.simpleName
|
|
164
|
+
return n.contains("ReactRootView") ||
|
|
165
|
+
n.contains("RNGestureHandlerEnabledRootView") ||
|
|
166
|
+
(n.contains("React") && n.contains("Root"))
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@SuppressLint("ClickableViewAccessibility")
|
|
170
|
+
private fun ensureContainer(localRoot: ViewGroup, ctx: Context): FrameLayout? {
|
|
171
|
+
var container = containerRef?.get()
|
|
172
|
+
|
|
173
|
+
if (container == null || container.parent !== localRoot) {
|
|
174
|
+
container?.let { (it.parent as? ViewGroup)?.removeView(it) }
|
|
175
|
+
|
|
176
|
+
container = OverlayContainer(ctx).apply {
|
|
177
|
+
layoutParams = ViewGroup.LayoutParams(
|
|
178
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
179
|
+
ViewGroup.LayoutParams.MATCH_PARENT
|
|
180
|
+
)
|
|
181
|
+
isClickable = false
|
|
182
|
+
isFocusable = false
|
|
183
|
+
elevation = 10000f
|
|
184
|
+
translationZ = 10000f
|
|
185
|
+
visibility = View.VISIBLE
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
localRoot.addView(container)
|
|
189
|
+
container.bringToFront()
|
|
190
|
+
localRoot.requestLayout()
|
|
191
|
+
containerRef = WeakReference(container)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return container
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private fun findPaddingTargetWithin(anchor: View, localRoot: ViewGroup): View {
|
|
198
|
+
fun dfs(g: ViewGroup): View? {
|
|
199
|
+
if (isReactRoot(g)) return g
|
|
200
|
+
for (i in 0 until g.childCount) {
|
|
201
|
+
val c = g.getChildAt(i)
|
|
202
|
+
if (c is ViewGroup) {
|
|
203
|
+
val hit = dfs(c)
|
|
204
|
+
if (hit != null) return hit
|
|
205
|
+
} else if (isReactRoot(c)) {
|
|
206
|
+
return c
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return null
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
dfs(localRoot)?.let { return it }
|
|
213
|
+
|
|
214
|
+
var cur: View? = anchor
|
|
215
|
+
var last: View = anchor
|
|
216
|
+
while (cur != null && cur !== localRoot) {
|
|
217
|
+
last = cur
|
|
218
|
+
cur = (cur.parent as? View)
|
|
219
|
+
}
|
|
220
|
+
return last as? ViewGroup ?: localRoot
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
@Suppress("unused")
|
|
224
|
+
private fun findActivityFrom(view: View): Activity? {
|
|
225
|
+
var ctx: Context? = view.context
|
|
226
|
+
while (ctx is ContextWrapper) {
|
|
227
|
+
if (ctx is Activity) return ctx
|
|
228
|
+
ctx = ctx.baseContext
|
|
229
|
+
}
|
|
230
|
+
return null
|
|
231
|
+
}
|
|
232
|
+
}
|