@momo-kits/calculator-keyboard 0.150.2-beta.3 → 0.150.2-beta.31
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 +225 -90
- package/android/src/main/java/com/calculatorkeyboard/Event.kt +36 -0
- package/android/src/main/java/com/calculatorkeyboard/InputCalculatorViewManager.kt +270 -0
- package/android/src/main/java/com/calculatorkeyboard/KeyboardOverplayHost.kt +232 -0
- package/ios/CalculatorKeyboardView.h +30 -0
- package/ios/CalculatorKeyboardView.mm +231 -0
- package/ios/NativeInputCalculator.h +11 -0
- package/ios/NativeInputCalculator.mm +369 -0
- package/package.json +21 -131
- package/react-native-calculator-keyboard.podspec +5 -4
- package/src/InputCalculatorNativeComponent.ts +62 -0
- package/src/index.tsx +104 -31
- package/android/src/main/java/com/calculatorkeyboard/RCTInputCalculator.kt +0 -179
- 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
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
package com.calculatorkeyboard
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.graphics.Color
|
|
6
|
+
import android.graphics.Typeface
|
|
7
|
+
import android.os.Build
|
|
8
|
+
import android.provider.Settings.Global.putString
|
|
9
|
+
import android.text.Editable
|
|
10
|
+
import android.text.TextWatcher
|
|
11
|
+
import android.view.KeyEvent
|
|
12
|
+
import android.view.inputmethod.InputMethodManager
|
|
13
|
+
import androidx.core.graphics.toColorInt
|
|
14
|
+
import androidx.core.view.ViewCompat
|
|
15
|
+
import androidx.core.view.WindowInsetsCompat
|
|
16
|
+
import androidx.core.widget.addTextChangedListener
|
|
17
|
+
import com.facebook.react.bridge.Arguments
|
|
18
|
+
import com.facebook.react.bridge.ReactContext
|
|
19
|
+
import com.facebook.react.bridge.ReadableArray
|
|
20
|
+
import com.facebook.react.bridge.ReadableMap
|
|
21
|
+
import com.facebook.react.bridge.UiThreadUtil
|
|
22
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
23
|
+
import com.facebook.react.uimanager.PixelUtil.dpToPx
|
|
24
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
25
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
26
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
27
|
+
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
28
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
29
|
+
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
30
|
+
import com.facebook.react.viewmanagers.NativeInputCalculatorManagerDelegate
|
|
31
|
+
import com.facebook.react.viewmanagers.NativeInputCalculatorManagerInterface
|
|
32
|
+
import com.facebook.react.views.textinput.ReactEditText
|
|
33
|
+
import com.facebook.react.views.textinput.ReactTextInputManager
|
|
34
|
+
|
|
35
|
+
@ReactModule(name = InputCalculatorViewManager.REACT_CLASS)
|
|
36
|
+
class InputCalculatorViewManager : SimpleViewManager<ReactEditText>(), NativeInputCalculatorManagerInterface<ReactEditText> {
|
|
37
|
+
private val delegate: NativeInputCalculatorManagerDelegate<ReactEditText, InputCalculatorViewManager> =
|
|
38
|
+
NativeInputCalculatorManagerDelegate(this)
|
|
39
|
+
|
|
40
|
+
override fun getDelegate(): ViewManagerDelegate<ReactEditText> = delegate
|
|
41
|
+
|
|
42
|
+
override fun getName() = REACT_CLASS
|
|
43
|
+
|
|
44
|
+
private var keyboardView: CustomKeyboardView? = null
|
|
45
|
+
private lateinit var editText: CalculatorEditText
|
|
46
|
+
private lateinit var imm: InputMethodManager
|
|
47
|
+
|
|
48
|
+
private val overlayHost = KeyboardOverlayHost()
|
|
49
|
+
|
|
50
|
+
private var backListenerAttached = false
|
|
51
|
+
private var overlayShowing = false
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
companion object {
|
|
55
|
+
const val REACT_CLASS = "NativeInputCalculator"
|
|
56
|
+
val calculatorHeight: Int = 240.dpToPx().toInt()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@ReactProp(name = "value")
|
|
60
|
+
override fun setValue(view: ReactEditText, value: String?) {
|
|
61
|
+
UiThreadUtil.runOnUiThread {
|
|
62
|
+
val e = view.editableText ?: run { view.setText(value ?: ""); return@runOnUiThread }
|
|
63
|
+
val newText = value ?: ""
|
|
64
|
+
if (e.toString() == newText) return@runOnUiThread
|
|
65
|
+
|
|
66
|
+
val wasFocused = view.hasFocus()
|
|
67
|
+
val atEnd = wasFocused && view.selectionStart == e.length && view.selectionEnd == e.length
|
|
68
|
+
e.replace(0, e.length, newText)
|
|
69
|
+
if (!wasFocused || atEnd) view.setSelection(newText.length)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
override fun setTextAttributes(view: ReactEditText?, value: ReadableMap?) {
|
|
74
|
+
val fontWeightStr = value?.getString("fontWeight")
|
|
75
|
+
val fontSize = value?.getDouble("fontSize")
|
|
76
|
+
|
|
77
|
+
fontWeightStr?.let { weightStr ->
|
|
78
|
+
val weight = when (weightStr) {
|
|
79
|
+
"100" -> 100
|
|
80
|
+
"200" -> 200
|
|
81
|
+
"300" -> 300
|
|
82
|
+
"400", "normal" -> 400
|
|
83
|
+
"500" -> 500
|
|
84
|
+
"600" -> 600
|
|
85
|
+
"700", "bold" -> 700
|
|
86
|
+
"800" -> 800
|
|
87
|
+
"900" -> 900
|
|
88
|
+
else -> 400
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
92
|
+
view?.typeface = Typeface.create(view.typeface, weight, false)
|
|
93
|
+
} else {
|
|
94
|
+
view?.typeface = if (weight >= 600) {
|
|
95
|
+
Typeface.create(view.typeface, Typeface.BOLD)
|
|
96
|
+
} else {
|
|
97
|
+
Typeface.create(view.typeface, Typeface.NORMAL)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fontSize?.let {
|
|
103
|
+
view?.textSize = it.toFloat()
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@ReactProp(name = "mode")
|
|
109
|
+
override fun setMode(view: ReactEditText, mode: String?) {
|
|
110
|
+
keyboardView?.setMode(mode ?: "NumDefault")
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@ReactProp(name = "customKeyText")
|
|
114
|
+
override fun setCustomKeyText(view: ReactEditText, text: String?) {
|
|
115
|
+
keyboardView?.setCustomKeyText(text ?: "Tiếp")
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@ReactProp(name = "customKeyBackground")
|
|
119
|
+
override fun setCustomKeyBackground(view: ReactEditText, background: String?) {
|
|
120
|
+
keyboardView?.setCustomKeyBackground((background ?: "#d8d8d8").toColorInt())
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@ReactProp(name = "customKeyTextColor")
|
|
124
|
+
override fun setCustomKeyTextColor(view: ReactEditText, textColor: String?) {
|
|
125
|
+
keyboardView?.setCustomKeyTextColor(textColor?.toColorInt() ?: Color.BLACK)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@ReactProp(name = "customKeyState")
|
|
129
|
+
override fun setCustomKeyState(view: ReactEditText, state: String?) {
|
|
130
|
+
keyboardView?.setCustomKeyState(state ?: "default")
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
override fun focus(view: ReactEditText?) {
|
|
134
|
+
UiThreadUtil.runOnUiThread { if (!editText.isFocused) editText.requestFocus() }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
override fun blur(view: ReactEditText?) {
|
|
138
|
+
UiThreadUtil.runOnUiThread { if (editText.isFocused) editText.clearFocus() }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@SuppressLint("ClickableViewAccessibility")
|
|
142
|
+
override fun createViewInstance(context: ThemedReactContext): ReactEditText {
|
|
143
|
+
imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
144
|
+
|
|
145
|
+
editText = CalculatorEditText(context).apply {
|
|
146
|
+
showSoftInputOnFocus = false
|
|
147
|
+
isFocusableInTouchMode = true
|
|
148
|
+
setOnTouchListener { v, event ->
|
|
149
|
+
if (event.action == android.view.MotionEvent.ACTION_DOWN) {
|
|
150
|
+
showSoftInputOnFocus = false
|
|
151
|
+
ViewCompat.getWindowInsetsController(this)?.hide(WindowInsetsCompat.Type.ime())
|
|
152
|
+
imm.hideSoftInputFromWindow(windowToken, 0)
|
|
153
|
+
|
|
154
|
+
if (!isFocused) requestFocus()
|
|
155
|
+
|
|
156
|
+
val kb = keyboardView
|
|
157
|
+
if (kb != null && !overlayShowing) {
|
|
158
|
+
overlayHost.show(
|
|
159
|
+
anchorView = this,
|
|
160
|
+
keyboardView = kb,
|
|
161
|
+
heightPx = calculatorHeight
|
|
162
|
+
)
|
|
163
|
+
overlayShowing = true
|
|
164
|
+
}
|
|
165
|
+
return@setOnTouchListener true
|
|
166
|
+
}
|
|
167
|
+
false
|
|
168
|
+
}
|
|
169
|
+
addTextChangedListener(object : TextWatcher{
|
|
170
|
+
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
|
171
|
+
|
|
172
|
+
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
|
173
|
+
|
|
174
|
+
override fun afterTextChanged(s: Editable?) {
|
|
175
|
+
emitOnChange(context, s.toString())
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
keyboardView = CustomKeyboardView(context, editText).apply {
|
|
182
|
+
setBackgroundColor("#f2f2f6".toColorInt())
|
|
183
|
+
elevation = 24f
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!backListenerAttached) {
|
|
187
|
+
editText.setOnKeyListener { v, keyCode, event ->
|
|
188
|
+
if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP && v.hasFocus()) {
|
|
189
|
+
v.clearFocus()
|
|
190
|
+
true
|
|
191
|
+
} else false
|
|
192
|
+
}
|
|
193
|
+
backListenerAttached = true
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
editText.onFocusListener = object : CalculatorEditText.OnFocusChangeListener {
|
|
197
|
+
override fun onFocusChange(view: CalculatorEditText, hasFocus: Boolean) {
|
|
198
|
+
UiThreadUtil.runOnUiThread {
|
|
199
|
+
if (hasFocus) {
|
|
200
|
+
disableSystemImeFor(view)
|
|
201
|
+
if (!overlayShowing) {
|
|
202
|
+
val kb = keyboardView ?: return@runOnUiThread
|
|
203
|
+
overlayHost.show(
|
|
204
|
+
anchorView = view,
|
|
205
|
+
keyboardView = kb,
|
|
206
|
+
heightPx = calculatorHeight
|
|
207
|
+
)
|
|
208
|
+
overlayShowing = true
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
overlayHost.hide()
|
|
212
|
+
overlayShowing = false
|
|
213
|
+
enableSystemImeFor(view)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return editText
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
override fun onDropViewInstance(view: ReactEditText) {
|
|
224
|
+
super.onDropViewInstance(view)
|
|
225
|
+
overlayHost.hide()
|
|
226
|
+
overlayHost.detach()
|
|
227
|
+
overlayShowing = false
|
|
228
|
+
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private fun disableSystemImeFor(v: ReactEditText) {
|
|
232
|
+
v.showSoftInputOnFocus = false
|
|
233
|
+
if (android.os.Build.VERSION.SDK_INT >= 30) {
|
|
234
|
+
ViewCompat.getWindowInsetsController(v)?.hide(WindowInsetsCompat.Type.ime())
|
|
235
|
+
}
|
|
236
|
+
imm.hideSoftInputFromWindow(v.windowToken, 0)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private fun enableSystemImeFor(v: ReactEditText) {
|
|
240
|
+
v.showSoftInputOnFocus = true
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private fun emitOnChange(context: Context, text: String) {
|
|
244
|
+
val reactContext = context as ReactContext
|
|
245
|
+
val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
|
|
246
|
+
val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, editText.id)
|
|
247
|
+
val payload =
|
|
248
|
+
Arguments.createMap().apply {
|
|
249
|
+
putString("text", text)
|
|
250
|
+
}
|
|
251
|
+
val event = OnChangeEvent(surfaceId, editText.id, payload)
|
|
252
|
+
|
|
253
|
+
eventDispatcher?.dispatchEvent(event)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
override fun getExportedCustomBubblingEventTypeConstants(): MutableMap<String, Any> {
|
|
257
|
+
val base = super.getExportedCustomBubblingEventTypeConstants()?.toMutableMap() ?: mutableMapOf()
|
|
258
|
+
base["onKeyPress"] = mapOf("phasedRegistrationNames" to mapOf("bubbled" to "onKeyPress"))
|
|
259
|
+
base["onChange"] = mapOf("phasedRegistrationNames" to mapOf("bubbled" to "onChange"))
|
|
260
|
+
return base
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
|
|
264
|
+
val base = super.getExportedCustomDirectEventTypeConstants()?.toMutableMap() ?: mutableMapOf()
|
|
265
|
+
base["onCustomKeyEvent"] = mapOf("registrationName" to "onCustomKeyEvent")
|
|
266
|
+
return base
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
}
|
|
270
|
+
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#import <UIKit/UIKit.h>
|
|
2
|
+
|
|
3
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
4
|
+
|
|
5
|
+
@protocol CalculatorKeyboardInput <NSObject>
|
|
6
|
+
|
|
7
|
+
- (void)keyDidPress:(NSString *)key;
|
|
8
|
+
- (void)clearText;
|
|
9
|
+
- (void)onBackSpace;
|
|
10
|
+
- (void)calculateResult;
|
|
11
|
+
- (void)emitCustomKey;
|
|
12
|
+
- (void)emitKeyPress:(NSString *)key;
|
|
13
|
+
|
|
14
|
+
@end
|
|
15
|
+
|
|
16
|
+
@interface CalculatorKeyboardView : UIView
|
|
17
|
+
|
|
18
|
+
@property (nonatomic, weak, nullable) id<CalculatorKeyboardInput> input;
|
|
19
|
+
@property (nonatomic, strong, nullable) NSString *keyboardMode;
|
|
20
|
+
@property (nonatomic, strong, nullable) NSString *customKeyText;
|
|
21
|
+
@property (nonatomic, strong, nullable) NSString *customKeyBackground;
|
|
22
|
+
@property (nonatomic, strong, nullable) NSString *customKeyTextColor;
|
|
23
|
+
@property (nonatomic, strong, nullable) NSString *customKeyState;
|
|
24
|
+
|
|
25
|
+
- (void)setKeyboardColor:(UIColor *)color;
|
|
26
|
+
|
|
27
|
+
@end
|
|
28
|
+
|
|
29
|
+
NS_ASSUME_NONNULL_END
|
|
30
|
+
|