Skip to content

Commit e623531

Browse files
authored
Merge pull request #186 from Polarisation/feature/margins
Add support for setting bottom margin
2 parents 707415c + 0450417 commit e623531

File tree

7 files changed

+78
-22
lines changed

7 files changed

+78
-22
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ Shows a Snackbar, dismissing any existing Snackbar first. Accepts an object with
6868
| `text` | `string` | Required. | The message to show. |
6969
| `duration` | See below | `Snackbar.LENGTH_SHORT` | How long to display the Snackbar. |
7070
| `numberOfLines` | `number` | `2` | The max number of text lines to allow before ellipsizing. |
71+
| `marginBottom` | `number` | `0` | Margin from bottom. |
7172
| `textColor` | `string` or `style` | `'white'` | The color of the message text. |
7273
| `backgroundColor` | `string` or `style` | `undefined` (dark gray) | The background color for the whole Snackbar. |
7374
| `fontFamily` | `string` | `undefined` | [Android only] The basename of a `.ttf` font from `assets/fonts/` (see [setup guide](https://github.com/facebook/react-native/issues/25852) and [example app](/example), remember to `react-native link` after). |
@@ -134,7 +135,7 @@ If you want to help contribute to this library, here are local setup steps:
134135
1. Set up the example app too: `cd example && yarn install`
135136
1. Within the example directory, `react-native run-android` to run it
136137
137-
The example app will update automatically when changing JS code. To see your changes in the example app after updating native library code:
138+
The example app will update automatically when changing JS code. To see your changes in the example app after updating native library code, reinstall it via:
138139
139-
1. `yarn install && react-native run-android` in the example directory
140+
1. `yarn add file:.. && react-native run-android` in the example directory
140141
1. Type "rr" in the app to trigger a reload

android/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,6 @@ repositories {
4848

4949
dependencies {
5050
implementation "com.facebook.react:react-native:${_reactNativeVersion}"
51+
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
5152
implementation "com.google.android.material:material:${_materialVersion}"
5253
}

android/src/main/java/com/azendoo/reactnativesnackbar/SnackbarModule.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22

33
import android.graphics.Color;
44
import android.graphics.Typeface;
5+
6+
import com.google.android.material.snackbar.BaseTransientBottomBar;
57
import com.google.android.material.snackbar.Snackbar;
68

79
import android.os.Build;
10+
import android.content.Context;
11+
import android.util.DisplayMetrics;
812
import android.util.Log;
913
import android.view.View;
1014
import android.view.ViewGroup;
@@ -109,6 +113,7 @@ private void displaySnackbar(View view, ReadableMap options, final Callback call
109113
int numberOfLines = getOptionValue(options, "numberOfLines", 2);
110114
int textColor = getOptionValue(options, "textColor", Color.WHITE);
111115
boolean rtl = getOptionValue(options, "rtl", false);
116+
int marginBottom = getOptionValue(options, "marginBottom", 0);
112117
String fontFamily = getOptionValue(options, "fontFamily", null);
113118
Typeface font = null;
114119
if (fontFamily != null) {
@@ -128,13 +133,23 @@ private void displaySnackbar(View view, ReadableMap options, final Callback call
128133
e.printStackTrace();
129134
return;
130135
}
136+
137+
snackbar.setAnimationMode(marginBottom == 0
138+
? BaseTransientBottomBar.ANIMATION_MODE_SLIDE
139+
: BaseTransientBottomBar.ANIMATION_MODE_FADE
140+
);
141+
131142
View snackbarView = snackbar.getView();
132143

133144
if (rtl && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
134145
snackbarView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
135146
snackbarView.setTextDirection(View.TEXT_DIRECTION_RTL);
136147
}
137148

149+
if (marginBottom != 0) {
150+
snackbarView.setTranslationY(-(convertDpToPixel(marginBottom, snackbarView.getContext())));
151+
}
152+
138153
TextView snackbarText = snackbarView.findViewById(com.google.android.material.R.id.snackbar_text);
139154
snackbarText.setMaxLines(numberOfLines);
140155
snackbarText.setTextColor(textColor);
@@ -179,6 +194,10 @@ public void onClick(View v) {
179194
snackbar.show();
180195
}
181196

197+
public static float convertDpToPixel(float dp, Context context){
198+
return dp * ((float) context.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT);
199+
}
200+
182201
/**
183202
* Loop through all child modals and save references to them.
184203
*/

example/src/App.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,15 @@ class Example extends Component {
8282
<Text style={styles.button}>Snackbar with RTL text</Text>
8383
</TouchableOpacity>
8484

85+
<TouchableOpacity
86+
onPress={() => Snackbar.show({
87+
text: 'Use a bottom margin to avoid covering navigational elements such as a tab bar.',
88+
marginBottom: 500,
89+
})}
90+
>
91+
<Text style={styles.button}>Snackbar with bottom margin</Text>
92+
</TouchableOpacity>
93+
8594
<TouchableOpacity onPress={() => Snackbar.dismiss()}>
8695
<Text style={styles.button}>Dismiss active Snackbar</Text>
8796
</TouchableOpacity>

ios/RNSnackBarView.m

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ @interface RNSnackBarView () {
2929
@property(nonatomic, strong) UIColor *textColor;
3030
@property(nonatomic, strong) NSString *actionText;
3131
@property(nonatomic, strong) UIColor *actionTextColor;
32+
@property(nonatomic, strong) NSNumber *marginBottom;
33+
@property(nonatomic, strong) NSArray<NSLayoutConstraint *> *verticalPaddingConstraints;
3234
@property(nonatomic, strong) void (^pendingCallback)();
3335
@property(nonatomic, strong) void (^callback)();
3436

@@ -71,16 +73,6 @@ - (instancetype)init {
7173
}
7274

7375
- (void)buildView {
74-
CGFloat topPadding = 14;
75-
CGFloat bottomPadding = topPadding;
76-
77-
if (@available(iOS 11.0, *)) {
78-
UIWindow *window = [[UIApplication sharedApplication] delegate].window;
79-
80-
if (window.safeAreaInsets.bottom > bottomPadding)
81-
bottomPadding = window.safeAreaInsets.bottom;
82-
}
83-
8476
self.backgroundColor = [UIColor colorWithRed:0.196078F
8577
green:0.196078F
8678
blue:0.196078F
@@ -104,13 +96,6 @@ - (void)buildView {
10496
[actionButton setTranslatesAutoresizingMaskIntoConstraints:NO];
10597
[self addSubview:actionButton];
10698

107-
[self addConstraints:[NSLayoutConstraint
108-
constraintsWithVisualFormat:
109-
[NSString stringWithFormat:@"V:|-%f-[textLabel]-%f-|", topPadding,
110-
bottomPadding]
111-
options:0
112-
metrics:nil
113-
views:@{@"textLabel" : textLabel}]];
11499
[self addConstraint:[NSLayoutConstraint constraintWithItem:actionButton
115100
attribute:NSLayoutAttributeCenterY
116101
relatedBy:NSLayoutRelationEqual
@@ -185,7 +170,30 @@ - (void)presentWithDuration:(NSNumber *)duration {
185170
UIWindow *keyWindow = [[UIApplication sharedApplication] delegate].window;
186171
[keyWindow addSubview:self];
187172
[self setTranslatesAutoresizingMaskIntoConstraints:false];
188-
[keyWindow addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[self(>=48)]|"
173+
174+
// Set vertical padding
175+
CGFloat topPadding = 14;
176+
CGFloat bottomPadding = topPadding;
177+
if (@available(iOS 11.0, *)) {
178+
UIWindow *window = [[UIApplication sharedApplication] delegate].window;
179+
180+
// If no bottom margin, increase bottom padding to size of safe area inset
181+
if ([self.marginBottom integerValue] == 0 && window.safeAreaInsets.bottom > bottomPadding)
182+
bottomPadding = window.safeAreaInsets.bottom;
183+
}
184+
NSLog([NSString stringWithFormat:@"V:|-%f-[textLabel]-%f-|", topPadding,
185+
bottomPadding]);
186+
if (self.verticalPaddingConstraints) // Remove old constraints
187+
[self removeConstraints:self.verticalPaddingConstraints];
188+
self.verticalPaddingConstraints = [NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"V:|-%f-[textLabel]-%f-|", topPadding,
189+
bottomPadding]
190+
options:0
191+
metrics:nil
192+
views:@{@"textLabel" : textLabel}];
193+
[self addConstraints:self.verticalPaddingConstraints];
194+
195+
// Set margins
196+
[keyWindow addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"V:[self(>=48)]-%@-|", self.marginBottom]
189197
options:0
190198
metrics:nil
191199
views:@{@"self" : self}]];
@@ -194,15 +202,20 @@ - (void)presentWithDuration:(NSNumber *)duration {
194202
metrics:nil
195203
views:@{@"self" : self}]];
196204

197-
self.transform = CGAffineTransformMakeTranslation(0, self.bounds.size.height);
205+
// Snackbar will slide up from bottom, unless a bottom margin is set in which case we use a fade animation
206+
self.transform = CGAffineTransformMakeTranslation(0, [self.marginBottom integerValue] == 0 ? self.bounds.size.height : 0);
198207
textLabel.alpha = 0;
199208
actionButton.alpha = 0;
209+
if ([self.marginBottom integerValue] == 0) {
210+
self.alpha = 0;
211+
}
200212
self.state = RNSnackBarViewStatePresenting;
201213
[UIView animateWithDuration:ANIMATION_DURATION
202214
animations:^{
203215
self.transform = CGAffineTransformIdentity;
204216
textLabel.alpha = 1;
205217
actionButton.alpha = 1;
218+
self.alpha = 1;
206219
}
207220
completion:^(BOOL finished) {
208221
self.state = RNSnackBarViewStateDisplayed;
@@ -227,7 +240,8 @@ - (void)dismiss {
227240
self.state = RNSnackBarViewStateDismissing;
228241
[UIView animateWithDuration:ANIMATION_DURATION
229242
animations:^{
230-
self.transform = CGAffineTransformMakeTranslation(0, self.bounds.size.height);
243+
self.transform = CGAffineTransformMakeTranslation(0, [self.marginBottom integerValue] == 0 ? self.bounds.size.height : 0);
244+
self.alpha = 0;
231245
}
232246
completion:^(BOOL finished) {
233247
self.state = RNSnackBarViewStateDismissed;
@@ -253,6 +267,8 @@ - (void)show {
253267
NSNumber *numberOfLines = _pendingOptions[@"numberOfLines"];
254268
self.numberOfLines = [RCTConvert int:numberOfLines] ? [RCTConvert int:numberOfLines] : 2;
255269

270+
self.marginBottom = _pendingOptions[@"marginBottom"] ? _pendingOptions[@"marginBottom"] : @(0);
271+
256272
id backgroundColor = _pendingOptions[@"backgroundColor"];
257273
self.backgroundColor = backgroundColor ? [RCTConvert UIColor:backgroundColor]
258274
: [UIColor colorWithRed:0.196078F

src/index.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ export interface SnackBarOptions {
4242
*/
4343
duration?: number;
4444

45+
/**
46+
* Margin from bottom, defaults to 0.
47+
*/
48+
marginBottom?: number;
49+
4550
/**
4651
* Snackbar text color.
4752
* Accepts various forms of colors such as hex, literals, rgba, etc.

src/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ type SnackBarOptions = {
4444
*/
4545
duration?: number,
4646

47+
/**
48+
* Margin from bottom, defaults to 0.
49+
*/
50+
marginBottom?: number,
51+
4752
/**
4853
* Snackbar text color.
4954
* Accepts various forms of colors such as hex, literals, rgba, etc.

0 commit comments

Comments
 (0)