@momo-kits/calculator-keyboard 0.150.2-beta.2 → 0.150.2-beta.20

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.
@@ -9,12 +9,12 @@ import android.widget.Button
9
9
  import android.widget.ImageButton
10
10
  import androidx.appcompat.app.AppCompatActivity
11
11
  import androidx.constraintlayout.widget.ConstraintLayout
12
- import androidx.core.graphics.ColorUtils
13
12
  import com.facebook.react.uimanager.ThemedReactContext
14
13
  import org.mariuszgromada.math.mxparser.Expression
15
14
  import androidx.core.graphics.toColorInt
16
- import com.facebook.react.uimanager.PixelUtil.dpToPx
17
-
15
+ import com.calculatorkeyboard.RCTInputCalculator.Companion.calculatorHeight
16
+ import com.facebook.react.bridge.Arguments
17
+ import com.facebook.react.uimanager.events.RCTEventEmitter
18
18
 
19
19
  @SuppressLint("SetTextI18n", "ViewConstructor")
20
20
  class CustomKeyboardView(
@@ -22,22 +22,32 @@ class CustomKeyboardView(
22
22
  private val editText: CalculatorEditText
23
23
  ) : ConstraintLayout(context) {
24
24
  private val keys = listOf(
25
- listOf("AC", "÷", "×", "back"),
26
- listOf("7", "8", "9", "-"),
27
- listOf("4", "5", "6", "+"),
28
- listOf("1", "2", "3", "="),
29
- listOf("000", "0")
25
+ listOf("1", "2", "3", "÷", "back"),
26
+ listOf("4", "5", "6", "×", "="),
27
+ listOf("7", "8", "9", "-", "Xong"),
28
+ listOf("000", "0", "+")
30
29
  )
31
- private val specialKeys = listOf("=", "-", "×", "÷", "AC", "back", "+")
30
+ private val specialKeys = listOf("=", "-", "×", "÷", "back", "+")
32
31
  private val separatorWidth = 8f
33
32
  private var specialButtonColor: Int = "#D8D8D8".toColorInt()
34
33
 
34
+ private var customKeyButton: Button? = null
35
+ private var customKeyButtonBackground: Int = "#D8D8D8".toColorInt()
36
+ private var customKeyButtonTextColor: Int = Color.BLACK
37
+ private var customKeyButtonState: String = "default"
38
+
35
39
  init {
36
40
  val activity = context.currentActivity as? AppCompatActivity
37
41
  if (activity != null) {
38
42
  val displayMetrics = resources.displayMetrics
39
- val widthButton = (displayMetrics.widthPixels - separatorWidth * 2 - 3 * separatorWidth) / 4f
40
- val heightButton = (290.dpToPx() - separatorWidth * 2 - 4 * separatorWidth) / 5
43
+ val widthButton = (displayMetrics.widthPixels - separatorWidth * 2 - 4 * separatorWidth) / 5f
44
+ val heightButton = (calculatorHeight - separatorWidth * 2 - 3 * separatorWidth) / 4
45
+
46
+ isClickable = false
47
+ isFocusable = false
48
+ isFocusableInTouchMode = false
49
+ clipToPadding = false
50
+ clipChildren = false
41
51
 
42
52
  renderUI(widthButton, heightButton)
43
53
  }
@@ -46,16 +56,19 @@ class CustomKeyboardView(
46
56
 
47
57
  private fun renderUI(buttonWidth: Float, buttonHeight: Float) {
48
58
  var yOffset = separatorWidth
49
- for ((_, row) in keys.withIndex()) {
59
+ for ((rowIndex, row) in keys.withIndex()) {
50
60
  var xOffset = separatorWidth
51
- for ((_, key) in row.withIndex()) {
61
+ for ((colIndex, key) in row.withIndex()) {
62
+ val isCustomKey = rowIndex == 2 && colIndex == 4
52
63
  val width = if (key == "000") buttonWidth * 2 + separatorWidth else buttonWidth
53
- val height = if (key == "=") buttonWidth + separatorWidth else buttonHeight
64
+ val height = if (isCustomKey) buttonHeight * 2 + separatorWidth else buttonHeight
54
65
 
55
66
  val button = if (key == "back") {
56
67
  createImageButton(key, xOffset, yOffset, buttonWidth.toInt(), buttonHeight.toInt())
57
68
  } else {
58
- createButton(key, xOffset, yOffset, width.toInt(), height.toInt())
69
+ createButton(key, xOffset, yOffset, width.toInt(), height.toInt(), isCustomKey).also { b ->
70
+ if (isCustomKey) customKeyButton = b
71
+ }
59
72
  }
60
73
 
61
74
  addView(button)
@@ -72,8 +85,8 @@ class CustomKeyboardView(
72
85
  yOffset: Float,
73
86
  buttonWidth: Int,
74
87
  buttonHeight: Int,
88
+ isCustomKey: Boolean
75
89
  ): Button {
76
- val specialKeys = listOf("=", "-", "×", "÷", "AC", "back", "+")
77
90
  return Button(context).apply {
78
91
  val shapeInit = GradientDrawable().apply {
79
92
  shape = GradientDrawable.RECTANGLE
@@ -85,9 +98,11 @@ class CustomKeyboardView(
85
98
  background = shapeInit
86
99
  text = key
87
100
  setTypeface(typeface)
88
- textSize = 24.toFloat()
101
+ textSize = (if (isCustomKey) 18 else 24).toFloat()
89
102
  setTextColor(Color.BLACK)
90
103
  stateListAnimator = null
104
+ maxLines = 1
105
+ isAllCaps = false
91
106
  layoutParams = LayoutParams(
92
107
  buttonWidth,
93
108
  buttonHeight
@@ -104,10 +119,13 @@ class CustomKeyboardView(
104
119
  setTextColor(Color.BLACK)
105
120
  }
106
121
 
122
+ isClickable = true
123
+ isFocusable = false
124
+ isFocusableInTouchMode = false
107
125
 
108
126
  translationX = xOffset.toInt().toFloat()
109
127
  translationY = yOffset.toInt().toFloat()
110
- setOnClickListener { onKeyPress(key) }
128
+ setOnClickListener { onKeyPress(key, isCustomKey) }
111
129
  }
112
130
  }
113
131
 
@@ -132,11 +150,16 @@ class CustomKeyboardView(
132
150
  ).apply {
133
151
  constrainedWidth = false
134
152
  }
153
+
154
+ isClickable = true
155
+ isFocusable = false
156
+ isFocusableInTouchMode = false
157
+
135
158
  translationX = xOffset
136
159
  translationY = yOffset
137
160
  setImageResource(android.R.drawable.ic_input_delete)
138
161
  setImageTintList(ColorStateList.valueOf(Color.BLACK))
139
- setOnClickListener { onKeyPress(key) }
162
+ setOnClickListener { onKeyPress(key, false) }
140
163
  }
141
164
  }
142
165
 
@@ -172,25 +195,18 @@ class CustomKeyboardView(
172
195
  }
173
196
  }
174
197
 
175
- private fun onKeyPress(key: String) {
176
- when (key) {
177
- "AC" -> {
178
- clearText()
179
- }
180
-
181
- "back" -> {
182
- onBackSpace()
183
- }
184
-
185
- "=" -> {
186
- calculateResult()
187
- }
198
+ private fun onKeyPress(key: String, isCustomKey: Boolean) {
199
+ if (isCustomKey) {
200
+ emitCustomKey()
201
+ return
202
+ }
188
203
 
204
+ emitKeyPress(key)
205
+ when (key) {
206
+ "back" -> onBackSpace()
207
+ "=" -> calculateResult()
189
208
  "×", "+", "-", "÷" -> keyDidPress(" $key ")
190
-
191
- else -> {
192
- editText.text?.insert(editText.selectionStart, key)
193
- }
209
+ else -> editText.text?.insert(editText.selectionStart, key)
194
210
  }
195
211
  }
196
212
 
@@ -199,10 +215,6 @@ class CustomKeyboardView(
199
215
  editText.text?.replace(editText.selectionStart, editText.selectionEnd, key)
200
216
  }
201
217
 
202
- private fun clearText() {
203
- editText.text?.clear()
204
- }
205
-
206
218
  private fun onBackSpace() {
207
219
  val start = editText.selectionStart
208
220
  val end = editText.selectionEnd
@@ -239,4 +251,51 @@ class CustomKeyboardView(
239
251
  }
240
252
  }
241
253
 
254
+ private fun emitKeyPress(key: String) {
255
+ val reactContext = context as ThemedReactContext
256
+ val params = Arguments.createMap().apply {
257
+ putString("key", key)
258
+ }
259
+ reactContext.getJSModule(RCTEventEmitter::class.java)
260
+ .receiveEvent(editText.id, "onKeyPress", params)
261
+ }
262
+
263
+ private fun emitCustomKey() {
264
+ val reactContext = context as ThemedReactContext
265
+ reactContext.getJSModule(RCTEventEmitter::class.java)
266
+ .receiveEvent(editText.id, "onCustomKeyEvent", null)
267
+ }
268
+
269
+ fun setCustomKeyText(text: String) {
270
+ customKeyButton?.text = text
271
+ }
272
+
273
+ fun setCustomKeyBackground(background: Int) {
274
+ customKeyButtonBackground = background
275
+ updateCustomKeyUI(background, customKeyButtonTextColor, customKeyButtonState)
276
+ }
277
+
278
+ fun setCustomKeyTextColor(textColor: Int) {
279
+ customKeyButtonTextColor = textColor
280
+ updateCustomKeyUI(customKeyButtonBackground, textColor, customKeyButtonState)
281
+ }
282
+
283
+
284
+ fun setCustomKeyState(state: String) {
285
+ customKeyButtonState = state
286
+ customKeyButton?.isEnabled = state != "disable"
287
+ updateCustomKeyUI(customKeyButtonBackground, customKeyButtonTextColor, state)
288
+ }
289
+
290
+ private fun updateCustomKeyUI(background: Int, textColor: Int, state: String){
291
+
292
+ customKeyButton?.background = GradientDrawable().apply {
293
+ shape = GradientDrawable.RECTANGLE
294
+ cornerRadius = 24f
295
+ setColor(background)
296
+ }
297
+ customKeyButton?.setTextColor(textColor)
298
+ }
299
+
300
+
242
301
  }
@@ -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
+ }