@momo-kits/calculator-keyboard 0.150.2-phuc.15 → 0.151.1-beta.2

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 +242 -108
  4. package/android/src/main/java/com/calculatorkeyboard/Event.kt +129 -0
  5. package/android/src/main/java/com/calculatorkeyboard/{RCTInputCalculator.kt → InputCalculatorViewManager.kt} +105 -33
  6. package/ios/CalculatorKeyboardView.h +32 -0
  7. package/ios/CalculatorKeyboardView.mm +274 -0
  8. package/ios/NativeInputCalculator.h +11 -0
  9. package/ios/NativeInputCalculator.mm +500 -0
  10. package/ios/Utils.h +10 -0
  11. package/ios/Utils.mm +25 -0
  12. package/package.json +21 -131
  13. package/react-native-calculator-keyboard.podspec +5 -4
  14. package/src/InputCalculatorNativeComponent.ts +71 -0
  15. package/src/index.tsx +93 -43
  16. package/ios/CalculatorKeyboardView.swift +0 -153
  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 -67
  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 -23
  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 -23
  30. 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 { CalculatorKeyboardView } from "react-native-calculator-keyboard";
14
+ import InputCalculator from '@momo-kits/calculator-keyboard';
16
15
 
17
16
  // ...
18
17
 
19
- <CalculatorKeyboardView color="tomato" />
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
 
@@ -12,6 +12,6 @@ class CalculatorKeyboardPackage : ReactPackage {
12
12
  }
13
13
 
14
14
  override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
15
- return listOf(RCTInputCalculator())
15
+ return listOf(InputCalculatorViewManager())
16
16
  }
17
17
  }
@@ -4,70 +4,73 @@ import android.annotation.SuppressLint
4
4
  import android.content.res.ColorStateList
5
5
  import android.graphics.Color
6
6
  import android.graphics.drawable.GradientDrawable
7
+ import android.text.Editable
7
8
  import android.view.Gravity
8
9
  import android.widget.Button
10
+ import android.widget.EditText
9
11
  import android.widget.ImageButton
10
- import androidx.appcompat.app.AppCompatActivity
11
12
  import androidx.constraintlayout.widget.ConstraintLayout
13
+ import androidx.core.graphics.toColorInt
14
+ import com.calculatorkeyboard.InputCalculatorViewManager.Companion.calculatorHeight
12
15
  import com.facebook.react.uimanager.ThemedReactContext
13
16
  import org.mariuszgromada.math.mxparser.Expression
14
- import androidx.core.graphics.toColorInt
15
- import com.calculatorkeyboard.RCTInputCalculator.Companion.calculatorHeight
16
- import com.facebook.react.bridge.Arguments
17
- 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 keys = listOf(
23
+ private val numWithCTAKeys = listOf(
25
24
  listOf("1", "2", "3", "÷", "back"),
26
25
  listOf("4", "5", "6", "×", "="),
27
- listOf("7", "8", "9", "-", "Xong"),
26
+ listOf("7", "8", "9", "-", "Tiếp"),
28
27
  listOf("000", "0", "+")
29
28
  )
30
- private val specialKeys = listOf("=", "-", "×", "÷", "back", "+")
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", "+")
34
+ )
35
+ private val specialKeys = listOf("=", "-", "×", "÷", "back", "+", "AC")
31
36
  private val separatorWidth = 8f
32
37
  private var specialButtonColor: Int = "#D8D8D8".toColorInt()
33
38
 
39
+ private var keyboardMode: String = "NumDefault"
40
+
34
41
  private var customKeyButton: Button? = null
35
42
  private var customKeyButtonBackground: Int = "#D8D8D8".toColorInt()
36
43
  private var customKeyButtonTextColor: Int = Color.BLACK
37
44
  private var customKeyButtonState: String = "default"
38
45
 
39
46
  init {
40
- val activity = context.currentActivity as? AppCompatActivity
41
- if (activity != null) {
42
- val displayMetrics = resources.displayMetrics
43
- val widthButton = (displayMetrics.widthPixels - separatorWidth * 2 - 4 * separatorWidth) / 5f
44
- val heightButton = (calculatorHeight - separatorWidth * 2 - 3 * separatorWidth) / 4
45
-
46
47
  isClickable = false
47
48
  isFocusable = false
48
49
  isFocusableInTouchMode = false
49
50
  clipToPadding = false
50
51
  clipChildren = false
51
-
52
- renderUI(widthButton, heightButton)
53
- }
54
-
55
52
  }
56
53
 
57
- private fun renderUI(buttonWidth: Float, buttonHeight: Float) {
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
+
58
59
  var yOffset = separatorWidth
60
+ val keys = if (keyboardMode == "NumWithCTA") numWithCTAKeys else defaultKeys
59
61
  for ((rowIndex, row) in keys.withIndex()) {
60
62
  var xOffset = separatorWidth
61
63
  for ((colIndex, key) in row.withIndex()) {
62
- val isCustomKey = rowIndex == 2 && colIndex == 4
64
+ val isMainKey = rowIndex == 2 && colIndex == 4
65
+ val isMainCTAKey = isMainKey && keyboardMode == "NumWithCTA"
63
66
  val width = if (key == "000") buttonWidth * 2 + separatorWidth else buttonWidth
64
- val height = if (isCustomKey) buttonHeight * 2 + separatorWidth else buttonHeight
67
+ val height = if (isMainKey) buttonHeight * 2 + separatorWidth else buttonHeight
65
68
 
66
69
  val button = if (key == "back") {
67
70
  createImageButton(key, xOffset, yOffset, buttonWidth.toInt(), buttonHeight.toInt())
68
71
  } else {
69
- createButton(key, xOffset, yOffset, width.toInt(), height.toInt(), isCustomKey).also { b ->
70
- if (isCustomKey) customKeyButton = b
72
+ createButton(key, xOffset, yOffset, width.toInt(), height.toInt(), isMainKey, isMainCTAKey).also { b ->
73
+ if (isMainCTAKey) customKeyButton = b
71
74
  }
72
75
  }
73
76
 
@@ -85,7 +88,8 @@ class CustomKeyboardView(
85
88
  yOffset: Float,
86
89
  buttonWidth: Int,
87
90
  buttonHeight: Int,
88
- isCustomKey: Boolean
91
+ isMainKey: Boolean,
92
+ isMainCTAKey: Boolean
89
93
  ): Button {
90
94
  return Button(context).apply {
91
95
  val shapeInit = GradientDrawable().apply {
@@ -98,7 +102,7 @@ class CustomKeyboardView(
98
102
  background = shapeInit
99
103
  text = key
100
104
  setTypeface(typeface)
101
- textSize = (if (isCustomKey) 18 else 24).toFloat()
105
+ textSize = (if (isMainCTAKey) 18 else 24).toFloat()
102
106
  setTextColor(Color.BLACK)
103
107
  stateListAnimator = null
104
108
  maxLines = 1
@@ -110,7 +114,7 @@ class CustomKeyboardView(
110
114
  constrainedWidth = false
111
115
  }
112
116
 
113
- if (specialKeys.contains(key)) {
117
+ if (specialKeys.contains(key) || isMainKey) {
114
118
  background = GradientDrawable().apply {
115
119
  shape = GradientDrawable.RECTANGLE
116
120
  cornerRadius = 24f
@@ -125,7 +129,7 @@ class CustomKeyboardView(
125
129
 
126
130
  translationX = xOffset.toInt().toFloat()
127
131
  translationY = yOffset.toInt().toFloat()
128
- setOnClickListener { onKeyPress(key, isCustomKey) }
132
+ setOnClickListener { onKeyPress(key, isMainCTAKey) }
129
133
  }
130
134
  }
131
135
 
@@ -163,132 +167,192 @@ class CustomKeyboardView(
163
167
  }
164
168
  }
165
169
 
166
- fun updateButtonColors(color: Int) {
167
- specialButtonColor = color
168
- for (i in 0 until childCount) {
169
- val child = getChildAt(i)
170
- if (child is Button) {
171
- val key = child.text.toString()
172
- if (specialKeys.contains(key)) {
173
- if (key == "=") {
174
- child.background = GradientDrawable().apply {
175
- shape = GradientDrawable.RECTANGLE
176
- cornerRadius = 24f
177
- setColor(specialButtonColor)
178
- }
179
- } else {
180
- child.background = GradientDrawable().apply {
181
- shape = GradientDrawable.RECTANGLE
182
- cornerRadius = 24f
183
- setColor(specialButtonColor)
184
- }
185
- }
186
- child.setTextColor(Color.BLACK)
187
- }
188
- } else if (child is ImageButton) {
189
- child.background = GradientDrawable().apply {
190
- shape = GradientDrawable.RECTANGLE
191
- cornerRadius = 24f
192
- setColor(specialButtonColor)
193
- }
194
- }
195
- }
170
+ private fun String.endsWithAny(vararg tails: String): Boolean =
171
+ tails.any { endsWith(it) }
172
+
173
+ private fun Editable.insertAtCaret(editText: EditText, s: String) {
174
+ insert(editText.selectionStart.coerceAtLeast(0), s)
196
175
  }
197
176
 
198
- private fun onKeyPress(key: String, isCustomKey: Boolean) {
199
- if (isCustomKey) {
200
- emitCustomKey()
177
+ private fun onKeyPress(key: String, isMainCTAKey: Boolean) {
178
+ if (isMainCTAKey) {
179
+ Event.emitCustomKey(context, editText.id)
201
180
  return
202
181
  }
203
182
 
204
- emitKeyPress(key)
183
+ Event.emitKeyPress(context, editText.id, key)
184
+
185
+ val text = editText.text ?: return
186
+ val plain = text.toString()
187
+ val isEmpty = plain.isEmpty()
188
+ val isJustZero = plain == "0"
189
+ val endsWithOperator = plain.endsWithAny(" + ", " - ", " × ", " ÷ ")
190
+ val endsWithSingleZeroAfterOp = plain.endsWith(" 0")
191
+
205
192
  when (key) {
193
+ "AC" -> clearText()
206
194
  "back" -> onBackSpace()
207
195
  "=" -> calculateResult()
208
- "×", "+", "-", "÷" -> keyDidPress(" $key ")
209
- else -> editText.text?.insert(editText.selectionStart, key)
196
+ "×", "+", "-", "÷" -> if (!text.isEmpty()) keyDidPress(" $key ")
197
+ "0", "000" -> {
198
+ when {
199
+ isEmpty -> {}
200
+ key == "000" && endsWithOperator -> text.insertAtCaret(editText, "0")
201
+ endsWithSingleZeroAfterOp || isJustZero -> {}
202
+ else -> text.insertAtCaret(editText, key)
203
+ }
204
+ }
205
+ else -> {
206
+ when {
207
+ isJustZero -> {
208
+ text.replace(0, 1, key)
209
+ }
210
+ endsWithSingleZeroAfterOp -> {
211
+ val i = text.length - 1
212
+ text.replace(i, i + 1, key)
213
+ }
214
+ else -> text.insertAtCaret(editText, key)
215
+ }
216
+ }
210
217
  }
218
+
219
+ reformatAndKeepSelection(editText)
211
220
  }
212
221
 
213
222
  private fun keyDidPress(key: String) {
214
- println("Key pressed: $key")
215
- editText.text?.replace(editText.selectionStart, editText.selectionEnd, key)
223
+ val editable = editText.text ?: return
224
+ val selStart = editText.selectionStart.coerceAtLeast(0)
225
+ val selEnd = editText.selectionEnd.coerceAtLeast(selStart)
226
+
227
+ if (selStart != selEnd) {
228
+ editable.replace(selStart, selEnd, key)
229
+ return
230
+ }
231
+
232
+ val s = editable.toString()
233
+ val ops3 = setOf(" + ", " - ", " × ", " ÷ ")
234
+
235
+ if (selStart >= 3 && ops3.contains(s.substring(selStart - 3, selStart))) {
236
+ editable.replace(selStart - 3, selStart, key)
237
+ return
238
+ }
239
+
240
+ editable.replace(selStart, selEnd, key)
241
+ }
242
+
243
+ private fun clearText() {
244
+ editText.text?.clear()
216
245
  }
217
246
 
218
247
  private fun onBackSpace() {
219
- val start = editText.selectionStart
220
- val end = editText.selectionEnd
221
- if (start > 0) {
222
- val newText = end.let { editText.text?.replaceRange(start - 1, it, "") }
223
- editText.setText(newText)
224
- editText.setSelection(start - 1)
248
+ val formatted = editText.text?.toString().orEmpty()
249
+ val caretFmt = editText.selectionStart.coerceAtLeast(0)
250
+
251
+ val rawBefore = stripGroupDots(formatted)
252
+ val caretRaw = formattedCaretToRaw(formatted, caretFmt)
253
+
254
+ if (caretRaw <= 0) return
255
+
256
+ val ops = setOf(" + ", " - ", " × ", " ÷ ")
257
+ val len = rawBefore.length
258
+
259
+ var delStart = (caretRaw - 1).coerceAtLeast(0)
260
+ var delEnd = caretRaw
261
+
262
+ fun matches(start: Int, end: Int): Boolean {
263
+ if (start < 0 || end > len) return false
264
+ if (end - start != 3) return false
265
+ return ops.contains(rawBefore.substring(start, end))
225
266
  }
226
- }
227
267
 
228
- private fun calculateResult() {
229
- val text = editText?.text.toString().replace("×", "*").replace("÷", "/")
230
- val pattern = "^\\s*(-?\\d+(\\.\\d+)?\\s*[-+*/]\\s*)*-?\\d+(\\.\\d+)?\\s*$"
231
- val regex = Regex(pattern)
232
- if (regex.matches(text)) {
233
- try {
234
- val result = eval(text).toString()
235
- editText.setTextKeepState(result)
236
- } catch (e: Exception) {
237
- e.printStackTrace()
238
- }
239
- } else {
240
- println("Invalid expression")
268
+ if (matches(caretRaw - 3, caretRaw)) {
269
+ delStart = caretRaw - 3
270
+ delEnd = caretRaw
271
+ }
272
+ else if (matches(caretRaw - 2, caretRaw + 1)) {
273
+ delStart = caretRaw - 2
274
+ delEnd = caretRaw + 1
275
+ }
276
+ else if (matches(caretRaw - 1, caretRaw + 2)) {
277
+ delStart = caretRaw - 1
278
+ delEnd = caretRaw + 2
241
279
  }
242
- }
243
280
 
244
- private fun eval(str: String): Long? {
245
- val e = Expression(str)
246
- println("Expression: $e")
247
- return if (e.checkSyntax()) {
248
- e.calculate().toLong()
249
- } else {
250
- null
281
+ val rawAfter = buildString(len - (delEnd - delStart)) {
282
+ append(rawBefore, 0, delStart)
283
+ append(rawBefore, delEnd, len)
251
284
  }
285
+
286
+ val formattedAfter = formatNumberGroups(rawAfter)
287
+ editText.setText(formattedAfter)
288
+
289
+ val newCaretFmt = rawCaretToFormatted(delStart, formattedAfter)
290
+ editText.setSelection(newCaretFmt.coerceIn(0, formattedAfter.length))
291
+ }
292
+
293
+ private fun calculateResult(){
294
+ val result = editText.calculate()
295
+ if (result == "invalid") return
296
+ editText.setTextKeepState(formatNumberGroups(result))
297
+ editText.setSelection(editText.text?.length ?: 0)
252
298
  }
253
299
 
254
- private fun emitKeyPress(key: String) {
255
- val reactContext = context as ThemedReactContext
256
- val params = Arguments.createMap().apply {
257
- putString("key", key)
300
+ fun formatNumberGroups(input: String): String {
301
+ val noSep = stripGroupDots(input)
302
+ return Regex("\\d+").replace(noSep) { m ->
303
+ val s = m.value
304
+ val rev = s.reversed().chunked(3).joinToString(".")
305
+ rev.reversed()
258
306
  }
259
- reactContext.getJSModule(RCTEventEmitter::class.java)
260
- .receiveEvent(editText.id, "onKeyPress", params)
261
307
  }
262
308
 
263
- private fun emitCustomKey() {
264
- val reactContext = context as ThemedReactContext
265
- reactContext.getJSModule(RCTEventEmitter::class.java)
266
- .receiveEvent(editText.id, "onCustomKeyEvent", null)
309
+
310
+ fun stripGroupDots(input: String): String {
311
+ val out = StringBuilder(input.length)
312
+ for (i in input.indices) {
313
+ val c = input[i]
314
+ if (c == '.' && isGroupDotAt(input, i)) {
315
+ } else {
316
+ out.append(c)
317
+ }
318
+ }
319
+ return out.toString()
320
+ }
321
+ private fun isGroupDotAt(s: String, i: Int): Boolean {
322
+ if (i < 0 || i >= s.length) return false
323
+ if (s[i] != '.') return false
324
+ val leftIsDigit = i - 1 >= 0 && s[i - 1].isDigit()
325
+ val rightIsDigit = i + 1 < s.length && s[i + 1].isDigit()
326
+ return leftIsDigit && rightIsDigit
267
327
  }
268
328
 
269
329
  fun setCustomKeyText(text: String) {
270
330
  customKeyButton?.text = text
271
331
  }
272
332
 
333
+ fun setMode(mode: String) {
334
+ keyboardMode = mode
335
+ renderUI()
336
+ }
337
+
273
338
  fun setCustomKeyBackground(background: Int) {
274
339
  customKeyButtonBackground = background
275
- updateCustomKeyUI(background, customKeyButtonTextColor, customKeyButtonState)
340
+ updateCustomKeyUI(background, customKeyButtonTextColor)
276
341
  }
277
342
 
278
343
  fun setCustomKeyTextColor(textColor: Int) {
279
344
  customKeyButtonTextColor = textColor
280
- updateCustomKeyUI(customKeyButtonBackground, textColor, customKeyButtonState)
345
+ updateCustomKeyUI(customKeyButtonBackground, textColor)
281
346
  }
282
347
 
283
348
 
284
349
  fun setCustomKeyState(state: String) {
285
350
  customKeyButtonState = state
286
351
  customKeyButton?.isEnabled = state != "disable"
287
- updateCustomKeyUI(customKeyButtonBackground, customKeyButtonTextColor, state)
352
+ updateCustomKeyUI(customKeyButtonBackground, customKeyButtonTextColor)
288
353
  }
289
354
 
290
- private fun updateCustomKeyUI(background: Int, textColor: Int, state: String){
291
-
355
+ private fun updateCustomKeyUI(background: Int, textColor: Int){
292
356
  customKeyButton?.background = GradientDrawable().apply {
293
357
  shape = GradientDrawable.RECTANGLE
294
358
  cornerRadius = 24f
@@ -297,5 +361,75 @@ class CustomKeyboardView(
297
361
  customKeyButton?.setTextColor(textColor)
298
362
  }
299
363
 
364
+ fun reformatAndKeepSelection(editText: CalculatorEditText) {
365
+ val formattedBefore = editText.text?.toString() ?: return
366
+ val caretFmtBefore = editText.selectionStart.coerceAtLeast(0)
300
367
 
368
+ val caretRaw = formattedCaretToRaw(formattedBefore, caretFmtBefore)
369
+
370
+ val formattedAfter = formatNumberGroups(formattedBefore)
371
+
372
+ if (formattedAfter != formattedBefore) {
373
+ editText.setText(formattedAfter)
374
+ }
375
+
376
+ val caretFmtAfter = rawCaretToFormatted(caretRaw, formattedAfter)
377
+ editText.setSelection(caretFmtAfter.coerceIn(0, formattedAfter.length))
378
+ }
379
+
380
+ private fun formattedCaretToRaw(formatted: String, caretFmt: Int): Int {
381
+ var rawIdx = 0
382
+ var i = 0
383
+ while (i < caretFmt && i < formatted.length) {
384
+ val c = formatted[i]
385
+ if (!(c == '.' && isGroupDotAt(formatted, i))) {
386
+ rawIdx++
387
+ }
388
+ i++
389
+ }
390
+ return rawIdx
391
+ }
392
+
393
+ private fun rawCaretToFormatted(rawCaret: Int, formatted: String): Int {
394
+ var rawSeen = 0
395
+ var i = 0
396
+ while (i < formatted.length) {
397
+ val c = formatted[i]
398
+ if (!(c == '.' && isGroupDotAt(formatted, i))) {
399
+ if (rawSeen == rawCaret) return i
400
+ rawSeen++
401
+ }
402
+ i++
403
+ }
404
+ return formatted.length
405
+ }
406
+
407
+ companion object {
408
+ fun CalculatorEditText.calculate() : String {
409
+ val invalid = "invalid"
410
+ val raw = this.text?.toString().orEmpty()
411
+ val normalized = raw.replace(".", "")
412
+ .replace("×", "*")
413
+ .replace("÷", "/")
414
+
415
+ val pattern = "^\\s*(-?\\d+(\\.\\d+)?\\s*[-+*/]\\s*)*-?\\d+(\\.\\d+)?\\s*$"
416
+ if (Regex(pattern).matches(normalized)) {
417
+ try {
418
+ val result = eval(normalized)?.toString() ?: return invalid
419
+ return result
420
+ } catch (_: Exception) { return invalid }
421
+ } else {
422
+ return invalid
423
+ }
424
+ }
425
+ private fun eval(str: String): Long? {
426
+ val e = Expression(str)
427
+ println("Expression: $e")
428
+ return if (e.checkSyntax()) {
429
+ e.calculate().toLong()
430
+ } else {
431
+ null
432
+ }
433
+ }
434
+ }
301
435
  }
@@ -0,0 +1,129 @@
1
+ package com.calculatorkeyboard
2
+
3
+ import android.content.Context
4
+ import com.facebook.react.bridge.Arguments
5
+ import com.facebook.react.bridge.ReactContext
6
+ import com.facebook.react.bridge.WritableMap
7
+ import com.facebook.react.uimanager.UIManagerHelper
8
+ import com.facebook.react.uimanager.events.Event
9
+
10
+ class OnChangeEvent(
11
+ surfaceId: Int,
12
+ viewId: Int,
13
+ private val payload: WritableMap
14
+ ) : Event<OnChangeEvent>(surfaceId, viewId) {
15
+ override fun getEventName() = "onChange"
16
+
17
+ override fun getEventData() = payload
18
+ }
19
+
20
+ class OnKeyPressEvent(
21
+ surfaceId: Int,
22
+ viewId: Int,
23
+ private val payload: WritableMap
24
+ ) : Event<OnKeyPressEvent>(surfaceId, viewId) {
25
+ override fun getEventName() = "onKeyPress"
26
+
27
+ override fun getEventData() = payload
28
+ }
29
+
30
+ class OnResultEvent(
31
+ surfaceId: Int,
32
+ viewId: Int,
33
+ private val payload: WritableMap
34
+ ) : Event<OnResultEvent>(surfaceId, viewId) {
35
+ override fun getEventName() = "onResult"
36
+
37
+ override fun getEventData() = payload
38
+ }
39
+
40
+ class OnFocusEvent(
41
+ surfaceId: Int,
42
+ viewId: Int
43
+ ) : Event<OnFocusEvent>(surfaceId, viewId) {
44
+ override fun getEventName() = "onFocus"
45
+
46
+ override fun getEventData() = null
47
+ }
48
+
49
+ class OnBlurEvent(
50
+ surfaceId: Int,
51
+ viewId: Int
52
+ ) : Event<OnBlurEvent>(surfaceId, viewId) {
53
+ override fun getEventName() = "onBlur"
54
+
55
+ override fun getEventData() = null
56
+ }
57
+
58
+ class OnCustomKeyPressEvent(
59
+ surfaceId: Int,
60
+ viewId: Int
61
+ ) : Event<OnCustomKeyPressEvent>(surfaceId, viewId) {
62
+ override fun getEventName() = "onCustomKeyEvent"
63
+
64
+ override fun getEventData() = null
65
+ }
66
+
67
+ object Event {
68
+ fun emitKeyPress(context: Context, tag: Int, key: String) {
69
+ val reactContext = context as ReactContext
70
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
71
+ val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, tag)
72
+ val payload =
73
+ Arguments.createMap().apply {
74
+ putString("key", key)
75
+ }
76
+ val event = OnKeyPressEvent(surfaceId, tag, payload)
77
+
78
+ eventDispatcher?.dispatchEvent(event)
79
+ }
80
+
81
+ fun emitCustomKey(context: Context, tag: Int) {
82
+ val reactContext = context as ReactContext
83
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
84
+ val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, tag)
85
+
86
+ val event = OnCustomKeyPressEvent(surfaceId, tag)
87
+
88
+ eventDispatcher?.dispatchEvent(event)
89
+ }
90
+
91
+ fun emitOnChange(context: Context, tag: Int, text: String) {
92
+ val reactContext = context as ReactContext
93
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
94
+ val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, tag)
95
+ val payload =
96
+ Arguments.createMap().apply {
97
+ putString("text", text)
98
+ }
99
+ val event = OnChangeEvent(surfaceId, tag, payload)
100
+
101
+ eventDispatcher?.dispatchEvent(event)
102
+ }
103
+
104
+ fun emitOnBlurFocus(context: Context, tag: Int, isFocus: Boolean) {
105
+ val reactContext = context as ReactContext
106
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
107
+ val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, tag)
108
+
109
+ val event = if (isFocus) OnFocusEvent(surfaceId, tag) else OnBlurEvent(surfaceId, tag)
110
+
111
+ eventDispatcher?.dispatchEvent(event)
112
+ }
113
+
114
+ fun emitOnResult(context: Context, tag: Int, result: String) {
115
+ val reactContext = context as ReactContext
116
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
117
+ val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, tag)
118
+ val payload =
119
+ Arguments.createMap().apply {
120
+ putString("result", result)
121
+ }
122
+ val event = OnResultEvent(surfaceId, tag, payload)
123
+
124
+ eventDispatcher?.dispatchEvent(event)
125
+ }
126
+ }
127
+
128
+
129
+