Skip to content

Commit c6e4bcc

Browse files
mgefimovvonovak
andauthored
feat: tap to dismiss (vonovak#64)
* feat: tap to dismiss * feat: tapToDismiss option * refactor: rename styles to options * docs: tapToDismissEnabled example * fix: race condition in setTapToDismissEnabled * docs: minor tweaks * refactor: undo breaking change --------- Co-authored-by: Vojtech Novak <vonovak@gmail.com>
1 parent 76999d5 commit c6e4bcc

10 files changed

+112
-44
lines changed

README.md

+11-6
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,22 @@ then rebuild your project
4949

5050
## Usage
5151

52-
the module exposes the following functions, same as `ToastAndroid`, with extra styling parameter for iOS only:
52+
the module exposes the following functions, same as `ToastAndroid`, with extra configuration parameter for iOS only:
5353

5454
```ts
5555
import Toast from 'react-native-simple-toast';
5656

57-
Toast.show(message, duration, styles);
57+
Toast.show(message, duration, options);
5858

59-
Toast.showWithGravity(message, duration, gravity, styles);
59+
Toast.showWithGravity(message, duration, gravity, options);
6060

6161
Toast.showWithGravityAndOffset(
6262
message,
6363
duration,
6464
gravity,
6565
xOffset,
6666
yOffset,
67-
styles,
67+
options,
6868
);
6969
```
7070

@@ -82,12 +82,13 @@ Toast.CENTER;
8282

8383
Please note that `yOffset` and `xOffset` are [ignored on Android 11 and above](<https://developer.android.com/reference/android/widget/Toast#setGravity(int,%20int,%20int)>).
8484

85-
For styling on iOS, you can pass an object with the following properties:
85+
For customizing on iOS, you can pass an object with the following properties:
8686

8787
```ts
88-
type StylesIOS = {
88+
type OptionsIOS = {
8989
textColor?: ColorValue;
9090
backgroundColor?: ColorValue;
91+
tapToDismissEnabled?: boolean;
9192
};
9293
```
9394

@@ -109,6 +110,10 @@ Toast.showWithGravity(
109110
Toast.show('This is a styled toast on iOS.', Toast.LONG, {
110111
backgroundColor: 'blue',
111112
});
113+
114+
Toast.show('This is a toast that can be dismissed (iOS only).', Toast.LONG, {
115+
tapToDismissEnabled: true,
116+
});
112117
```
113118

114119
## License

example/src/App.tsx

+20
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ export default function App() {
5454
Toast.show('This is a toast.', Toast.SHORT);
5555
}}
5656
/>
57+
<Button
58+
title={'tap to dismiss toast'}
59+
onPress={() => {
60+
Toast.show('Tap to dismiss toast.', Toast.LONG, {
61+
tapToDismissEnabled: true,
62+
});
63+
}}
64+
/>
5765
<Button
5866
onPress={() => {
5967
setModalVisible(true);
@@ -78,6 +86,18 @@ export default function App() {
7886
});
7987
}}
8088
/>
89+
<Button
90+
title={'two toasts on top'}
91+
onPress={() => {
92+
Toast.show('_____ This is a bottom toast _____', 7, {
93+
backgroundColor: 'rgb(255, 0, 255)',
94+
textColor: 'black',
95+
});
96+
Toast.show('Tap to dismiss toast on iOS.', 7, {
97+
tapToDismissEnabled: true,
98+
});
99+
}}
100+
/>
81101
<Button
82102
title={'toast with offset'}
83103
onPress={() => {

ios/RNSimpleToast.mm

+17-14
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#import "UIView+Toast.h"
44
#import "RNToastViewController.h"
55
#import <React/RCTConvert.h>
6+
#import "RNToastView.h"
67

78
static double defaultPositionId = 2.0;
89

@@ -26,7 +27,6 @@ @implementation RNSimpleToast {
2627
- (instancetype)init {
2728
if (self = [super init]) {
2829
_kbdHeight = 0;
29-
[CSToastManager setTapToDismissEnabled:NO];
3030
[CSToastManager setQueueEnabled:YES];
3131
[[NSNotificationCenter defaultCenter] addObserver:self
3232
selector:@selector(keyboardDidShow:)
@@ -78,29 +78,29 @@ - (NSDictionary*)constantsToExport {
7878
}
7979
#endif
8080

81-
RCT_EXPORT_METHOD(show:(NSString *)message duration:(double)duration styles:(NSDictionary*)styles {
82-
[self _show:message duration:duration position:defaultPositionId offset:CGPointZero styles:styles];
81+
RCT_EXPORT_METHOD(show:(NSString *)message duration:(double)duration options:(NSDictionary*)options {
82+
[self _show:message duration:duration position:defaultPositionId offset:CGPointZero options:options];
8383
});
8484

85-
RCT_EXPORT_METHOD(showWithGravity:(NSString *)message duration:(double)duration gravity:(double)gravity styles:(NSDictionary*)styles {
86-
[self _show:message duration:duration position:gravity offset:CGPointZero styles:styles];
85+
RCT_EXPORT_METHOD(showWithGravity:(NSString *)message duration:(double)duration gravity:(double)gravity options:(NSDictionary*)options {
86+
[self _show:message duration:duration position:gravity offset:CGPointZero options:options];
8787
});
8888

89-
RCT_EXPORT_METHOD(showWithGravityAndOffset:(NSString *)message duration:(double)duration gravity:(double)gravity xOffset:(double)xOffset yOffset:(double)yOffset styles:(NSDictionary*)styles {
90-
[self _show:message duration:duration position:gravity offset:CGPointMake(xOffset, yOffset) styles:styles];
89+
RCT_EXPORT_METHOD(showWithGravityAndOffset:(NSString *)message duration:(double)duration gravity:(double)gravity xOffset:(double)xOffset yOffset:(double)yOffset options:(NSDictionary*)options {
90+
[self _show:message duration:duration position:gravity offset:CGPointMake(xOffset, yOffset) options:options];
9191
});
9292

9393
- (void)_show:(NSString *)msg
9494
duration:(NSTimeInterval)duration
9595
position:(double)position
9696
offset:(CGPoint)offset
97-
styles:(NSDictionary*)styles {
97+
options:(NSDictionary*)options {
9898
CSToastStyle *style = [[CSToastStyle alloc] initWithDefaultStyle];
99-
if (styles[@"backgroundColor"]) {
100-
style.backgroundColor = [RCTConvert UIColor:styles[@"backgroundColor"]];
99+
if (options[@"backgroundColor"]) {
100+
style.backgroundColor = [RCTConvert UIColor:options[@"backgroundColor"]];
101101
}
102-
if (styles[@"messageColor"]) {
103-
style.messageColor = [RCTConvert UIColor:styles[@"messageColor"]];
102+
if (options[@"messageColor"]) {
103+
style.messageColor = [RCTConvert UIColor:options[@"messageColor"]];
104104
}
105105

106106

@@ -117,6 +117,10 @@ - (void)_show:(NSString *)msg
117117
[weakView removeFromSuperview];
118118
[controller hide];
119119
};
120+
// CSToastManager state is shared among toasts, and is used when toast is shown
121+
// so modifications to it should happen in the dispatch_get_main_queue block
122+
[CSToastManager setTapToDismissEnabled:options[@"tapToDismissEnabled"]];
123+
120124
if (!CGPointEqualToPoint(offset, CGPointZero)) {
121125
CGPoint centerWithOffset = [self getCenterWithOffset:offset view:view toast:toast position:positionString];
122126
[view showToast:toast duration:duration position:[NSValue valueWithCGPoint:centerWithOffset] completion:completion];
@@ -163,8 +167,7 @@ - (UIView *)getToastView:(RNToastViewController *)ctrl {
163167
CGRect bounds = rootView.bounds;
164168
bounds.size.height -= _kbdHeight;
165169

166-
UIView *view = [[UIView alloc] initWithFrame:bounds];
167-
view.userInteractionEnabled = NO;
170+
UIView *view = [[RNToastView alloc] initWithFrame:bounds];
168171
[rootView addSubview:view];
169172
return view;
170173
}

ios/RNToastView.h

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#import <UIKit/UIKit.h>
2+
3+
@interface RNToastView : UIView
4+
5+
@end

ios/RNToastView.m

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#import "RNToastView.h"
2+
3+
@implementation RNToastView
4+
5+
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
6+
id hitView = [super hitTest:point withEvent:event];
7+
if (hitView == self) return nil;
8+
else return hitView;
9+
}
10+
11+
@end

ios/RNToastViewController.m

+3-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22
#import "RNToastViewController.h"
33
#import <React/RCTUtils.h>
4+
#import "RNToastWindow.h"
45

56
@implementation RNToastViewController
67

@@ -12,17 +13,12 @@ - (UIWindow *)toastWindow
1213
if (_toastWindow == nil) {
1314
UIWindow *keyWindow = RCTSharedApplication().keyWindow;
1415
if (keyWindow) {
15-
_toastWindow = [[UIWindow alloc] initWithFrame:keyWindow.bounds];
16+
_toastWindow = [[RNToastWindow alloc] initWithFrame:keyWindow.bounds];
1617
} else {
1718
// keyWindow is nil, so we cannot create and initialize _toastWindow
1819
NSLog(@"Unable to create alert window: keyWindow is nil");
1920
}
2021
}
21-
22-
if (_toastWindow) {
23-
_toastWindow.windowLevel = UIWindowLevelAlert + 1;
24-
_toastWindow.userInteractionEnabled = NO;
25-
}
2622
}
2723

2824
return _toastWindow;
@@ -48,7 +44,7 @@ - (UIWindow *)getUIWindowFromScene
4844
for (UIScene *scene in RCTSharedApplication().connectedScenes) {
4945
if (scene.activationState == UISceneActivationStateForegroundActive &&
5046
[scene isKindOfClass:[UIWindowScene class]]) {
51-
return [[UIWindow alloc] initWithWindowScene:(UIWindowScene *)scene];
47+
return [[RNToastWindow alloc] initWithWindowScene:(UIWindowScene *)scene];
5248
}
5349
}
5450
}

ios/RNToastWindow.h

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#import <UIKit/UIKit.h>
2+
3+
@interface RNToastWindow : UIWindow
4+
5+
@end

ios/RNToastWindow.m

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#import "RNToastWindow.h"
2+
3+
@implementation RNToastWindow
4+
5+
- (instancetype)initWithFrame:(CGRect)frame {
6+
if (self = [super initWithFrame:frame]) {
7+
self.windowLevel = UIWindowLevelAlert + 1;
8+
}
9+
return self;
10+
}
11+
12+
- (instancetype)initWithWindowScene:(UIWindowScene *)windowScene {
13+
if (self = [super initWithWindowScene:windowScene]) {
14+
self.windowLevel = UIWindowLevelAlert + 1;
15+
}
16+
return self;
17+
}
18+
19+
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
20+
id hitView = [super hitTest:point withEvent:event];
21+
if (hitView == self) return nil;
22+
else return hitView;
23+
}
24+
25+
@end

src/NativeSimpleToast.ts

+5-8
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
import type { TurboModule, ColorValue } from 'react-native';
22
import { TurboModuleRegistry } from 'react-native';
33

4+
// TODO rename to OptionsIOS
45
export type StylesIOS = {
56
textColor?: ColorValue;
67
backgroundColor?: ColorValue;
8+
tapToDismissEnabled?: boolean;
79
};
810

9-
// type NativeStyles = {
10-
// messageColor?: number;
11-
// backgroundColor?: number;
12-
// };
13-
1411
export interface Spec extends TurboModule {
1512
getConstants: () => {
1613
SHORT: number;
@@ -19,20 +16,20 @@ export interface Spec extends TurboModule {
1916
BOTTOM: number;
2017
CENTER: number;
2118
};
22-
show: (message: string, duration: number, styles: Object) => void;
19+
show: (message: string, duration: number, options: Object) => void;
2320
showWithGravity: (
2421
message: string,
2522
duration: number,
2623
gravity: number,
27-
styles: Object,
24+
options: Object,
2825
) => void;
2926
showWithGravityAndOffset: (
3027
message: string,
3128
duration: number,
3229
gravity: number,
3330
xOffset: number,
3431
yOffset: number,
35-
styles: Object,
32+
options: Object,
3633
) => void;
3734
}
3835

src/index.ts

+10-9
Original file line numberDiff line numberDiff line change
@@ -33,25 +33,25 @@ export default {
3333
BOTTOM: constantsSource.BOTTOM,
3434
CENTER: constantsSource.CENTER,
3535

36-
show(message: string, durationSeconds: number, styles: StylesIOS = {}) {
36+
show(message: string, durationSeconds: number, options: StylesIOS = {}) {
3737
RCTToast.show(
3838
message,
3939
durationSeconds ?? constantsSource.SHORT,
40-
processColors(styles),
40+
processColors(options),
4141
);
4242
},
4343

4444
showWithGravity(
4545
message: string,
4646
durationSeconds: number,
4747
gravity: number,
48-
styles: StylesIOS = {},
48+
options: StylesIOS = {},
4949
) {
5050
RCTToast.showWithGravity(
5151
message,
5252
durationSeconds ?? constantsSource.SHORT,
5353
gravity,
54-
processColors(styles),
54+
processColors(options),
5555
);
5656
},
5757

@@ -61,25 +61,26 @@ export default {
6161
gravity: number,
6262
xOffset: number,
6363
yOffset: number,
64-
styles: StylesIOS = {},
64+
options: StylesIOS = {},
6565
) {
6666
RCTToast.showWithGravityAndOffset(
6767
message,
6868
duration,
6969
gravity,
7070
xOffset,
7171
yOffset,
72-
processColors(styles),
72+
processColors(options),
7373
);
7474
},
7575
};
7676

77-
function processColors(styles: StylesIOS) {
77+
function processColors(options: StylesIOS) {
7878
if (Platform.OS === 'ios') {
7979
return {
8080
// the types are not 100% correct
81-
messageColor: processColor(styles.textColor) as number | undefined,
82-
backgroundColor: processColor(styles.backgroundColor) as
81+
...options,
82+
messageColor: processColor(options.textColor) as number | undefined,
83+
backgroundColor: processColor(options.backgroundColor) as
8384
| number
8485
| undefined,
8586
};

0 commit comments

Comments
 (0)