[notifications] Refactor for better support of separate messages

This commit is contained in:
Chris Bianca 2018-02-22 15:52:24 +00:00
parent 831eec82f7
commit 303cb4c428
12 changed files with 463 additions and 326 deletions

View File

@ -39,8 +39,8 @@ static NSString *const MESSAGING_TOKEN_REFRESHED = @"messaging_token_refreshed";
static NSString *const MESSAGING_NOTIFICATION_RECEIVED = @"messaging_notification_received";
// Notifications
static NSString *const NOTIFICATIONS_NOTIFICATION_CLICKED = @"notifications_notification_clicked";
static NSString *const NOTIFICATIONS_NOTIFICATION_DISPLAYED = @"notifications_notification_displayed";
static NSString *const NOTIFICATIONS_NOTIFICATION_PRESSED = @"notifications_notification_pressed";
static NSString *const NOTIFICATIONS_NOTIFICATION_RECEIVED = @"notifications_notification_received";
// AdMob

View File

@ -15,7 +15,8 @@
@property _Nullable RCTPromiseResolveBlock permissionResolver;
#if !TARGET_OS_TV
- (void)didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings;
- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo;
- (void)didRegisterUserNotificationSettings:(nonnull UIUserNotificationSettings *)notificationSettings;
#endif
@end

View File

@ -70,6 +70,12 @@ RCT_EXPORT_MODULE()
_permissionResolver = nil;
}
// Listen for FCM data messages that arrive as a remote notification
- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo {
NSDictionary *message = [self parseUserInfo:userInfo];
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message];
}
// *******************************************************
// ** Finish AppDelegate methods
// *******************************************************
@ -88,8 +94,15 @@ RCT_EXPORT_MODULE()
// Listen for data messages in the foreground
- (void)applicationReceivedRemoteMessage:(nonnull FIRMessagingRemoteMessage *)remoteMessage {
NSDictionary *message = [self parseFIRMessagingRemoteMessage:remoteMessage openedFromTray:false];
NSDictionary *message = [self parseFIRMessagingRemoteMessage:remoteMessage];
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message];
}
// Receive data messages on iOS 10+ directly from FCM (bypassing APNs) when the app is in the foreground.
// To enable direct data messages, you can set [Messaging messaging].shouldEstablishDirectChannel to YES.
- (void)messaging:(nonnull FIRMessaging *)messaging
didReceiveMessage:(nonnull FIRMessagingRemoteMessage *)remoteMessage {
NSDictionary *message = [self parseFIRMessagingRemoteMessage:remoteMessage];
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message];
}
@ -250,8 +263,7 @@ RCT_EXPORT_METHOD(completeRemoteNotification: (NSString*) messageId
// ** Start internals **
- (NSDictionary*)parseFIRMessagingRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage
openedFromTray:(bool)openedFromTray {
- (NSDictionary*)parseFIRMessagingRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage {
NSDictionary *appData = remoteMessage.appData;
NSMutableDictionary *message = [[NSMutableDictionary alloc] init];
@ -262,46 +274,46 @@ RCT_EXPORT_METHOD(completeRemoteNotification: (NSString*) messageId
} else if ([k1 isEqualToString:@"from"]) {
message[@"from"] = appData[k1];
} else if ([k1 isEqualToString:@"notification"]) {
NSDictionary *notification = appData[k1];
NSMutableDictionary *notif = [[NSMutableDictionary alloc] init];
for (id k2 in notification) {
if ([k2 isEqualToString:@"badge"]) {
notif[@"badge"] = notification[k2];
} else if ([k2 isEqualToString:@"body"]) {
notif[@"body"] = notification[k2];
} else if ([k2 isEqualToString:@"body_loc_args"]) {
notif[@"bodyLocalizationArgs"] = notification[k2];
} else if ([k2 isEqualToString:@"body_loc_key"]) {
notif[@"bodyLocalizationKey"] = notification[k2];
} else if ([k2 isEqualToString:@"click_action"]) {
notif[@"clickAction"] = notification[k2];
} else if ([k2 isEqualToString:@"sound"]) {
notif[@"sound"] = notification[k2];
} else if ([k2 isEqualToString:@"subtitle"]) {
notif[@"subtitle"] = notification[k2];
} else if ([k2 isEqualToString:@"title"]) {
notif[@"title"] = notification[k2];
} else if ([k2 isEqualToString:@"title_loc_args"]) {
notif[@"titleLocalizationArgs"] = notification[k2];
} else if ([k2 isEqualToString:@"title_loc_key"]) {
notif[@"titleLocalizationKey"] = notification[k2];
} else {
NSLog(@"Unknown notification key: %@", k2);
}
}
message[@"notification"] = notif;
// Ignore for messages
} else {
// Assume custom data key
data[k1] = appData[k1];
}
}
message[@"messageType"] = @"RemoteMessage";
message[@"data"] = data;
message[@"openedFromTray"] = @(false);
return message;
}
- (NSDictionary*)parseUserInfo:(NSDictionary *)userInfo {
NSMutableDictionary *message = [[NSMutableDictionary alloc] init];
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
for (id k1 in userInfo) {
if ([k1 isEqualToString:@"aps"]) {
// Ignore notification section
} else if ([k1 isEqualToString:@"gcm.message_id"]) {
message[@"messageId"] = userInfo[k1];
} else if ([k1 isEqualToString:@"google.c.a.ts"]) {
message[@"sentTime"] = userInfo[k1];
} else if ([k1 isEqualToString:@"gcm.n.e"]
|| [k1 isEqualToString:@"gcm.notification.sound2"]
|| [k1 isEqualToString:@"google.c.a.c_id"]
|| [k1 isEqualToString:@"google.c.a.c_l"]
|| [k1 isEqualToString:@"google.c.a.e"]
|| [k1 isEqualToString:@"google.c.a.udt"]) {
// Ignore known keys
} else {
// Assume custom data
data[k1] = userInfo[k1];
}
}
message[@"data"] = data;
return message;
}
- (NSArray<NSString *> *)supportedEvents {
return @[MESSAGING_MESSAGE_RECEIVED, MESSAGING_TOKEN_REFRESHED];
}
@ -317,4 +329,3 @@ RCT_EXPORT_METHOD(completeRemoteNotification: (NSString*) messageId
@implementation RNFirebaseMessaging
@end
#endif

View File

@ -2,6 +2,7 @@
#if __has_include(<FirebaseMessaging/FIRMessaging.h>)
#import "RNFirebaseEvents.h"
#import "RNFirebaseMessaging.h"
#import "RNFirebaseUtil.h"
#import <React/RCTUtils.h>
@ -37,7 +38,7 @@ RCT_EXPORT_MODULE();
- (void)configure {
// If we're on iOS 10 then we need to set this as a delegate for the UNUserNotificationCenter
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
// [UNUserNotificationCenter currentNotificationCenter].delegate = self;
#endif
// Set static instance for use from AppDelegate
@ -50,26 +51,78 @@ RCT_EXPORT_MODULE();
// *******************************************************
- (void)didReceiveLocalNotification:(nonnull UILocalNotification *)notification {
NSString *event;
if (RCTSharedApplication().applicationState == UIApplicationStateBackground) {
// notification_displayed
event = NOTIFICATIONS_NOTIFICATION_DISPLAYED;
} else if (RCTSharedApplication().applicationState == UIApplicationStateInactive) {
// notification_displayed
event = NOTIFICATIONS_NOTIFICATION_PRESSED;
} else {
// notification_received
event = NOTIFICATIONS_NOTIFICATION_RECEIVED;
}
NSDictionary *message = [self parseUILocalNotification:notification];
[RNFirebaseUtil sendJSEvent:self name:event body:message];
}
// Listen for background messages
- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo {
BOOL isFromBackground = (RCTSharedApplication().applicationState == UIApplicationStateInactive);
NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotification" clickAction:nil openedFromTray:isFromBackground];
// FCM Data messages come through here if they specify content-available=true
// Pass them over to the RNFirebaseMessaging handler instead
if (userInfo[@"aps"] && ((NSDictionary*)userInfo[@"aps"]).count == 1 && userInfo[@"aps"][@"content-available"]) {
[[RNFirebaseMessaging instance] didReceiveRemoteNotification:userInfo];
return;
}
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message];
NSString *event;
if (RCTSharedApplication().applicationState == UIApplicationStateBackground) {
// notification_displayed
event = NOTIFICATIONS_NOTIFICATION_DISPLAYED;
} else if (RCTSharedApplication().applicationState == UIApplicationStateInactive) {
// notification_displayed
event = NOTIFICATIONS_NOTIFICATION_PRESSED;
} else {
// notification_received
event = NOTIFICATIONS_NOTIFICATION_RECEIVED;
}
// TODO: Proper notification structure
NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotification" clickAction:nil];
[RNFirebaseUtil sendJSEvent:self name:event body:message];
}
// Listen for background messages
- (void)didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
BOOL isFromBackground = (RCTSharedApplication().applicationState == UIApplicationStateInactive);
NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotificationHandler" clickAction:nil openedFromTray:isFromBackground];
// FCM Data messages come through here if they specify content-available=true
// Pass them over to the RNFirebaseMessaging handler instead
if (userInfo[@"aps"] && ((NSDictionary*)userInfo[@"aps"]).count == 1 && userInfo[@"aps"][@"content-available"]) {
[[RNFirebaseMessaging instance] didReceiveRemoteNotification:userInfo];
return;
}
NSString *event;
if (RCTSharedApplication().applicationState == UIApplicationStateBackground) {
// notification_displayed
event = NOTIFICATIONS_NOTIFICATION_DISPLAYED;
} else if (RCTSharedApplication().applicationState == UIApplicationStateInactive) {
// notification_displayed
event = NOTIFICATIONS_NOTIFICATION_PRESSED;
} else {
// notification_received
event = NOTIFICATIONS_NOTIFICATION_RECEIVED;
}
// TODO: Proper notification structure
NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotificationHandler" clickAction:nil];
// TODO: Callback handler
// [_callbackHandlers setObject:[completionHandler copy] forKey:message[@"messageId"]];
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message];
[RNFirebaseUtil sendJSEvent:self name:event body:message];
}
// *******************************************************
@ -93,7 +146,7 @@ RCT_EXPORT_MODULE();
NSString *event;
UNNotificationPresentationOptions options;
NSDictionary *message = [self parseUNNotification:notification messageType:@"PresentNotification" openedFromTray:false];
NSDictionary *message = [self parseUNNotification:notification messageType:@"PresentNotification"];
if (isFcm || isScheduled) {
// If app is in the background
@ -116,9 +169,7 @@ RCT_EXPORT_MODULE();
event = NOTIFICATIONS_NOTIFICATION_DISPLAYED;
}
if (event) {
[RNFirebaseUtil sendJSEvent:self name:event body:message];
}
[RNFirebaseUtil sendJSEvent:self name:event body:message];
completionHandler(options);
}
@ -130,9 +181,9 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
#else
withCompletionHandler:(void(^)())completionHandler {
#endif
NSDictionary *message = [self parseUNNotification:response.notification messageType:@"NotificationResponse" openedFromTray:true];
NSDictionary *message = [self parseUNNotification:response.notification messageType:@"NotificationResponse"];
[RNFirebaseUtil sendJSEvent:self name:NOTIFICATIONS_NOTIFICATION_CLICKED body:message];
[RNFirebaseUtil sendJSEvent:self name:NOTIFICATIONS_NOTIFICATION_PRESSED body:message];
completionHandler();
}
@ -200,7 +251,13 @@ RCT_EXPORT_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve rejecte
NSDictionary *notification = [self parseUILocalNotification:localNotification];
resolve(notification);
} else {
resolve(nil);
NSDictionary *remoteNotification = [self bridge].launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
if (remoteNotification) {
NSDictionary *message = [self parseUserInfo:remoteNotification messageType:@"InitialMessage" clickAction:nil];
resolve(message);
} else {
resolve(nil);
}
}
}
@ -485,7 +542,7 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification
}
ios[@"attachments"] = attachments;
}
if (localNotification.content.badge) {
ios[@"badge"] = localNotification.content.badge;
}
@ -504,21 +561,20 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification
}
- (NSDictionary*)parseUNNotification:(UNNotification *)notification
messageType:(NSString *)messageType
openedFromTray:(bool)openedFromTray {
messageType:(NSString *)messageType {
NSDictionary *userInfo = notification.request.content.userInfo;
NSString *clickAction = notification.request.content.categoryIdentifier;
return [self parseUserInfo:userInfo messageType:messageType clickAction:clickAction openedFromTray:openedFromTray];
return [self parseUserInfo:userInfo messageType:messageType clickAction:clickAction];
}
- (NSDictionary*)parseUserInfo:(NSDictionary *)userInfo
messageType:(NSString *) messageType
clickAction:(NSString *) clickAction
openedFromTray:(bool)openedFromTray {
NSMutableDictionary *message = [[NSMutableDictionary alloc] init];
NSMutableDictionary *notif = [[NSMutableDictionary alloc] init];
clickAction:(NSString *) clickAction {
NSMutableDictionary *notification = [[NSMutableDictionary alloc] init];
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
NSMutableDictionary *ios = [[NSMutableDictionary alloc] init];
for (id k1 in userInfo) {
if ([k1 isEqualToString:@"aps"]) {
@ -528,37 +584,42 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification
NSDictionary *alert = aps[k2];
for (id k3 in alert) {
if ([k3 isEqualToString:@"body"]) {
notif[@"body"] = alert[k3];
notification[@"body"] = alert[k3];
} else if ([k3 isEqualToString:@"loc-args"]) {
notif[@"bodyLocalizationArgs"] = alert[k3];
// TODO: What to do with this?
// notif[@"bodyLocalizationArgs"] = alert[k3];
} else if ([k3 isEqualToString:@"loc-key"]) {
notif[@"bodyLocalizationKey"] = alert[k3];
// TODO: What to do with this?
// notif[@"bodyLocalizationKey"] = alert[k3];
} else if ([k3 isEqualToString:@"subtitle"]) {
notif[@"subtitle"] = alert[k3];
notification[@"subtitle"] = alert[k3];
} else if ([k3 isEqualToString:@"title"]) {
notif[@"title"] = alert[k3];
notification[@"title"] = alert[k3];
} else if ([k3 isEqualToString:@"title-loc-args"]) {
notif[@"titleLocalizationArgs"] = alert[k3];
// TODO: What to do with this?
// notif[@"titleLocalizationArgs"] = alert[k3];
} else if ([k3 isEqualToString:@"title-loc-key"]) {
notif[@"titleLocalizationKey"] = alert[k3];
// TODO: What to do with this?
// notif[@"titleLocalizationKey"] = alert[k3];
} else {
NSLog(@"Unknown alert key: %@", k2);
}
}
} else if ([k2 isEqualToString:@"badge"]) {
notif[@"badge"] = aps[k2];
ios[@"badge"] = aps[k2];
} else if ([k2 isEqualToString:@"category"]) {
notif[@"clickAction"] = aps[k2];
ios[@"category"] = aps[k2];
} else if ([k2 isEqualToString:@"sound"]) {
notif[@"sound"] = aps[k2];
notification[@"sound"] = aps[k2];
} else {
NSLog(@"Unknown aps key: %@", k2);
}
}
} else if ([k1 isEqualToString:@"gcm.message_id"]) {
message[@"messageId"] = userInfo[k1];
notification[@"notificationId"] = userInfo[k1];
} else if ([k1 isEqualToString:@"google.c.a.ts"]) {
message[@"sentTime"] = userInfo[k1];
// TODO: What to do with this?
// message[@"sentTime"] = userInfo[k1];
} else if ([k1 isEqualToString:@"gcm.n.e"]
|| [k1 isEqualToString:@"gcm.notification.sound2"]
|| [k1 isEqualToString:@"google.c.a.c_id"]
@ -572,26 +633,17 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification
}
}
if (!notif[@"clickAction"] && clickAction) {
notif[@"clickAction"] = clickAction;
}
// TODO: What to do with this?
// message[@"messageType"] = messageType;
// Generate a message ID if one was not present in the notification
// This is used for resolving click handlers
if (!message[@"messageId"]) {
message[@"messageId"] = [[NSUUID UUID] UUIDString];
}
message[@"messageType"] = messageType;
notification[@"data"] = data;
notification[@"ios"] = ios;
message[@"data"] = data;
message[@"notification"] = notif;
message[@"openedFromTray"] = @(openedFromTray);
return message;
return notification;
}
- (NSArray<NSString *> *)supportedEvents {
return @[NOTIFICATIONS_NOTIFICATION_CLICKED, NOTIFICATIONS_NOTIFICATION_DISPLAYED, NOTIFICATIONS_NOTIFICATION_RECEIVED];
return @[NOTIFICATIONS_NOTIFICATION_DISPLAYED, NOTIFICATIONS_NOTIFICATION_PRESSED, NOTIFICATIONS_NOTIFICATION_RECEIVED];
}
+ (BOOL)requiresMainQueueSetup
@ -605,4 +657,3 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification
@implementation RNFirebaseNotifications
@end
#endif

View File

@ -11,9 +11,7 @@ import {
} from './types';
import type Messaging from './';
import type {
MessageTypeType,
NativeMessage,
Notification,
PresentNotificationResultType,
RemoteNotificationResultType,
} from './types';
@ -47,18 +45,6 @@ export default class Message {
return this._message.messageId;
}
get messageType(): ?MessageTypeType {
return this._message.messageType;
}
get openedFromTray(): boolean {
return this._message.openedFromTray;
}
get notification(): ?Notification {
return this._message.notification;
}
get sentTime(): ?number {
return this._message.sentTime;
}

View File

@ -55,8 +55,8 @@ export default class Messaging extends ModuleBase {
// sub to internal native event - this fans out to
// public event name: onMessage
'messaging_message_received',
(message: Message) => {
SharedEventEmitter.emit('onMessage', message);
(message: NativeMessage) => {
SharedEventEmitter.emit('onMessage', new Message(this, message));
}
);
@ -89,8 +89,7 @@ export default class Messaging extends ModuleBase {
getLogger(this).info('Creating onMessage listener');
const wrappedListener = async (nativeMessage: NativeMessage) => {
const message = new Message(this, nativeMessage);
const wrappedListener = async (message: Message) => {
await listener(message);
message.complete();
};

View File

@ -51,9 +51,6 @@ export type NativeMessage = {
data: { [string]: string },
from?: string,
messageId: string,
messageType?: MessageTypeType,
openedFromTray: boolean,
notification?: Notification,
sentTime?: number,
to?: string,
ttl?: number,

View File

@ -2,128 +2,29 @@
* @flow
* AndroidNotification representation wrapper
*/
import { Category } from './types';
import type Notification from './Notification';
type Lights = {
argb: number,
onMs: number,
offMs: number,
};
type Progress = {
max: number,
progress: number,
indeterminate: boolean,
};
type SmallIcon = {
icon: string,
level?: number,
};
export type NativeAndroidNotification = {|
// TODO actions: Action[],
autoCancel: boolean,
badgeIconType: BadgeIconTypeType,
category: CategoryType,
channelId: string,
clickAction?: string,
color: string,
colorized: boolean,
contentInfo: string,
defaults: DefaultsType[],
group: string,
groupAlertBehaviour: GroupAlertType,
groupSummary: boolean,
largeIcon: string,
lights: Lights,
localOnly: boolean,
number: number,
ongoing: boolean,
onlyAlertOnce: boolean,
people: string[],
priority: PriorityType,
progress: Progress,
// publicVersion: Notification,
remoteInputHistory: string[],
shortcutId: string,
showWhen: boolean,
smallIcon: SmallIcon,
sortKey: string,
// TODO: style: Style,
ticker: string,
timeoutAfter: number,
usesChronometer: boolean,
vibrate: number[],
visibility: VisibilityType,
when: number,
|};
export const BadgeIconType = {
Large: 2,
None: 0,
Small: 1,
};
export const Category = {
Alarm: 'alarm',
Call: 'call',
Email: 'email',
Error: 'err',
Event: 'event',
Message: 'msg',
Progress: 'progress',
Promo: 'promo',
Recommendation: 'recommendation',
Reminder: 'reminder',
Service: 'service',
Social: 'social',
Status: 'status',
System: 'system',
Transport: 'transport',
};
export const Defaults = {
All: -1,
Lights: 4,
Sound: 1,
Vibrate: 2,
};
export const GroupAlert = {
All: 0,
Children: 2,
Summary: 1,
};
export const Priority = {
Default: 0,
High: 1,
Low: -1,
Max: 2,
Min: -2,
};
export const Visibility = {
Private: 0,
Public: 1,
Secret: -1,
};
type BadgeIconTypeType = $Values<typeof BadgeIconType>;
type CategoryType = $Values<typeof Category>;
type DefaultsType = $Values<typeof Defaults>;
type GroupAlertType = $Values<typeof GroupAlert>;
type PriorityType = $Values<typeof Priority>;
type VisibilityType = $Values<typeof Visibility>;
import type {
BadgeIconTypeType,
CategoryType,
DefaultsType,
GroupAlertType,
Lights,
NativeAndroidNotification,
PriorityType,
Progress,
SmallIcon,
VisibilityType,
} from './types';
export default class AndroidNotification {
// TODO optional fields
// TODO actions: Action[]; // icon, title, ??pendingIntent??, allowGeneratedReplies, extender, extras, remoteinput (ugh)
_autoCancel: boolean;
_badgeIconType: BadgeIconTypeType;
_category: CategoryType;
_channelId: string;
_clickAction: string;
_clickAction: string | void;
_color: string;
_colorized: boolean;
_contentInfo: string;
@ -145,9 +46,7 @@ export default class AndroidNotification {
_remoteInputHistory: string[];
_shortcutId: string;
_showWhen: boolean;
_smallIcon: SmallIcon = {
icon: 'ic_launcher',
};
_smallIcon: SmallIcon;
_sortKey: string;
// TODO: style: Style; // Need to figure out if this can work
_ticker: string;
@ -167,9 +66,50 @@ export default class AndroidNotification {
// fullScreenIntent: PendingIntent
// sound.streamType
constructor(notification: Notification) {
constructor(notification: Notification, data?: NativeAndroidNotification) {
this._notification = notification;
this._people = [];
if (data) {
this._autoCancel = data.autoCancel;
this._badgeIconType = data.badgeIconType;
this._category = data.category;
this._channelId = data.channelId;
this._clickAction = data.clickAction;
this._color = data.color;
this._colorized = data.colorized;
this._contentInfo = data.contentInfo;
this._defaults = data.defaults;
this._group = data.group;
this._groupAlertBehaviour = data.groupAlertBehaviour;
this._groupSummary = data.groupSummary;
this._largeIcon = data.largeIcon;
this._lights = data.lights;
this._localOnly = data.localOnly;
this._number = data.number;
this._ongoing = data.ongoing;
this._onlyAlertOnce = data.onlyAlertOnce;
this._people = data.people;
this._priority = data.priority;
this._progress = data.progress;
// _publicVersion: Notification;
this._remoteInputHistory = data.remoteInputHistory;
this._shortcutId = data.shortcutId;
this._showWhen = data.showWhen;
this._smallIcon = data.smallIcon;
this._sortKey = data.sortKey;
this._ticker = data.ticker;
this._timeoutAfter = data.timeoutAfter;
this._usesChronometer = data.usesChronometer;
this._vibrate = data.vibrate;
this._visibility = data.visibility;
this._when = data.when;
}
// Defaults
this._people = this._people || [];
this._smallIcon = this._smallIcon || {
icon: 'ic_launcher',
};
}
/**
@ -506,7 +446,7 @@ export default class AndroidNotification {
}
build(): NativeAndroidNotification {
// TODO: Validation
// TODO: Validation of required fields
if (!this._channelId) {
throw new Error(
'AndroidNotification: Missing required `channelId` property'

View File

@ -3,48 +3,37 @@
* IOSNotification representation wrapper
*/
import type Notification from './Notification';
type AttachmentOptions = {|
TypeHint: string,
ThumbnailHidden: boolean,
ThumbnailClippingRect: {
height: number,
width: number,
x: number,
y: number,
},
ThumbnailTime: number,
|};
type Attachment = {|
identifier: string,
options?: AttachmentOptions,
url: string,
|};
export type NativeIOSNotification = {|
alertAction?: string,
attachments: Attachment[],
badge?: number,
category?: string,
hasAction?: boolean,
launchImage?: string,
threadIdentifier?: string,
|};
import type {
Attachment,
AttachmentOptions,
NativeIOSNotification,
} from './types';
export default class IOSNotification {
_alertAction: string; // alertAction | N/A
_alertAction: string | void; // alertAction | N/A
_attachments: Attachment[]; // N/A | attachments
_badge: number; // applicationIconBadgeNumber | badge
_category: string;
_hasAction: boolean; // hasAction | N/A
_launchImage: string; // alertLaunchImage | launchImageName
_badge: number | void; // applicationIconBadgeNumber | badge
_category: string | void;
_hasAction: boolean | void; // hasAction | N/A
_launchImage: string | void; // alertLaunchImage | launchImageName
_notification: Notification;
_threadIdentifier: string; // N/A | threadIdentifier
_threadIdentifier: string | void; // N/A | threadIdentifier
constructor(notification: Notification) {
this._attachments = [];
constructor(notification: Notification, data?: NativeIOSNotification) {
this._notification = notification;
if (data) {
this._alertAction = data.alertAction;
this._attachments = data.attachments;
this._badge = data.badge;
this._category = data.category;
this._hasAction = data.hasAction;
this._launchImage = data.launchImage;
this._threadIdentifier = data.threadIdentifier;
}
// Defaults
this._attachments = this._attachments || [];
}
/**
@ -128,7 +117,7 @@ export default class IOSNotification {
}
build(): NativeIOSNotification {
// TODO: Validation
// TODO: Validation of required fields
return {
alertAction: this._alertAction,

View File

@ -7,21 +7,7 @@ import AndroidNotification from './AndroidNotification';
import IOSNotification from './IOSNotification';
import { generatePushID, isObject } from '../../utils';
import type { NativeAndroidNotification } from './AndroidNotification';
import type { NativeIOSNotification } from './IOSNotification';
import type { Schedule } from './';
type NativeNotification = {|
android?: NativeAndroidNotification,
body: string,
data: { [string]: string },
ios?: NativeIOSNotification,
notificationId: string,
schedule?: Schedule,
sound?: string,
subtitle?: string,
title: string,
|};
import type { NativeNotification } from './types';
export default class Notification {
// iOS 8/9 | 10+ | Android
@ -34,12 +20,23 @@ export default class Notification {
_subtitle: string | void; // N/A | subtitle | subText
_title: string; // alertTitle | title | contentTitle
constructor() {
this._android = new AndroidNotification(this);
this._data = {};
this._ios = new IOSNotification(this);
// TODO: Is this the best way to generate an ID?
this._notificationId = generatePushID();
constructor(data?: NativeNotification) {
this._android = new AndroidNotification(this, data && data.android);
this._ios = new IOSNotification(this, data && data.ios);
if (data) {
this._body = data.body;
this._data = data.data;
// TODO: Is this the best way to generate an ID?
this._notificationId = data.notificationId;
this._sound = data.sound;
this._subtitle = data.subtitle;
this._title = data.title;
}
// Defaults
this._data = this._data || {};
this._notificationId = this._notificationId || generatePushID();
}
get android(): AndroidNotification {

View File

@ -15,26 +15,20 @@ import {
GroupAlert,
Priority,
Visibility,
} from './AndroidNotification';
} from './types';
import type App from '../core/app';
import type { NativeNotification, Schedule } from './types';
// TODO: Received notification type will be different from sent notification
type OnNotification = Notification => any;
type OnNotificationObserver = {
next: OnNotification,
};
export type Schedule = {
exact?: boolean,
fireDate: number,
repeatInterval?: 'minute' | 'hour' | 'day' | 'week',
};
const NATIVE_EVENTS = [
'notifications_notification_clicked',
'notifications_notification_displayed',
'notifications_notification_pressed',
'notifications_notification_received',
];
@ -69,10 +63,13 @@ export default class Notifications extends ModuleBase {
SharedEventEmitter.addListener(
// sub to internal native event - this fans out to
// public event name: onNotificationClicked
'notifications_notification_clicked',
(notification: Notification) => {
SharedEventEmitter.emit('onNotificationClicked', notification);
// public event name: onNotificationPressed
'notifications_notification_pressed',
(notification: NativeNotification) => {
SharedEventEmitter.emit(
'onNotificationPressed',
new Notification(notification)
);
}
);
@ -80,8 +77,11 @@ export default class Notifications extends ModuleBase {
// sub to internal native event - this fans out to
// public event name: onNotificationDisplayed
'notifications_notification_displayed',
(notification: Notification) => {
SharedEventEmitter.emit('onNotificationDisplayed', notification);
(notification: NativeNotification) => {
SharedEventEmitter.emit(
'onNotificationDisplayed',
new Notification(notification)
);
}
);
@ -89,8 +89,11 @@ export default class Notifications extends ModuleBase {
// sub to internal native event - this fans out to
// public event name: onNotification
'notifications_notification_received',
(notification: Notification) => {
SharedEventEmitter.emit('onNotification', notification);
(notification: NativeNotification) => {
SharedEventEmitter.emit(
'onNotification',
new Notification(notification)
);
}
);
}
@ -166,29 +169,6 @@ export default class Notifications extends ModuleBase {
};
}
onNotificationClicked(
nextOrObserver: OnNotification | OnNotificationObserver
): () => any {
let listener;
if (isFunction(nextOrObserver)) {
listener = nextOrObserver;
} else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) {
listener = nextOrObserver.next;
} else {
throw new Error(
'Notifications.onNotificationClicked failed: First argument must be a function or observer object with a `next` function.'
);
}
getLogger(this).info('Creating onNotificationClicked listener');
SharedEventEmitter.addListener('onNotificationClicked', listener);
return () => {
getLogger(this).info('Removing onNotificationClicked listener');
SharedEventEmitter.removeListener('onNotificationClicked', listener);
};
}
onNotificationDisplayed(
nextOrObserver: OnNotification | OnNotificationObserver
): () => any {
@ -212,6 +192,30 @@ export default class Notifications extends ModuleBase {
};
}
onNotificationPressed(
nextOrObserver: OnNotification | OnNotificationObserver
): () => any {
let listener: Notification => any;
if (isFunction(nextOrObserver)) {
// $FlowBug: Not coping with the overloaded method signature
listener = nextOrObserver;
} else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) {
listener = nextOrObserver.next;
} else {
throw new Error(
'Notifications.onNotificationPressed failed: First argument must be a function or observer object with a `next` function.'
);
}
getLogger(this).info('Creating onNotificationPressed listener');
SharedEventEmitter.addListener('onNotificationPressed', listener);
return () => {
getLogger(this).info('Removing onNotificationPressed listener');
SharedEventEmitter.removeListener('onNotificationPressed', listener);
};
}
/**
* Remove all delivered notifications.
* @returns {*}

View File

@ -0,0 +1,162 @@
/**
* @flow
*/
export const BadgeIconType = {
Large: 2,
None: 0,
Small: 1,
};
export const Category = {
Alarm: 'alarm',
Call: 'call',
Email: 'email',
Error: 'err',
Event: 'event',
Message: 'msg',
Progress: 'progress',
Promo: 'promo',
Recommendation: 'recommendation',
Reminder: 'reminder',
Service: 'service',
Social: 'social',
Status: 'status',
System: 'system',
Transport: 'transport',
};
export const Defaults = {
All: -1,
Lights: 4,
Sound: 1,
Vibrate: 2,
};
export const GroupAlert = {
All: 0,
Children: 2,
Summary: 1,
};
export const Priority = {
Default: 0,
High: 1,
Low: -1,
Max: 2,
Min: -2,
};
export const Visibility = {
Private: 0,
Public: 1,
Secret: -1,
};
export type BadgeIconTypeType = $Values<typeof BadgeIconType>;
export type CategoryType = $Values<typeof Category>;
export type DefaultsType = $Values<typeof Defaults>;
export type GroupAlertType = $Values<typeof GroupAlert>;
export type PriorityType = $Values<typeof Priority>;
export type VisibilityType = $Values<typeof Visibility>;
export type Lights = {
argb: number,
onMs: number,
offMs: number,
};
export type Progress = {
max: number,
progress: number,
indeterminate: boolean,
};
export type SmallIcon = {
icon: string,
level?: number,
};
export type NativeAndroidNotification = {|
// TODO actions: Action[],
autoCancel: boolean,
badgeIconType: BadgeIconTypeType,
category: CategoryType,
channelId: string,
clickAction?: string,
color: string,
colorized: boolean,
contentInfo: string,
defaults: DefaultsType[],
group: string,
groupAlertBehaviour: GroupAlertType,
groupSummary: boolean,
largeIcon: string,
lights: Lights,
localOnly: boolean,
number: number,
ongoing: boolean,
onlyAlertOnce: boolean,
people: string[],
priority: PriorityType,
progress: Progress,
// publicVersion: Notification,
remoteInputHistory: string[],
shortcutId: string,
showWhen: boolean,
smallIcon: SmallIcon,
sortKey: string,
// TODO: style: Style,
ticker: string,
timeoutAfter: number,
usesChronometer: boolean,
vibrate: number[],
visibility: VisibilityType,
when: number,
|};
export type AttachmentOptions = {|
TypeHint: string,
ThumbnailHidden: boolean,
ThumbnailClippingRect: {
height: number,
width: number,
x: number,
y: number,
},
ThumbnailTime: number,
|};
export type Attachment = {|
identifier: string,
options?: AttachmentOptions,
url: string,
|};
export type NativeIOSNotification = {|
alertAction?: string,
attachments: Attachment[],
badge?: number,
category?: string,
hasAction?: boolean,
launchImage?: string,
threadIdentifier?: string,
|};
export type Schedule = {
exact?: boolean,
fireDate: number,
repeatInterval?: 'minute' | 'hour' | 'day' | 'week',
};
export type NativeNotification = {|
android?: NativeAndroidNotification,
body: string,
data: { [string]: string },
ios?: NativeIOSNotification,
notificationId: string,
schedule?: Schedule,
sound?: string,
subtitle?: string,
title: string,
|};