Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement snackbar visibility events #191

Merged
merged 14 commits into from
May 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,53 @@ The old keys will continue to work for now but are deprecated and may be removed

Dismisses any existing Snackbars.

## Advanced usage

### Snackbar events
You can have information on snackbar visibility.

```js
componentDidMount() {
const SnackbarEventEmitter = new NativeEventEmitter(
NativeModules.RNSnackbar,
);
this.eventListener = SnackbarEventEmitter.addListener('onSnackbarVisibility', (event) => {
console.log(event.event);
});
}
componentWillUnmount() {
this.eventListener.remove();
}
```

Or, with functional components:

```js
useEffect(() => {
const subscription = new NativeEventEmitter(
NativeModules.RNSnackbar,
).addListener('onSnackbarVisibility', event => {
console.log(event.event);
});
return () => {
subscription.remove();
};
}, []);
```

Where event is one of the following options :

| Key | Data type | Value | Description |
|-----|-----------|----------------|-------------|
| `Snackbar.DISMISS_EVENT_SWIPE` | `number` | 0 | Indicates that the Snackbar was dismissed via a swipe. |
| `Snackbar.DISMISS_EVENT_ACTION` | `number` | 1 | Indicates that the Snackbar was dismissed via an action click. |
| `Snackbar.DISMISS_EVENT_TIMEOUT` | `number` | 2 | Indicates that the Snackbar was dismissed via a timeout. |
| `Snackbar.DISMISS_EVENT_MANUAL` | `number` | 3 | Indicates that the Snackbar was dismissed via Snackbar.dismiss() call. |
| `Snackbar.DISMISS_EVENT_CONSECUTIVE` | `number` | 4 | Indicates that the Snackbar was dismissed from a new Snackbar being shown. |
| `Snackbar.SHOW_EVENT` | `number` | 5 | Indicates that Snackbar appears |


## Troubleshooting

#### Snackbar not appearing [Android]
Expand Down
1 change: 1 addition & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ repositories {

dependencies {
implementation "com.facebook.react:react-native:${_reactNativeVersion}"
implementation "androidx.appcompat:appcompat:1.3.1"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "com.google.android.material:material:${_materialVersion}"
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

import android.graphics.Color;
import android.graphics.Typeface;

import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;

import android.os.Build;
import android.content.Context;
import android.util.DisplayMetrics;
Expand All @@ -14,28 +10,42 @@
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Collections;

public class SnackbarModule extends ReactContextBaseJavaModule {

private static final String REACT_NAME = "RNSnackbar";

private List<Snackbar> mActiveSnackbars = new ArrayList<>();

private static final String ON_SNACKBAR_VISIBILITY_EVENT = "onSnackbarVisibility";
private static final int SHOW_EVENT = 5;

private final List<Snackbar> mActiveSnackbars = new ArrayList<>();

public SnackbarModule(ReactApplicationContext reactContext) {
super(reactContext);
}

@NonNull
@Override
public String getName() {
return REACT_NAME;
Expand All @@ -48,6 +58,12 @@ public Map<String, Object> getConstants() {
constants.put("LENGTH_LONG", Snackbar.LENGTH_LONG);
constants.put("LENGTH_SHORT", Snackbar.LENGTH_SHORT);
constants.put("LENGTH_INDEFINITE", Snackbar.LENGTH_INDEFINITE);
constants.put("DISMISS_EVENT_SWIPE", Snackbar.Callback.DISMISS_EVENT_SWIPE);
constants.put("DISMISS_EVENT_ACTION", Snackbar.Callback.DISMISS_EVENT_ACTION);
constants.put("DISMISS_EVENT_TIMEOUT", Snackbar.Callback.DISMISS_EVENT_TIMEOUT);
constants.put("DISMISS_EVENT_MANUAL", Snackbar.Callback.DISMISS_EVENT_MANUAL);
constants.put("DISMISS_EVENT_CONSECUTIVE", Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE);
constants.put("SHOW_EVENT", SHOW_EVENT);

return constants;
}
Expand Down Expand Up @@ -107,6 +123,16 @@ public void dismiss() {
mActiveSnackbars.clear();
}

@ReactMethod
public void addListener(String eventName) {
// Keep: Required for RN built in Event Emitter Calls.
}

@ReactMethod
public void removeListeners(Integer count) {
// Keep: Required for RN built in Event Emitter Calls.
}

private void displaySnackbar(View view, ReadableMap options, final Callback callback) {
String text = getOptionValue(options, "text", "");
int duration = getOptionValue(options, "duration", Snackbar.LENGTH_SHORT);
Expand Down Expand Up @@ -191,6 +217,18 @@ public void onClick(View v) {
}
}

snackbar.addCallback(new BaseTransientBottomBar.BaseCallback<Snackbar>() {
@Override
public void onDismissed(Snackbar transientBottomBar, int event) {
sendSnackbarVisibilityEvent(event);
}

@Override
public void onShown(Snackbar transientBottomBar) {
sendSnackbarVisibilityEvent(SHOW_EVENT);
}
});

snackbar.show();
}

Expand All @@ -217,6 +255,20 @@ private ArrayList<View> recursiveLoopChildren(ViewGroup view, ArrayList<View> mo
return modals;
}

private void sendSnackbarVisibilityEvent(int event) {
WritableMap params = Arguments.createMap();
params.putInt("event", event);
sendEvent(getReactApplicationContext(), ON_SNACKBAR_VISIBILITY_EVENT, params);
}

private void sendEvent(ReactContext reactContext,
String eventName,
@Nullable WritableMap params) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}

private String getOptionValue(ReadableMap options, String key, String fallback) {
return options.hasKey(key) ? options.getString(key) : fallback;
}
Expand Down
21 changes: 21 additions & 0 deletions example/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ target 'SnackbarExample' do
# Pods for testing
end
use_native_modules!

post_install do |installer|
## Fix for XCode 12.5
find_and_replace("../node_modules/react-native/React/CxxBridge/RCTCxxBridge.mm",
"_initializeModules:(NSArray<id<RCTBridgeModule>> *)modules", "_initializeModules:(NSArray<Class> *)modules")
find_and_replace("../node_modules/react-native/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm",
"RCTBridgeModuleNameForClass(module))", "RCTBridgeModuleNameForClass(Class(module)))")
end
end
target 'SnackbarExample-tvOS' do
# Pods for SnackbarExample-tvOS
Expand All @@ -43,3 +51,16 @@ target 'SnackbarExample-tvOS' do
# Pods for testing
end
end

def find_and_replace(dir, findstr, replacestr)
Dir[dir].each do |name|
text = File.read(name)
replace = text.gsub(findstr,replacestr)
if text != replace
puts "Fix: " + name
File.open(name, "w") { |file| file.puts replace }
STDOUT.flush
end
end
Dir[dir + '*/'].each(&method(:find_and_replace))
end
10 changes: 5 additions & 5 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ PODS:
- React-cxxreact (= 0.61.2)
- React-jsi (= 0.61.2)
- ReactCommon/jscallinvoker (= 0.61.2)
- RNSnackbar (2.2.4):
- RNSnackbar (2.4.0):
- React-Core
- Yoga (1.14.0)

Expand Down Expand Up @@ -253,7 +253,7 @@ DEPENDENCIES:
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)

SPEC REPOS:
https://github.com/CocoaPods/Specs.git:
trunk:
- boost-for-react-native

EXTERNAL SOURCES:
Expand Down Expand Up @@ -336,9 +336,9 @@ SPEC CHECKSUMS:
React-RCTText: e3ef6191cdb627855ff7fe8fa0c1e14094967fb8
React-RCTVibration: fb54c732fd20405a76598e431aa2f8c2bf527de9
ReactCommon: 5848032ed2f274fcb40f6b9ec24067787c42d479
RNSnackbar: 86092381dba7a0ed9fbd7acdb82ebbd5bfd26372
RNSnackbar: be3333a21a453ccc272f41a8add5a71f7d44dfcd
Yoga: 14927e37bd25376d216b150ab2a561773d57911f

PODFILE CHECKSUM: 4ec39b585cf6ab1637af9ccf9756b2ea164b2d94
PODFILE CHECKSUM: fb6c1683e9500b74f4151c37cfa77a763e247845

COCOAPODS: 1.9.3
COCOAPODS: 1.10.1
15 changes: 14 additions & 1 deletion example/src/App.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
import React, { Component } from 'react';
import { Text, View, TouchableOpacity } from 'react-native';
import { Text, View, TouchableOpacity, NativeEventEmitter, NativeModules } from 'react-native';
import Snackbar from 'react-native-snackbar';

import styles from '../styles';

// eslint-disable-next-line react/prefer-stateless-function
class Example extends Component {
componentDidMount() {
const SnackbarEventEmitter = new NativeEventEmitter(
NativeModules.RNSnackbar,
);
this.eventListener = SnackbarEventEmitter.addListener('onSnackbarVisibility', (event) => {
console.log(event.event);
});
}

componentWillUnmount() {
this.eventListener.remove();
}

render() {
return (
<View style={styles.container}>
Expand Down
4 changes: 4 additions & 0 deletions example/src/__tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import renderer from 'react-test-renderer';

import App from '../App';

jest.mock(
'../../node_modules/react-native/Libraries/EventEmitter/NativeEventEmitter',
);

describe('Snackbar example app', () => {
it('renders without crashing', () => {
renderer.create(<App />);
Expand Down
2 changes: 1 addition & 1 deletion example/sync.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
#
# Saved as its own file so that Travis uses the correct shell to run it.

mkdir node_modules/react-native-snackbar
mkdir -p node_modules/react-native-snackbar
cp -R ../{package.json,android,ios,lib} node_modules/react-native-snackbar/
3 changes: 2 additions & 1 deletion ios/RNSnackBarView.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
//

#import <UIKit/UIKit.h>
#import "RNSnackbar.h"

@interface RNSnackBarView : UIView

+ (void)showWithOptions:(NSDictionary *)options andCallback:(void (^)())callback;
+ (void)showWithOptions:(NSDictionary *)options andCallback:(void (^)())callback rnSnackbar:(RNSnackbar *)rnSnackbar;
+ (void)dismiss;

@end
Loading