[notifications] First pass at notifications JS API

This commit is contained in:
Chris Bianca 2018-02-09 17:00:03 +00:00
parent 7b95613ec6
commit fb57dc5482
6 changed files with 971 additions and 41 deletions

View File

@ -0,0 +1,25 @@
package io.invertase.firebase.notifications;
import android.support.v4.app.NotificationCompat;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
public class RNFirebaseNotifications extends ReactContextBaseJavaModule {
public RNFirebaseNotifications(ReactApplicationContext context) {
super(context);
}
@Override
public String getName() {
return "RNFirebaseNotifications";
}
@ReactMethod
public void sendNotification(Promise promise) {
//
NotificationCompat.Builder builder = new NotificationCompat.Builder()
}
}

View File

@ -0,0 +1,37 @@
package io.invertase.firebase.notifications;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class RNFirebaseNotificationsPackage implements ReactPackage {
public RNFirebaseNotificationsPackage() {
}
/**
* @param reactContext react application context that can be used to create modules
* @return list of native modules to register with the newly created catalyst instance
*/
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new RNFirebaseNotifications(reactContext));
return modules;
}
/**
* @param reactContext
* @return a list of view managers that should be registered with {@link UIManagerModule}
*/
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View File

@ -0,0 +1,546 @@
/**
* @flow
* AndroidNotification representation wrapper
*/
import type Notification from './Notification';
type Lights = {
argb: number,
onMs: number,
offMs: number,
};
type Progress = {
max: number,
progress: number,
indeterminate: boolean,
};
type SmallIcon = {
icon: number,
level?: number,
};
export type NativeAndroidNotification = {
// TODO actions: Action[],
autoCancel: boolean,
badgeIconType: BadgeIconTypeType,
category: CategoryType,
channelId: string,
color: number,
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>;
export default class AndroidNotification {
// TODO actions: Action[]; // icon, title, ??pendingIntent??, allowGeneratedReplies, extender, extras, remoteinput (ugh)
_autoCancel: boolean;
_badgeIconType: BadgeIconTypeType;
_category: CategoryType;
_channelId: string;
_color: number;
_colorized: boolean;
_contentInfo: string;
_defaults: DefaultsType[];
_group: string;
_groupAlertBehaviour: GroupAlertType;
_groupSummary: boolean;
_largeIcon: string;
_lights: Lights;
_localOnly: boolean;
_notification: Notification;
_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; // Need to figure out if this can work
_ticker: string;
_timeoutAfter: number;
_usesChronometer: boolean;
_vibrate: number[];
_visibility: VisibilityType;
_when: number;
// android unsupported
// content: RemoteViews
// contentIntent: PendingIntent - need to look at what this is
// customBigContentView: RemoteViews
// customContentView: RemoteViews
// customHeadsUpContentView: RemoteViews
// deleteIntent: PendingIntent
// fullScreenIntent: PendingIntent
// sound.streamType
constructor(notification: Notification) {
this._notification = notification;
this._people = [];
}
/**
*
* @param identifier
* @param identifier
* @param identifier
* @returns {Notification}
*/
addPerson(person: string): Notification {
this._people.push(person);
return this._notification;
}
/**
*
* @param autoCancel
* @returns {Notification}
*/
setAutoCancel(autoCancel: boolean): Notification {
this._autoCancel = autoCancel;
return this._notification;
}
/**
*
* @param badgeIconType
* @returns {Notification}
*/
setBadgeIconType(badgeIconType: BadgeIconTypeType): Notification {
this._badgeIconType = badgeIconType;
return this._notification;
}
/**
*
* @param category
* @returns {Notification}
*/
setCategory(category: CategoryType): Notification {
if (!Object.values(Category).includes(category)) {
throw new Error(`AndroidNotification: Invalid Category: ${category}`);
}
this._category = category;
return this._notification;
}
/**
*
* @param channelId
* @returns {Notification}
*/
setChannelId(channelId: string): Notification {
this._channelId = channelId;
return this._notification;
}
/**
*
* @param color
* @returns {Notification}
*/
setColor(color: number): Notification {
this._color = color;
return this._notification;
}
/**
*
* @param colorized
* @returns {Notification}
*/
setColorized(colorized: boolean): Notification {
this._colorized = colorized;
return this._notification;
}
/**
*
* @param contentInfo
* @returns {Notification}
*/
setContentInfo(contentInfo: string): Notification {
this._contentInfo = contentInfo;
return this._notification;
}
/**
*
* @param defaults
* @returns {Notification}
*/
setDefaults(defaults: DefaultsType[]): Notification {
this._defaults = defaults;
return this._notification;
}
/**
*
* @param group
* @returns {Notification}
*/
setGroup(group: string): Notification {
this._group = group;
return this._notification;
}
/**
*
* @param groupAlertBehaviour
* @returns {Notification}
*/
setGroupAlertBehaviour(groupAlertBehaviour: GroupAlertType): Notification {
this._groupAlertBehaviour = groupAlertBehaviour;
return this._notification;
}
/**
*
* @param groupSummary
* @returns {Notification}
*/
setGroupSummary(groupSummary: boolean): Notification {
this._groupSummary = groupSummary;
return this._notification;
}
/**
*
* @param largeIcon
* @returns {Notification}
*/
setLargeIcon(largeIcon: string): Notification {
this._largeIcon = largeIcon;
return this._notification;
}
/**
*
* @param argb
* @param onMs
* @param offMs
* @returns {Notification}
*/
setLights(argb: number, onMs: number, offMs: number): Notification {
this._lights = {
argb,
onMs,
offMs,
};
return this._notification;
}
/**
*
* @param localOnly
* @returns {Notification}
*/
setLocalOnly(localOnly: boolean): Notification {
this._localOnly = localOnly;
return this._notification;
}
/**
*
* @param number
* @returns {Notification}
*/
setNumber(number: number): Notification {
this._number = number;
return this._notification;
}
/**
*
* @param ongoing
* @returns {Notification}
*/
setOngoing(ongoing: boolean): Notification {
this._ongoing = ongoing;
return this._notification;
}
/**
*
* @param onlyAlertOnce
* @returns {Notification}
*/
setOnlyAlertOnce(onlyAlertOnce: boolean): Notification {
this._onlyAlertOnce = onlyAlertOnce;
return this._notification;
}
/**
*
* @param priority
* @returns {Notification}
*/
setPriority(priority: PriorityType): Notification {
this._priority = priority;
return this._notification;
}
/**
*
* @param max
* @param progress
* @param indeterminate
* @returns {Notification}
*/
setProgress(
max: number,
progress: number,
indeterminate: boolean
): Notification {
this._progress = {
max,
progress,
indeterminate,
};
return this._notification;
}
/**
*
* @param publicVersion
* @returns {Notification}
*/
setPublicVersion(publicVersion: Notification): Notification {
this._publicVersion = publicVersion;
return this._notification;
}
/**
*
* @param remoteInputHistory
* @returns {Notification}
*/
setRemoteInputHistory(remoteInputHistory: string[]): Notification {
this._remoteInputHistory = remoteInputHistory;
return this._notification;
}
/**
*
* @param shortcutId
* @returns {Notification}
*/
setShortcutId(shortcutId: string): Notification {
this._shortcutId = shortcutId;
return this._notification;
}
/**
*
* @param showWhen
* @returns {Notification}
*/
setShowWhen(showWhen: boolean): Notification {
this._showWhen = showWhen;
return this._notification;
}
/**
*
* @param icon
* @param level
* @returns {Notification}
*/
setSmallIcon(icon: number, level?: number): Notification {
this._smallIcon = {
icon,
level,
};
return this._notification;
}
/**
*
* @param sortKey
* @returns {Notification}
*/
setSortKey(sortKey: string): Notification {
this._sortKey = sortKey;
return this._notification;
}
/**
*
* @param ticker
* @returns {Notification}
*/
setTicker(ticker: string): Notification {
this._ticker = ticker;
return this._notification;
}
/**
*
* @param timeoutAfter
* @returns {Notification}
*/
setTimeoutAfter(timeoutAfter: number): Notification {
this._timeoutAfter = timeoutAfter;
return this._notification;
}
/**
*
* @param usesChronometer
* @returns {Notification}
*/
setUsesChronometer(usesChronometer: boolean): Notification {
this._usesChronometer = usesChronometer;
return this._notification;
}
/**
*
* @param vibrate
* @returns {Notification}
*/
setVibrate(vibrate: number[]): Notification {
this._vibrate = vibrate;
return this._notification;
}
/**
*
* @param when
* @returns {Notification}
*/
setWhen(when: number): Notification {
this._when = when;
return this._notification;
}
build(): NativeAndroidNotification {
// TODO: Validation
return {
// TODO actions: Action[],
autoCancel: this._autoCancel,
badgeIconType: this._badgeIconType,
category: this._category,
channelId: this._channelId,
color: this._color,
colorized: this._colorized,
contentInfo: this._contentInfo,
defaults: this._defaults,
group: this._group,
groupAlertBehaviour: this._groupAlertBehaviour,
groupSummary: this._groupSummary,
largeIcon: this._largeIcon,
lights: this._lights,
localOnly: this._localOnly,
number: this._number,
ongoing: this._ongoing,
onlyAlertOnce: this._onlyAlertOnce,
people: this._people,
priority: this._priority,
progress: this._progress,
publicVersion: this._publicVersion,
remoteInputHistory: this._remoteInputHistory,
shortcutId: this._shortcutId,
showWhen: this._showWhen,
smallIcon: this._smallIcon,
sortKey: this._sortKey,
// TODO: style: Style,
ticker: this._ticker,
timeoutAfter: this._timeoutAfter,
usesChronometer: this._usesChronometer,
vibrate: this._vibrate,
visibility: this._visibility,
when: this._when,
};
}
}

View File

@ -0,0 +1,159 @@
/**
* @flow
* IOSNotification representation wrapper
*/
import { generatePushID } from '../../utils';
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,
identifier?: string,
launchImage?: string,
threadIdentifier?: string,
};
export default class IOSNotification {
_alertAction: string; // alertAction | N/A
_attachments: Attachment[]; // N/A | attachments
_badge: number; // applicationIconBadgeNumber | badge
_category: string;
_hasAction: boolean; // hasAction | N/A
_identifier: string; // N/A | identifier
_launchImage: string; // alertLaunchImage | launchImageName
_notification: Notification;
_threadIdentifier: string; // N/A | threadIdentifier
constructor(notification: Notification) {
this._attachments = [];
// TODO: Is this the best way to generate an ID?
this._identifier = generatePushID();
this._notification = notification;
}
/**
*
* @param identifier
* @param identifier
* @param identifier
* @returns {Notification}
*/
addAttachment(
identifier: string,
url: string,
options?: AttachmentOptions
): Notification {
this._attachments.push({
identifier,
options,
url,
});
return this._notification;
}
/**
*
* @param alertAction
* @returns {Notification}
*/
setAlertAction(alertAction: string): Notification {
this._alertAction = alertAction;
return this._notification;
}
/**
*
* @param badge
* @returns {Notification}
*/
setBadge(badge: number): Notification {
this._badge = badge;
return this._notification;
}
/**
*
* @param category
* @returns {Notification}
*/
setCategory(category: string): Notification {
this._category = category;
return this._notification;
}
/**
*
* @param hasAction
* @returns {Notification}
*/
setHasAction(hasAction: boolean): Notification {
this._hasAction = hasAction;
return this._notification;
}
/**
*
* @param identifier
* @returns {Notification}
*/
setIdentifier(identifier: string): Notification {
this._identifier = identifier;
return this._notification;
}
/**
*
* @param launchImage
* @returns {Notification}
*/
setLaunchImage(launchImage: string): Notification {
this._launchImage = launchImage;
return this._notification;
}
/**
*
* @param threadIdentifier
* @returns {Notification}
*/
setThreadIdentifier(threadIdentifier: string): Notification {
this._threadIdentifier = threadIdentifier;
return this._notification;
}
build(): NativeIOSNotification {
// TODO: Validation
return {
alertAction: this._alertAction,
attachments: this._attachments,
badge: this._badge,
category: this._category,
hasAction: this._hasAction,
identifier: this._identifier,
launchImage: this._launchImage,
threadIdentifier: this._threadIdentifier,
};
}
}

View File

@ -0,0 +1,120 @@
/**
* @flow
* Notification representation wrapper
*/
import AndroidNotification from './AndroidNotification';
import IOSNotification from './IOSNotification';
import { isObject } from '../../utils';
import type { NativeAndroidNotification } from './AndroidNotification';
import type { NativeIOSNotification } from './IOSNotification';
type NativeNotification = {|
android: NativeAndroidNotification,
body: string,
data: { [string]: string },
ios: NativeIOSNotification,
sound?: string,
subtitle?: string,
title: string,
|};
export default class Notification {
// iOS 8/9 | 10+ | Android
_android: AndroidNotification;
_body: string; // alertBody | body | contentText
_data: { [string]: string }; // userInfo | userInfo | extras
_ios: IOSNotification;
_sound: string | void; // soundName | sound | sound
_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);
}
get android(): AndroidNotification {
return this._android;
}
get ios(): IOSNotification {
return this._ios;
}
/**
*
* @param body
* @returns {Notification}
*/
setBody(body: string): Notification {
this._body = body;
return this;
}
/**
*
* @param data
* @returns {Notification}
*/
setData(data: Object = {}): Notification {
if (!isObject(data)) {
throw new Error(
`Notification:withData expects an object but got type '${typeof data}'.`
);
}
this._data = data;
return this;
}
/**
*
* @param sound
* @returns {Notification}
*/
setSound(sound: string): Notification {
this._sound = sound;
return this;
}
/**
*
* @param subtitle
* @returns {Notification}
*/
setSubtitle(subtitle: string): Notification {
this._subtitle = subtitle;
return this;
}
/**
*
* @param title
* @returns {Notification}
*/
setTitle(title: string): Notification {
this._title = title;
return this;
}
build(): NativeNotification {
// Android required fields: body, title, smallicon
// iOS required fields: TODO
if (!this.body) {
throw new Error('Notification: Missing required `body` property');
} else if (!this.title) {
throw new Error('Notification: Missing required `title` property');
}
return {
android: this._android.build(),
body: this._body,
data: this._data,
ios: this._ios.build(),
sound: this._sound,
subtitle: this._subtitle,
title: this._title,
};
}
}

View File

@ -7,28 +7,49 @@ import { getLogger } from '../../utils/log';
import ModuleBase from '../../utils/ModuleBase';
import { getNativeModule } from '../../utils/native';
import { isFunction, isObject } from '../../utils';
import Notification from './Notification';
import {
BadgeIconType,
Category,
Defaults,
GroupAlert,
Priority,
Visibility,
} from './AndroidNotification';
import type App from '../core/firebase-app';
type CreateNotification = {
// TODO
};
type Notification = {
// TODO
};
// TODO: Received notification type will be different from sent notification
type OnNotification = Notification => any;
type OnNotificationObserver = {
next: OnNotification,
};
// TODO: Schedule type
type Schedule = {
build: () => Object,
};
const NATIVE_EVENTS = ['notifications_notification_received'];
export const MODULE_NAME = 'RNFirebaseNotifications';
export const NAMESPACE = 'notifications';
// iOS 8/9 scheduling
// fireDate: Date;
// timeZone: TimeZone;
// repeatInterval: NSCalendar.Unit;
// repeatCalendar: Calendar;
// region: CLRegion;
// regionTriggersOnce: boolean;
// iOS 10 scheduling
// TODO
// Android scheduling
// TODO
/**
* @class Notifications
*/
@ -51,28 +72,34 @@ export default class Notifications extends ModuleBase {
);
}
/**
* Cancel a local notification by id - using '*' will cancel
* all local notifications.
* @param id
* @returns {*}
*/
cancelNotification(id: string): Promise<void> {
if (!id) return Promise.reject(new Error('Missing notification id'));
if (id === '*') return getNativeModule(this).cancelAllLocalNotifications();
return getNativeModule(this).cancelLocalNotification(id);
cancelAllNotifications(): Promise<void> {
return getNativeModule(this).cancelAllLocalNotifications();
}
/**
* Create and display a local notification
* Cancel a local notification by id.
* @param id
* @returns {*}
*/
cancelNotification(notificationId: string): Promise<void> {
if (!notificationId) {
return Promise.reject(new Error('Missing notificationId'));
}
return getNativeModule(this).cancelLocalNotification(notificationId);
}
/**
* Display a local notification
* @param notification
* @returns {*}
*/
createNotification(notification: CreateNotification): Promise<void> {
const _notification = Object.assign({}, notification);
_notification.id = _notification.id || new Date().getTime().toString();
_notification.local_notification = true;
return getNativeModule(this).createLocalNotification(_notification);
displayNotification(notification: Notification): Promise<void> {
if (!(notification instanceof Notification)) {
throw new Error(
`Notifications:displayNotification expects a 'Notification' but got type ${typeof notification}`
);
}
return getNativeModule(this).displayNotification(notification.build());
}
/**
@ -108,22 +135,23 @@ export default class Notifications extends ModuleBase {
}
/**
* Remove a delivered notification.
* @param id
* Remove all delivered notifications.
* @returns {*}
*/
removeDeliveredNotification(id: string): Promise<void> {
if (!id) return Promise.reject(new Error('Missing notification id'));
return getNativeModule(this).removeDeliveredNotification(id);
removeAllDeliveredNotifications(): Promise<void> {
return getNativeModule(this).removeAllDeliveredNotifications();
}
/**
* Remove all delivered notifications.
* @param id
* Remove a delivered notification.
* @param notificationId
* @returns {*}
*/
removeDeliveredNotifications(): Promise<void> {
return getNativeModule(this).removeDeliveredNotifications();
removeDeliveredNotification(notificationId: string): Promise<void> {
if (!notificationId) {
return Promise.reject(new Error('Missing notificationId'));
}
return getNativeModule(this).removeDeliveredNotification(notificationId);
}
/**
@ -131,15 +159,30 @@ export default class Notifications extends ModuleBase {
* @param notification
* @returns {*}
*/
scheduleNotification(notification: CreateNotification): Promise<void> {
const _notification = Object.assign({}, notification);
if (!notification.id)
return Promise.reject(
new Error('An id is required to schedule a local notification.')
scheduleNotification(
notification: Notification,
schedule: Schedule
): Promise<void> {
if (!(notification instanceof Notification)) {
throw new Error(
`Notifications:scheduleNotification expects a 'Notification' but got type ${typeof notification}`
);
}
return getNativeModule(this).scheduleNotification(
notification.build(),
schedule.build()
);
_notification.local_notification = true;
return getNativeModule(this).scheduleLocalNotification(_notification);
}
}
export const statics = {};
export const statics = {
Android: {
BadgeIconType,
Category,
Defaults,
GroupAlert,
Priority,
Visibility,
},
Notification,
};