[notifications] Add support for android actions
This commit is contained in:
parent
983bbbcada
commit
e537955212
@ -110,23 +110,28 @@ public class BundleJSONConverter {
|
||||
SETTERS.put(JSONArray.class, new Setter() {
|
||||
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
|
||||
JSONArray jsonArray = (JSONArray) value;
|
||||
ArrayList<String> stringArrayList = new ArrayList<String>();
|
||||
// Empty list, can't even figure out the type, assume an ArrayList<String>
|
||||
if (jsonArray.length() == 0) {
|
||||
bundle.putStringArrayList(key, stringArrayList);
|
||||
bundle.putStringArrayList(key, new ArrayList<String>());
|
||||
return;
|
||||
}
|
||||
|
||||
// Only strings are supported for now
|
||||
for (int i = 0; i < jsonArray.length(); i++) {
|
||||
Object current = jsonArray.get(i);
|
||||
if (current instanceof String) {
|
||||
stringArrayList.add((String) current);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unexpected type in an array: " + current.getClass());
|
||||
if (jsonArray.get(0) instanceof String) {
|
||||
ArrayList<String> stringArrayList = new ArrayList<String>();
|
||||
for (int i = 0; i < jsonArray.length(); i++) {
|
||||
stringArrayList.add((String) jsonArray.get(i));
|
||||
}
|
||||
bundle.putStringArrayList(key, stringArrayList);
|
||||
} else if (jsonArray.get(0) instanceof JSONObject) {
|
||||
ArrayList<Bundle> bundleArrayList = new ArrayList<>();
|
||||
for (int i =0; i < jsonArray.length(); i++) {
|
||||
bundleArrayList.add(convertToBundle((JSONObject) jsonArray.get(i)));
|
||||
}
|
||||
bundle.putSerializable(key, bundleArrayList);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unexpected type in an array: " + jsonArray.get(0).getClass());
|
||||
}
|
||||
bundle.putStringArrayList(key, stringArrayList);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -152,13 +157,18 @@ public class BundleJSONConverter {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Special case List<String> as getClass would not work, since List is an interface
|
||||
// Special case List<?> as getClass would not work, since List is an interface
|
||||
if (value instanceof List<?>) {
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> listValue = (List<String>) value;
|
||||
for (String stringValue : listValue) {
|
||||
jsonArray.put(stringValue);
|
||||
List<Object> listValue = (List<Object>) value;
|
||||
for (Object objValue : listValue) {
|
||||
if (objValue instanceof String) {
|
||||
jsonArray.put(objValue);
|
||||
} else if (objValue instanceof Bundle) {
|
||||
jsonArray.put(convertToJSON((Bundle) objValue));
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported type: " + objValue.getClass());
|
||||
}
|
||||
}
|
||||
json.put(key, jsonArray);
|
||||
continue;
|
||||
|
@ -17,7 +17,9 @@ import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.RemoteInput;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.util.Log;
|
||||
|
||||
@ -325,10 +327,8 @@ public class RNFirebaseNotificationManager {
|
||||
}
|
||||
if (android.containsKey("smallIcon")) {
|
||||
Bundle smallIcon = android.getBundle("smallIcon");
|
||||
int smallIconResourceId = getResourceId("mipmap", smallIcon.getString("icon"));
|
||||
if (smallIconResourceId == 0) {
|
||||
smallIconResourceId = getResourceId("drawable", smallIcon.getString("icon"));
|
||||
}
|
||||
int smallIconResourceId = getIcon(smallIcon.getString("icon"));
|
||||
|
||||
if (smallIconResourceId != 0) {
|
||||
if (smallIcon.containsKey("level")) {
|
||||
Double level = smallIcon.getDouble("level");
|
||||
@ -386,17 +386,17 @@ public class RNFirebaseNotificationManager {
|
||||
|
||||
notification.setStyle(bigPicture);
|
||||
} */
|
||||
|
||||
// Create the notification intent
|
||||
Intent intent = new Intent(context, intentClass);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
intent.putExtras(notification);
|
||||
if (android.containsKey("clickAction")) {
|
||||
intent.setAction(android.getString("clickAction"));
|
||||
// Build any actions
|
||||
if (android.containsKey("actions")) {
|
||||
List<Bundle> actions = (List) android.getSerializable("actions");
|
||||
for (Bundle a : actions) {
|
||||
NotificationCompat.Action action = createAction(a, intentClass, notification);
|
||||
nb = nb.addAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
PendingIntent contentIntent = PendingIntent.getActivity(context, notificationId.hashCode(), intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
// Create the notification intent
|
||||
PendingIntent contentIntent = createIntent(intentClass, notification, android.getString("clickAction"));
|
||||
nb = nb.setContentIntent(contentIntent);
|
||||
|
||||
// Build the notification and send it
|
||||
@ -415,6 +415,75 @@ public class RNFirebaseNotificationManager {
|
||||
}
|
||||
}
|
||||
|
||||
private NotificationCompat.Action createAction(Bundle action, Class intentClass, Bundle notification) {
|
||||
String actionKey = action.getString("action");
|
||||
PendingIntent actionIntent = createIntent(intentClass, notification, actionKey);
|
||||
|
||||
int icon = getIcon(action.getString("icon"));
|
||||
String title = action.getString("title");
|
||||
|
||||
NotificationCompat.Action.Builder ab = new NotificationCompat.Action.Builder(icon, title, actionIntent);
|
||||
|
||||
if (action.containsKey("allowGeneratedReplies")) {
|
||||
ab = ab.setAllowGeneratedReplies(action.getBoolean("allowGeneratedReplies"));
|
||||
}
|
||||
if (action.containsKey("remoteInputs")) {
|
||||
List<Bundle> remoteInputs = (List) action.getSerializable("remoteInputs");
|
||||
for (Bundle ri : remoteInputs) {
|
||||
RemoteInput remoteInput = createRemoteInput(ri);
|
||||
ab = ab.addRemoteInput(remoteInput);
|
||||
}
|
||||
}
|
||||
// TODO: SemanticAction and ShowsUserInterface only available on v28?
|
||||
// if (action.containsKey("semanticAction")) {
|
||||
// Double semanticAction = action.getDouble("semanticAction");
|
||||
// ab = ab.setSemanticAction(semanticAction.intValue());
|
||||
// }
|
||||
// if (action.containsKey("showsUserInterface")) {
|
||||
// ab = ab.setShowsUserInterface(action.getBoolean("showsUserInterface"));
|
||||
// }
|
||||
|
||||
return ab.build();
|
||||
}
|
||||
|
||||
private PendingIntent createIntent(Class intentClass, Bundle notification, String action) {
|
||||
Intent intent = new Intent(context, intentClass);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
intent.putExtras(notification);
|
||||
|
||||
if (action != null) {
|
||||
intent.setAction(action);
|
||||
}
|
||||
|
||||
String notificationId = notification.getString("notificationId");
|
||||
|
||||
return PendingIntent.getActivity(context, notificationId.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
private RemoteInput createRemoteInput(Bundle remoteInput) {
|
||||
String resultKey = remoteInput.getString("resultKey");
|
||||
|
||||
RemoteInput.Builder rb = new RemoteInput.Builder(resultKey);
|
||||
|
||||
if (remoteInput.containsKey("allowedDataTypes")) {
|
||||
List<Bundle> allowedDataTypes = (List) remoteInput.getSerializable("allowedDataTypes");
|
||||
for (Bundle adt : allowedDataTypes) {
|
||||
rb.setAllowDataType(adt.getString("mimeType"), adt.getBoolean("allow"));
|
||||
}
|
||||
}
|
||||
if (remoteInput.containsKey("allowFreeFormInput")) {
|
||||
rb.setAllowFreeFormInput(remoteInput.getBoolean("allowFreeFormInput"));
|
||||
}
|
||||
if (remoteInput.containsKey("choices")) {
|
||||
rb.setChoices(remoteInput.getStringArray("choices"));
|
||||
}
|
||||
if (remoteInput.containsKey("label")) {
|
||||
rb.setLabel(remoteInput.getString("label"));
|
||||
}
|
||||
|
||||
return rb.build();
|
||||
}
|
||||
|
||||
private Bitmap getBitmap(String image) {
|
||||
if (image.startsWith("http://") || image.startsWith("https://")) {
|
||||
return getBitmapFromUrl(image);
|
||||
@ -436,6 +505,14 @@ public class RNFirebaseNotificationManager {
|
||||
}
|
||||
}
|
||||
|
||||
private int getIcon(String icon) {
|
||||
int smallIconResourceId = getResourceId("mipmap", icon);
|
||||
if (smallIconResourceId == 0) {
|
||||
smallIconResourceId = getResourceId("drawable", icon);
|
||||
}
|
||||
return smallIconResourceId;
|
||||
}
|
||||
|
||||
private Class getMainActivityClass() {
|
||||
String packageName = context.getPackageName();
|
||||
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
|
||||
@ -555,9 +632,9 @@ public class RNFirebaseNotificationManager {
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, notificationId.hashCode(),
|
||||
notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
if (schedule.containsKey("interval")) {
|
||||
if (schedule.containsKey("repeatInterval")) {
|
||||
Long interval = null;
|
||||
switch (schedule.getString("interval")) {
|
||||
switch (schedule.getString("repeatInterval")) {
|
||||
case "minute":
|
||||
interval = 60000L;
|
||||
break;
|
||||
|
@ -7,12 +7,12 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.RemoteInput;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.ActivityEventListener;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.LifecycleEventListener;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
@ -198,6 +198,12 @@ public class RNFirebaseNotifications extends ReactContextBaseJavaModule implemen
|
||||
notificationOpenMap.putString("action", intent.getAction());
|
||||
notificationOpenMap.putMap("notification", notificationMap);
|
||||
|
||||
// Check for remote input results
|
||||
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
|
||||
if (remoteInput != null) {
|
||||
notificationOpenMap.putMap("results", Arguments.makeNativeMap(remoteInput));
|
||||
}
|
||||
|
||||
return notificationOpenMap;
|
||||
}
|
||||
|
||||
|
150
lib/modules/notifications/AndroidAction.js
Normal file
150
lib/modules/notifications/AndroidAction.js
Normal file
@ -0,0 +1,150 @@
|
||||
/**
|
||||
* @flow
|
||||
* AndroidAction representation wrapper
|
||||
*/
|
||||
import RemoteInput, {
|
||||
fromNativeAndroidRemoteInput,
|
||||
} from './AndroidRemoteInput';
|
||||
import { SemanticAction } from './types';
|
||||
import type { NativeAndroidAction, SemanticActionType } from './types';
|
||||
|
||||
export default class AndroidAction {
|
||||
_action: string;
|
||||
_allowGeneratedReplies: boolean | void;
|
||||
_icon: string;
|
||||
_remoteInputs: RemoteInput[];
|
||||
_semanticAction: SemanticActionType | void;
|
||||
_showUserInterface: boolean | void;
|
||||
_title: string;
|
||||
|
||||
constructor(action: string, icon: string, title: string) {
|
||||
this._action = action;
|
||||
this._icon = icon;
|
||||
this._remoteInputs = [];
|
||||
this._title = title;
|
||||
}
|
||||
|
||||
get action(): string {
|
||||
return this._action;
|
||||
}
|
||||
|
||||
get allowGeneratedReplies(): ?boolean {
|
||||
return this._allowGeneratedReplies;
|
||||
}
|
||||
|
||||
get icon(): string {
|
||||
return this._icon;
|
||||
}
|
||||
|
||||
get remoteInputs(): RemoteInput[] {
|
||||
return this._remoteInputs;
|
||||
}
|
||||
|
||||
get semanticAction(): ?SemanticActionType {
|
||||
return this._semanticAction;
|
||||
}
|
||||
|
||||
get showUserInterface(): ?boolean {
|
||||
return this._showUserInterface;
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param remoteInput
|
||||
* @returns {AndroidAction}
|
||||
*/
|
||||
addRemoteInput(remoteInput: RemoteInput): AndroidAction {
|
||||
if (!(remoteInput instanceof RemoteInput)) {
|
||||
throw new Error(
|
||||
`AndroidAction:addRemoteInput expects an 'RemoteInput' but got type ${typeof remoteInput}`
|
||||
);
|
||||
}
|
||||
this._remoteInputs.push(remoteInput);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param allowGeneratedReplies
|
||||
* @returns {AndroidAction}
|
||||
*/
|
||||
setAllowGenerateReplies(allowGeneratedReplies: boolean): AndroidAction {
|
||||
this._allowGeneratedReplies = allowGeneratedReplies;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param semanticAction
|
||||
* @returns {AndroidAction}
|
||||
*/
|
||||
setSemanticAction(semanticAction: SemanticActionType): AndroidAction {
|
||||
if (!Object.values(SemanticAction).includes(semanticAction)) {
|
||||
throw new Error(
|
||||
`AndroidAction:setSemanticAction Invalid Semantic Action: ${semanticAction}`
|
||||
);
|
||||
}
|
||||
this._semanticAction = semanticAction;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param showUserInterface
|
||||
* @returns {AndroidAction}
|
||||
*/
|
||||
setShowUserInterface(showUserInterface: boolean): AndroidAction {
|
||||
this._showUserInterface = showUserInterface;
|
||||
return this;
|
||||
}
|
||||
|
||||
build(): NativeAndroidAction {
|
||||
if (!this._action) {
|
||||
throw new Error('AndroidAction: Missing required `action` property');
|
||||
} else if (!this._icon) {
|
||||
throw new Error('AndroidAction: Missing required `icon` property');
|
||||
} else if (!this._title) {
|
||||
throw new Error('AndroidAction: Missing required `title` property');
|
||||
}
|
||||
|
||||
return {
|
||||
action: this._action,
|
||||
allowGeneratedReplies: this._allowGeneratedReplies,
|
||||
icon: this._icon,
|
||||
remoteInputs: this._remoteInputs.map(remoteInput => remoteInput.build()),
|
||||
semanticAction: this._semanticAction,
|
||||
showUserInterface: this._showUserInterface,
|
||||
title: this._title,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const fromNativeAndroidAction = (
|
||||
nativeAction: NativeAndroidAction
|
||||
): AndroidAction => {
|
||||
const action = new AndroidAction(
|
||||
nativeAction.action,
|
||||
nativeAction.icon,
|
||||
nativeAction.title
|
||||
);
|
||||
if (nativeAction.allowGeneratedReplies) {
|
||||
action.setAllowGenerateReplies(nativeAction.allowGeneratedReplies);
|
||||
}
|
||||
if (nativeAction.remoteInputs) {
|
||||
nativeAction.remoteInputs.forEach(remoteInput => {
|
||||
action.addRemoteInput(fromNativeAndroidRemoteInput(remoteInput));
|
||||
});
|
||||
}
|
||||
if (nativeAction.semanticAction) {
|
||||
action.setSemanticAction(nativeAction.semanticAction);
|
||||
}
|
||||
if (nativeAction.showUserInterface) {
|
||||
action.setShowUserInterface(nativeAction.showUserInterface);
|
||||
}
|
||||
|
||||
return action;
|
||||
};
|
@ -2,6 +2,7 @@
|
||||
* @flow
|
||||
* AndroidChannel representation wrapper
|
||||
*/
|
||||
import { Importance, Visibility } from './types';
|
||||
import type { ImportanceType, VisibilityType } from './types';
|
||||
|
||||
type NativeAndroidChannel = {|
|
||||
@ -31,6 +32,15 @@ export default class AndroidChannel {
|
||||
_sound: string | void;
|
||||
_vibrationPattern: number[] | void;
|
||||
|
||||
constructor(channelId: string, name: string, importance: ImportanceType) {
|
||||
if (!Object.values(Importance).includes(importance)) {
|
||||
throw new Error(`AndroidChannel() Invalid Importance: ${importance}`);
|
||||
}
|
||||
this._channelId = channelId;
|
||||
this._name = name;
|
||||
this._importance = importance;
|
||||
}
|
||||
|
||||
get bypassDnd(): ?boolean {
|
||||
return this._bypassDnd;
|
||||
}
|
||||
@ -85,16 +95,6 @@ export default class AndroidChannel {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param channelId
|
||||
* @returns {AndroidChannel}
|
||||
*/
|
||||
setChannelId(channelId: string): AndroidChannel {
|
||||
this._channelId = channelId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param description
|
||||
@ -115,16 +115,6 @@ export default class AndroidChannel {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param importance
|
||||
* @returns {AndroidChannel}
|
||||
*/
|
||||
setImportance(importance: ImportanceType): AndroidChannel {
|
||||
this._importance = importance;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param lightColor
|
||||
@ -143,20 +133,15 @@ export default class AndroidChannel {
|
||||
setLockScreenVisibility(
|
||||
lockScreenVisibility: VisibilityType
|
||||
): AndroidChannel {
|
||||
if (!Object.values(Visibility).includes(lockScreenVisibility)) {
|
||||
throw new Error(
|
||||
`AndroidChannel:setLockScreenVisibility Invalid Visibility: ${lockScreenVisibility}`
|
||||
);
|
||||
}
|
||||
this._lockScreenVisibility = lockScreenVisibility;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
* @returns {AndroidChannel}
|
||||
*/
|
||||
setName(name: string): AndroidChannel {
|
||||
this._name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param showBadge
|
||||
|
@ -8,10 +8,15 @@ type NativeAndroidChannelGroup = {|
|
||||
name: string,
|
||||
|};
|
||||
|
||||
export default class AndroidChannel {
|
||||
export default class AndroidChannelGroup {
|
||||
_groupId: string;
|
||||
_name: string;
|
||||
|
||||
constructor(groupId: string, name: string) {
|
||||
this._groupId = groupId;
|
||||
this._name = name;
|
||||
}
|
||||
|
||||
get groupId(): string {
|
||||
return this._groupId;
|
||||
}
|
||||
@ -20,26 +25,6 @@ export default class AndroidChannel {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param groupId
|
||||
* @returns {AndroidChannel}
|
||||
*/
|
||||
setGroupId(groupId: string): AndroidChannel {
|
||||
this._groupId = groupId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name
|
||||
* @returns {AndroidChannel}
|
||||
*/
|
||||
setName(name: string): AndroidChannel {
|
||||
this._name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
build(): NativeAndroidChannelGroup {
|
||||
if (!this._groupId) {
|
||||
throw new Error(
|
||||
|
@ -2,7 +2,8 @@
|
||||
* @flow
|
||||
* AndroidNotification representation wrapper
|
||||
*/
|
||||
import { Category } from './types';
|
||||
import AndroidAction, { fromNativeAndroidAction } from './AndroidAction';
|
||||
import { BadgeIconType, Category, GroupAlert, Priority } from './types';
|
||||
import type Notification from './Notification';
|
||||
import type {
|
||||
BadgeIconTypeType,
|
||||
@ -18,8 +19,7 @@ import type {
|
||||
} from './types';
|
||||
|
||||
export default class AndroidNotification {
|
||||
// TODO optional fields
|
||||
// TODO actions: Action[]; // icon, title, ??pendingIntent??, allowGeneratedReplies, extender, extras, remoteinput (ugh)
|
||||
_actions: AndroidAction[];
|
||||
_autoCancel: boolean | void;
|
||||
_badgeIconType: BadgeIconTypeType | void;
|
||||
_category: CategoryType | void;
|
||||
@ -70,6 +70,9 @@ export default class AndroidNotification {
|
||||
this._notification = notification;
|
||||
|
||||
if (data) {
|
||||
this._actions = data.actions
|
||||
? data.actions.map(action => fromNativeAndroidAction(action))
|
||||
: [];
|
||||
this._autoCancel = data.autoCancel;
|
||||
this._badgeIconType = data.badgeIconType;
|
||||
this._category = data.category;
|
||||
@ -106,12 +109,17 @@ export default class AndroidNotification {
|
||||
}
|
||||
|
||||
// Defaults
|
||||
this._actions = this._actions || [];
|
||||
this._people = this._people || [];
|
||||
this._smallIcon = this._smallIcon || {
|
||||
icon: 'ic_launcher',
|
||||
};
|
||||
}
|
||||
|
||||
get actions(): AndroidAction[] {
|
||||
return this._actions;
|
||||
}
|
||||
|
||||
get autoCancel(): ?boolean {
|
||||
return this._autoCancel;
|
||||
}
|
||||
@ -240,6 +248,21 @@ export default class AndroidNotification {
|
||||
return this._when;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param action
|
||||
* @returns {Notification}
|
||||
*/
|
||||
addAction(action: AndroidAction): Notification {
|
||||
if (!(action instanceof AndroidAction)) {
|
||||
throw new Error(
|
||||
`AndroidNotification:addAction expects an 'AndroidAction' but got type ${typeof action}`
|
||||
);
|
||||
}
|
||||
this._actions.push(action);
|
||||
return this._notification;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param person
|
||||
@ -266,6 +289,11 @@ export default class AndroidNotification {
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setBadgeIconType(badgeIconType: BadgeIconTypeType): Notification {
|
||||
if (!Object.values(BadgeIconType).includes(badgeIconType)) {
|
||||
throw new Error(
|
||||
`AndroidNotification:setBadgeIconType Invalid BadgeIconType: ${badgeIconType}`
|
||||
);
|
||||
}
|
||||
this._badgeIconType = badgeIconType;
|
||||
return this._notification;
|
||||
}
|
||||
@ -277,7 +305,9 @@ export default class AndroidNotification {
|
||||
*/
|
||||
setCategory(category: CategoryType): Notification {
|
||||
if (!Object.values(Category).includes(category)) {
|
||||
throw new Error(`AndroidNotification: Invalid Category: ${category}`);
|
||||
throw new Error(
|
||||
`AndroidNotification:setCategory Invalid Category: ${category}`
|
||||
);
|
||||
}
|
||||
this._category = category;
|
||||
return this._notification;
|
||||
@ -359,6 +389,11 @@ export default class AndroidNotification {
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setGroupAlertBehaviour(groupAlertBehaviour: GroupAlertType): Notification {
|
||||
if (!Object.values(GroupAlert).includes(groupAlertBehaviour)) {
|
||||
throw new Error(
|
||||
`AndroidNotification:setGroupAlertBehaviour Invalid GroupAlert: ${groupAlertBehaviour}`
|
||||
);
|
||||
}
|
||||
this._groupAlertBehaviour = groupAlertBehaviour;
|
||||
return this._notification;
|
||||
}
|
||||
@ -445,6 +480,11 @@ export default class AndroidNotification {
|
||||
* @returns {Notification}
|
||||
*/
|
||||
setPriority(priority: PriorityType): Notification {
|
||||
if (!Object.values(Priority).includes(priority)) {
|
||||
throw new Error(
|
||||
`AndroidNotification:setPriority Invalid Priority: ${priority}`
|
||||
);
|
||||
}
|
||||
this._priority = priority;
|
||||
return this._notification;
|
||||
}
|
||||
@ -596,7 +636,7 @@ export default class AndroidNotification {
|
||||
}
|
||||
|
||||
return {
|
||||
// TODO actions: Action[],
|
||||
actions: this._actions.map(action => action.build()),
|
||||
autoCancel: this._autoCancel,
|
||||
badgeIconType: this._badgeIconType,
|
||||
category: this._category,
|
||||
|
123
lib/modules/notifications/AndroidRemoteInput.js
Normal file
123
lib/modules/notifications/AndroidRemoteInput.js
Normal file
@ -0,0 +1,123 @@
|
||||
/**
|
||||
* @flow
|
||||
* AndroidRemoteInput representation wrapper
|
||||
*/
|
||||
|
||||
import type { AndroidAllowDataType, NativeAndroidRemoteInput } from './types';
|
||||
|
||||
export default class AndroidRemoteInput {
|
||||
_allowedDataTypes: AndroidAllowDataType[];
|
||||
_allowFreeFormInput: boolean | void;
|
||||
_choices: string[];
|
||||
_label: string | void;
|
||||
_resultKey: string;
|
||||
|
||||
constructor(resultKey: string) {
|
||||
this._allowedDataTypes = [];
|
||||
this._choices = [];
|
||||
this._resultKey = resultKey;
|
||||
}
|
||||
|
||||
get allowedDataTypes(): AndroidAllowDataType[] {
|
||||
return this._allowedDataTypes;
|
||||
}
|
||||
|
||||
get allowFreeFormInput(): ?boolean {
|
||||
return this._allowFreeFormInput;
|
||||
}
|
||||
|
||||
get choices(): string[] {
|
||||
return this._choices;
|
||||
}
|
||||
|
||||
get label(): ?string {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
get resultKey(): string {
|
||||
return this._resultKey;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param mimeType
|
||||
* @param allow
|
||||
* @returns {AndroidRemoteInput}
|
||||
*/
|
||||
setAllowDataType(mimeType: string, allow: boolean): AndroidRemoteInput {
|
||||
this._allowedDataTypes.push({
|
||||
allow,
|
||||
mimeType,
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param allowFreeFormInput
|
||||
* @returns {AndroidRemoteInput}
|
||||
*/
|
||||
setAllowFreeFormInput(allowFreeFormInput: boolean): AndroidRemoteInput {
|
||||
this._allowFreeFormInput = allowFreeFormInput;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param choices
|
||||
* @returns {AndroidRemoteInput}
|
||||
*/
|
||||
setChoices(choices: string[]): AndroidRemoteInput {
|
||||
this._choices = choices;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param label
|
||||
* @returns {AndroidRemoteInput}
|
||||
*/
|
||||
setLabel(label: string): AndroidRemoteInput {
|
||||
this._label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
build(): NativeAndroidRemoteInput {
|
||||
if (!this._resultKey) {
|
||||
throw new Error(
|
||||
'AndroidRemoteInput: Missing required `resultKey` property'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
allowedDataTypes: this._allowedDataTypes,
|
||||
allowFreeFormInput: this._allowFreeFormInput,
|
||||
choices: this._choices,
|
||||
label: this._label,
|
||||
resultKey: this._resultKey,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const fromNativeAndroidRemoteInput = (
|
||||
nativeRemoteInput: NativeAndroidRemoteInput
|
||||
): AndroidRemoteInput => {
|
||||
const remoteInput = new AndroidRemoteInput(nativeRemoteInput.resultKey);
|
||||
if (nativeRemoteInput.allowDataType) {
|
||||
for (let i = 0; i < nativeRemoteInput.allowDataType.length; i++) {
|
||||
const allowDataType = nativeRemoteInput.allowDataType[i];
|
||||
remoteInput.setAllowDataType(allowDataType.mimeType, allowDataType.allow);
|
||||
}
|
||||
}
|
||||
if (nativeRemoteInput.allowFreeFormInput) {
|
||||
remoteInput.setAllowFreeFormInput(nativeRemoteInput.allowFreeFormInput);
|
||||
}
|
||||
if (nativeRemoteInput.choices) {
|
||||
remoteInput.setChoices(nativeRemoteInput.choices);
|
||||
}
|
||||
if (nativeRemoteInput.label) {
|
||||
remoteInput.setLabel(nativeRemoteInput.label);
|
||||
}
|
||||
|
||||
return remoteInput;
|
||||
};
|
@ -9,10 +9,11 @@ import { generatePushID, isObject } from '../../utils';
|
||||
|
||||
import type { NativeNotification } from './types';
|
||||
|
||||
export type NotificationOpen = {
|
||||
export type NotificationOpen = {|
|
||||
action: string,
|
||||
notification: Notification,
|
||||
};
|
||||
results?: { [string]: string },
|
||||
|};
|
||||
|
||||
export default class Notification {
|
||||
// iOS 8/9 | 10+ | Android
|
||||
|
@ -7,9 +7,11 @@ import { getLogger } from '../../utils/log';
|
||||
import ModuleBase from '../../utils/ModuleBase';
|
||||
import { getNativeModule } from '../../utils/native';
|
||||
import { isFunction, isObject } from '../../utils';
|
||||
import AndroidAction from './AndroidAction';
|
||||
import AndroidChannel from './AndroidChannel';
|
||||
import AndroidChannelGroup from './AndroidChannelGroup';
|
||||
import AndroidNotifications from './AndroidNotifications';
|
||||
import AndroidRemoteInput from './AndroidRemoteInput';
|
||||
import Notification from './Notification';
|
||||
import {
|
||||
BadgeIconType,
|
||||
@ -18,6 +20,7 @@ import {
|
||||
GroupAlert,
|
||||
Importance,
|
||||
Priority,
|
||||
SemanticAction,
|
||||
Visibility,
|
||||
} from './types';
|
||||
|
||||
@ -99,6 +102,7 @@ export default class Notifications extends ModuleBase {
|
||||
SharedEventEmitter.emit('onNotificationOpened', {
|
||||
action: notificationOpen.action,
|
||||
notification: new Notification(notificationOpen.notification),
|
||||
results: notificationOpen.results,
|
||||
});
|
||||
}
|
||||
);
|
||||
@ -166,6 +170,7 @@ export default class Notifications extends ModuleBase {
|
||||
return {
|
||||
action: notificationOpen.action,
|
||||
notification: new Notification(notificationOpen.notification),
|
||||
results: notificationOpen.results,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
@ -295,6 +300,7 @@ export default class Notifications extends ModuleBase {
|
||||
|
||||
export const statics = {
|
||||
Android: {
|
||||
Action: AndroidAction,
|
||||
BadgeIconType,
|
||||
Category,
|
||||
Channel: AndroidChannel,
|
||||
@ -303,6 +309,8 @@ export const statics = {
|
||||
GroupAlert,
|
||||
Importance,
|
||||
Priority,
|
||||
RemoteInput: AndroidRemoteInput,
|
||||
SemanticAction,
|
||||
Visibility,
|
||||
},
|
||||
Notification,
|
||||
|
@ -1,7 +1,6 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export const BadgeIconType = {
|
||||
Large: 2,
|
||||
None: 0,
|
||||
@ -57,6 +56,20 @@ export const Priority = {
|
||||
Min: -2,
|
||||
};
|
||||
|
||||
export const SemanticAction = {
|
||||
Archive: 5,
|
||||
Call: 10,
|
||||
Delete: 4,
|
||||
MarkAsRead: 2,
|
||||
MarkAsUnread: 3,
|
||||
Mute: 6,
|
||||
None: 0,
|
||||
Reply: 1,
|
||||
ThumbsDown: 9,
|
||||
ThumbsUp: 8,
|
||||
Unmute: 7,
|
||||
};
|
||||
|
||||
export const Visibility = {
|
||||
Private: 0,
|
||||
Public: 1,
|
||||
@ -69,27 +82,51 @@ export type DefaultsType = $Values<typeof Defaults>;
|
||||
export type GroupAlertType = $Values<typeof GroupAlert>;
|
||||
export type ImportanceType = $Values<typeof Importance>;
|
||||
export type PriorityType = $Values<typeof Priority>;
|
||||
export type SemanticActionType = $Values<typeof SemanticAction>;
|
||||
export type VisibilityType = $Values<typeof Visibility>;
|
||||
|
||||
export type Lights = {
|
||||
export type Lights = {|
|
||||
argb: number,
|
||||
onMs: number,
|
||||
offMs: number,
|
||||
};
|
||||
|};
|
||||
|
||||
export type Progress = {
|
||||
export type Progress = {|
|
||||
max: number,
|
||||
progress: number,
|
||||
indeterminate: boolean,
|
||||
};
|
||||
|};
|
||||
|
||||
export type SmallIcon = {
|
||||
export type SmallIcon = {|
|
||||
icon: string,
|
||||
level?: number,
|
||||
|};
|
||||
|
||||
export type AndroidAllowDataType = {
|
||||
allow: boolean,
|
||||
mimeType: string,
|
||||
};
|
||||
|
||||
export type NativeAndroidRemoteInput = {|
|
||||
allowedDataTypes: AndroidAllowDataType[],
|
||||
allowFreeFormInput?: boolean,
|
||||
choices: string[],
|
||||
label?: string,
|
||||
resultKey: string,
|
||||
|};
|
||||
|
||||
export type NativeAndroidAction = {|
|
||||
action: string,
|
||||
allowGeneratedReplies?: boolean,
|
||||
icon: string,
|
||||
remoteInputs: NativeAndroidRemoteInput[],
|
||||
semanticAction?: SemanticActionType,
|
||||
showUserInterface?: boolean,
|
||||
title: string,
|
||||
|};
|
||||
|
||||
export type NativeAndroidNotification = {|
|
||||
// TODO actions: Action[],
|
||||
actions?: NativeAndroidAction[],
|
||||
autoCancel?: boolean,
|
||||
badgeIconType?: BadgeIconTypeType,
|
||||
category?: CategoryType,
|
||||
@ -154,11 +191,11 @@ export type NativeIOSNotification = {|
|
||||
threadIdentifier?: string,
|
||||
|};
|
||||
|
||||
export type Schedule = {
|
||||
export type Schedule = {|
|
||||
exact?: boolean,
|
||||
fireDate: number,
|
||||
repeatInterval?: 'minute' | 'hour' | 'day' | 'week',
|
||||
};
|
||||
|};
|
||||
|
||||
export type NativeNotification = {|
|
||||
android?: NativeAndroidNotification,
|
||||
@ -175,4 +212,5 @@ export type NativeNotification = {|
|
||||
export type NativeNotificationOpen = {|
|
||||
action: string,
|
||||
notification: NativeNotification,
|
||||
results?: { [string]: string },
|
||||
|};
|
||||
|
@ -37,22 +37,35 @@ const init = async () => {
|
||||
console.log('onNotificationDisplayed: ', notification);
|
||||
});
|
||||
// RNfirebase.instanceid().delete();
|
||||
const channel = new RNfirebase.notifications.Android.Channel();
|
||||
channel
|
||||
.setChannelId('test')
|
||||
.setName('test')
|
||||
.setImportance(RNfirebase.notifications.Android.Importance.Max)
|
||||
.setDescription('test channel');
|
||||
const channel = new RNfirebase.notifications.Android.Channel(
|
||||
'test',
|
||||
'test',
|
||||
RNfirebase.notifications.Android.Importance.Max
|
||||
);
|
||||
channel.setDescription('test channel');
|
||||
RNfirebase.notifications().android.createChannel(channel);
|
||||
|
||||
const remoteInput = new RNfirebase.notifications.Android.RemoteInput(
|
||||
'inputText'
|
||||
);
|
||||
remoteInput.setLabel('Message');
|
||||
const action = new RNfirebase.notifications.Android.Action(
|
||||
'test_action',
|
||||
'ic_launcher',
|
||||
'My Test Action'
|
||||
);
|
||||
action.addRemoteInput(remoteInput);
|
||||
|
||||
const notification = new RNfirebase.notifications.Notification();
|
||||
notification
|
||||
.setTitle('Test title')
|
||||
.setBody('Test body')
|
||||
.setNotificationId('displayed')
|
||||
.android.addAction(action)
|
||||
.android.setChannelId('test')
|
||||
.android.setClickAction('action')
|
||||
.android.setPriority(RNfirebase.notifications.Android.Priority.Max);
|
||||
|
||||
const date = new Date();
|
||||
date.setMinutes(date.getMinutes() + 1);
|
||||
setTimeout(() => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user