Commit 1f1da63f authored by Libin Lu's avatar Libin Lu

Merge branch 'sdk-26' of https://github.com/evollu/react-native-fcm into sdk-26

parents c3466ab4 b3d921be
...@@ -131,6 +131,9 @@ android { ...@@ -131,6 +131,9 @@ android {
} }
dependencies { dependencies {
compile(project(':react-native-maps')) {
exclude group: 'com.google.android.gms', module: 'play-services-base'
}
compile project(':react-native-fcm') compile project(':react-native-fcm')
compile fileTree(dir: "libs", include: ["*.jar"]) compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.facebook.react:react-native:+" // From node_modules compile "com.facebook.react:react-native:+" // From node_modules
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-sdk <uses-sdk
android:minSdkVersion="16" android:minSdkVersion="16"
......
package com.google.firebase.quickstart.fcm; package com.google.firebase.quickstart.fcm;
import android.content.Intent;
import com.facebook.react.ReactActivity; import com.facebook.react.ReactActivity;
public class MainActivity extends ReactActivity { public class MainActivity extends ReactActivity {
...@@ -12,4 +14,11 @@ public class MainActivity extends ReactActivity { ...@@ -12,4 +14,11 @@ public class MainActivity extends ReactActivity {
protected String getMainComponentName() { protected String getMainComponentName() {
return "SimpleFcmClient"; return "SimpleFcmClient";
} }
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
} }
...@@ -4,6 +4,7 @@ import android.app.Application; ...@@ -4,6 +4,7 @@ import android.app.Application;
import android.util.Log; import android.util.Log;
import com.facebook.react.ReactApplication; import com.facebook.react.ReactApplication;
import com.airbnb.android.react.maps.MapsPackage;
import com.evollu.react.fcm.FIRMessagingPackage; import com.evollu.react.fcm.FIRMessagingPackage;
import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactNativeHost;
...@@ -25,6 +26,7 @@ public class MainApplication extends Application implements ReactApplication { ...@@ -25,6 +26,7 @@ public class MainApplication extends Application implements ReactApplication {
protected List<ReactPackage> getPackages() { protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList( return Arrays.<ReactPackage>asList(
new MainReactPackage(), new MainReactPackage(),
new MapsPackage(),
new FIRMessagingPackage() new FIRMessagingPackage()
); );
} }
......
rootProject.name = 'SimpleFcmClient' rootProject.name = 'SimpleFcmClient'
include ':react-native-maps'
project(':react-native-maps').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-maps/lib/android')
include ':app' include ':app'
include ':react-native-fcm' include ':react-native-fcm'
......
...@@ -11,17 +11,20 @@ import { ...@@ -11,17 +11,20 @@ import {
TouchableOpacity, TouchableOpacity,
View, View,
Clipboard, Clipboard,
Platform Platform,
ScrollView
} from 'react-native'; } from 'react-native';
import FCM from "react-native-fcm"; import { StackNavigator } from 'react-navigation';
import FCM, {NotificationActionType} from "react-native-fcm";
import {registerKilledListener, registerAppListener} from "./Listeners"; import {registerKilledListener, registerAppListener} from "./Listeners";
import firebaseClient from "./FirebaseClient"; import firebaseClient from "./FirebaseClient";
registerKilledListener(); registerKilledListener();
export default class App extends Component { class MainPage extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
...@@ -38,11 +41,16 @@ export default class App extends Component { ...@@ -38,11 +41,16 @@ export default class App extends Component {
description: 'used for example', description: 'used for example',
priority: 'high' priority: 'high'
}) })
registerAppListener(); registerAppListener(this.props.navigation);
FCM.getInitialNotification().then(notif => { FCM.getInitialNotification().then(notif => {
this.setState({ this.setState({
initNotif: notif initNotif: notif
}) })
if(notif && notif.targetScreen === 'detail'){
setTimeout(()=>{
this.props.navigation.navigate('Detail')
}, 500)
}
}); });
try{ try{
...@@ -65,16 +73,30 @@ export default class App extends Component { ...@@ -65,16 +73,30 @@ export default class App extends Component {
showLocalNotification() { showLocalNotification() {
FCM.presentLocalNotification({ FCM.presentLocalNotification({
vibrate: 500,
title: 'Hello',
channel: 'default', channel: 'default',
body: 'Test Notification', id: new Date().valueOf().toString(), // (optional for instant notification)
big_text: 'i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large, i am large', title: "Test Notification with action", // as FCM payload
priority: "high", body: "Force touch to reply", // as FCM payload (required)
sound: "bell.mp3", sound: "bell.mp3", // "default" or filename
large_icon: "https://image.freepik.com/free-icon/small-boy-cartoon_318-38077.jpg", priority: "high", // as FCM payload
show_in_foreground: true, click_action: "com.myapp.MyCategory", // as FCM payload - this is used as category identifier on iOS.
number: 10 badge: 10, // as FCM payload IOS only, set 0 to clear badges
number: 10, // Android only
ticker: "My Notification Ticker", // Android only
auto_cancel: true, // Android only (default true)
large_icon: "https://image.freepik.com/free-icon/small-boy-cartoon_318-38077.jpg", // Android only
icon: "ic_launcher", // as FCM payload, you can relace this with custom icon you put in mipmap
big_text: "Show when notification is expanded", // Android only
sub_text: "This is a subText", // Android only
color: "red", // Android only
vibrate: 300, // Android only default: 300, no vibration if you pass 0
wake_screen: true, // Android only, wake up screen when notification arrives
group: "group", // Android only
picture: "https://google.png", // Android only bigPicture style
ongoing: true, // Android only
my_custom_data:'my_custom_field_value', // extra data you want to throw
lights: true, // Android only, LED blinking (default false)
show_in_foreground: true // notification when app is in foreground (local & remote)
}); });
} }
...@@ -89,7 +111,10 @@ export default class App extends Component { ...@@ -89,7 +111,10 @@ export default class App extends Component {
priority: "high", priority: "high",
large_icon: "https://image.freepik.com/free-icon/small-boy-cartoon_318-38077.jpg", large_icon: "https://image.freepik.com/free-icon/small-boy-cartoon_318-38077.jpg",
show_in_foreground: true, show_in_foreground: true,
picture: 'https://firebase.google.com/_static/af7ae4b3fc/images/firebase/lockup.png' picture: 'https://firebase.google.com/_static/af7ae4b3fc/images/firebase/lockup.png',
wake_screen: true,
extra1: {a: 1},
extra2: 1
}); });
} }
...@@ -102,10 +127,11 @@ export default class App extends Component { ...@@ -102,10 +127,11 @@ export default class App extends Component {
"data":{ "data":{
"custom_notification": { "custom_notification": {
"title": "Simple FCM Client", "title": "Simple FCM Client",
"body": "This is a notification with only NOTIFICATION.", "body": "Click me to go to detail",
"sound": "default", "sound": "default",
"priority": "high", "priority": "high",
"show_in_foreground": true "show_in_foreground": true,
targetScreen: 'detail'
} }
}, },
"priority": 10 "priority": 10
...@@ -115,9 +141,12 @@ export default class App extends Component { ...@@ -115,9 +141,12 @@ export default class App extends Component {
"to": token, "to": token,
"notification":{ "notification":{
"title": "Simple FCM Client", "title": "Simple FCM Client",
"body": "This is a notification with only NOTIFICATION.", "body": "Click me to go to detail",
"sound": "default" "sound": "default"
}, },
data: {
targetScreen: 'detail'
},
"priority": 10 "priority": 10
} }
} }
...@@ -139,21 +168,21 @@ export default class App extends Component { ...@@ -139,21 +168,21 @@ export default class App extends Component {
firebaseClient.send(JSON.stringify(body), "data"); firebaseClient.send(JSON.stringify(body), "data");
} }
sendRemoteNotificationWithData(token) { showLocalNotificationWithAction() {
let body = { FCM.presentLocalNotification({
"to": token, title: 'Test Notification with action',
"notification":{ body: 'Force touch to reply',
"title": "Simple FCM Client", priority: "high",
"body": "This is a notification with NOTIFICATION and DATA (NOTIF).", show_in_foreground: true,
"sound": "default" click_action: "com.myidentifi.fcm.text", // for ios
}, android_actions: JSON.stringify([{
"data":{ id: "view",
"hello": "there" title: 'view'
}, },{
"priority": "high" id: "dismiss",
} title: 'dismiss'
}]) // for android, take syntax similar to ios's. only buttons are supported
firebaseClient.send(JSON.stringify(body), "notification-data"); });
} }
render() { render() {
...@@ -161,19 +190,11 @@ export default class App extends Component { ...@@ -161,19 +190,11 @@ export default class App extends Component {
return ( return (
<View style={styles.container}> <View style={styles.container}>
<ScrollView style={{paddingHorizontal: 20}}>
<Text style={styles.welcome}> <Text style={styles.welcome}>
Welcome to Simple Fcm Client! Welcome to Simple Fcm Client!
</Text> </Text>
<Text>
Init notif: {JSON.stringify(this.state.initNotif)}
</Text>
<Text selectable={true} onPress={() => this.setClipboardContent(this.state.token)} style={styles.instructions}>
Token: {this.state.token}
</Text>
<Text style={styles.feedback}> <Text style={styles.feedback}>
{this.state.tokenCopyFeedback} {this.state.tokenCopyFeedback}
</Text> </Text>
...@@ -190,17 +211,32 @@ export default class App extends Component { ...@@ -190,17 +211,32 @@ export default class App extends Component {
<Text style={styles.buttonText}>Send Remote Data</Text> <Text style={styles.buttonText}>Send Remote Data</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity onPress={() => this.sendRemoteNotificationWithData(token)} style={styles.button}> <TouchableOpacity onPress={() => this.showLocalNotification()} style={styles.button}>
<Text style={styles.buttonText}>Send Remote Notification With Data</Text> <Text style={styles.buttonText}>Show Local Notification</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity onPress={() => this.showLocalNotification()} style={styles.button}> <TouchableOpacity onPress={() => this.showLocalNotificationWithAction(token)} style={styles.button}>
<Text style={styles.buttonText}>Send Local Notification</Text> <Text style={styles.buttonText}>Show Local Notification with Action</Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity onPress={() => this.scheduleLocalNotification()} style={styles.button}> <TouchableOpacity onPress={() => this.scheduleLocalNotification()} style={styles.button}>
<Text style={styles.buttonText}>Schedule Notification in 5s</Text> <Text style={styles.buttonText}>Schedule Notification in 5s</Text>
</TouchableOpacity> </TouchableOpacity>
<Text style={styles.instructions}>
Init notif:
</Text>
<Text>
{JSON.stringify(this.state.initNotif)}
</Text>
<Text style={styles.instructions}>
Token:
</Text>
<Text selectable={true} onPress={() => this.setClipboardContent(this.state.token)}>
{this.state.token}
</Text>
</ScrollView>
</View> </View>
); );
} }
...@@ -216,6 +252,25 @@ export default class App extends Component { ...@@ -216,6 +252,25 @@ export default class App extends Component {
} }
} }
class DetailPage extends Component {
render(){
return <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Detail page</Text>
</View>
}
}
export default StackNavigator({
Main: {
screen: MainPage,
},
Detail: {
screen: DetailPage
}
}, {
initialRouteName: 'Main',
});
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
...@@ -241,8 +296,8 @@ const styles = StyleSheet.create({ ...@@ -241,8 +296,8 @@ const styles = StyleSheet.create({
button: { button: {
backgroundColor: "teal", backgroundColor: "teal",
paddingHorizontal: 20, paddingHorizontal: 20,
paddingVertical: 10, paddingVertical: 15,
marginVertical: 15, marginVertical: 10,
borderRadius: 10 borderRadius: 10
}, },
buttonText: { buttonText: {
......
import { Platform, AsyncStorage } from 'react-native'; import { Platform, AsyncStorage, AppState } from 'react-native';
import FCM, {FCMEvent, RemoteNotificationResult, WillPresentNotificationResult, NotificationType} from "react-native-fcm"; import FCM, {FCMEvent, RemoteNotificationResult, WillPresentNotificationResult, NotificationType, NotificationActionType, NotificationActionOption, NotificationCategoryOption} from "react-native-fcm";
AsyncStorage.getItem('lastNotification').then(data=>{ AsyncStorage.getItem('lastNotification').then(data=>{
if(data){ if(data){
...@@ -10,22 +10,60 @@ AsyncStorage.getItem('lastNotification').then(data=>{ ...@@ -10,22 +10,60 @@ AsyncStorage.getItem('lastNotification').then(data=>{
} }
}) })
AsyncStorage.getItem('lastMessage').then(data=>{
if(data){
// if notification arrives when app is killed, it should still be logged here
console.log('last message', JSON.parse(data));
AsyncStorage.removeItem('lastMessage');
}
})
export function registerKilledListener(){ export function registerKilledListener(){
// these callback will be triggered even when app is killed // these callback will be triggered even when app is killed
FCM.on(FCMEvent.Notification, notif => { FCM.on(FCMEvent.Notification, notif => {
AsyncStorage.setItem('lastNotification', JSON.stringify(notif)); AsyncStorage.setItem('lastNotification', JSON.stringify(notif));
if(notif.opened_from_tray){
setTimeout(()=>{
if(notif._actionIdentifier === 'reply'){
if(AppState.currentState !== 'background'){
console.log('User replied '+ JSON.stringify(notif._userText))
alert('User replied '+ JSON.stringify(notif._userText));
} else {
AsyncStorage.setItem('lastMessage', JSON.stringify(notif._userText));
}
}
if(notif._actionIdentifier === 'view'){
alert("User clicked View in App");
}
if(notif._actionIdentifier === 'dismiss'){
alert("User clicked Dismiss");
}
}, 1000)
}
}); });
} }
// these callback will be triggered only when app is foreground or background // these callback will be triggered only when app is foreground or background
export function registerAppListener(){ export function registerAppListener(navigation){
FCM.on(FCMEvent.Notification, notif => { FCM.on(FCMEvent.Notification, notif => {
console.log("Notification", notif); console.log("Notification", notif);
if(notif.local_notification){
if(Platform.OS ==='ios' && notif._notificationType === NotificationType.WillPresent && !notif.local_notification){
// this notification is only to decide if you want to show the notification when user if in forground.
// usually you can ignore it. just decide to show or not.
notif.finish(WillPresentNotificationResult.All)
return; return;
} }
if(notif.opened_from_tray){ if(notif.opened_from_tray){
return; if(notif.targetScreen === 'detail'){
setTimeout(()=>{
navigation.navigate('Detail')
}, 500)
}
setTimeout(()=>{
alert(`User tapped notification\n${JSON.stringify(notif)}`)
}, 500)
} }
if(Platform.OS ==='ios'){ if(Platform.OS ==='ios'){
...@@ -42,6 +80,8 @@ export function registerAppListener(){ ...@@ -42,6 +80,8 @@ export function registerAppListener(){
break; break;
case NotificationType.WillPresent: case NotificationType.WillPresent:
notif.finish(WillPresentNotificationResult.All) //other types available: WillPresentNotificationResult.None notif.finish(WillPresentNotificationResult.All) //other types available: WillPresentNotificationResult.None
// this type of notificaiton will be called only when you are in foreground.
// if it is a remote notification, don't do any app logic here. Another notification callback will be triggered with type NotificationType.Remote
break; break;
} }
} }
...@@ -49,7 +89,6 @@ export function registerAppListener(){ ...@@ -49,7 +89,6 @@ export function registerAppListener(){
FCM.on(FCMEvent.RefreshToken, token => { FCM.on(FCMEvent.RefreshToken, token => {
console.log("TOKEN (refreshUnsubscribe)", token); console.log("TOKEN (refreshUnsubscribe)", token);
this.props.onChangeToken(token);
}); });
FCM.enableDirectChannel(); FCM.enableDirectChannel();
...@@ -60,3 +99,35 @@ export function registerAppListener(){ ...@@ -60,3 +99,35 @@ export function registerAppListener(){
FCM.isDirectChannelEstablished().then(d => console.log(d)); FCM.isDirectChannelEstablished().then(d => console.log(d));
}, 1000); }, 1000);
} }
FCM.setNotificationCategories([
{
id: 'com.myidentifi.fcm.text',
actions: [
{
type: NotificationActionType.TextInput,
id: 'reply',
title: 'Quick Reply',
textInputButtonTitle: 'Send',
textInputPlaceholder: 'Say something',
intentIdentifiers: [],
options: NotificationActionOption.AuthenticationRequired
},
{
type: NotificationActionType.Default,
id: 'view',
title: 'View in App',
intentIdentifiers: [],
options: NotificationActionOption.Foreground
},
{
type: NotificationActionType.Default,
id: 'dismiss',
title: 'Dismiss',
intentIdentifiers: [],
options: NotificationActionOption.Destructive
}
],
options: [NotificationCategoryOption.CustomDismissAction, NotificationCategoryOption.PreviewsShowTitle]
}
])
PODS: PODS:
- Firebase/Core (4.7.0): - Firebase/Core (4.9.0):
- FirebaseAnalytics (= 4.0.5) - FirebaseAnalytics (= 4.0.9)
- FirebaseCore (= 4.0.12) - FirebaseCore (= 4.0.15)
- Firebase/Messaging (4.7.0): - Firebase/Messaging (4.9.0):
- Firebase/Core - Firebase/Core
- FirebaseMessaging (= 2.0.7) - FirebaseMessaging (= 2.1.0)
- FirebaseAnalytics (4.0.5): - FirebaseAnalytics (4.0.9):
- FirebaseCore (~> 4.0) - FirebaseCore (~> 4.0)
- FirebaseInstanceID (~> 2.0) - FirebaseInstanceID (~> 2.0)
- GoogleToolboxForMac/NSData+zlib (~> 2.1) - GoogleToolboxForMac/NSData+zlib (~> 2.1)
- nanopb (~> 0.3) - nanopb (~> 0.3)
- FirebaseCore (4.0.12): - FirebaseCore (4.0.15):
- GoogleToolboxForMac/NSData+zlib (~> 2.1) - GoogleToolboxForMac/NSData+zlib (~> 2.1)
- FirebaseInstanceID (2.0.7) - FirebaseInstanceID (2.0.9):
- FirebaseMessaging (2.0.7): - FirebaseCore (~> 4.0)
- FirebaseMessaging (2.1.0):
- FirebaseAnalytics (~> 4.0) - FirebaseAnalytics (~> 4.0)
- FirebaseCore (~> 4.0) - FirebaseCore (~> 4.0)
- FirebaseInstanceID (~> 2.0) - FirebaseInstanceID (~> 2.0)
...@@ -35,15 +36,15 @@ DEPENDENCIES: ...@@ -35,15 +36,15 @@ DEPENDENCIES:
- Firebase/Messaging - Firebase/Messaging
SPEC CHECKSUMS: SPEC CHECKSUMS:
Firebase: dbfb98ccec2dcfcd21ab9cc1b4981a3f3c8c5e26 Firebase: 632216af3ed7f31e3be34776947fdc7546cfb572
FirebaseAnalytics: 5b02a63ead2c3f0259cfc7f15e053e440587ecf8 FirebaseAnalytics: 388b630c15713f5dbf364071f5f3d6077fb52f4e
FirebaseCore: 6cf108b63997bc08c04a1ffa55a3ac0d71a59ffc FirebaseCore: 3bd047463058fa6b5d312c97502c52e45401cdfb
FirebaseInstanceID: 148c25c986c8699e67304b114e365713dce467f2 FirebaseInstanceID: d2058a35e9bebda1b6dd42486b84917bde552a9d
FirebaseMessaging: 1a11d1c0a9ed9b3f75a0685bb0ae5932e1062f5f FirebaseMessaging: 2bafab2d0f3ab3dfd753101c2c32995c2051b5da
GoogleToolboxForMac: 2501e2ad72a52eb3dfe7bd9aee7dad11b858bd20 GoogleToolboxForMac: 2501e2ad72a52eb3dfe7bd9aee7dad11b858bd20
nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3 nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3
Protobuf: 8a9838fba8dae3389230e1b7f8c104aa32389c03 Protobuf: 8a9838fba8dae3389230e1b7f8c104aa32389c03
PODFILE CHECKSUM: 31f07bb14b00eef65c77cff51721f530ad6eb826 PODFILE CHECKSUM: 31f07bb14b00eef65c77cff51721f530ad6eb826
COCOAPODS: 1.2.1 COCOAPODS: 1.4.0
...@@ -54,6 +54,7 @@ ...@@ -54,6 +54,7 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<Testables> <Testables>
<TestableReference <TestableReference
...@@ -83,6 +84,7 @@ ...@@ -83,6 +84,7 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0" launchStyle = "0"
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
......
...@@ -9,7 +9,9 @@ ...@@ -9,7 +9,9 @@
"dependencies": { "dependencies": {
"react": "16.0.0-alpha.12", "react": "16.0.0-alpha.12",
"react-native": "^0.47.2", "react-native": "^0.47.2",
"react-native-fcm": "../../" "react-native-fcm": "^14.1.0",
"react-native-maps": "^0.20.1",
"react-navigation": "^1.2.1"
}, },
"jest": { "jest": {
"preset": "jest-react-native" "preset": "jest-react-native"
......
This diff is collapsed.
This diff is collapsed.
...@@ -58,7 +58,10 @@ public class FIRLocalMessagingHelper { ...@@ -58,7 +58,10 @@ public class FIRLocalMessagingHelper {
return; return;
} }
Long fireDate = Math.round(bundle.getDouble("fire_date")); long fireDate = Math.round(bundle.getDouble("fire_date"));
if(fireDate == 0){
fireDate = Math.round(bundle.getLong("fire_date"));
}
if (fireDate == 0) { if (fireDate == 0) {
Log.e(TAG, "failed to schedule notification because fire date is missing"); Log.e(TAG, "failed to schedule notification because fire date is missing");
return; return;
......
...@@ -37,6 +37,7 @@ import java.util.ArrayList; ...@@ -37,6 +37,7 @@ import java.util.ArrayList;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import com.google.firebase.FirebaseApp;
import static android.content.Context.NOTIFICATION_SERVICE; import static android.content.Context.NOTIFICATION_SERVICE;
...@@ -106,6 +107,7 @@ public class FIRMessagingModule extends ReactContextBaseJavaModule implements Li ...@@ -106,6 +107,7 @@ public class FIRMessagingModule extends ReactContextBaseJavaModule implements Li
} }
if (mngr.getNotificationChannel(id) != null) { if (mngr.getNotificationChannel(id) != null) {
promise.resolve(null); promise.resolve(null);
return;
} }
// //
NotificationChannel channel = new NotificationChannel( NotificationChannel channel = new NotificationChannel(
...@@ -132,6 +134,31 @@ public class FIRMessagingModule extends ReactContextBaseJavaModule implements Li ...@@ -132,6 +134,31 @@ public class FIRMessagingModule extends ReactContextBaseJavaModule implements Li
} }
} }
@ReactMethod
public void getEntityFCMToken(Promise promise) {
try {
String senderId = FirebaseApp.getInstance().getOptions().getGcmSenderId();
String token = FirebaseInstanceId.getInstance().getToken(senderId, "FCM");
Log.d(TAG, "Firebase token: " + token);
promise.resolve(token);
} catch (Throwable e) {
e.printStackTrace();
promise.reject(null,e.getMessage());
}
}
@ReactMethod
public void deleteEntityFCMToken(Promise promise) {
try {
String senderId = FirebaseApp.getInstance().getOptions().getGcmSenderId();
FirebaseInstanceId.getInstance().deleteToken(senderId, "FCM");
promise.resolve(null);
} catch (Throwable e) {
e.printStackTrace();
promise.reject(null,e.getMessage());
}
}
@ReactMethod @ReactMethod
public void deleteInstanceId(Promise promise){ public void deleteInstanceId(Promise promise){
try { try {
...@@ -353,4 +380,3 @@ public class FIRMessagingModule extends ReactContextBaseJavaModule implements Li ...@@ -353,4 +380,3 @@ public class FIRMessagingModule extends ReactContextBaseJavaModule implements Li
sendEvent("FCMNotificationReceived", parseIntent(intent)); sendEvent("FCMNotificationReceived", parseIntent(intent));
} }
} }
...@@ -2,9 +2,14 @@ package com.evollu.react.fcm; ...@@ -2,9 +2,14 @@ package com.evollu.react.fcm;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.util.Log; import android.util.Log;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import com.google.firebase.iid.FirebaseInstanceId; import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.FirebaseInstanceIdService; import com.google.firebase.iid.FirebaseInstanceIdService;
...@@ -25,11 +30,35 @@ public class InstanceIdService extends FirebaseInstanceIdService { ...@@ -25,11 +30,35 @@ public class InstanceIdService extends FirebaseInstanceIdService {
Log.d(TAG, "Refreshed token: " + refreshedToken); Log.d(TAG, "Refreshed token: " + refreshedToken);
// Broadcast refreshed token // Broadcast refreshed token
Intent i = new Intent("com.evollu.react.fcm.FCMRefreshToken"); Intent i = new Intent("com.evollu.react.fcm.FCMRefreshToken");
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putString("token", refreshedToken); bundle.putString("token", refreshedToken);
i.putExtras(bundle); i.putExtras(bundle);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(i);
final Intent message = i;
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
public void run() {
// Construct and load our normal React JS code bundle
ReactInstanceManager mReactInstanceManager = ((ReactApplication) getApplication()).getReactNativeHost().getReactInstanceManager();
ReactContext context = mReactInstanceManager.getCurrentReactContext();
// If it's constructed, send a notification
if (context != null) {
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(message);
} else {
// Otherwise wait for construction, then send the notification
mReactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {
public void onReactContextInitialized(ReactContext context) {
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(message);
}
});
if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
// Construct it in the background
mReactInstanceManager.createReactContextInBackground();
}
}
}
});
} }
} }
package com.evollu.react.fcm;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Iterator;
public class ReactNativeJson {
public static WritableMap convertJsonToMap(JSONObject jsonObject) throws JSONException {
WritableMap map = new WritableNativeMap();
Iterator<String> iterator = jsonObject.keys();
while (iterator.hasNext()) {
String key = iterator.next();
Object value = jsonObject.get(key);
if (value instanceof JSONObject) {
map.putMap(key, convertJsonToMap((JSONObject) value));
} else if (value instanceof JSONArray) {
map.putArray(key, convertJsonToArray((JSONArray) value));
} else if (value instanceof Boolean) {
map.putBoolean(key, (Boolean) value);
} else if (value instanceof Integer) {
map.putInt(key, (Integer) value);
} else if (value instanceof Double) {
map.putDouble(key, (Double) value);
} else if (value instanceof String) {
map.putString(key, (String) value);
} else {
map.putString(key, value.toString());
}
}
return map;
}
public static WritableArray convertJsonToArray(JSONArray jsonArray) throws JSONException {
WritableArray array = new WritableNativeArray();
for (int i = 0; i < jsonArray.length(); i++) {
Object value = jsonArray.get(i);
if (value instanceof JSONObject) {
array.pushMap(convertJsonToMap((JSONObject) value));
} else if (value instanceof JSONArray) {
array.pushArray(convertJsonToArray((JSONArray) value));
} else if (value instanceof Boolean) {
array.pushBoolean((Boolean) value);
} else if (value instanceof Integer) {
array.pushInt((Integer) value);
} else if (value instanceof Double) {
array.pushDouble((Double) value);
} else if (value instanceof String) {
array.pushString((String) value);
} else {
array.pushString(value.toString());
}
}
return array;
}
public static JSONObject convertMapToJson(ReadableMap readableMap) throws JSONException {
JSONObject object = new JSONObject();
ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
while (iterator.hasNextKey()) {
String key = iterator.nextKey();
switch (readableMap.getType(key)) {
case Null:
object.put(key, JSONObject.NULL);
break;
case Boolean:
object.put(key, readableMap.getBoolean(key));
break;
case Number:
object.put(key, readableMap.getDouble(key));
break;
case String:
object.put(key, readableMap.getString(key));
break;
case Map:
object.put(key, convertMapToJson(readableMap.getMap(key)));
break;
case Array:
object.put(key, convertArrayToJson(readableMap.getArray(key)));
break;
}
}
return object;
}
public static JSONArray convertArrayToJson(ReadableArray readableArray) throws JSONException {
JSONArray array = new JSONArray();
for (int i = 0; i < readableArray.size(); i++) {
switch (readableArray.getType(i)) {
case Null:
break;
case Boolean:
array.put(readableArray.getBoolean(i));
break;
case Number:
array.put(readableArray.getDouble(i));
break;
case String:
array.put(readableArray.getString(i));
break;
case Map:
array.put(convertMapToJson(readableArray.getMap(i)));
break;
case Array:
array.put(convertArrayToJson(readableArray.getArray(i)));
break;
}
}
return array;
}
}
...@@ -15,11 +15,17 @@ import android.net.Uri; ...@@ -15,11 +15,17 @@ import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.PowerManager;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat; import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.util.Log; import android.util.Log;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import org.json.JSONArray;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
...@@ -35,7 +41,7 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> { ...@@ -35,7 +41,7 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> {
private SharedPreferences sharedPreferences; private SharedPreferences sharedPreferences;
private Boolean mIsForeground; private Boolean mIsForeground;
public SendNotificationTask(Context context, SharedPreferences sharedPreferences, Boolean mIsForeground, Bundle bundle){ SendNotificationTask(Context context, SharedPreferences sharedPreferences, Boolean mIsForeground, Bundle bundle){
this.mContext = context; this.mContext = context;
this.bundle = bundle; this.bundle = bundle;
this.sharedPreferences = sharedPreferences; this.sharedPreferences = sharedPreferences;
...@@ -70,9 +76,12 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> { ...@@ -70,9 +76,12 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> {
.setAutoCancel(bundle.getBoolean("auto_cancel", true)) .setAutoCancel(bundle.getBoolean("auto_cancel", true))
.setNumber((int)bundle.getDouble("number")) .setNumber((int)bundle.getDouble("number"))
.setSubText(bundle.getString("sub_text")) .setSubText(bundle.getString("sub_text"))
.setGroup(bundle.getString("group"))
.setVibrate(new long[]{0, DEFAULT_VIBRATION}) .setVibrate(new long[]{0, DEFAULT_VIBRATION})
.setExtras(bundle.getBundle("data")); .setExtras(bundle.getBundle("data"));
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
notification.setGroup(bundle.getString("group"));
}
if (bundle.containsKey("ongoing") && bundle.getBoolean("ongoing")) { if (bundle.containsKey("ongoing") && bundle.getBoolean("ongoing")) {
notification.setOngoing(bundle.getBoolean("ongoing")); notification.setOngoing(bundle.getBoolean("ongoing"));
...@@ -207,11 +216,40 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> { ...@@ -207,11 +216,40 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> {
PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent.FLAG_UPDATE_CURRENT);
notification.setContentIntent(pendingIntent); notification.setContentIntent(pendingIntent);
if (bundle.containsKey("android_actions")) {
WritableArray actions = ReactNativeJson.convertJsonToArray(new JSONArray(bundle.getString("android_actions")));
for (int a = 0; a < actions.size(); a++) {
ReadableMap action = actions.getMap(a);
String actionTitle = action.getString("title");
String actionId = action.getString("id");
Intent actionIntent = new Intent();
actionIntent.setClassName(mContext, intentClassName);
actionIntent.setAction("com.evollu.react.fcm." + actionId + "_ACTION");
actionIntent.putExtras(bundle);
actionIntent.putExtra("_actionIdentifier", actionId);
actionIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendingActionIntent = PendingIntent.getActivity(mContext, notificationID, actionIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
notification.addAction(0, actionTitle, pendingActionIntent);
}
}
Notification info = notification.build(); Notification info = notification.build();
NotificationManagerCompat.from(mContext).notify(notificationID, info); NotificationManagerCompat.from(mContext).notify(notificationID, info);
} }
if(bundle.getBoolean("wake_screen", false)){
PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
if(pm != null && !pm.isScreenOn())
{
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK |PowerManager.ACQUIRE_CAUSES_WAKEUP |PowerManager.ON_AFTER_RELEASE,"FCMLock");
wl.acquire(5000);
}
}
//clear out one time scheduled notification once fired //clear out one time scheduled notification once fired
if(!bundle.containsKey("repeat_interval") && bundle.containsKey("fire_date")) { if(!bundle.containsKey("repeat_interval") && bundle.containsKey("fire_date")) {
SharedPreferences.Editor editor = sharedPreferences.edit(); SharedPreferences.Editor editor = sharedPreferences.edit();
...@@ -224,7 +262,7 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> { ...@@ -224,7 +262,7 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> {
return null; return null;
} }
public Bitmap getBitmapFromURL(String strURL) { private Bitmap getBitmapFromURL(String strURL) {
try { try {
URL url = new URL(strURL); URL url = new URL(strURL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); HttpURLConnection connection = (HttpURLConnection) url.openConnection();
...@@ -238,11 +276,10 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> { ...@@ -238,11 +276,10 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> {
} }
} }
public String getMainActivityClassName() { protected String getMainActivityClassName() {
String packageName = mContext.getPackageName(); String packageName = mContext.getPackageName();
Intent launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(packageName); Intent launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(packageName);
String className = launchIntent.getComponent().getClassName(); return launchIntent != null ? launchIntent.getComponent().getClassName() : null;
return className;
} }
} }
...@@ -25,6 +25,26 @@ declare module "react-native-fcm" { ...@@ -25,6 +25,26 @@ declare module "react-native-fcm" {
const Local = "local_notification"; const Local = "local_notification";
} }
export enum NotificationCategoryOption {
CustomDismissAction = 'UNNotificationCategoryOptionCustomDismissAction',
AllowInCarPlay = 'UNNotificationCategoryOptionAllowInCarPlay',
PreviewsShowTitle = 'UNNotificationCategoryOptionHiddenPreviewsShowTitle',
PreviewsShowSubtitle = 'UNNotificationCategoryOptionHiddenPreviewsShowSubtitle',
None = 'UNNotificationCategoryOptionNone'
}
export enum NotificationActionOption {
AuthenticationRequired = 'UNNotificationActionOptionAuthenticationRequired',
Destructive = 'UNNotificationActionOptionDestructive',
Foreground = 'UNNotificationActionOptionForeground',
None = 'UNNotificationActionOptionNone'
}
export enum NotificationActionType {
Default = 'UNNotificationActionTypeDefault',
TextInput = 'UNNotificationActionTypeTextInput',
}
export interface Notification { export interface Notification {
collapse_key: string; collapse_key: string;
opened_from_tray: boolean; opened_from_tray: boolean;
...@@ -44,6 +64,8 @@ declare module "react-native-fcm" { ...@@ -44,6 +64,8 @@ declare module "react-native-fcm" {
}; };
local_notification?: boolean; local_notification?: boolean;
_notificationType: string; _notificationType: string;
_actionIdentifier?: string;
_userText?: string;
finish(type?: string): void; finish(type?: string): void;
[key: string]: any; [key: string]: any;
} }
...@@ -83,6 +105,23 @@ declare module "react-native-fcm" { ...@@ -83,6 +105,23 @@ declare module "react-native-fcm" {
remove(): void; remove(): void;
} }
export interface NotificationAction {
type: NotificationActionType;
id: string;
title?: string;
textInputButtonTitle?: string;
textInputPlaceholder?: string;
options: NotificationActionOption | NotificationActionOption[];
}
export interface NotificationCategory {
id: string;
actions: NotificationAction[];
intentIdentifiers: string[];
hiddenPreviewsBodyPlaceholder?: string;
options?: NotificationCategoryOption | NotificationCategoryOption[];
}
export class FCM { export class FCM {
static requestPermissions(): Promise<void>; static requestPermissions(): Promise<void>;
static getFCMToken(): Promise<string>; static getFCMToken(): Promise<string>;
...@@ -109,6 +148,8 @@ declare module "react-native-fcm" { ...@@ -109,6 +148,8 @@ declare module "react-native-fcm" {
static enableDirectChannel(): void static enableDirectChannel(): void
static isDirectChannelEstablished(): Promise<boolean> static isDirectChannelEstablished(): Promise<boolean>
static getAPNSToken(): Promise<string> static getAPNSToken(): Promise<string>
static setNotificationCategories(categories: NotificationCategory[]): void;
} }
export default FCM; export default FCM;
......
...@@ -26,6 +26,26 @@ export const NotificationType = { ...@@ -26,6 +26,26 @@ export const NotificationType = {
Local: 'local_notification' Local: 'local_notification'
}; };
export const NotificationCategoryOption = {
CustomDismissAction: 'UNNotificationCategoryOptionCustomDismissAction',
AllowInCarPlay: 'UNNotificationCategoryOptionAllowInCarPlay',
PreviewsShowTitle: 'UNNotificationCategoryOptionHiddenPreviewsShowTitle',
PreviewsShowSubtitle: 'UNNotificationCategoryOptionHiddenPreviewsShowSubtitle',
None: 'UNNotificationCategoryOptionNone'
};
export const NotificationActionOption = {
AuthenticationRequired: 'UNNotificationActionOptionAuthenticationRequired',
Destructive: 'UNNotificationActionOptionDestructive',
Foreground: 'UNNotificationActionOptionForeground',
None: 'UNNotificationActionOptionNone',
};
export const NotificationActionType = {
Default: 'UNNotificationActionTypeDefault',
TextInput: 'UNNotificationActionTypeTextInput',
};
const RNFIRMessaging = NativeModules.RNFIRMessaging; const RNFIRMessaging = NativeModules.RNFIRMessaging;
const FCM = {}; const FCM = {};
...@@ -48,6 +68,14 @@ FCM.getFCMToken = () => { ...@@ -48,6 +68,14 @@ FCM.getFCMToken = () => {
return RNFIRMessaging.getFCMToken(); return RNFIRMessaging.getFCMToken();
}; };
FCM.getEntityFCMToken = () => {
return RNFIRMessaging.getEntityFCMToken();
}
FCM.deleteEntityFCMToken = () => {
return RNFIRMessaging.deleteEntityFCMToken();
}
FCM.deleteInstanceId = () =>{ FCM.deleteInstanceId = () =>{
return RNFIRMessaging.deleteInstanceId(); return RNFIRMessaging.deleteInstanceId();
}; };
...@@ -64,7 +92,7 @@ FCM.requestPermissions = () => { ...@@ -64,7 +92,7 @@ FCM.requestPermissions = () => {
FCM.createNotificationChannel = (channel) => { FCM.createNotificationChannel = (channel) => {
if (Platform.OS === 'android') { if (Platform.OS === 'android') {
return RNFIRMessaging.createNotificationChannel(); return RNFIRMessaging.createNotificationChannel(channel);
} }
} }
...@@ -157,7 +185,7 @@ FCM.on = (event, callback) => { ...@@ -157,7 +185,7 @@ FCM.on = (event, callback) => {
try { try {
await callback(data); await callback(data);
} catch (err) { } catch (err) {
console.error('Notification handler err', err); console.error('Notification handler err:\n'+err.stack);
throw err; throw err;
} }
if (!data._finishCalled) { if (!data._finishCalled) {
...@@ -180,4 +208,12 @@ FCM.send = (senderId, payload) => { ...@@ -180,4 +208,12 @@ FCM.send = (senderId, payload) => {
RNFIRMessaging.send(senderId, payload); RNFIRMessaging.send(senderId, payload);
}; };
FCM.setNotificationCategories = (categories) => {
if (Platform.OS === 'ios') {
RNFIRMessaging.setNotificationCategories(categories);
}
}
export default FCM; export default FCM;
export {};
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@import FirebaseCore; #if __has_include(<Firebase.h>)
@import FirebaseMessaging; #import <FirebaseCore/FirebaseCore.h>
@import FirebaseInstanceID; #import <FirebaseMessaging/FirebaseMessaging.h>
#import <FirebaseInstanceID/FirebaseInstanceID.h>
#else
@import Firebase;
#endif
#import <React/RCTEventEmitter.h> #import <React/RCTEventEmitter.h>
@import UserNotifications; @import UserNotifications;
......
This diff is collapsed.
...@@ -230,6 +230,7 @@ ...@@ -230,6 +230,7 @@
"$(PROJECT_DIR)/../../../ios/Frameworks/**", "$(PROJECT_DIR)/../../../ios/Frameworks/**",
"$(SRCROOT)/../../../node_modules/react-native-firestack/ios/**", "$(SRCROOT)/../../../node_modules/react-native-firestack/ios/**",
"$(PROJECT_DIR)/../../../ios/Pods/Firebase/**", "$(PROJECT_DIR)/../../../ios/Pods/Firebase/**",
"$(PROJECT_DIR)/../../../ios/Pods/Headers/Public/**",
); );
LIBRARY_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
...@@ -257,6 +258,7 @@ ...@@ -257,6 +258,7 @@
"$(PROJECT_DIR)/../../../ios/Frameworks/**", "$(PROJECT_DIR)/../../../ios/Frameworks/**",
"$(SRCROOT)/../../../node_modules/react-native-firestack/ios/**", "$(SRCROOT)/../../../node_modules/react-native-firestack/ios/**",
"$(PROJECT_DIR)/../../../ios/Pods/Firebase/**", "$(PROJECT_DIR)/../../../ios/Pods/Firebase/**",
"$(PROJECT_DIR)/../../../ios/Pods/Headers/Public/**",
); );
LIBRARY_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
......
...@@ -24,5 +24,5 @@ ...@@ -24,5 +24,5 @@
"type": "git", "type": "git",
"url": "git+https://github.com/evollu/react-native-fcm.git" "url": "git+https://github.com/evollu/react-native-fcm.git"
}, },
"version": "11.0.1" "version": "14.1.2"
} }
...@@ -12,6 +12,8 @@ Pod::Spec.new do |s| ...@@ -12,6 +12,8 @@ Pod::Spec.new do |s|
s.source = { :git => 'https://github.com/evollu/react-native-fcm.git' } s.source = { :git => 'https://github.com/evollu/react-native-fcm.git' }
s.platform = :ios, '8.0' s.platform = :ios, '8.0'
s.source_files = "ios/*.{h,m}" s.source_files = "ios/*.{h,m}"
s.public_header_files = ['ios/RNFIRMessaging.h']
s.static_framework = true
s.dependency "React" s.dependency "React"
s.dependency "Firebase/Messaging" s.dependency "Firebase/Messaging"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment