@momo-kits/calculator-keyboard 0.150.2-phuc.13 → 0.151.1-beta.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.
Files changed (30) hide show
  1. package/README.md +45 -3
  2. package/android/src/main/java/com/calculatorkeyboard/CalculatorKeyboardPackage.kt +1 -1
  3. package/android/src/main/java/com/calculatorkeyboard/CustomKeyboardView.kt +184 -95
  4. package/android/src/main/java/com/calculatorkeyboard/Event.kt +36 -0
  5. package/android/src/main/java/com/calculatorkeyboard/InputCalculatorViewManager.kt +270 -0
  6. package/android/src/main/java/com/calculatorkeyboard/KeyboardOverplayHost.kt +232 -0
  7. package/ios/CalculatorKeyboardView.h +30 -0
  8. package/ios/CalculatorKeyboardView.mm +231 -0
  9. package/ios/NativeInputCalculator.h +11 -0
  10. package/ios/NativeInputCalculator.mm +369 -0
  11. package/package.json +21 -131
  12. package/react-native-calculator-keyboard.podspec +5 -4
  13. package/src/InputCalculatorNativeComponent.ts +62 -0
  14. package/src/index.tsx +124 -82
  15. package/android/src/main/java/com/calculatorkeyboard/RCTInputCalculator.kt +0 -339
  16. package/ios/CalculatorKeyboardView.swift +0 -165
  17. package/ios/InputCalculator-Bridging-Header.h +0 -23
  18. package/ios/InputCalculator.m +0 -85
  19. package/ios/InputCalculator.swift +0 -158
  20. package/ios/extension.swift +0 -23
  21. package/lib/commonjs/index.js +0 -72
  22. package/lib/commonjs/index.js.map +0 -1
  23. package/lib/module/index.js +0 -68
  24. package/lib/module/index.js.map +0 -1
  25. package/lib/typescript/commonjs/package.json +0 -1
  26. package/lib/typescript/commonjs/src/index.d.ts +0 -31
  27. package/lib/typescript/commonjs/src/index.d.ts.map +0 -1
  28. package/lib/typescript/module/package.json +0 -1
  29. package/lib/typescript/module/src/index.d.ts +0 -31
  30. 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
+