@momo-kits/calculator-keyboard 0.167.1-test.2 → 0.168.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.
@@ -3,9 +3,12 @@ package com.momokits.calculatorkeyboard.native
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
5
  import android.content.res.ColorStateList
6
+ import android.graphics.Bitmap
7
+ import android.graphics.BitmapFactory
6
8
  import android.graphics.Color
7
9
  import android.graphics.drawable.GradientDrawable
8
10
  import android.text.Editable
11
+ import android.util.DisplayMetrics
9
12
  import android.view.Gravity
10
13
  import android.view.MotionEvent
11
14
  import android.widget.Button
@@ -17,6 +20,8 @@ import androidx.core.graphics.toColorInt
17
20
  import com.momokits.calculatorkeyboard.native.utils.CalculatorEngine
18
21
  import com.momokits.calculatorkeyboard.native.utils.CalculatorFormatter
19
22
  import com.momokits.calculatorkeyboard.R
23
+ import java.net.URL
24
+ import kotlin.concurrent.thread
20
25
 
21
26
  @SuppressLint("SetTextI18n", "ViewConstructor")
22
27
  class CustomKeyboardView(
@@ -181,6 +186,26 @@ class CustomKeyboardView(
181
186
  setImageTintList(ColorStateList.valueOf(Color.BLACK))
182
187
  setOnClickListener { onKeyPress(key, false) }
183
188
  applyImagePressedOpacity()
189
+ loadBackIcon()
190
+ }
191
+ }
192
+
193
+ // Swap the bundled fallback icon for the design-system icon once it's downloaded
194
+ private fun ImageButton.loadBackIcon() {
195
+ backIconCache?.let { return setImageBitmap(it) }
196
+
197
+ thread {
198
+ val bitmap = runCatching {
199
+ URL(BACK_ICON_URL).openConnection().apply {
200
+ connectTimeout = BACK_ICON_TIMEOUT_MS
201
+ readTimeout = BACK_ICON_TIMEOUT_MS
202
+ }.getInputStream().use(BitmapFactory::decodeStream)
203
+ }.getOrNull() ?: return@thread
204
+
205
+ // Density makes the drawable render at BACK_ICON_SIZE_DP without resampling the bitmap
206
+ bitmap.density = bitmap.width * DisplayMetrics.DENSITY_DEFAULT / BACK_ICON_SIZE_DP
207
+ backIconCache = bitmap
208
+ post { setImageBitmap(bitmap) }
184
209
  }
185
210
  }
186
211
 
@@ -400,4 +425,13 @@ class CustomKeyboardView(
400
425
  }
401
426
  customKeyButton?.setTextColor(textColor)
402
427
  }
428
+
429
+ companion object {
430
+ private const val BACK_ICON_URL = "https://img.mservice.com.vn/app/img/kits/chat_back_space.png"
431
+ private const val BACK_ICON_SIZE_DP = 24
432
+ private const val BACK_ICON_TIMEOUT_MS = 10_000
433
+
434
+ @Volatile
435
+ private var backIconCache: Bitmap? = null
436
+ }
403
437
  }
@@ -1,6 +1,10 @@
1
1
  #import "CalculatorKeyboardView.h"
2
2
  #import "Utils.h"
3
3
 
4
+ static NSString *const kBackIconURL = @"https://img.mservice.com.vn/app/img/kits/chat_back_space.png";
5
+ static const CGFloat kBackIconSize = 24;
6
+ static UIImage *sBackIcon;
7
+
4
8
  @interface CalculatorKeyboardView ()
5
9
  @property (nonatomic, strong) NSArray<NSArray<NSString *> *> *numWithCTAKeys;
6
10
  @property (nonatomic, strong) NSArray<NSArray<NSString *> *> *defaultKeys;
@@ -10,6 +14,8 @@
10
14
 
11
15
  @implementation CalculatorKeyboardView {
12
16
  CGFloat _separatorWidth;
17
+ UIView *_contentView;
18
+ NSArray<NSArray<UIButton *> *> *_buttonRows;
13
19
  }
14
20
 
15
21
  - (instancetype)initWithFrame:(CGRect)frame
@@ -85,28 +91,26 @@
85
91
 
86
92
  self.backgroundColor = [Utils colorFromHex:@"#f2f2f6"];
87
93
 
88
- CGFloat buttonWidth = (UIScreen.mainScreen.bounds.size.width - _separatorWidth * 2 - 4 * _separatorWidth) / 5;
89
- CGFloat buttonHeight = (240 - _separatorWidth * 2 - 3 * _separatorWidth) / 4;
90
-
91
94
  // Create content view
92
95
  UIView *contentView = [[UIView alloc] init];
93
96
  contentView.translatesAutoresizingMaskIntoConstraints = NO;
94
97
  [self addSubview:contentView];
98
+ _contentView = contentView;
95
99
 
96
- // Set contentView constraints
100
+ // Set contentView constraints, keeping clear of the side safe area (landscape, iPhone Duo)
97
101
  [NSLayoutConstraint activateConstraints:@[
98
- [contentView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:_separatorWidth],
99
- [contentView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-_separatorWidth],
102
+ [contentView.leadingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leadingAnchor constant:_separatorWidth],
103
+ [contentView.trailingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.trailingAnchor constant:-_separatorWidth],
100
104
  [contentView.topAnchor constraintEqualToAnchor:self.topAnchor constant:_separatorWidth],
101
105
  [contentView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-_separatorWidth]
102
106
  ]];
103
107
 
104
- CGFloat yOffset = 0;
108
+ NSMutableArray<NSArray<UIButton *> *> *buttonRows = [NSMutableArray array];
105
109
  NSArray<NSArray<NSString *> *> *keys = [_keyboardMode isEqualToString:@"NumDefault"] ? _defaultKeys : _numWithCTAKeys;
106
110
 
107
111
  for (NSInteger rowIndex = 0; rowIndex < keys.count; rowIndex++) {
108
112
  NSArray<NSString *> *row = keys[rowIndex];
109
- CGFloat xOffset = 0;
113
+ NSMutableArray<UIButton *> *buttonRow = [NSMutableArray array];
110
114
 
111
115
  for (NSInteger colIndex = 0; colIndex < row.count; colIndex++) {
112
116
  NSString *key = row[colIndex];
@@ -124,22 +128,13 @@
124
128
  button.accessibilityIdentifier = key;
125
129
  button.tag = isMainCTAKey ? 1 : 0;
126
130
 
127
- CGRect buttonFrame = CGRectMake(xOffset, yOffset, buttonWidth, buttonHeight);
128
- if (isMainKey) {
129
- buttonFrame.size.height = buttonHeight * 2 + _separatorWidth;
130
- }
131
- if ([key isEqualToString:@"000"]) {
132
- buttonFrame.size.width = buttonWidth * 2 + _separatorWidth;
133
- }
134
-
135
- button.frame = buttonFrame;
136
-
137
131
  if ([key isEqualToString:@"back"]) {
138
132
  [button setTitle:@"" forState:UIControlStateNormal];
139
133
  UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:24 weight:UIImageSymbolWeightBold];
140
134
  UIImage *image = [UIImage systemImageNamed:@"delete.backward" withConfiguration:config];
141
135
  [button setImage:image forState:UIControlStateNormal];
142
136
  button.tintColor = UIColor.blackColor;
137
+ [self loadBackIconForButton:button];
143
138
  }
144
139
 
145
140
  if ([_specialKeys containsObject:key] || isMainKey) {
@@ -153,7 +148,44 @@
153
148
 
154
149
  [button addTarget:self action:@selector(keyPressed:) forControlEvents:UIControlEventTouchUpInside];
155
150
  [contentView addSubview:button];
151
+ [buttonRow addObject:button];
152
+ }
153
+
154
+ [buttonRows addObject:buttonRow];
155
+ }
156
+
157
+ _buttonRows = buttonRows;
158
+ [self setNeedsLayout];
159
+ }
160
+
161
+ // Size the keys from the live keyboard width, which follows the window through
162
+ // rotation, fold and unfold, instead of the screen width at setup
163
+ - (void)layoutSubviews
164
+ {
165
+ [super layoutSubviews];
166
+
167
+ CGFloat buttonWidth = (_contentView.bounds.size.width - 4 * _separatorWidth) / 5;
168
+ CGFloat buttonHeight = (240 - _separatorWidth * 2 - 3 * _separatorWidth) / 4;
169
+ CGFloat yOffset = 0;
170
+
171
+ for (NSInteger rowIndex = 0; rowIndex < _buttonRows.count; rowIndex++) {
172
+ NSArray<UIButton *> *row = _buttonRows[rowIndex];
173
+ CGFloat xOffset = 0;
174
+
175
+ for (NSInteger colIndex = 0; colIndex < row.count; colIndex++) {
176
+ UIButton *button = row[colIndex];
177
+ NSString *key = button.accessibilityIdentifier;
178
+ BOOL isMainKey = (colIndex == 4 && rowIndex == 2);
156
179
 
180
+ CGRect buttonFrame = CGRectMake(xOffset, yOffset, buttonWidth, buttonHeight);
181
+ if (isMainKey) {
182
+ buttonFrame.size.height = buttonHeight * 2 + _separatorWidth;
183
+ }
184
+ if ([key isEqualToString:@"000"]) {
185
+ buttonFrame.size.width = buttonWidth * 2 + _separatorWidth;
186
+ }
187
+
188
+ button.frame = buttonFrame;
157
189
  xOffset += buttonFrame.size.width + _separatorWidth;
158
190
  }
159
191
 
@@ -161,6 +193,32 @@
161
193
  }
162
194
  }
163
195
 
196
+ - (void)loadBackIconForButton:(UIButton *)button
197
+ {
198
+ if (sBackIcon) {
199
+ [button setImage:sBackIcon forState:UIControlStateNormal];
200
+ return;
201
+ }
202
+
203
+ __weak UIButton *weakButton = button;
204
+ [[NSURLSession.sharedSession dataTaskWithURL:[NSURL URLWithString:kBackIconURL]
205
+ completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
206
+ CGImageRef cgImage = [UIImage imageWithData:data].CGImage;
207
+ if (!cgImage) return;
208
+
209
+ // Scale makes the image size kBackIconSize points without redrawing it
210
+ UIImage *icon = [[UIImage imageWithCGImage:cgImage
211
+ scale:CGImageGetWidth(cgImage) / kBackIconSize
212
+ orientation:UIImageOrientationUp]
213
+ imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
214
+
215
+ dispatch_async(dispatch_get_main_queue(), ^{
216
+ sBackIcon = icon;
217
+ [weakButton setImage:icon forState:UIControlStateNormal];
218
+ });
219
+ }] resume];
220
+ }
221
+
164
222
  - (void)updateCustomKeyTitle
165
223
  {
166
224
  if (!self.customKeyButton || !_customKeyText) {
@@ -3,6 +3,7 @@
3
3
 
4
4
  #import <React/RCTConversions.h>
5
5
  #import <React/RCTFabricComponentsPlugins.h>
6
+ #import <React/RCTUtils.h>
6
7
  #import <react/renderer/components/InputCalculatorSpec/InputCalculatorComponentDescriptor.h>
7
8
  #import <react/renderer/components/InputCalculatorSpec/EventEmitters.h>
8
9
  #import <react/renderer/components/InputCalculatorSpec/Props.h>
@@ -45,9 +46,13 @@ typedef struct {
45
46
  _textField.delegate = (id<UITextFieldDelegate>)self;
46
47
 
47
48
  // Create keyboard view
49
+ // UIKit sizes the input view to the keyboard width; the keys follow it in layoutSubviews
50
+ UIWindow *window = RCTKeyWindow();
51
+ CGFloat keyboardWidth = window ? window.bounds.size.width : UIScreen.mainScreen.bounds.size.width;
48
52
  CGFloat bottomInset = [self getBottomInset];
49
- CGRect keyboardFrame = CGRectMake(0, 0, UIScreen.mainScreen.bounds.size.width, 240 + bottomInset);
53
+ CGRect keyboardFrame = CGRectMake(0, 0, keyboardWidth, 240 + bottomInset);
50
54
  _keyboardView = [[CalculatorKeyboardView alloc] initWithFrame:keyboardFrame];
55
+ _keyboardView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
51
56
  _keyboardView.input = (id)self; // Bridge pattern
52
57
 
53
58
  // Set custom keyboard
@@ -94,7 +99,8 @@ typedef struct {
94
99
 
95
100
  - (CGFloat)getBottomInset
96
101
  {
97
- UIWindow *window = UIApplication.sharedApplication.windows.firstObject;
102
+ // key window of the active scene; windows.firstObject is unreliable with the UIScene life cycle
103
+ UIWindow *window = RCTKeyWindow();
98
104
  CGFloat bottom = window.safeAreaInsets.bottom > 0 ? 21 : 0;
99
105
  return bottom;
100
106
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/calculator-keyboard",
3
- "version": "0.167.1-test.2",
3
+ "version": "0.168.1-beta.2",
4
4
  "description": "react native calculator keyboard",
5
5
  "main": "./src/index.tsx",
6
6
  "files": [