@momo-kits/calculator-keyboard 0.150.1-rn80.8 → 0.150.2-beta-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/android/src/main/java/com/calculatorkeyboard/CustomKeyboardView.kt +99 -40
- package/android/src/main/java/com/calculatorkeyboard/KeyboardOverplayHost.kt +232 -0
- package/android/src/main/java/com/calculatorkeyboard/RCTInputCalculator.kt +121 -90
- package/ios/CalculatorKeyboardView.swift +53 -15
- package/ios/InputCalculator.m +9 -0
- package/ios/InputCalculator.swift +73 -18
- package/package.json +7 -131
- package/src/index.tsx +60 -15
- 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
|
@@ -1,46 +1,71 @@
|
|
|
1
1
|
package com.calculatorkeyboard
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
|
-
import android.
|
|
5
|
-
import android.graphics.
|
|
6
|
-
import android.graphics.drawable.ColorDrawable
|
|
7
|
-
import android.view.Gravity
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.graphics.Color
|
|
8
6
|
import android.view.KeyEvent
|
|
9
|
-
import android.view.
|
|
10
|
-
import android.view.WindowInsets
|
|
11
|
-
import android.view.WindowManager
|
|
12
|
-
import android.widget.PopupWindow
|
|
13
|
-
import androidx.constraintlayout.widget.ConstraintLayout
|
|
7
|
+
import android.view.inputmethod.InputMethodManager
|
|
14
8
|
import androidx.core.graphics.toColorInt
|
|
9
|
+
import androidx.core.view.ViewCompat
|
|
10
|
+
import androidx.core.view.WindowInsetsCompat
|
|
15
11
|
import com.facebook.react.bridge.ReadableArray
|
|
16
12
|
import com.facebook.react.bridge.UiThreadUtil
|
|
13
|
+
import com.facebook.react.uimanager.PixelUtil.dpToPx
|
|
17
14
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
18
15
|
import com.facebook.react.uimanager.annotations.ReactProp
|
|
19
16
|
import com.facebook.react.views.textinput.ReactEditText
|
|
20
17
|
import com.facebook.react.views.textinput.ReactTextInputManager
|
|
21
|
-
import androidx.core.graphics.drawable.toDrawable
|
|
22
|
-
import androidx.core.view.ViewCompat
|
|
23
|
-
import androidx.core.view.WindowInsetsCompat
|
|
24
|
-
import com.facebook.react.uimanager.PixelUtil.dpToPx
|
|
25
18
|
|
|
26
19
|
class RCTInputCalculator : ReactTextInputManager() {
|
|
27
20
|
|
|
28
21
|
private var keyboardView: CustomKeyboardView? = null
|
|
29
|
-
private var calculatorHeight: Int = 290.dpToPx().toInt()
|
|
30
|
-
private var popup: PopupWindow? = null
|
|
31
|
-
private val animationDuration = 250L
|
|
32
|
-
|
|
33
22
|
private lateinit var editText: CalculatorEditText
|
|
23
|
+
private lateinit var imm: InputMethodManager
|
|
24
|
+
|
|
25
|
+
private val overlayHost = KeyboardOverlayHost()
|
|
26
|
+
|
|
27
|
+
private var backListenerAttached = false
|
|
28
|
+
private var overlayShowing = false
|
|
34
29
|
|
|
35
30
|
override fun getName() = REACT_CLASS
|
|
36
31
|
|
|
37
32
|
companion object {
|
|
38
33
|
const val REACT_CLASS = "RCTInputCalculator"
|
|
34
|
+
val calculatorHeight: Int = 240.dpToPx().toInt()
|
|
39
35
|
}
|
|
40
36
|
|
|
41
37
|
@ReactProp(name = "value")
|
|
42
38
|
fun setValue(view: ReactEditText, value: String?) {
|
|
43
|
-
|
|
39
|
+
UiThreadUtil.runOnUiThread {
|
|
40
|
+
val e = view.editableText ?: run { view.setText(value ?: ""); return@runOnUiThread }
|
|
41
|
+
val newText = value ?: ""
|
|
42
|
+
if (e.toString() == newText) return@runOnUiThread
|
|
43
|
+
|
|
44
|
+
val wasFocused = view.hasFocus()
|
|
45
|
+
val atEnd = wasFocused && view.selectionStart == e.length && view.selectionEnd == e.length
|
|
46
|
+
e.replace(0, e.length, newText)
|
|
47
|
+
if (!wasFocused || atEnd) view.setSelection(newText.length)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@ReactProp(name = "customKeyText")
|
|
52
|
+
fun setCustomKeyText(view: ReactEditText, text: String?) {
|
|
53
|
+
keyboardView?.setCustomKeyText(text ?: "Xong")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@ReactProp(name = "customKeyBackground")
|
|
57
|
+
fun setCustomKeyBackground(view: ReactEditText, background: String?) {
|
|
58
|
+
keyboardView?.setCustomKeyBackground((background ?: "#d8d8d8").toColorInt())
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@ReactProp(name = "customKeyTextColor")
|
|
62
|
+
fun setCustomKeyTextColor(view: ReactEditText, textColor: String?) {
|
|
63
|
+
keyboardView?.setCustomKeyTextColor(textColor?.toColorInt() ?: Color.BLACK)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@ReactProp(name = "customKeyState")
|
|
67
|
+
fun setCustomKeyState(view: ReactEditText, state: String?) {
|
|
68
|
+
keyboardView?.setCustomKeyState(state ?: "default")
|
|
44
69
|
}
|
|
45
70
|
|
|
46
71
|
@ReactProp(name = "keyboardColor")
|
|
@@ -50,8 +75,32 @@ class RCTInputCalculator : ReactTextInputManager() {
|
|
|
50
75
|
|
|
51
76
|
@SuppressLint("ClickableViewAccessibility")
|
|
52
77
|
override fun createViewInstance(context: ThemedReactContext): ReactEditText {
|
|
78
|
+
imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
79
|
+
|
|
53
80
|
editText = CalculatorEditText(context).apply {
|
|
54
81
|
showSoftInputOnFocus = false
|
|
82
|
+
isFocusableInTouchMode = true
|
|
83
|
+
setOnTouchListener { v, event ->
|
|
84
|
+
if (event.action == android.view.MotionEvent.ACTION_DOWN) {
|
|
85
|
+
showSoftInputOnFocus = false
|
|
86
|
+
ViewCompat.getWindowInsetsController(this)?.hide(WindowInsetsCompat.Type.ime())
|
|
87
|
+
imm.hideSoftInputFromWindow(windowToken, 0)
|
|
88
|
+
|
|
89
|
+
if (!isFocused) requestFocus()
|
|
90
|
+
|
|
91
|
+
val kb = keyboardView
|
|
92
|
+
if (kb != null && !overlayShowing) {
|
|
93
|
+
overlayHost.show(
|
|
94
|
+
anchorView = this,
|
|
95
|
+
keyboardView = kb,
|
|
96
|
+
heightPx = calculatorHeight
|
|
97
|
+
)
|
|
98
|
+
overlayShowing = true
|
|
99
|
+
}
|
|
100
|
+
return@setOnTouchListener true
|
|
101
|
+
}
|
|
102
|
+
false
|
|
103
|
+
}
|
|
55
104
|
}
|
|
56
105
|
|
|
57
106
|
keyboardView = CustomKeyboardView(context, editText).apply {
|
|
@@ -59,19 +108,34 @@ class RCTInputCalculator : ReactTextInputManager() {
|
|
|
59
108
|
elevation = 24f
|
|
60
109
|
}
|
|
61
110
|
|
|
111
|
+
if (!backListenerAttached) {
|
|
112
|
+
editText.setOnKeyListener { v, keyCode, event ->
|
|
113
|
+
if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP && v.hasFocus()) {
|
|
114
|
+
v.clearFocus()
|
|
115
|
+
true
|
|
116
|
+
} else false
|
|
117
|
+
}
|
|
118
|
+
backListenerAttached = true
|
|
119
|
+
}
|
|
120
|
+
|
|
62
121
|
editText.onFocusListener = object : CalculatorEditText.OnFocusChangeListener {
|
|
63
122
|
override fun onFocusChange(view: CalculatorEditText, hasFocus: Boolean) {
|
|
64
123
|
UiThreadUtil.runOnUiThread {
|
|
65
124
|
if (hasFocus) {
|
|
66
|
-
|
|
125
|
+
disableSystemImeFor(view)
|
|
126
|
+
if (!overlayShowing) {
|
|
127
|
+
val kb = keyboardView ?: return@runOnUiThread
|
|
128
|
+
overlayHost.show(
|
|
129
|
+
anchorView = view,
|
|
130
|
+
keyboardView = kb,
|
|
131
|
+
heightPx = calculatorHeight
|
|
132
|
+
)
|
|
133
|
+
overlayShowing = true
|
|
134
|
+
}
|
|
67
135
|
} else {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (keyCode == KeyEvent.KEYCODE_BACK && hasFocus) {
|
|
72
|
-
v.clearFocus()
|
|
73
|
-
true
|
|
74
|
-
} else false
|
|
136
|
+
overlayHost.hide()
|
|
137
|
+
overlayShowing = false
|
|
138
|
+
enableSystemImeFor(view)
|
|
75
139
|
}
|
|
76
140
|
}
|
|
77
141
|
}
|
|
@@ -80,87 +144,54 @@ class RCTInputCalculator : ReactTextInputManager() {
|
|
|
80
144
|
return editText
|
|
81
145
|
}
|
|
82
146
|
|
|
83
|
-
override fun getCommandsMap(): Map<String, Int> {
|
|
84
|
-
return mapOf(
|
|
85
|
-
"blur" to 1,
|
|
86
|
-
"focus" to 2
|
|
87
|
-
)
|
|
88
|
-
}
|
|
89
147
|
|
|
90
|
-
override fun
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
148
|
+
override fun onDropViewInstance(view: ReactEditText) {
|
|
149
|
+
super.onDropViewInstance(view)
|
|
150
|
+
overlayHost.hide()
|
|
151
|
+
overlayHost.detach()
|
|
152
|
+
overlayShowing = false
|
|
153
|
+
|
|
95
154
|
}
|
|
96
155
|
|
|
97
|
-
private fun
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
popup = PopupWindow(
|
|
102
|
-
content,
|
|
103
|
-
WindowManager.LayoutParams.MATCH_PARENT,
|
|
104
|
-
calculatorHeight + bottomInsetFrom(editText.rootView),
|
|
105
|
-
false
|
|
106
|
-
).apply {
|
|
107
|
-
setBackgroundDrawable(android.graphics.Color.TRANSPARENT.toDrawable())
|
|
108
|
-
isOutsideTouchable = false
|
|
109
|
-
isClippingEnabled = false
|
|
110
|
-
softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
|
|
111
|
-
inputMethodMode = PopupWindow.INPUT_METHOD_NOT_NEEDED
|
|
156
|
+
private fun disableSystemImeFor(v: ReactEditText) {
|
|
157
|
+
v.showSoftInputOnFocus = false
|
|
158
|
+
if (android.os.Build.VERSION.SDK_INT >= 30) {
|
|
159
|
+
ViewCompat.getWindowInsetsController(v)?.hide(WindowInsetsCompat.Type.ime())
|
|
112
160
|
}
|
|
161
|
+
imm.hideSoftInputFromWindow(v.windowToken, 0)
|
|
113
162
|
}
|
|
114
163
|
|
|
115
|
-
private fun
|
|
116
|
-
|
|
117
|
-
val mask = WindowInsetsCompat.Type.navigationBars() or WindowInsetsCompat.Type.displayCutout()
|
|
118
|
-
return insets.getInsets(mask).bottom
|
|
164
|
+
private fun enableSystemImeFor(v: ReactEditText) {
|
|
165
|
+
v.showSoftInputOnFocus = true
|
|
119
166
|
}
|
|
120
167
|
|
|
121
|
-
|
|
122
|
-
ensurePopup()
|
|
123
|
-
val p = popup ?: return
|
|
124
|
-
if (p.isShowing) return
|
|
168
|
+
override fun getCommandsMap(): Map<String, Int> = mapOf("blur" to 1, "focus" to 2)
|
|
125
169
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
root.animate()
|
|
132
|
-
?.translationY(0f)
|
|
133
|
-
?.setDuration(animationDuration)
|
|
134
|
-
?.start()
|
|
170
|
+
override fun receiveCommand(reactEditText: ReactEditText, commandId: Int, args: ReadableArray?) {
|
|
171
|
+
when (commandId) {
|
|
172
|
+
1 -> blur()
|
|
173
|
+
2 -> focus()
|
|
174
|
+
}
|
|
135
175
|
}
|
|
136
176
|
|
|
137
|
-
|
|
138
|
-
val
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
root.animate()
|
|
143
|
-
.translationY(calculatorHeight.toFloat())
|
|
144
|
-
.setDuration(animationDuration)
|
|
145
|
-
.withEndAction {
|
|
146
|
-
try {
|
|
147
|
-
if (p.isShowing) p.dismiss()
|
|
148
|
-
} catch (_: Throwable) { }
|
|
149
|
-
}
|
|
150
|
-
.start()
|
|
177
|
+
override fun getExportedCustomBubblingEventTypeConstants(): MutableMap<String, Any> {
|
|
178
|
+
val base = super.getExportedCustomBubblingEventTypeConstants().toMutableMap()
|
|
179
|
+
base["onKeyPress"] = mapOf("phasedRegistrationNames" to mapOf("bubbled" to "onKeyPress"))
|
|
180
|
+
return base
|
|
181
|
+
}
|
|
151
182
|
|
|
152
|
-
|
|
183
|
+
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
|
|
184
|
+
val base = super.getExportedCustomDirectEventTypeConstants().toMutableMap()
|
|
185
|
+
base["onCustomKeyEvent"] = mapOf("registrationName" to "onCustomKeyEvent")
|
|
186
|
+
return base
|
|
153
187
|
}
|
|
154
188
|
|
|
155
189
|
private fun focus() {
|
|
156
|
-
UiThreadUtil.runOnUiThread {
|
|
157
|
-
if (!editText.isFocused) editText.requestFocus()
|
|
158
|
-
}
|
|
190
|
+
UiThreadUtil.runOnUiThread { if (!editText.isFocused) editText.requestFocus() }
|
|
159
191
|
}
|
|
160
192
|
|
|
161
193
|
private fun blur() {
|
|
162
|
-
UiThreadUtil.runOnUiThread {
|
|
163
|
-
if (editText.isFocused) editText.clearFocus()
|
|
164
|
-
}
|
|
194
|
+
UiThreadUtil.runOnUiThread { if (editText.isFocused) editText.clearFocus() }
|
|
165
195
|
}
|
|
166
196
|
}
|
|
197
|
+
|
|
@@ -8,14 +8,20 @@ class CalculatorKeyboardView: UIView {
|
|
|
8
8
|
weak var input: InputCalculator?
|
|
9
9
|
|
|
10
10
|
private let keys: [[String]] = [
|
|
11
|
-
["
|
|
12
|
-
["
|
|
13
|
-
["
|
|
14
|
-
["
|
|
15
|
-
["000", "0"]
|
|
11
|
+
["1", "2", "3", "÷", "back"],
|
|
12
|
+
["4", "5", "6", "×", "="],
|
|
13
|
+
["7", "8", "9", "-", "Xong"],
|
|
14
|
+
["000", "0", "+"],
|
|
16
15
|
]
|
|
17
16
|
private let SEPARATOR_WIDTH: CGFloat = 4
|
|
18
|
-
private let specialKeys: Set<String> = ["=", "-", "×", "÷", "
|
|
17
|
+
private let specialKeys: Set<String> = ["=", "-", "×", "÷", "back", "+"]
|
|
18
|
+
|
|
19
|
+
var customKeyText: String? { didSet { updateCustomKeyTitle() } }
|
|
20
|
+
var customKeyBackground: String? { didSet { updateCustomKeyBackground() } }
|
|
21
|
+
var customKeyTextColor: String? { didSet { updateCustomKeyBackground() } }
|
|
22
|
+
var customKeyState: String? { didSet { updateCustomKeyBackground() } }
|
|
23
|
+
private weak var customKeyButton: UIButton?
|
|
24
|
+
|
|
19
25
|
|
|
20
26
|
override init(frame: CGRect) {
|
|
21
27
|
super.init(frame: frame)
|
|
@@ -34,8 +40,8 @@ class CalculatorKeyboardView: UIView {
|
|
|
34
40
|
self.subviews.forEach { $0.removeFromSuperview() }
|
|
35
41
|
|
|
36
42
|
backgroundColor = UIColor(hex: "#f2f2f6")
|
|
37
|
-
let buttonWidth = (UIScreen.main.bounds.width - SEPARATOR_WIDTH * 2 -
|
|
38
|
-
let buttonHeight: CGFloat = (
|
|
43
|
+
let buttonWidth = (UIScreen.main.bounds.width - SEPARATOR_WIDTH * 2 - 4 * SEPARATOR_WIDTH) / 5
|
|
44
|
+
let buttonHeight: CGFloat = (240 - SEPARATOR_WIDTH * 2 - 3 * SEPARATOR_WIDTH) / 4
|
|
39
45
|
|
|
40
46
|
// Create a wrapper view
|
|
41
47
|
let contentView = UIView()
|
|
@@ -52,19 +58,22 @@ class CalculatorKeyboardView: UIView {
|
|
|
52
58
|
|
|
53
59
|
// Add buttons to the wrapper view
|
|
54
60
|
var yOffset: CGFloat = 0
|
|
55
|
-
for row in keys {
|
|
61
|
+
for (rowIndex, row) in keys.enumerated() {
|
|
56
62
|
var xOffset: CGFloat = 0
|
|
57
|
-
for key in row {
|
|
63
|
+
for (colIndex, key) in row.enumerated() {
|
|
64
|
+
let isCustomKey = colIndex == 4 && rowIndex == 2
|
|
58
65
|
let button = UIButton(type: .system)
|
|
59
66
|
button.backgroundColor = UIColor.white
|
|
60
67
|
button.layer.cornerRadius = 8
|
|
61
|
-
|
|
68
|
+
let title = isCustomKey ? (customKeyText ?? key) : key
|
|
69
|
+
button.setTitle(title, for: .normal)
|
|
62
70
|
button.setTitleColor(.black, for: .normal)
|
|
63
|
-
button.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: .medium)
|
|
71
|
+
button.titleLabel?.font = UIFont.systemFont(ofSize: isCustomKey ? 18 : 24, weight: .medium)
|
|
64
72
|
button.nativeID = key
|
|
73
|
+
button.tag = isCustomKey ? 1 : 0
|
|
65
74
|
|
|
66
75
|
var buttonFrame = CGRect(x: xOffset, y: yOffset, width: buttonWidth, height: buttonHeight)
|
|
67
|
-
if
|
|
76
|
+
if isCustomKey {
|
|
68
77
|
buttonFrame.size.height = buttonHeight * 2 + SEPARATOR_WIDTH
|
|
69
78
|
}
|
|
70
79
|
if key == "000" {
|
|
@@ -85,6 +94,10 @@ class CalculatorKeyboardView: UIView {
|
|
|
85
94
|
button.backgroundColor = color
|
|
86
95
|
}
|
|
87
96
|
|
|
97
|
+
if isCustomKey {
|
|
98
|
+
self.customKeyButton = button
|
|
99
|
+
}
|
|
100
|
+
|
|
88
101
|
button.addTarget(self, action: #selector(keyPressed(_:)), for: .touchUpInside)
|
|
89
102
|
contentView.addSubview(button)
|
|
90
103
|
|
|
@@ -96,12 +109,37 @@ class CalculatorKeyboardView: UIView {
|
|
|
96
109
|
}
|
|
97
110
|
}
|
|
98
111
|
|
|
112
|
+
private func updateCustomKeyTitle() {
|
|
113
|
+
guard let btn = customKeyButton, let title = customKeyText else { return }
|
|
114
|
+
btn.setTitle(title, for: .normal)
|
|
115
|
+
btn.setImage(nil, for: .normal)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private func updateCustomKeyBackground() {
|
|
119
|
+
guard let btn = customKeyButton,
|
|
120
|
+
let background = customKeyBackground,
|
|
121
|
+
let textColor = customKeyTextColor,
|
|
122
|
+
let state = customKeyState else { return }
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
btn.isEnabled = state != "disable"
|
|
127
|
+
btn.backgroundColor = UIColor(hex: background)
|
|
128
|
+
btn.setTitleColor(UIColor(hex: textColor), for: .normal)
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
99
132
|
|
|
100
133
|
@objc private func keyPressed(_ sender: UIButton) {
|
|
101
134
|
guard let key = sender.nativeID else { return }
|
|
135
|
+
let isCustomKey = sender.tag == 1
|
|
136
|
+
if (isCustomKey) {
|
|
137
|
+
input?.emitCustomKey()
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
input?.emitKeyPress(key)
|
|
102
142
|
switch key {
|
|
103
|
-
case "AC":
|
|
104
|
-
input?.clearText()
|
|
105
143
|
case "back":
|
|
106
144
|
input?.onBackSpace()
|
|
107
145
|
case "=":
|
package/ios/InputCalculator.m
CHANGED
|
@@ -53,6 +53,11 @@ RCT_EXPORT_VIEW_PROPERTY(mostRecentEventCount, NSInteger)
|
|
|
53
53
|
RCT_EXPORT_SHADOW_PROPERTY(text, NSString)
|
|
54
54
|
RCT_EXPORT_SHADOW_PROPERTY(placeholder, NSString)
|
|
55
55
|
RCT_EXPORT_SHADOW_PROPERTY(onContentSizeChange, RCTDirectEventBlock)
|
|
56
|
+
RCT_EXPORT_VIEW_PROPERTY(value, NSString)
|
|
57
|
+
RCT_EXPORT_VIEW_PROPERTY(onFocus, RCTBubblingEventBlock)
|
|
58
|
+
RCT_EXPORT_VIEW_PROPERTY(onBlur, RCTBubblingEventBlock)
|
|
59
|
+
RCT_EXPORT_VIEW_PROPERTY(onKeyPress, RCTBubblingEventBlock)
|
|
60
|
+
RCT_EXPORT_VIEW_PROPERTY(onCustomKeyEvent, RCTDirectEventBlock)
|
|
56
61
|
|
|
57
62
|
RCT_EXPORT_METHOD(focus : (nonnull NSNumber *)viewTag)
|
|
58
63
|
{
|
|
@@ -71,6 +76,10 @@ RCT_EXPORT_METHOD(blur : (nonnull NSNumber *)viewTag)
|
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
RCT_EXPORT_VIEW_PROPERTY(keyboardColor, UIColor)
|
|
79
|
+
RCT_EXPORT_VIEW_PROPERTY(customKeyText, NSString)
|
|
80
|
+
RCT_EXPORT_VIEW_PROPERTY(customKeyBackground, NSString)
|
|
81
|
+
RCT_EXPORT_VIEW_PROPERTY(customKeyTextColor, NSString)
|
|
82
|
+
RCT_EXPORT_VIEW_PROPERTY(customKeyState, NSString)
|
|
74
83
|
|
|
75
84
|
@end
|
|
76
85
|
|
|
@@ -6,34 +6,87 @@ class RCTInputCalculator: RCTBaseTextInputViewManager {
|
|
|
6
6
|
override func view() -> UIView! {
|
|
7
7
|
return InputCalculator(bridge: bridge)
|
|
8
8
|
}
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
override static func requiresMainQueueSetup() -> Bool {
|
|
11
11
|
return true
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
class InputCalculator: RCTSinglelineTextInputView {
|
|
17
17
|
var bridge: RCTBridge?
|
|
18
18
|
var keyboardView: CalculatorKeyboardView?
|
|
19
|
+
|
|
20
|
+
@objc var onFocus: RCTBubblingEventBlock?
|
|
21
|
+
@objc var onBlur: RCTBubblingEventBlock?
|
|
22
|
+
@objc var onKeyPress: RCTBubblingEventBlock?
|
|
23
|
+
@objc var onCustomKeyEvent: RCTDirectEventBlock?
|
|
24
|
+
|
|
25
|
+
@objc var customKeyText: String? {
|
|
26
|
+
didSet { keyboardView?.customKeyText = customKeyText as String? }
|
|
27
|
+
}
|
|
19
28
|
|
|
20
|
-
@objc var
|
|
29
|
+
@objc var customKeyBackground: String? {
|
|
30
|
+
didSet { keyboardView?.customKeyBackground = customKeyBackground }
|
|
31
|
+
}
|
|
21
32
|
|
|
33
|
+
@objc var customKeyTextColor: String? {
|
|
34
|
+
didSet { keyboardView?.customKeyTextColor = customKeyTextColor }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@objc var customKeyState: String? {
|
|
38
|
+
didSet { keyboardView?.customKeyState = customKeyState }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@objc func beginEditingInput(_ note: Notification) { onFocus?([:]) }
|
|
42
|
+
@objc func endEditingInput(_ note: Notification) { onBlur?([:]) }
|
|
43
|
+
|
|
44
|
+
@objc var value: String = "" {
|
|
45
|
+
didSet {
|
|
46
|
+
guard let tf = backedTextInputView as? UITextField else { return }
|
|
47
|
+
let old = tf.text ?? ""
|
|
48
|
+
if old == value { return }
|
|
49
|
+
|
|
50
|
+
let isFirstResponder = tf.isFirstResponder
|
|
51
|
+
let atEnd = isFirstResponder && tf.offset(from: tf.beginningOfDocument, to: tf.selectedTextRange?.start ?? tf.endOfDocument) == old.count
|
|
52
|
+
tf.text = value
|
|
53
|
+
if !isFirstResponder || atEnd,
|
|
54
|
+
let end = tf.position(from: tf.beginningOfDocument, offset: value.count) {
|
|
55
|
+
tf.selectedTextRange = tf.textRange(from: end, to: end)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
22
60
|
@objc var keyboardColor: UIColor = UIColor(hex: "#d9d9d9") {
|
|
23
61
|
didSet {
|
|
24
62
|
self.keyboardView?.setKeyboardColor(keyboardColor)
|
|
25
63
|
}
|
|
26
64
|
}
|
|
27
|
-
|
|
65
|
+
|
|
66
|
+
|
|
28
67
|
override init(bridge: RCTBridge) {
|
|
29
68
|
super.init(bridge: bridge)
|
|
30
69
|
self.bridge = bridge
|
|
31
70
|
self.keyboardView = CalculatorKeyboardView()
|
|
32
|
-
self.keyboardView!.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height:
|
|
71
|
+
self.keyboardView!.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 240 + getbottomInset())
|
|
33
72
|
self.keyboardView!.input = self
|
|
34
|
-
|
|
73
|
+
|
|
35
74
|
backedTextInputView.inputView = self.keyboardView
|
|
36
75
|
backedTextInputView.inputView?.reloadInputViews()
|
|
76
|
+
|
|
77
|
+
NotificationCenter.default.addObserver(
|
|
78
|
+
self,
|
|
79
|
+
selector: #selector(beginEditingInput(_:)),
|
|
80
|
+
name: UITextField.textDidBeginEditingNotification,
|
|
81
|
+
object: backedTextInputView
|
|
82
|
+
)
|
|
83
|
+
NotificationCenter.default.addObserver(
|
|
84
|
+
self,
|
|
85
|
+
selector: #selector(endEditingInput(_:)),
|
|
86
|
+
name: UITextField.textDidEndEditingNotification,
|
|
87
|
+
object: backedTextInputView
|
|
88
|
+
)
|
|
89
|
+
|
|
37
90
|
}
|
|
38
91
|
func getbottomInset() -> CGFloat {
|
|
39
92
|
let window = UIApplication.shared.windows.first?.rootViewController?.view
|
|
@@ -44,19 +97,12 @@ class InputCalculator: RCTSinglelineTextInputView {
|
|
|
44
97
|
func keyDidPress(_ key: String) {
|
|
45
98
|
backedTextInputView.insertText(key)
|
|
46
99
|
value += key
|
|
100
|
+
|
|
47
101
|
if let bridge = bridge {
|
|
48
102
|
bridge.eventDispatcher().sendTextEvent(with: .change, reactTag: reactTag, text: value, key: "\(key)", eventCount: 1)
|
|
49
103
|
}
|
|
50
104
|
}
|
|
51
|
-
|
|
52
|
-
func clearText() {
|
|
53
|
-
value = ""
|
|
54
|
-
(backedTextInputView as? UITextField)?.text = ""
|
|
55
|
-
if let bridge = bridge {
|
|
56
|
-
bridge.eventDispatcher().sendTextEvent(with: .change, reactTag: reactTag, text: value, key: "clear", eventCount: 1)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
105
|
+
|
|
60
106
|
func onBackSpace() {
|
|
61
107
|
value = value.dropLast().description
|
|
62
108
|
DispatchQueue.main.async {
|
|
@@ -72,23 +118,24 @@ class InputCalculator: RCTSinglelineTextInputView {
|
|
|
72
118
|
bridge.eventDispatcher().sendTextEvent(with: .change, reactTag: reactTag, text: value, key: "back", eventCount: 1)
|
|
73
119
|
}
|
|
74
120
|
}
|
|
75
|
-
|
|
121
|
+
|
|
76
122
|
func calculateResult() {
|
|
77
123
|
guard let textField = backedTextInputView as? UITextField,
|
|
78
124
|
let text = textField.text?.replacingOccurrences(of: "×", with: "*").replacingOccurrences(of: "÷", with: "/")
|
|
79
125
|
else {
|
|
80
126
|
return
|
|
81
127
|
}
|
|
82
|
-
|
|
128
|
+
|
|
83
129
|
let pattern = "^\\s*(-?\\d+(\\.\\d+)?\\s*[-+*/]\\s*)*-?\\d+(\\.\\d+)?\\s*$"
|
|
84
130
|
let regex = try? NSRegularExpression(pattern: pattern)
|
|
85
131
|
let range = NSRange(location: 0, length: text.utf16.count)
|
|
86
|
-
|
|
132
|
+
|
|
87
133
|
if regex?.firstMatch(in: text, options: [], range: range) != nil {
|
|
88
134
|
let expression = NSExpression(format: text)
|
|
89
135
|
if let result = expression.expressionValue(with: nil, context: nil) as? NSNumber {
|
|
90
136
|
textField.text = result.stringValue
|
|
91
137
|
value = result.stringValue
|
|
138
|
+
|
|
92
139
|
if let bridge = bridge {
|
|
93
140
|
bridge.eventDispatcher().sendTextEvent(with: .change, reactTag: reactTag, text: value, key: "=", eventCount: 1)
|
|
94
141
|
}
|
|
@@ -99,5 +146,13 @@ class InputCalculator: RCTSinglelineTextInputView {
|
|
|
99
146
|
}
|
|
100
147
|
}
|
|
101
148
|
|
|
149
|
+
func emitCustomKey() {
|
|
150
|
+
onCustomKeyEvent?([:])
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
func emitKeyPress(_ key: String) {
|
|
154
|
+
onKeyPress?(["key": key])
|
|
155
|
+
}
|
|
156
|
+
|
|
102
157
|
}
|
|
103
158
|
|