Browse Source

ADD: Notifications

ADD: Android notifications

ADD: Switches for push type

FIX: Removed unused var

FIX: Remove unused prop
pushn
Marcos Rodriguez 5 years ago
parent
commit
b6e4f9ebf5
  1. 5
      MainBottomTabs.js
  2. 1
      android/app/build.gradle
  3. 40
      android/app/google-services.json
  4. 3
      android/app/src/main/assets/appcenter-config.json
  5. 2
      android/build.gradle
  6. 71
      class/BlueNotifications.js
  7. 14
      ios/AppCenter-Config.plist
  8. 3
      ios/BlueWallet.xcodeproj/project.pbxproj
  9. 4
      ios/BlueWallet/AppDelegate.h
  10. 33
      ios/BlueWallet/AppDelegate.m
  11. 8
      ios/BlueWallet/BlueWalletRelease.entitlements
  12. 10
      ios/BlueWallet/Info.plist
  13. 14
      ios/Podfile.lock
  14. 69
      package-lock.json
  15. 1
      package.json
  16. 73
      screen/settings/notifications.js
  17. 8
      screen/settings/settings.js

5
MainBottomTabs.js

@ -48,6 +48,7 @@ import LappBrowser from './screen/lnd/browser';
import LNDCreateInvoice from './screen/lnd/lndCreateInvoice';
import LNDViewInvoice from './screen/lnd/lndViewInvoice';
import LNDViewAdditionalInvoiceInformation from './screen/lnd/lndViewAdditionalInvoiceInformation';
import SettingsNotifications from './screen/settings/notifications';
const ReorderWalletsStackNavigator = createStackNavigator({
ReorderWallets: {
@ -141,6 +142,10 @@ const WalletsStackNavigator = createStackNavigator(
screen: ElectrumSettings,
path: 'ElectrumSettings',
},
SettingsNotifications: {
screen: SettingsNotifications,
path: 'SettingsNotifications',
},
LNDViewInvoice: {
screen: LNDViewInvoice,
swipeEnabled: false,

1
android/app/build.gradle

@ -173,3 +173,4 @@ task copyDownloadableDepsToLibs(type: Copy) {
}
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
apply plugin: 'com.google.gms.google-services'

40
android/app/google-services.json

@ -0,0 +1,40 @@
{
"project_info": {
"project_number": "541972087747",
"firebase_url": "https://bluewallet-a33f1.firebaseio.com",
"project_id": "bluewallet-a33f1",
"storage_bucket": "bluewallet-a33f1.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:541972087747:android:ee6cc9c4a1be5b37df3376",
"android_client_info": {
"package_name": "io.bluewallet.bluewallet"
}
},
"oauth_client": [
{
"client_id": "541972087747-20h4jb8vovckcdr56skfigcf3odocg64.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyDLYHQwXTcvPlSgT7bIEoCxqdRSqMNMuac"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "541972087747-20h4jb8vovckcdr56skfigcf3odocg64.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}

3
android/app/src/main/assets/appcenter-config.json

@ -1,3 +1,4 @@
{
"app_secret": "7a010505-cccc-4e40-aa6b-fbbe0624c8d9"
"app_secret": "7a010505-cccc-4e40-aa6b-fbbe0624c8d9",
"enable_push_in_javascript": true
}

2
android/build.gradle

@ -16,7 +16,7 @@ buildscript {
}
dependencies {
classpath("com.android.tools.build:gradle:3.4.1")
classpath 'com.google.gms:google-services:4.3.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}

71
class/BlueNotifications.js

@ -0,0 +1,71 @@
import Push from 'appcenter-push';
import { AppState, Alert } from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
export default class BlueNotifications {
static PRICE_FLUCTUATION = 'NOTIFICATION_PRICE_FLUCTUATION';
static NEWS = 'NOTIFICATION_NEWS';
static async setEnabled(value) {
let setListener = !(await Push.isEnabled());
await Push.setEnabled(value);
if (setListener) {
await BlueNotifications.setListener();
}
await BlueNotifications.setNotificationTypeEnabled(BlueNotifications.NEWS, value);
await BlueNotifications.setNotificationTypeEnabled(BlueNotifications.PRICE_FLUCTUATION, values);
}
static async isEnabled() {
return Push.isEnabled();
}
static async isNotificationTypeEnabled(notificatonType) {
try {
const notificationEnabled = await AsyncStorage.getItem(notificatonType);
return !!notificationEnabled;
} catch {
return false;
}
}
static async setNotificationTypeEnabled(notificatonType, isEnabled) {
return AsyncStorage.setItem(notificatonType, isEnabled ? '1' : '');
}
static async setListener() {
if (await Push.isEnabled()) {
Push.setListener({
onPushNotificationReceived: async function(pushNotification) {
let message = pushNotification.message;
let title = pushNotification.title;
console.log(pushNotification);
if (message === null) {
// Android messages received in the background don't include a message. On Android, that fact can be used to
// check if the message was received in the background or foreground. For iOS the message is always present.
title = 'Android background';
message = '<empty>';
}
// Custom name/value pairs set in the App Center web portal are in customProperties
if (pushNotification.customProperties && Object.keys(pushNotification.customProperties).length > 0) {
if (!(await BlueNotifications.isNotificationTypeEnabled(pushNotification.customProperties.type))) {
return;
}
}
if (AppState.currentState === 'active') {
Alert.alert(title, message);
} else {
// Sometimes the push callback is received shortly before the app is fully active in the foreground.
// In this case you'll want to save off the notification info and wait until the app is fully shown
// in the foreground before displaying any UI. You could use AppState.addEventListener to be notified
// when the app is fully in the foreground.
}
},
});
}
}
}
BlueNotifications.setListener();

14
ios/AppCenter-Config.plist

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AppSecret</key>
<string>e83710b1-61c2-497b-b0f7-c3b6ab79f2d8</string>
</dict>
</plist>
<dict>
<key>EnablePushInJavascript</key>
<true/>
<key>AppSecret</key>
<string>e83710b1-61c2-497b-b0f7-c3b6ab79f2d8</string>
</dict>
</plist>

3
ios/BlueWallet.xcodeproj/project.pbxproj

@ -132,6 +132,7 @@
2D16E6891FA4F8E400B85C8A /* libReact.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libReact.a; sourceTree = BUILT_PRODUCTS_DIR; };
2FCC2CD6FF4448229D0CE0F3 /* MaterialCommunityIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialCommunityIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf"; sourceTree = "<group>"; };
3208E93822F63279007F5A27 /* AppCenter-Config.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "AppCenter-Config.plist"; sourceTree = "<group>"; };
32574C6C2349A5FC00C726A9 /* BlueWalletRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = BlueWalletRelease.entitlements; path = BlueWallet/BlueWalletRelease.entitlements; sourceTree = "<group>"; };
32B5A3282334450100F8D608 /* BlueWallet-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BlueWallet-Bridging-Header.h"; sourceTree = "<group>"; };
32B5A3292334450100F8D608 /* Bridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bridge.swift; sourceTree = "<group>"; };
32F0A24F2310B0700095C559 /* BlueWalletWatch Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "BlueWalletWatch Extension.entitlements"; sourceTree = "<group>"; };
@ -294,6 +295,7 @@
13B07FAE1A68108700A75B9A /* BlueWallet */ = {
isa = PBXGroup;
children = (
32574C6C2349A5FC00C726A9 /* BlueWalletRelease.entitlements */,
32F0A2502310B0910095C559 /* BlueWallet.entitlements */,
3208E93822F63279007F5A27 /* AppCenter-Config.plist */,
008F07F21AC5B25A0029DE68 /* main.jsbundle */,
@ -1131,6 +1133,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = BlueWallet/BlueWalletRelease.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;

4
ios/BlueWallet/AppDelegate.h

@ -6,13 +6,15 @@
*/
#import <UIKit/UIKit.h>
#import <UserNotifications/UserNotifications.h>
@import WatchConnectivity;
@class WatchBridge;
@interface AppDelegate : UIResponder <UIApplicationDelegate, WCSessionDelegate>
@interface AppDelegate : UIResponder <UIApplicationDelegate, WCSessionDelegate, UNUserNotificationCenterDelegate>
@property (nonatomic, strong) UIWindow *window;
@property(nonatomic, strong) WatchBridge *watchBridge;
@property(nonatomic, strong) WCSession *session;
@property(nonatomic) BOOL didReceiveNotificationInForeground;
@end

33
ios/BlueWallet/AppDelegate.m

@ -20,6 +20,7 @@
#import <AppCenterReactNative.h>
#import <AppCenterReactNativeAnalytics.h>
#import <AppCenterReactNativeCrashes.h>
#import <AppCenterReactNativePush.h>
@implementation AppDelegate
@ -28,6 +29,12 @@
[AppCenterReactNative register];
[AppCenterReactNativeAnalytics registerWithInitiallyEnabled:true];
[AppCenterReactNativeCrashes registerWithAutomaticProcessing];
[AppCenterReactNativePush register];
if (@available(iOS 10.0, *)) {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
}
NSURL *jsCodeLocation;
@ -48,6 +55,7 @@
self.session = self.watchBridge.session;
[self.session activateSession];
self.session.delegate = self;
return YES;
}
@ -59,6 +67,31 @@
return NO;
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions options)) completionHandler API_AVAILABLE(ios(10.0)) {
// Do something, e.g. set a BOOL @property to track the foreground state.
self.didReceiveNotificationInForeground = YES;
// Complete handling the notification.
completionHandler(UNNotificationPresentationOptionNone);
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(ios(10.0)) {
// Perform the task associated with the action.
if ([[response actionIdentifier] isEqualToString:UNNotificationDefaultActionIdentifier]) {
// User tapped on notification
}
// Complete handling the notification.
completionHandler();
}
- (void)sessionDidDeactivate:(WCSession *)session {
[session activateSession];
}

8
ios/BlueWallet/BlueWalletRelease.entitlements

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
</dict>
</plist>

10
ios/BlueWallet/Info.plist

@ -62,10 +62,10 @@
<string>This alert should not show up as we do not require this data</string>
<key>NSCameraUsageDescription</key>
<string>In order to quickly scan the recipient&apos;s address, we need your permission to use the camera to scan their QR Code.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This alert should not show up as we do not require this data</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This alert should not show up as we do not require this data</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This alert should not show up as we do not require this data</string>
<key>NSMicrophoneUsageDescription</key>
<string>This alert should not show up as we do not require this data</string>
<key>NSMotionUsageDescription</key>
@ -94,6 +94,12 @@
<string>SimpleLineIcons.ttf</string>
<string>Zocial.ttf</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>

14
ios/Podfile.lock

@ -10,11 +10,17 @@ PODS:
- AppCenter/Crashes
- AppCenterReactNativeShared
- React
- appcenter-push (2.4.0):
- AppCenter/Push
- AppCenterReactNativeShared
- React
- AppCenter/Analytics (2.4.0):
- AppCenter/Core
- AppCenter/Core (2.4.0)
- AppCenter/Crashes (2.4.0):
- AppCenter/Core
- AppCenter/Push (2.4.0):
- AppCenter/Core
- AppCenterReactNativeShared (2.4.0):
- AppCenter/Core (= 2.4.0)
- boost-for-react-native (1.63.0)
@ -160,7 +166,7 @@ PODS:
- React
- Sentry (~> 4.1.3)
- swift_qrcodejs (1.1.2)
- TcpSockets (3.3.1):
- TcpSockets (3.3.2):
- React
- ToolTipMenu (5.2.1):
- React
@ -170,6 +176,7 @@ DEPENDENCIES:
- appcenter (from `../node_modules/appcenter/ios`)
- appcenter-analytics (from `../node_modules/appcenter-analytics/ios`)
- appcenter-crashes (from `../node_modules/appcenter-crashes/ios`)
- appcenter-push (from `../node_modules/appcenter-push/ios`)
- BVLinearGradient (from `../node_modules/react-native-linear-gradient`)
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- EFQRCode (= 5.1.0)
@ -236,6 +243,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/appcenter-analytics/ios"
appcenter-crashes:
:path: "../node_modules/appcenter-crashes/ios"
appcenter-push:
:path: "../node_modules/appcenter-push/ios"
BVLinearGradient:
:path: "../node_modules/react-native-linear-gradient"
DoubleConversion:
@ -336,6 +345,7 @@ SPEC CHECKSUMS:
appcenter: 4072e79b8d037d99056e6fc6556224b4525b5829
appcenter-analytics: 94be52eca805d586207d710aac95e0ddca8b3ddb
appcenter-crashes: 30caece47856aee7c4c7449d4f4dae2d69795bd6
appcenter-push: f6c62e8e55cf09f53076674c185df72f68ea9dd7
AppCenterReactNativeShared: 57a66e6539e9abe6079ba8f93a002bc3f95788cc
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
BVLinearGradient: 8cbc5155c978f2e43098818c91d206d07aae6d30
@ -386,7 +396,7 @@ SPEC CHECKSUMS:
Sentry: 4e8a17b61ddd116f89536cc81d567fdee1ebca96
SentryReactNative: 07237139c00366ea2e75ae3e5c566e7a71c27a90
swift_qrcodejs: 4d024fc98b0778b804ec6a5c810880fd092aec9d
TcpSockets: c5a7c1a459b5e4b0964d64aa2f47202d6c9d1b73
TcpSockets: 8d839b9b14f6f344d98e4642ded13ab3112b462d
ToolTipMenu: c158702a26154d892bc9e6eaa7d7382f0f1ee16e
yoga: 312528f5bbbba37b4dcea5ef00e8b4033fdd9411

69
package-lock.json

@ -1892,6 +1892,75 @@
}
}
},
"appcenter-push": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/appcenter-push/-/appcenter-push-2.4.0.tgz",
"integrity": "sha512-L+MRzEOEkyV8/NAVUPkwdMXJPJywE74ln39wyZpTJQ+rWtaAd3X3BsRem2sy8OBxjvWT251ILUDT611D6hZw2A==",
"requires": {
"appcenter": "2.4.0"
},
"dependencies": {
"appcenter": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/appcenter/-/appcenter-2.4.0.tgz",
"integrity": "sha512-BV9/b9lH8mkg4O6NMXRFZ2PzTTt8GXnIWTSOZRTJBJxUOm4XgsguQ/yfdWckZeUTwsnA8u4aE0C/pKzNaw9d3A==",
"requires": {
"appcenter-link-scripts": "2.4.0"
}
},
"appcenter-link-scripts": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/appcenter-link-scripts/-/appcenter-link-scripts-2.4.0.tgz",
"integrity": "sha512-83uH2hb2C2ORp9yqipKaA3uR4yt2w+0x5Tyli6ukvqTrKm1hBJwL+I4lM2TD4jOrfkycgiRtVmPPIwQyFZkp6g==",
"requires": {
"debug": "2.2.0",
"glob": "5.0.15",
"mkdirp": "0.5.1",
"plist": "3.0.1",
"which": "1.2.11",
"xcode": "2.0.0"
}
},
"debug": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
"integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
"requires": {
"ms": "0.7.1"
}
},
"glob": {
"version": "5.0.15",
"resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
"integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=",
"requires": {
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "2 || 3",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"isexe": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-1.1.2.tgz",
"integrity": "sha1-NvPiLmB1CSD15yQaR2qMakInWtA="
},
"ms": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
"integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg="
},
"which": {
"version": "1.2.11",
"resolved": "https://registry.npmjs.org/which/-/which-1.2.11.tgz",
"integrity": "sha1-yLLu6muMFln6fB3U/aq+lTPcXos=",
"requires": {
"isexe": "^1.1.1"
}
}
}
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",

1
package.json

@ -53,6 +53,7 @@
"appcenter": "2.4.0",
"appcenter-analytics": "2.4.0",
"appcenter-crashes": "2.4.0",
"appcenter-push": "2.4.0",
"bech32": "1.1.3",
"bignumber.js": "9.0.0",
"bip21": "2.0.2",

73
screen/settings/notifications.js

@ -0,0 +1,73 @@
import React, { Component } from 'react';
import { View } from 'react-native';
import { SafeBlueArea, BlueNavigationStyle, BlueListItem, BlueText, BlueCard } from '../../BlueComponents';
import BlueNotifications from '../../class/BlueNotifications';
export default class SettingsNotifications extends Component {
static navigationOptions = () => ({
...BlueNavigationStyle(),
title: 'Notifications',
});
constructor(props) {
super(props);
this.state = { notificationsEnabled: false, blueWalletNewsNotificationEnabled: false, bitcoinPriceNotificationEnabled: false };
}
notificationSwitchValueChanged = async value => {
await BlueNotifications.setEnabled(value);
this.setState({ notificationsEnabled: value, bitcoinPriceNotificationEnabled: value, blueWalletNewsNotificationEnabled: value });
};
bitcoinPriceNotificationSwitchValueChanged = async value => {
await BlueNotifications.setNotificationTypeEnabled(BlueNotifications.PRICE_FLUCTUATION, value);
this.setState({ bitcoinPriceNotificationEnabled: value });
};
blueWalletNewsNotificationSwitchValueChanged = async value => {
await BlueNotifications.setNotificationTypeEnabled(BlueNotifications.NEWS, value);
this.setState({ blueWalletNewsNotificationEnabled: value });
};
async componentDidMount() {
const notificationsEnabled = await BlueNotifications.isEnabled();
const blueWalletNewsNotificationEnabled = await BlueNotifications.isNotificationTypeEnabled(BlueNotifications.NEWS);
const bitcoinPriceNotificationEnabled = await BlueNotifications.isNotificationTypeEnabled(BlueNotifications.PRICE_FLUCTUATION);
this.setState({ notificationsEnabled, blueWalletNewsNotificationEnabled, bitcoinPriceNotificationEnabled });
}
render() {
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1 }}>
<View style={{ flex: 1 }}>
<BlueListItem
hideChevron
switchButton
switched={this.state.notificationsEnabled}
onSwitch={this.notificationSwitchValueChanged}
title="Notifications"
/>
<BlueCard>
<BlueText>By enabling, you will receive notifications related to:</BlueText>
</BlueCard>
<BlueListItem
hideChevron
switchButton
switchDisabled={!this.state.notificationsEnabled}
switched={this.state.blueWalletNewsNotificationEnabled}
onSwitch={this.blueWalletNewsNotificationSwitchValueChanged}
title="BlueWallet News"
/>
<BlueListItem
hideChevron
switchButton
switchDisabled={!this.state.notificationsEnabled}
switched={this.state.bitcoinPriceNotificationEnabled}
onSwitch={this.bitcoinPriceNotificationSwitchValueChanged}
title="Bitcoin Price Fluctuation"
/>
</View>
</SafeBlueArea>
);
}
}

8
screen/settings/settings.js

@ -29,7 +29,7 @@ export default class Settings extends Component {
}
async componentDidMount() {
let advancedModeEnabled = !!(await AsyncStorage.getItem(AppStorage.ADVANCED_MODE_ENABLED));
const advancedModeEnabled = !!(await AsyncStorage.getItem(AppStorage.ADVANCED_MODE_ENABLED));
this.setState({
isLoading: false,
advancedModeEnabled,
@ -57,6 +57,11 @@ export default class Settings extends Component {
{BlueApp.getWallets().length > 1 && (
<BlueListItem component={TouchableOpacity} onPress={() => this.props.navigation.navigate('DefaultView')} title="On Launch" />
)}
<BlueListItem
component={TouchableOpacity}
title="Notifications"
onPress={() => this.props.navigation.navigate('SettingsNotifications')}
/>
<TouchableOpacity onPress={() => this.props.navigation.navigate('EncryptStorage')}>
<BlueListItem title={loc.settings.encrypt_storage} />
</TouchableOpacity>
@ -96,6 +101,5 @@ export default class Settings extends Component {
Settings.propTypes = {
navigation: PropTypes.shape({
navigate: PropTypes.func,
goBack: PropTypes.func,
}),
};

Loading…
Cancel
Save