diff --git a/android/capacitor/build.gradle b/android/capacitor/build.gradle index 934fd0427b..35bbffa879 100644 --- a/android/capacitor/build.gradle +++ b/android/capacitor/build.gradle @@ -3,9 +3,7 @@ ext { androidxCoreVersion = project.hasProperty('androidxCoreVersion') ? rootProject.ext.androidxCoreVersion : '1.2.0' androidxMaterialVersion = project.hasProperty('androidxMaterialVersion') ? rootProject.ext.androidxMaterialVersion : '1.1.0-rc02' androidxBrowserVersion = project.hasProperty('androidxBrowserVersion') ? rootProject.ext.androidxBrowserVersion : '1.2.0' - androidxLocalbroadcastmanagerVersion = project.hasProperty('androidxLocalbroadcastmanagerVersion') ? rootProject.ext.androidxLocalbroadcastmanagerVersion : '1.0.0' androidxExifInterfaceVersion = project.hasProperty('androidxExifInterfaceVersion') ? rootProject.ext.androidxExifInterfaceVersion : '1.2.0' - firebaseMessagingVersion = project.hasProperty('firebaseMessagingVersion') ? rootProject.ext.firebaseMessagingVersion : '20.1.2' playServicesLocationVersion = project.hasProperty('playServicesLocationVersion') ? rootProject.ext.playServicesLocationVersion : '17.0.0' junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.12' androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.1.1' @@ -64,9 +62,7 @@ dependencies { implementation "androidx.core:core:$androidxCoreVersion" implementation "com.google.android.material:material:$androidxMaterialVersion" implementation "androidx.browser:browser:$androidxBrowserVersion" - implementation "androidx.localbroadcastmanager:localbroadcastmanager:$androidxLocalbroadcastmanagerVersion" implementation "androidx.exifinterface:exifinterface:$androidxExifInterfaceVersion" - implementation "com.google.firebase:firebase-messaging:$firebaseMessagingVersion" implementation "com.google.android.gms:play-services-location:$playServicesLocationVersion" testImplementation "junit:junit:$junitVersion" androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" diff --git a/android/capacitor/src/main/AndroidManifest.xml b/android/capacitor/src/main/AndroidManifest.xml index cf14288244..156de97b1b 100644 --- a/android/capacitor/src/main/AndroidManifest.xml +++ b/android/capacitor/src/main/AndroidManifest.xml @@ -5,24 +5,7 @@ android:required="false" /> - - - - - - - - - - - - - - - - - - - + + \ No newline at end of file diff --git a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java index c6acf88275..646387c619 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java @@ -15,8 +15,6 @@ import android.webkit.ValueCallback; import android.webkit.WebSettings; import android.webkit.WebView; -import com.getcapacitor.plugin.LocalNotifications; -import com.getcapacitor.plugin.PushNotifications; import com.getcapacitor.plugin.SplashScreen; import com.getcapacitor.util.HostMask; import java.io.File; @@ -382,8 +380,6 @@ private void initWebView() { * Register our core Plugin APIs */ private void registerAllPlugins() { - this.registerPlugin(LocalNotifications.class); - this.registerPlugin(PushNotifications.class); this.registerPlugin(SplashScreen.class); this.registerPlugin(com.getcapacitor.plugin.WebView.class); diff --git a/android/capacitor/src/main/java/com/getcapacitor/CapacitorFirebaseMessagingService.java b/android/capacitor/src/main/java/com/getcapacitor/CapacitorFirebaseMessagingService.java deleted file mode 100644 index 2e92b68762..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/CapacitorFirebaseMessagingService.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.getcapacitor; - -import com.getcapacitor.plugin.PushNotifications; -import com.google.firebase.messaging.FirebaseMessagingService; -import com.google.firebase.messaging.RemoteMessage; - -public class CapacitorFirebaseMessagingService extends FirebaseMessagingService { - - @Override - public void onNewToken(String newToken) { - super.onNewToken(newToken); - PushNotifications.onNewToken(newToken); - } - - @Override - public void onMessageReceived(RemoteMessage remoteMessage) { - super.onMessageReceived(remoteMessage); - PushNotifications.sendRemoteMessage(remoteMessage); - } -} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/LocalNotifications.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/LocalNotifications.java deleted file mode 100644 index 600b182795..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/LocalNotifications.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.getcapacitor.plugin; - -import android.content.Intent; -import com.getcapacitor.JSArray; -import com.getcapacitor.JSObject; -import com.getcapacitor.NativePlugin; -import com.getcapacitor.Plugin; -import com.getcapacitor.PluginCall; -import com.getcapacitor.PluginMethod; -import com.getcapacitor.PluginRequestCodes; -import com.getcapacitor.plugin.notification.LocalNotification; -import com.getcapacitor.plugin.notification.LocalNotificationManager; -import com.getcapacitor.plugin.notification.NotificationAction; -import com.getcapacitor.plugin.notification.NotificationChannelManager; -import com.getcapacitor.plugin.notification.NotificationStorage; -import java.util.List; -import java.util.Map; -import org.json.JSONArray; - -/** - * Plugin for scheduling local notifications - * Plugins allows to create and trigger various types of notifications an specific times - * Please refer to individual documentation for reference - */ -@NativePlugin(requestCodes = PluginRequestCodes.NOTIFICATION_OPEN) -public class LocalNotifications extends Plugin { - - private LocalNotificationManager manager; - private NotificationStorage notificationStorage; - private NotificationChannelManager notificationChannelManager; - - public LocalNotifications() {} - - @Override - public void load() { - super.load(); - notificationStorage = new NotificationStorage(getContext()); - manager = new LocalNotificationManager(notificationStorage, getActivity(), getContext(), this.bridge.getConfig()); - manager.createNotificationChannel(); - notificationChannelManager = new NotificationChannelManager(getActivity()); - } - - @Override - protected void handleOnNewIntent(Intent data) { - super.handleOnNewIntent(data); - if (!Intent.ACTION_MAIN.equals(data.getAction())) { - return; - } - JSObject dataJson = manager.handleNotificationActionPerformed(data, notificationStorage); - if (dataJson != null) { - notifyListeners("localNotificationActionPerformed", dataJson, true); - } - } - - @Override - protected void handleOnActivityResult(int requestCode, int resultCode, Intent data) { - super.handleOnActivityResult(requestCode, resultCode, data); - this.handleOnNewIntent(data); - } - - /** - * Schedule a notification call from JavaScript - * Creates local notification in system. - */ - @PluginMethod - public void schedule(PluginCall call) { - List localNotifications = LocalNotification.buildNotificationList(call); - if (localNotifications == null) { - return; - } - JSONArray ids = manager.schedule(call, localNotifications); - if (ids != null) { - notificationStorage.appendNotifications(localNotifications); - JSObject result = new JSObject(); - JSArray jsArray = new JSArray(); - for (int i = 0; i < ids.length(); i++) { - try { - JSObject notification = new JSObject().put("id", ids.getString(i)); - jsArray.put(notification); - } catch (Exception ex) {} - } - result.put("notifications", jsArray); - call.success(result); - } - } - - @PluginMethod - public void requestPermission(PluginCall call) { - JSObject result = new JSObject(); - result.put("granted", true); - call.success(result); - } - - @PluginMethod - public void cancel(PluginCall call) { - manager.cancel(call); - } - - @PluginMethod - public void getPending(PluginCall call) { - List ids = notificationStorage.getSavedNotificationIds(); - JSObject result = LocalNotification.buildLocalNotificationPendingList(ids); - call.success(result); - } - - @PluginMethod - public void registerActionTypes(PluginCall call) { - JSArray types = call.getArray("types"); - Map typesArray = NotificationAction.buildTypes(types); - notificationStorage.writeActionGroup(typesArray); - call.success(); - } - - @PluginMethod - public void areEnabled(PluginCall call) { - JSObject data = new JSObject(); - data.put("value", manager.areNotificationsEnabled()); - call.success(data); - } - - @PluginMethod - public void createChannel(PluginCall call) { - notificationChannelManager.createChannel(call); - } - - @PluginMethod - public void deleteChannel(PluginCall call) { - notificationChannelManager.deleteChannel(call); - } - - @PluginMethod - public void listChannels(PluginCall call) { - notificationChannelManager.listChannels(call); - } -} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/PushNotifications.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/PushNotifications.java deleted file mode 100644 index 05500616e6..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/PushNotifications.java +++ /dev/null @@ -1,239 +0,0 @@ -package com.getcapacitor.plugin; - -import android.app.Notification; -import android.app.NotificationManager; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.service.notification.StatusBarNotification; -import com.getcapacitor.Bridge; -import com.getcapacitor.JSArray; -import com.getcapacitor.JSObject; -import com.getcapacitor.NativePlugin; -import com.getcapacitor.Plugin; -import com.getcapacitor.PluginCall; -import com.getcapacitor.PluginHandle; -import com.getcapacitor.PluginMethod; -import com.getcapacitor.plugin.notification.NotificationChannelManager; -import com.google.firebase.iid.FirebaseInstanceId; -import com.google.firebase.messaging.FirebaseMessaging; -import com.google.firebase.messaging.RemoteMessage; -import java.util.ArrayList; -import java.util.List; -import org.json.JSONException; -import org.json.JSONObject; - -@NativePlugin -public class PushNotifications extends Plugin { - - public static Bridge staticBridge = null; - public static RemoteMessage lastMessage = null; - public NotificationManager notificationManager; - private NotificationChannelManager notificationChannelManager; - - private static final String EVENT_TOKEN_CHANGE = "registration"; - private static final String EVENT_TOKEN_ERROR = "registrationError"; - - @Override - public void load() { - notificationManager = (NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE); - staticBridge = this.bridge; - if (lastMessage != null) { - fireNotification(lastMessage); - lastMessage = null; - } - notificationChannelManager = new NotificationChannelManager(getActivity(), notificationManager); - } - - @Override - protected void handleOnNewIntent(Intent data) { - super.handleOnNewIntent(data); - Bundle bundle = data.getExtras(); - if (bundle != null && bundle.containsKey("google.message_id")) { - JSObject notificationJson = new JSObject(); - JSObject dataObject = new JSObject(); - for (String key : bundle.keySet()) { - if (key.equals("google.message_id")) { - notificationJson.put("id", bundle.get(key)); - } else { - Object value = bundle.get(key); - String valueStr = (value != null) ? value.toString() : null; - dataObject.put(key, valueStr); - } - } - notificationJson.put("data", dataObject); - JSObject actionJson = new JSObject(); - actionJson.put("actionId", "tap"); - actionJson.put("notification", notificationJson); - notifyListeners("pushNotificationActionPerformed", actionJson, true); - } - } - - @PluginMethod - public void register(PluginCall call) { - FirebaseMessaging.getInstance().setAutoInitEnabled(true); - FirebaseInstanceId - .getInstance() - .getInstanceId() - .addOnSuccessListener(getActivity(), instanceIdResult -> sendToken(instanceIdResult.getToken())); - FirebaseInstanceId.getInstance().getInstanceId().addOnFailureListener(e -> sendError(e.getLocalizedMessage())); - call.success(); - } - - @PluginMethod - public void requestPermission(PluginCall call) { - JSObject result = new JSObject(); - result.put("granted", true); - call.success(result); - } - - @PluginMethod - public void getDeliveredNotifications(PluginCall call) { - JSArray notifications = new JSArray(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - StatusBarNotification[] activeNotifications = notificationManager.getActiveNotifications(); - - for (StatusBarNotification notif : activeNotifications) { - JSObject jsNotif = new JSObject(); - - jsNotif.put("id", notif.getId()); - - Notification notification = notif.getNotification(); - if (notification != null) { - jsNotif.put("title", notification.extras.getCharSequence(Notification.EXTRA_TITLE)); - jsNotif.put("body", notification.extras.getCharSequence(Notification.EXTRA_TEXT)); - jsNotif.put("group", notification.getGroup()); - jsNotif.put("groupSummary", 0 != (notification.flags & Notification.FLAG_GROUP_SUMMARY)); - - JSObject extras = new JSObject(); - - for (String key : notification.extras.keySet()) { - extras.put(key, notification.extras.get(key)); - } - - jsNotif.put("data", extras); - } - - notifications.put(jsNotif); - } - } - - JSObject result = new JSObject(); - result.put("notifications", notifications); - call.resolve(result); - } - - @PluginMethod - public void removeDeliveredNotifications(PluginCall call) { - JSArray notifications = call.getArray("notifications"); - - List ids = new ArrayList<>(); - try { - for (Object o : notifications.toList()) { - if (o instanceof JSONObject) { - JSObject notif = JSObject.fromJSONObject((JSONObject) o); - Integer id = notif.getInteger("id"); - ids.add(id); - } else { - call.reject("Expected notifications to be a list of notification objects"); - } - } - } catch (JSONException e) { - call.reject(e.getMessage()); - } - - for (int id : ids) { - notificationManager.cancel(id); - } - - call.resolve(); - } - - @PluginMethod - public void removeAllDeliveredNotifications(PluginCall call) { - notificationManager.cancelAll(); - call.success(); - } - - @PluginMethod - public void createChannel(PluginCall call) { - notificationChannelManager.createChannel(call); - } - - @PluginMethod - public void deleteChannel(PluginCall call) { - notificationChannelManager.deleteChannel(call); - } - - @PluginMethod - public void listChannels(PluginCall call) { - notificationChannelManager.listChannels(call); - } - - public void sendToken(String token) { - JSObject data = new JSObject(); - data.put("value", token); - notifyListeners(EVENT_TOKEN_CHANGE, data, true); - } - - public void sendError(String error) { - JSObject data = new JSObject(); - data.put("error", error); - notifyListeners(EVENT_TOKEN_ERROR, data, true); - } - - public static void onNewToken(String newToken) { - PushNotifications pushPlugin = PushNotifications.getPushNotificationsInstance(); - if (pushPlugin != null) { - pushPlugin.sendToken(newToken); - } - } - - public static void sendRemoteMessage(RemoteMessage remoteMessage) { - PushNotifications pushPlugin = PushNotifications.getPushNotificationsInstance(); - if (pushPlugin != null) { - pushPlugin.fireNotification(remoteMessage); - } else { - lastMessage = remoteMessage; - } - } - - public void fireNotification(RemoteMessage remoteMessage) { - JSObject remoteMessageData = new JSObject(); - - JSObject data = new JSObject(); - remoteMessageData.put("id", remoteMessage.getMessageId()); - for (String key : remoteMessage.getData().keySet()) { - Object value = remoteMessage.getData().get(key); - data.put(key, value); - } - remoteMessageData.put("data", data); - - RemoteMessage.Notification notification = remoteMessage.getNotification(); - if (notification != null) { - remoteMessageData.put("title", notification.getTitle()); - remoteMessageData.put("body", notification.getBody()); - remoteMessageData.put("click_action", notification.getClickAction()); - - Uri link = notification.getLink(); - if (link != null) { - remoteMessageData.put("link", link.toString()); - } - } - - notifyListeners("pushNotificationReceived", remoteMessageData, true); - } - - public static PushNotifications getPushNotificationsInstance() { - if (staticBridge != null && staticBridge.getWebView() != null) { - PluginHandle handle = staticBridge.getPlugin("PushNotifications"); - if (handle == null) { - return null; - } - return (PushNotifications) handle.getInstance(); - } - return null; - } -} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/DateMatch.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/DateMatch.java deleted file mode 100644 index 15931de460..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/DateMatch.java +++ /dev/null @@ -1,213 +0,0 @@ -package com.getcapacitor.plugin.notification; - -import java.util.Calendar; -import java.util.Date; - -/** - * Class that holds logic for on triggers - * (Specific time) - */ -public class DateMatch { - - private static final String separator = " "; - - private Integer year; - private Integer month; - private Integer day; - private Integer hour; - private Integer minute; - - // Unit used to save the last used unit for a trigger. - // One of the Calendar constants values - private Integer unit = -1; - - public DateMatch() {} - - public Integer getYear() { - return year; - } - - public void setYear(Integer year) { - this.year = year; - } - - public Integer getMonth() { - return month; - } - - public void setMonth(Integer month) { - this.month = month; - } - - public Integer getDay() { - return day; - } - - public void setDay(Integer day) { - this.day = day; - } - - public Integer getHour() { - return hour; - } - - public void setHour(Integer hour) { - this.hour = hour; - } - - public Integer getMinute() { - return minute; - } - - public void setMinute(Integer minute) { - this.minute = minute; - } - - /** - * Gets a calendar instance pointing to the specified date. - * - * @param date The date to point. - */ - private Calendar buildCalendar(Date date) { - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - cal.set(Calendar.MILLISECOND, 0); - cal.set(Calendar.SECOND, 0); - return cal; - } - - /** - * Calculates next trigger date for - * - * @param date base date used to calculate trigger - * @return next trigger timestamp - */ - public long nextTrigger(Date date) { - Calendar current = buildCalendar(date); - Calendar next = buildNextTriggerTime(date); - return postponeTriggerIfNeeded(current, next); - } - - /** - * Postpone trigger if first schedule matches the past - */ - private long postponeTriggerIfNeeded(Calendar current, Calendar next) { - if (next.getTimeInMillis() <= current.getTimeInMillis() && unit != -1) { - Integer incrementUnit = -1; - if (unit == Calendar.YEAR || unit == Calendar.MONTH) { - incrementUnit = Calendar.YEAR; - } else if (unit == Calendar.DAY_OF_MONTH) { - incrementUnit = Calendar.MONTH; - } else if (unit == Calendar.HOUR_OF_DAY) { - incrementUnit = Calendar.DAY_OF_MONTH; - } else if (unit == Calendar.MINUTE) { - incrementUnit = Calendar.HOUR_OF_DAY; - } - - if (incrementUnit != -1) { - next.set(incrementUnit, next.get(incrementUnit) + 1); - } - } - return next.getTimeInMillis(); - } - - private Calendar buildNextTriggerTime(Date date) { - Calendar next = buildCalendar(date); - if (year != null) { - next.set(Calendar.YEAR, year); - if (unit == -1) unit = Calendar.YEAR; - } - if (month != null) { - next.set(Calendar.MONTH, month); - if (unit == -1) unit = Calendar.MONTH; - } - if (day != null) { - next.set(Calendar.DAY_OF_MONTH, day); - if (unit == -1) unit = Calendar.DAY_OF_MONTH; - } - if (hour != null) { - next.set(Calendar.HOUR_OF_DAY, hour); - if (unit == -1) unit = Calendar.HOUR_OF_DAY; - } - if (minute != null) { - next.set(Calendar.MINUTE, minute); - if (unit == -1) unit = Calendar.MINUTE; - } - return next; - } - - @Override - public String toString() { - return "DateMatch{" + "year=" + year + ", month=" + month + ", day=" + day + ", hour=" + hour + ", minute=" + minute + '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - DateMatch dateMatch = (DateMatch) o; - - if (year != null ? !year.equals(dateMatch.year) : dateMatch.year != null) return false; - if (month != null ? !month.equals(dateMatch.month) : dateMatch.month != null) return false; - if (day != null ? !day.equals(dateMatch.day) : dateMatch.day != null) return false; - if (hour != null ? !hour.equals(dateMatch.hour) : dateMatch.hour != null) return false; - return minute != null ? minute.equals(dateMatch.minute) : dateMatch.minute == null; - } - - @Override - public int hashCode() { - int result = year != null ? year.hashCode() : 0; - result = 31 * result + (month != null ? month.hashCode() : 0); - result = 31 * result + (day != null ? day.hashCode() : 0); - result = 31 * result + (hour != null ? hour.hashCode() : 0); - result = 31 * result + (minute != null ? minute.hashCode() : 0); - return result; - } - - /** - * Transform DateMatch object to CronString - * - * @return - */ - public String toMatchString() { - String matchString = year + separator + month + separator + day + separator + hour + separator + minute + separator + unit; - return matchString.replace("null", "*"); - } - - /** - * Create DateMatch object from stored string - * - * @param matchString - * @return - */ - public static DateMatch fromMatchString(String matchString) { - DateMatch date = new DateMatch(); - String[] split = matchString.split(separator); - if (split != null && split.length == 6) { - date.setYear(getValueFromCronElement(split[0])); - date.setMonth(getValueFromCronElement(split[1])); - date.setDay(getValueFromCronElement(split[2])); - date.setHour(getValueFromCronElement(split[3])); - date.setMinute(getValueFromCronElement(split[4])); - date.setUnit(getValueFromCronElement(split[5])); - } - return date; - } - - public static Integer getValueFromCronElement(String token) { - try { - return Integer.parseInt(token); - } catch (NumberFormatException e) { - return null; - } - } - - public Integer getUnit() { - return unit; - } - - public void setUnit(Integer unit) { - this.unit = unit; - } -} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotification.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotification.java deleted file mode 100644 index 7438845e73..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotification.java +++ /dev/null @@ -1,383 +0,0 @@ -package com.getcapacitor.plugin.notification; - -import android.content.ContentResolver; -import android.content.Context; -import com.getcapacitor.JSArray; -import com.getcapacitor.JSObject; -import com.getcapacitor.Logger; -import com.getcapacitor.PluginCall; -import com.getcapacitor.plugin.util.AssetUtil; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.List; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Local notification object mapped from json plugin - */ -public class LocalNotification { - - private String title; - private String body; - private Integer id; - private String sound; - private String smallIcon; - private String iconColor; - private String actionTypeId; - private String group; - private boolean groupSummary; - private boolean ongoing; - private boolean autoCancel; - private JSObject extra; - private List attachments; - private LocalNotificationSchedule schedule; - private String channelId; - - private String source; - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getBody() { - return body; - } - - public void setBody(String body) { - this.body = body; - } - - public LocalNotificationSchedule getSchedule() { - return schedule; - } - - public void setSchedule(LocalNotificationSchedule schedule) { - this.schedule = schedule; - } - - public String getSound(Context context, int defaultSound) { - String soundPath = null; - int resId = AssetUtil.RESOURCE_ID_ZERO_VALUE; - String name = AssetUtil.getResourceBaseName(sound); - if (name != null) { - resId = AssetUtil.getResourceID(context, name, "raw"); - } - if (resId == AssetUtil.RESOURCE_ID_ZERO_VALUE) { - resId = defaultSound; - } - if (resId != AssetUtil.RESOURCE_ID_ZERO_VALUE) { - soundPath = ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.getPackageName() + "/" + resId; - } - return soundPath; - } - - public void setSound(String sound) { - this.sound = sound; - } - - public void setSmallIcon(String smallIcon) { - this.smallIcon = AssetUtil.getResourceBaseName(smallIcon); - } - - public String getIconColor(String globalColor) { - // use the one defined local before trying for a globally defined color - if (iconColor != null) { - return iconColor; - } - - return globalColor; - } - - public void setIconColor(String iconColor) { - this.iconColor = iconColor; - } - - public List getAttachments() { - return attachments; - } - - public void setAttachments(List attachments) { - this.attachments = attachments; - } - - public String getActionTypeId() { - return actionTypeId; - } - - public void setActionTypeId(String actionTypeId) { - this.actionTypeId = actionTypeId; - } - - public String getGroup() { - return group; - } - - public void setGroup(String group) { - this.group = group; - } - - public JSObject getExtra() { - return extra; - } - - public void setExtra(JSObject extra) { - this.extra = extra; - } - - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - public boolean isGroupSummary() { - return groupSummary; - } - - public void setGroupSummary(boolean groupSummary) { - this.groupSummary = groupSummary; - } - - public boolean isOngoing() { - return ongoing; - } - - public void setOngoing(boolean ongoing) { - this.ongoing = ongoing; - } - - public boolean isAutoCancel() { - return autoCancel; - } - - public void setAutoCancel(boolean autoCancel) { - this.autoCancel = autoCancel; - } - - public String getChannelId() { - return channelId; - } - - public void setChannelId(String channelId) { - this.channelId = channelId; - } - - /** - * Build list of the notifications from remote plugin call - */ - public static List buildNotificationList(PluginCall call) { - JSArray notificationArray = call.getArray("notifications"); - if (notificationArray == null) { - call.error("Must provide notifications array as notifications option"); - return null; - } - List resultLocalNotifications = new ArrayList<>(notificationArray.length()); - List notificationsJson; - try { - notificationsJson = notificationArray.toList(); - } catch (JSONException e) { - call.error("Provided notification format is invalid"); - return null; - } - - for (JSONObject jsonNotification : notificationsJson) { - JSObject notification = null; - try { - notification = JSObject.fromJSONObject(jsonNotification); - } catch (JSONException e) { - call.error("Invalid JSON object sent to NotificationPlugin", e); - return null; - } - - try { - LocalNotification activeLocalNotification = buildNotificationFromJSObject(notification); - resultLocalNotifications.add(activeLocalNotification); - } catch (ParseException e) { - call.error("Invalid date format sent to Notification plugin", e); - return null; - } - } - return resultLocalNotifications; - } - - public static LocalNotification buildNotificationFromJSObject(JSObject jsonObject) throws ParseException { - LocalNotification localNotification = new LocalNotification(); - localNotification.setSource(jsonObject.toString()); - localNotification.setId(jsonObject.getInteger("id")); - localNotification.setBody(jsonObject.getString("body")); - localNotification.setActionTypeId(jsonObject.getString("actionTypeId")); - localNotification.setGroup(jsonObject.getString("group")); - localNotification.setSound(jsonObject.getString("sound")); - localNotification.setTitle(jsonObject.getString("title")); - localNotification.setSmallIcon(jsonObject.getString("smallIcon")); - localNotification.setIconColor(jsonObject.getString("iconColor")); - localNotification.setAttachments(LocalNotificationAttachment.getAttachments(jsonObject)); - localNotification.setGroupSummary(jsonObject.getBoolean("groupSummary", false)); - localNotification.setChannelId(jsonObject.getString("channelId")); - localNotification.setSchedule(new LocalNotificationSchedule(jsonObject)); - localNotification.setExtra(jsonObject.getJSObject("extra")); - localNotification.setOngoing(jsonObject.getBoolean("ongoing", false)); - localNotification.setAutoCancel(jsonObject.getBoolean("autoCancel", true)); - - return localNotification; - } - - public static List getLocalNotificationPendingList(PluginCall call) { - List notifications = null; - try { - notifications = call.getArray("notifications").toList(); - } catch (JSONException e) {} - if (notifications == null || notifications.size() == 0) { - call.error("Must provide notifications array as notifications option"); - return null; - } - List notificationsList = new ArrayList<>(notifications.size()); - for (JSONObject notificationToCancel : notifications) { - try { - notificationsList.add(notificationToCancel.getInt("id")); - } catch (JSONException e) {} - } - return notificationsList; - } - - public static JSObject buildLocalNotificationPendingList(List ids) { - JSObject result = new JSObject(); - JSArray jsArray = new JSArray(); - for (String id : ids) { - JSObject notification = new JSObject(); - notification.put("id", id); - jsArray.put(notification); - } - result.put("notifications", jsArray); - return result; - } - - public int getSmallIcon(Context context, int defaultIcon) { - int resId = AssetUtil.RESOURCE_ID_ZERO_VALUE; - - if (smallIcon != null) { - resId = AssetUtil.getResourceID(context, smallIcon, "drawable"); - } - - if (resId == AssetUtil.RESOURCE_ID_ZERO_VALUE) { - resId = defaultIcon; - } - - return resId; - } - - public boolean isScheduled() { - return ( - this.schedule != null && (this.schedule.getOn() != null || this.schedule.getAt() != null || this.schedule.getEvery() != null) - ); - } - - @Override - public String toString() { - return ( - "LocalNotification{" + - "title='" + - title + - '\'' + - ", body='" + - body + - '\'' + - ", id=" + - id + - ", sound='" + - sound + - '\'' + - ", smallIcon='" + - smallIcon + - '\'' + - ", iconColor='" + - iconColor + - '\'' + - ", actionTypeId='" + - actionTypeId + - '\'' + - ", group='" + - group + - '\'' + - ", extra=" + - extra + - ", attachments=" + - attachments + - ", schedule=" + - schedule + - ", groupSummary=" + - groupSummary + - ", ongoing=" + - ongoing + - ", autoCancel=" + - autoCancel + - '}' - ); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - LocalNotification that = (LocalNotification) o; - - if (title != null ? !title.equals(that.title) : that.title != null) return false; - if (body != null ? !body.equals(that.body) : that.body != null) return false; - if (id != null ? !id.equals(that.id) : that.id != null) return false; - if (sound != null ? !sound.equals(that.sound) : that.sound != null) return false; - if (smallIcon != null ? !smallIcon.equals(that.smallIcon) : that.smallIcon != null) return false; - if (iconColor != null ? !iconColor.equals(that.iconColor) : that.iconColor != null) return false; - if (actionTypeId != null ? !actionTypeId.equals(that.actionTypeId) : that.actionTypeId != null) return false; - if (group != null ? !group.equals(that.group) : that.group != null) return false; - if (extra != null ? !extra.equals(that.extra) : that.extra != null) return false; - if (attachments != null ? !attachments.equals(that.attachments) : that.attachments != null) return false; - if (groupSummary != that.groupSummary) return false; - if (ongoing != that.ongoing) return false; - if (autoCancel != that.autoCancel) return false; - return schedule != null ? schedule.equals(that.schedule) : that.schedule == null; - } - - @Override - public int hashCode() { - int result = title != null ? title.hashCode() : 0; - result = 31 * result + (body != null ? body.hashCode() : 0); - result = 31 * result + (id != null ? id.hashCode() : 0); - result = 31 * result + (sound != null ? sound.hashCode() : 0); - result = 31 * result + (smallIcon != null ? smallIcon.hashCode() : 0); - result = 31 * result + (iconColor != null ? iconColor.hashCode() : 0); - result = 31 * result + (actionTypeId != null ? actionTypeId.hashCode() : 0); - result = 31 * result + (group != null ? group.hashCode() : 0); - result = 31 * result + Boolean.hashCode(groupSummary); - result = 31 * result + Boolean.hashCode(ongoing); - result = 31 * result + Boolean.hashCode(autoCancel); - result = 31 * result + (extra != null ? extra.hashCode() : 0); - result = 31 * result + (attachments != null ? attachments.hashCode() : 0); - result = 31 * result + (schedule != null ? schedule.hashCode() : 0); - return result; - } - - public void setExtraFromString(String extraFromString) { - try { - JSONObject jsonObject = new JSONObject(extraFromString); - this.extra = JSObject.fromJSONObject(jsonObject); - } catch (JSONException e) { - Logger.error(Logger.tags("LN"), "Cannot rebuild extra data", e); - } - } - - public String getSource() { - return source; - } - - public void setSource(String source) { - this.source = source; - } -} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationAttachment.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationAttachment.java deleted file mode 100644 index 2e088d7ac8..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationAttachment.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.getcapacitor.plugin.notification; - -import com.getcapacitor.JSObject; -import java.util.ArrayList; -import java.util.List; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -public class LocalNotificationAttachment { - - private String id; - private String url; - private JSONObject options; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public JSONObject getOptions() { - return options; - } - - public void setOptions(JSONObject options) { - this.options = options; - } - - public static List getAttachments(JSObject notification) { - List attachmentsList = new ArrayList<>(); - JSONArray attachments = null; - try { - attachments = notification.getJSONArray("attachments"); - } catch (Exception e) {} - if (attachments != null) { - for (int i = 0; i < attachments.length(); i++) { - LocalNotificationAttachment newAttachment = new LocalNotificationAttachment(); - JSONObject jsonObject = null; - try { - jsonObject = attachments.getJSONObject(i); - } catch (JSONException e) {} - if (jsonObject != null) { - JSObject jsObject = null; - try { - jsObject = JSObject.fromJSONObject(jsonObject); - } catch (JSONException e) {} - newAttachment.setId(jsObject.getString("id")); - newAttachment.setUrl(jsObject.getString("url")); - try { - newAttachment.setOptions(jsObject.getJSONObject("options")); - } catch (JSONException e) {} - attachmentsList.add(newAttachment); - } - } - } - - return attachmentsList; - } -} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationManager.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationManager.java deleted file mode 100644 index 75771a43f8..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationManager.java +++ /dev/null @@ -1,427 +0,0 @@ -package com.getcapacitor.plugin.notification; - -import android.app.Activity; -import android.app.AlarmManager; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.PendingIntent; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.graphics.Color; -import android.media.AudioAttributes; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; -import androidx.core.app.RemoteInput; -import com.getcapacitor.CapConfig; -import com.getcapacitor.JSObject; -import com.getcapacitor.Logger; -import com.getcapacitor.PluginCall; -import com.getcapacitor.android.R; -import com.getcapacitor.plugin.util.AssetUtil; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Contains implementations for all notification actions - */ -public class LocalNotificationManager { - - private static final String CONFIG_KEY_PREFIX = "plugins.LocalNotifications."; - private static int defaultSoundID = AssetUtil.RESOURCE_ID_ZERO_VALUE; - private static int defaultSmallIconID = AssetUtil.RESOURCE_ID_ZERO_VALUE; - // Action constants - public static final String NOTIFICATION_INTENT_KEY = "LocalNotificationId"; - public static final String NOTIFICATION_OBJ_INTENT_KEY = "LocalNotficationObject"; - public static final String ACTION_INTENT_KEY = "LocalNotificationUserAction"; - public static final String NOTIFICATION_IS_REMOVABLE_KEY = "LocalNotificationRepeating"; - public static final String REMOTE_INPUT_KEY = "LocalNotificationRemoteInput"; - - public static final String DEFAULT_NOTIFICATION_CHANNEL_ID = "default"; - private static final String DEFAULT_PRESS_ACTION = "tap"; - - private Context context; - private Activity activity; - private NotificationStorage storage; - private CapConfig config; - - public LocalNotificationManager(NotificationStorage notificationStorage, Activity activity, Context context, CapConfig config) { - storage = notificationStorage; - this.activity = activity; - this.context = context; - this.config = config; - } - - /** - * Method extecuted when notification is launched by user from the notification bar. - */ - public JSObject handleNotificationActionPerformed(Intent data, NotificationStorage notificationStorage) { - Logger.debug(Logger.tags("LN"), "LocalNotification received: " + data.getDataString()); - int notificationId = data.getIntExtra(LocalNotificationManager.NOTIFICATION_INTENT_KEY, Integer.MIN_VALUE); - if (notificationId == Integer.MIN_VALUE) { - Logger.debug(Logger.tags("LN"), "Activity started without notification attached"); - return null; - } - boolean isRemovable = data.getBooleanExtra(LocalNotificationManager.NOTIFICATION_IS_REMOVABLE_KEY, true); - if (isRemovable) { - notificationStorage.deleteNotification(Integer.toString(notificationId)); - } - JSObject dataJson = new JSObject(); - - Bundle results = RemoteInput.getResultsFromIntent(data); - if (results != null) { - CharSequence input = results.getCharSequence(LocalNotificationManager.REMOTE_INPUT_KEY); - dataJson.put("inputValue", input.toString()); - } - String menuAction = data.getStringExtra(LocalNotificationManager.ACTION_INTENT_KEY); - - dismissVisibleNotification(notificationId); - - dataJson.put("actionId", menuAction); - JSONObject request = null; - try { - String notificationJsonString = data.getStringExtra(LocalNotificationManager.NOTIFICATION_OBJ_INTENT_KEY); - if (notificationJsonString != null) { - request = new JSObject(notificationJsonString); - } - } catch (JSONException e) {} - dataJson.put("notification", request); - return dataJson; - } - - /** - * Create notification channel - */ - public void createNotificationChannel() { - // Create the NotificationChannel, but only on API 26+ because - // the NotificationChannel class is new and not in the support library - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - CharSequence name = "Default"; - String description = "Default"; - int importance = android.app.NotificationManager.IMPORTANCE_DEFAULT; - NotificationChannel channel = new NotificationChannel(DEFAULT_NOTIFICATION_CHANNEL_ID, name, importance); - channel.setDescription(description); - AudioAttributes audioAttributes = new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_ALARM) - .build(); - Uri soundUri = this.getDefaultSoundUrl(context); - if (soundUri != null) { - channel.setSound(soundUri, audioAttributes); - } - // Register the channel with the system; you can't change the importance - // or other notification behaviors after this - android.app.NotificationManager notificationManager = context.getSystemService(android.app.NotificationManager.class); - notificationManager.createNotificationChannel(channel); - } - } - - @Nullable - public JSONArray schedule(PluginCall call, List localNotifications) { - JSONArray ids = new JSONArray(); - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); - - boolean notificationsEnabled = notificationManager.areNotificationsEnabled(); - if (!notificationsEnabled) { - if (call != null) { - call.error("Notifications not enabled on this device"); - } - return null; - } - for (LocalNotification localNotification : localNotifications) { - Integer id = localNotification.getId(); - if (localNotification.getId() == null) { - if (call != null) { - call.error("LocalNotification missing identifier"); - } - return null; - } - dismissVisibleNotification(id); - cancelTimerForNotification(id); - buildNotification(notificationManager, localNotification, call); - ids.put(id); - } - return ids; - } - - // TODO Progressbar support - // TODO System categories (DO_NOT_DISTURB etc.) - // TODO control visibility by flag Notification.VISIBILITY_PRIVATE - // TODO Group notifications (setGroup, setGroupSummary, setNumber) - // TODO use NotificationCompat.MessagingStyle for latest API - // TODO expandable notification NotificationCompat.MessagingStyle - // TODO media style notification support NotificationCompat.MediaStyle - // TODO custom small/large icons - private void buildNotification(NotificationManagerCompat notificationManager, LocalNotification localNotification, PluginCall call) { - String channelId = DEFAULT_NOTIFICATION_CHANNEL_ID; - if (localNotification.getChannelId() != null) { - channelId = localNotification.getChannelId(); - } - NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this.context, channelId) - .setContentTitle(localNotification.getTitle()) - .setContentText(localNotification.getBody()) - .setAutoCancel(localNotification.isAutoCancel()) - .setOngoing(localNotification.isOngoing()) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setGroupSummary(localNotification.isGroupSummary()); - - // support multiline text - mBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(localNotification.getBody())); - - String sound = localNotification.getSound(context, getDefaultSound(context)); - if (sound != null) { - Uri soundUri = Uri.parse(sound); - // Grant permission to use sound - context.grantUriPermission("com.android.systemui", soundUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); - mBuilder.setSound(soundUri); - mBuilder.setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_LIGHTS); - } else { - mBuilder.setDefaults(Notification.DEFAULT_ALL); - } - - String group = localNotification.getGroup(); - if (group != null) { - mBuilder.setGroup(group); - } - - // make sure scheduled time is shown instead of display time - if (localNotification.isScheduled() && localNotification.getSchedule().getAt() != null) { - mBuilder.setWhen(localNotification.getSchedule().getAt().getTime()).setShowWhen(true); - } - - mBuilder.setVisibility(NotificationCompat.VISIBILITY_PRIVATE); - mBuilder.setOnlyAlertOnce(true); - - mBuilder.setSmallIcon(localNotification.getSmallIcon(context, getDefaultSmallIcon(context))); - - String iconColor = localNotification.getIconColor(config.getString(CONFIG_KEY_PREFIX + "iconColor")); - if (iconColor != null) { - try { - mBuilder.setColor(Color.parseColor(iconColor)); - } catch (IllegalArgumentException ex) { - if (call != null) { - call.error("Invalid color provided. Must be a hex string (ex: #ff0000"); - } - return; - } - } - - createActionIntents(localNotification, mBuilder); - // notificationId is a unique int for each localNotification that you must define - Notification buildNotification = mBuilder.build(); - if (localNotification.isScheduled()) { - triggerScheduledNotification(buildNotification, localNotification); - } else { - notificationManager.notify(localNotification.getId(), buildNotification); - } - } - - // Create intents for open/dissmis actions - private void createActionIntents(LocalNotification localNotification, NotificationCompat.Builder mBuilder) { - // Open intent - Intent intent = buildIntent(localNotification, DEFAULT_PRESS_ACTION); - - PendingIntent pendingIntent = PendingIntent.getActivity( - context, - localNotification.getId(), - intent, - PendingIntent.FLAG_CANCEL_CURRENT - ); - mBuilder.setContentIntent(pendingIntent); - - // Build action types - String actionTypeId = localNotification.getActionTypeId(); - if (actionTypeId != null) { - NotificationAction[] actionGroup = storage.getActionGroup(actionTypeId); - for (NotificationAction notificationAction : actionGroup) { - // TODO Add custom icons to actions - Intent actionIntent = buildIntent(localNotification, notificationAction.getId()); - PendingIntent actionPendingIntent = PendingIntent.getActivity( - context, - localNotification.getId() + notificationAction.getId().hashCode(), - actionIntent, - PendingIntent.FLAG_CANCEL_CURRENT - ); - NotificationCompat.Action.Builder actionBuilder = new NotificationCompat.Action.Builder( - R.drawable.ic_transparent, - notificationAction.getTitle(), - actionPendingIntent - ); - if (notificationAction.isInput()) { - RemoteInput remoteInput = new RemoteInput.Builder(REMOTE_INPUT_KEY).setLabel(notificationAction.getTitle()).build(); - actionBuilder.addRemoteInput(remoteInput); - } - mBuilder.addAction(actionBuilder.build()); - } - } - - // Dismiss intent - Intent dissmissIntent = new Intent(context, NotificationDismissReceiver.class); - dissmissIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - dissmissIntent.putExtra(NOTIFICATION_INTENT_KEY, localNotification.getId()); - dissmissIntent.putExtra(ACTION_INTENT_KEY, "dismiss"); - LocalNotificationSchedule schedule = localNotification.getSchedule(); - dissmissIntent.putExtra(NOTIFICATION_IS_REMOVABLE_KEY, schedule == null || schedule.isRemovable()); - PendingIntent deleteIntent = PendingIntent.getBroadcast(context, localNotification.getId(), dissmissIntent, 0); - mBuilder.setDeleteIntent(deleteIntent); - } - - @NonNull - private Intent buildIntent(LocalNotification localNotification, String action) { - Intent intent; - if (activity != null) { - intent = new Intent(context, activity.getClass()); - } else { - String packageName = context.getPackageName(); - intent = context.getPackageManager().getLaunchIntentForPackage(packageName); - } - intent.setAction(Intent.ACTION_MAIN); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.putExtra(NOTIFICATION_INTENT_KEY, localNotification.getId()); - intent.putExtra(ACTION_INTENT_KEY, action); - intent.putExtra(NOTIFICATION_OBJ_INTENT_KEY, localNotification.getSource()); - LocalNotificationSchedule schedule = localNotification.getSchedule(); - intent.putExtra(NOTIFICATION_IS_REMOVABLE_KEY, schedule == null || schedule.isRemovable()); - return intent; - } - - /** - * Build a notification trigger, such as triggering each N seconds, or - * on a certain date "shape" (such as every first of the month) - */ - // TODO support different AlarmManager.RTC modes depending on priority - private void triggerScheduledNotification(Notification notification, LocalNotification request) { - AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - LocalNotificationSchedule schedule = request.getSchedule(); - Intent notificationIntent = new Intent(context, TimedNotificationPublisher.class); - notificationIntent.putExtra(NOTIFICATION_INTENT_KEY, request.getId()); - notificationIntent.putExtra(TimedNotificationPublisher.NOTIFICATION_KEY, notification); - PendingIntent pendingIntent = PendingIntent.getBroadcast( - context, - request.getId(), - notificationIntent, - PendingIntent.FLAG_CANCEL_CURRENT - ); - - // Schedule at specific time (with repeating support) - Date at = schedule.getAt(); - if (at != null) { - if (at.getTime() < new Date().getTime()) { - Logger.error(Logger.tags("LN"), "Scheduled time must be *after* current time", null); - return; - } - if (schedule.isRepeating()) { - long interval = at.getTime() - new Date().getTime(); - alarmManager.setRepeating(AlarmManager.RTC, at.getTime(), interval, pendingIntent); - } else { - alarmManager.setExact(AlarmManager.RTC, at.getTime(), pendingIntent); - } - return; - } - - // Schedule at specific intervals - String every = schedule.getEvery(); - if (every != null) { - Long everyInterval = schedule.getEveryInterval(); - if (everyInterval != null) { - long startTime = new Date().getTime() + everyInterval; - alarmManager.setRepeating(AlarmManager.RTC, startTime, everyInterval, pendingIntent); - } - return; - } - - // Cron like scheduler - DateMatch on = schedule.getOn(); - if (on != null) { - long trigger = on.nextTrigger(new Date()); - notificationIntent.putExtra(TimedNotificationPublisher.CRON_KEY, on.toMatchString()); - pendingIntent = PendingIntent.getBroadcast(context, request.getId(), notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT); - alarmManager.setExact(AlarmManager.RTC, trigger, pendingIntent); - SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); - Logger.debug(Logger.tags("LN"), "notification " + request.getId() + " will next fire at " + sdf.format(new Date(trigger))); - } - } - - public void cancel(PluginCall call) { - List notificationsToCancel = LocalNotification.getLocalNotificationPendingList(call); - if (notificationsToCancel != null) { - for (Integer id : notificationsToCancel) { - dismissVisibleNotification(id); - cancelTimerForNotification(id); - storage.deleteNotification(Integer.toString(id)); - } - } - call.success(); - } - - private void cancelTimerForNotification(Integer notificationId) { - Intent intent = new Intent(context, TimedNotificationPublisher.class); - PendingIntent pi = PendingIntent.getBroadcast(context, notificationId, intent, 0); - if (pi != null) { - AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - alarmManager.cancel(pi); - } - } - - private void dismissVisibleNotification(int notificationId) { - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this.context); - notificationManager.cancel(notificationId); - } - - public boolean areNotificationsEnabled() { - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); - return notificationManager.areNotificationsEnabled(); - } - - public Uri getDefaultSoundUrl(Context context) { - int soundId = this.getDefaultSound(context); - if (soundId != AssetUtil.RESOURCE_ID_ZERO_VALUE) { - return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.getPackageName() + "/" + soundId); - } - return null; - } - - private int getDefaultSound(Context context) { - if (defaultSoundID != AssetUtil.RESOURCE_ID_ZERO_VALUE) return defaultSoundID; - - int resId = AssetUtil.RESOURCE_ID_ZERO_VALUE; - String soundConfigResourceName = config.getString(CONFIG_KEY_PREFIX + "sound"); - soundConfigResourceName = AssetUtil.getResourceBaseName(soundConfigResourceName); - - if (soundConfigResourceName != null) { - resId = AssetUtil.getResourceID(context, soundConfigResourceName, "raw"); - } - - defaultSoundID = resId; - return resId; - } - - private int getDefaultSmallIcon(Context context) { - if (defaultSmallIconID != AssetUtil.RESOURCE_ID_ZERO_VALUE) return defaultSmallIconID; - - int resId = AssetUtil.RESOURCE_ID_ZERO_VALUE; - String smallIconConfigResourceName = config.getString(CONFIG_KEY_PREFIX + "smallIcon"); - smallIconConfigResourceName = AssetUtil.getResourceBaseName(smallIconConfigResourceName); - - if (smallIconConfigResourceName != null) { - resId = AssetUtil.getResourceID(context, smallIconConfigResourceName, "drawable"); - } - - if (resId == AssetUtil.RESOURCE_ID_ZERO_VALUE) { - resId = android.R.drawable.ic_dialog_info; - } - - defaultSmallIconID = resId; - return resId; - } -} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationRestoreReceiver.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationRestoreReceiver.java deleted file mode 100644 index 34d605f9df..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationRestoreReceiver.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.getcapacitor.plugin.notification; - -import static android.os.Build.VERSION.SDK_INT; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.UserManager; -import com.getcapacitor.CapConfig; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -public class LocalNotificationRestoreReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - if (SDK_INT >= 24) { - UserManager um = context.getSystemService(UserManager.class); - if (um == null || !um.isUserUnlocked()) return; - } - - NotificationStorage storage = new NotificationStorage(context); - List ids = storage.getSavedNotificationIds(); - - ArrayList notifications = new ArrayList<>(ids.size()); - ArrayList updatedNotifications = new ArrayList<>(); - for (String id : ids) { - LocalNotification notification = storage.getSavedNotification(id); - if (notification == null) { - continue; - } - - LocalNotificationSchedule schedule = notification.getSchedule(); - if (schedule != null) { - Date at = schedule.getAt(); - if (at != null && at.before(new Date())) { - // modify the scheduled date in order to show notifications that would have been delivered while device was off. - long newDateTime = new Date().getTime() + 15 * 1000; - schedule.setAt(new Date(newDateTime)); - notification.setSchedule(schedule); - updatedNotifications.add(notification); - } - } - - notifications.add(notification); - } - - if (updatedNotifications.size() > 0) { - storage.appendNotifications(updatedNotifications); - } - - CapConfig config = new CapConfig(context.getAssets(), null); - LocalNotificationManager localNotificationManager = new LocalNotificationManager(storage, null, context, config); - - localNotificationManager.schedule(null, notifications); - } -} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationSchedule.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationSchedule.java deleted file mode 100644 index ebe339a149..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationSchedule.java +++ /dev/null @@ -1,159 +0,0 @@ -package com.getcapacitor.plugin.notification; - -import android.text.format.DateUtils; -import com.getcapacitor.JSObject; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.TimeZone; - -public class LocalNotificationSchedule { - - public static String JS_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; - - private Date at; - private Boolean repeats; - private String every; - private Integer count; - - private DateMatch on; - - public LocalNotificationSchedule(JSObject jsonNotification) throws ParseException { - JSObject schedule = jsonNotification.getJSObject("schedule"); - if (schedule != null) { - // Every specific unit of time (always constant) - buildEveryElement(schedule); - // Count of units of time from every to repeat on - buildCountElement(schedule); - // At specific moment of time (with repeating option) - buildAtElement(schedule); - // Build on - recurring times. For e.g. every 1st day of the month at 8:30. - buildOnElement(schedule); - } - } - - public LocalNotificationSchedule() {} - - private void buildEveryElement(JSObject schedule) { - // 'year'|'month'|'two-weeks'|'week'|'day'|'hour'|'minute'|'second'; - this.every = schedule.getString("every"); - } - - private void buildCountElement(JSObject schedule) { - this.count = schedule.getInteger("count", 1); - } - - private void buildAtElement(JSObject schedule) throws ParseException { - this.repeats = schedule.getBool("repeats"); - String dateString = schedule.getString("at"); - if (dateString != null) { - SimpleDateFormat sdf = new SimpleDateFormat(JS_DATE_FORMAT); - sdf.setTimeZone(TimeZone.getTimeZone("UTC")); - this.at = sdf.parse(dateString); - } - } - - private void buildOnElement(JSObject schedule) { - JSObject onJson = schedule.getJSObject("on"); - if (onJson != null) { - this.on = new DateMatch(); - on.setYear(onJson.getInteger("year")); - on.setMonth(onJson.getInteger("month")); - on.setDay(onJson.getInteger("day")); - on.setHour(onJson.getInteger("hour")); - on.setMinute(onJson.getInteger("minute")); - } - } - - public DateMatch getOn() { - return on; - } - - public void setOn(DateMatch on) { - this.on = on; - } - - public Date getAt() { - return at; - } - - public void setAt(Date at) { - this.at = at; - } - - public Boolean getRepeats() { - return repeats; - } - - public void setRepeats(Boolean repeats) { - this.repeats = repeats; - } - - public String getEvery() { - return every; - } - - public void setEvery(String every) { - this.every = every; - } - - public int getCount() { - return count; - } - - public void setCount(int count) { - this.count = count; - } - - public boolean isRepeating() { - return Boolean.TRUE.equals(this.repeats); - } - - public boolean isRemovable() { - if (every == null && on == null) { - if (at != null) { - return !isRepeating(); - } else { - return true; - } - } - return false; - } - - /** - * Get constant long value representing specific interval of time (weeks, days etc.) - */ - public Long getEveryInterval() { - switch (every) { - case "year": - return count * DateUtils.YEAR_IN_MILLIS; - case "month": - // This case is just approximation as months have different number of days - return count * 30 * DateUtils.DAY_IN_MILLIS; - case "two-weeks": - return count * 2 * DateUtils.WEEK_IN_MILLIS; - case "week": - return count * DateUtils.WEEK_IN_MILLIS; - case "day": - return count * DateUtils.DAY_IN_MILLIS; - case "hour": - return count * DateUtils.HOUR_IN_MILLIS; - case "minute": - return count * DateUtils.MINUTE_IN_MILLIS; - case "second": - return count * DateUtils.SECOND_IN_MILLIS; - default: - return null; - } - } - - /** - * Get next trigger time based on calendar and current time - * - * @param currentTime - current time that will be used to calculate next trigger - * @return millisecond trigger - */ - public Long getNextOnSchedule(Date currentTime) { - return this.on.nextTrigger(currentTime); - } -} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationAction.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationAction.java deleted file mode 100644 index 624012fac0..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationAction.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.getcapacitor.plugin.notification; - -import com.getcapacitor.JSArray; -import com.getcapacitor.JSObject; -import com.getcapacitor.Logger; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.json.JSONArray; -import org.json.JSONObject; - -/** - * Action types that will be registered for the notifications - */ -public class NotificationAction { - - private String id; - private String title; - private Boolean input; - - public NotificationAction() {} - - public NotificationAction(String id, String title, Boolean input) { - this.id = id; - this.title = title; - this.input = input; - } - - public static Map buildTypes(JSArray types) { - Map actionTypeMap = new HashMap<>(); - try { - List objects = types.toList(); - for (JSONObject obj : objects) { - JSObject jsObject = JSObject.fromJSONObject(obj); - String actionGroupId = jsObject.getString("id"); - if (actionGroupId == null) { - return null; - } - JSONArray actions = jsObject.getJSONArray("actions"); - if (actions != null) { - NotificationAction[] typesArray = new NotificationAction[actions.length()]; - for (int i = 0; i < typesArray.length; i++) { - NotificationAction notificationAction = new NotificationAction(); - JSObject action = JSObject.fromJSONObject(actions.getJSONObject(i)); - notificationAction.setId(action.getString("id")); - notificationAction.setTitle(action.getString("title")); - notificationAction.setInput(action.getBool("input")); - typesArray[i] = notificationAction; - } - actionTypeMap.put(actionGroupId, typesArray); - } - } - } catch (Exception e) { - Logger.error(Logger.tags("LN"), "Error when building action types", e); - } - return actionTypeMap; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public boolean isInput() { - return Boolean.TRUE.equals(input); - } - - public void setInput(Boolean input) { - this.input = input; - } -} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationChannelManager.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationChannelManager.java deleted file mode 100644 index e85f4cb4ef..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationChannelManager.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.getcapacitor.plugin.notification; - -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.content.ContentResolver; -import android.content.Context; -import android.graphics.Color; -import android.media.AudioAttributes; -import android.net.Uri; -import androidx.core.app.NotificationCompat; -import com.getcapacitor.JSArray; -import com.getcapacitor.JSObject; -import com.getcapacitor.Logger; -import com.getcapacitor.PluginCall; -import java.util.List; - -public class NotificationChannelManager { - - private Context context; - private NotificationManager notificationManager; - - public NotificationChannelManager(Context context) { - this.context = context; - this.notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - } - - public NotificationChannelManager(Context context, NotificationManager manager) { - this.context = context; - this.notificationManager = manager; - } - - private static String CHANNEL_ID = "id"; - private static String CHANNEL_NAME = "name"; - private static String CHANNEL_DESCRIPTION = "description"; - private static String CHANNEL_IMPORTANCE = "importance"; - private static String CHANNEL_VISIBILITY = "visibility"; - private static String CHANNEL_SOUND = "sound"; - private static String CHANNEL_VIBRATE = "vibration"; - private static String CHANNEL_USE_LIGHTS = "lights"; - private static String CHANNEL_LIGHT_COLOR = "lightColor"; - - public void createChannel(PluginCall call) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - JSObject channel = new JSObject(); - channel.put(CHANNEL_ID, call.getString(CHANNEL_ID)); - channel.put(CHANNEL_NAME, call.getString(CHANNEL_NAME)); - channel.put(CHANNEL_DESCRIPTION, call.getString(CHANNEL_DESCRIPTION, "")); - channel.put(CHANNEL_VISIBILITY, call.getInt(CHANNEL_VISIBILITY, NotificationCompat.VISIBILITY_PUBLIC)); - channel.put(CHANNEL_IMPORTANCE, call.getInt(CHANNEL_IMPORTANCE)); - channel.put(CHANNEL_SOUND, call.getString(CHANNEL_SOUND, null)); - channel.put(CHANNEL_VIBRATE, call.getBoolean(CHANNEL_VIBRATE, false)); - channel.put(CHANNEL_USE_LIGHTS, call.getBoolean(CHANNEL_USE_LIGHTS, false)); - channel.put(CHANNEL_LIGHT_COLOR, call.getString(CHANNEL_LIGHT_COLOR, null)); - createChannel(channel); - call.success(); - } else { - call.unavailable(); - } - } - - public void createChannel(JSObject channel) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - NotificationChannel notificationChannel = new NotificationChannel( - channel.getString(CHANNEL_ID), - channel.getString(CHANNEL_NAME), - channel.getInteger(CHANNEL_IMPORTANCE) - ); - notificationChannel.setDescription(channel.getString(CHANNEL_DESCRIPTION)); - notificationChannel.setLockscreenVisibility(channel.getInteger(CHANNEL_VISIBILITY)); - notificationChannel.enableVibration(channel.getBool(CHANNEL_VIBRATE)); - notificationChannel.enableLights(channel.getBool(CHANNEL_USE_LIGHTS)); - String lightColor = channel.getString(CHANNEL_LIGHT_COLOR); - if (lightColor != null) { - try { - notificationChannel.setLightColor(Color.parseColor(lightColor)); - } catch (IllegalArgumentException ex) { - Logger.error(Logger.tags("NotificationChannel"), "Invalid color provided for light color.", null); - } - } - String sound = channel.getString(CHANNEL_SOUND, null); - if (sound != null && !sound.isEmpty()) { - if (sound.contains(".")) { - sound = sound.substring(0, sound.lastIndexOf('.')); - } - AudioAttributes audioAttributes = new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_NOTIFICATION) - .build(); - Uri soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.getPackageName() + "/raw/" + sound); - notificationChannel.setSound(soundUri, audioAttributes); - } - notificationManager.createNotificationChannel(notificationChannel); - } - } - - public void deleteChannel(PluginCall call) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - String channelId = call.getString("id"); - notificationManager.deleteNotificationChannel(channelId); - call.success(); - } else { - call.unavailable(); - } - } - - public void listChannels(PluginCall call) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - List notificationChannels = notificationManager.getNotificationChannels(); - JSArray channels = new JSArray(); - for (NotificationChannel notificationChannel : notificationChannels) { - JSObject channel = new JSObject(); - channel.put(CHANNEL_ID, notificationChannel.getId()); - channel.put(CHANNEL_NAME, notificationChannel.getName()); - channel.put(CHANNEL_DESCRIPTION, notificationChannel.getDescription()); - channel.put(CHANNEL_IMPORTANCE, notificationChannel.getImportance()); - channel.put(CHANNEL_VISIBILITY, notificationChannel.getLockscreenVisibility()); - channel.put(CHANNEL_SOUND, notificationChannel.getSound()); - channel.put(CHANNEL_VIBRATE, notificationChannel.shouldVibrate()); - channel.put(CHANNEL_USE_LIGHTS, notificationChannel.shouldShowLights()); - channel.put(CHANNEL_LIGHT_COLOR, String.format("#%06X", (0xFFFFFF & notificationChannel.getLightColor()))); - Logger.debug(Logger.tags("NotificationChannel"), "visibility " + notificationChannel.getLockscreenVisibility()); - Logger.debug(Logger.tags("NotificationChannel"), "importance " + notificationChannel.getImportance()); - channels.put(channel); - } - JSObject result = new JSObject(); - result.put("channels", channels); - call.success(result); - } else { - call.unavailable(); - } - } -} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationDismissReceiver.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationDismissReceiver.java deleted file mode 100644 index ad79b217c8..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationDismissReceiver.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.getcapacitor.plugin.notification; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import com.getcapacitor.Logger; - -/** - * Receiver called when notification is dismissed by user - */ -public class NotificationDismissReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - int intExtra = intent.getIntExtra(LocalNotificationManager.NOTIFICATION_INTENT_KEY, Integer.MIN_VALUE); - if (intExtra == Integer.MIN_VALUE) { - Logger.error(Logger.tags("LN"), "Invalid notification dismiss operation", null); - return; - } - boolean isRemovable = intent.getBooleanExtra(LocalNotificationManager.NOTIFICATION_IS_REMOVABLE_KEY, true); - if (isRemovable) { - NotificationStorage notificationStorage = new NotificationStorage(context); - notificationStorage.deleteNotification(Integer.toString(intExtra)); - } - } -} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationStorage.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationStorage.java deleted file mode 100644 index ed38c3a4ed..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationStorage.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.getcapacitor.plugin.notification; - -import android.content.Context; -import android.content.SharedPreferences; -import com.getcapacitor.JSObject; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Set; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Class used to abstract storage for notification data - */ -public class NotificationStorage { - - // Key for private preferences - private static final String NOTIFICATION_STORE_ID = "NOTIFICATION_STORE"; - - // Key used to save action types - private static final String ACTION_TYPES_ID = "ACTION_TYPE_STORE"; - - private static final String ID_KEY = "notificationIds"; - - private Context context; - - public NotificationStorage(Context context) { - this.context = context; - } - - /** - * Persist the id of currently scheduled notification - */ - public void appendNotifications(List localNotifications) { - SharedPreferences storage = getStorage(NOTIFICATION_STORE_ID); - SharedPreferences.Editor editor = storage.edit(); - for (LocalNotification request : localNotifications) { - String key = request.getId().toString(); - editor.putString(key, request.getSource()); - } - editor.apply(); - } - - public List getSavedNotificationIds() { - SharedPreferences storage = getStorage(NOTIFICATION_STORE_ID); - Map all = storage.getAll(); - if (all != null) { - return new ArrayList<>(all.keySet()); - } - return new ArrayList<>(); - } - - public JSObject getSavedNotificationAsJSObject(String key) { - SharedPreferences storage = getStorage(NOTIFICATION_STORE_ID); - String notificationString = storage.getString(key, null); - - if (notificationString == null) { - return null; - } - - JSObject jsNotification; - try { - jsNotification = new JSObject(notificationString); - } catch (JSONException ex) { - return null; - } - - return jsNotification; - } - - public LocalNotification getSavedNotification(String key) { - JSObject jsNotification = getSavedNotificationAsJSObject(key); - if (jsNotification == null) { - return null; - } - - LocalNotification notification; - try { - notification = LocalNotification.buildNotificationFromJSObject(jsNotification); - } catch (ParseException ex) { - return null; - } - - return notification; - } - - /** - * Remove the stored notifications - */ - public void deleteNotification(String id) { - SharedPreferences.Editor editor = getStorage(NOTIFICATION_STORE_ID).edit(); - editor.remove(id); - editor.apply(); - } - - /** - * Shared private preferences for the application. - */ - private SharedPreferences getStorage(String key) { - return context.getSharedPreferences(key, Context.MODE_PRIVATE); - } - - /** - * Writes new action types (actions that being displayed in notification) to storage. - * Write will override previous data. - * - * @param typesMap - map with groupId and actionArray assigned to group - */ - public void writeActionGroup(Map typesMap) { - Set typesIds = typesMap.keySet(); - for (String id : typesIds) { - SharedPreferences.Editor editor = getStorage(ACTION_TYPES_ID + id).edit(); - editor.clear(); - NotificationAction[] notificationActions = typesMap.get(id); - editor.putInt("count", notificationActions.length); - for (int i = 0; i < notificationActions.length; i++) { - editor.putString("id" + i, notificationActions[i].getId()); - editor.putString("title" + i, notificationActions[i].getTitle()); - editor.putBoolean("input" + i, notificationActions[i].isInput()); - } - editor.apply(); - } - } - - /** - * Retrieve array of notification actions per ActionTypeId - * - * @param forId - id of the group - */ - public NotificationAction[] getActionGroup(String forId) { - SharedPreferences storage = getStorage(ACTION_TYPES_ID + forId); - int count = storage.getInt("count", 0); - NotificationAction[] actions = new NotificationAction[count]; - for (int i = 0; i < count; i++) { - String id = storage.getString("id" + i, ""); - String title = storage.getString("title" + i, ""); - Boolean input = storage.getBoolean("input" + i, false); - actions[i] = new NotificationAction(id, title, input); - } - return actions; - } -} diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/TimedNotificationPublisher.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/TimedNotificationPublisher.java deleted file mode 100644 index 5706e8c588..0000000000 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/notification/TimedNotificationPublisher.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.getcapacitor.plugin.notification; - -import android.app.AlarmManager; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import com.getcapacitor.Logger; -import java.text.SimpleDateFormat; -import java.util.Date; - -/** - * Class used to create notification from timer event - * Note: Class is being registered in Android manifest as broadcast receiver - */ -public class TimedNotificationPublisher extends BroadcastReceiver { - - public static String NOTIFICATION_KEY = "NotificationPublisher.notification"; - public static String CRON_KEY = "NotificationPublisher.cron"; - - /** - * Restore and present notification - */ - @Override - public void onReceive(Context context, Intent intent) { - NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - Notification notification = intent.getParcelableExtra(NOTIFICATION_KEY); - int id = intent.getIntExtra(LocalNotificationManager.NOTIFICATION_INTENT_KEY, Integer.MIN_VALUE); - if (id == Integer.MIN_VALUE) { - Logger.error(Logger.tags("LN"), "No valid id supplied", null); - } - notificationManager.notify(id, notification); - rescheduleNotificationIfNeeded(context, intent, id); - } - - private void rescheduleNotificationIfNeeded(Context context, Intent intent, int id) { - String dateString = intent.getStringExtra(CRON_KEY); - if (dateString != null) { - DateMatch date = DateMatch.fromMatchString(dateString); - AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - long trigger = date.nextTrigger(new Date()); - Intent clone = (Intent) intent.clone(); - PendingIntent pendingIntent = PendingIntent.getBroadcast(context, id, clone, PendingIntent.FLAG_CANCEL_CURRENT); - alarmManager.setExact(AlarmManager.RTC, trigger, pendingIntent); - SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); - Logger.debug(Logger.tags("LN"), "notification " + id + " will next fire at " + sdf.format(new Date(trigger))); - } - } -} diff --git a/core/src/core-plugin-definitions.ts b/core/src/core-plugin-definitions.ts index 9b0396d999..5c24204316 100644 --- a/core/src/core-plugin-definitions.ts +++ b/core/src/core-plugin-definitions.ts @@ -1,9 +1,7 @@ /* eslint-disable */ -import { Plugin, PluginListenerHandle } from './definitions'; +import { Plugin } from './definitions'; export interface PluginRegistry { - LocalNotifications: LocalNotificationsPlugin; - PushNotifications: PushNotificationsPlugin; SplashScreen: SplashScreenPlugin; WebView: WebViewPlugin; @@ -29,323 +27,6 @@ export interface CancellableCallback { // -export interface LocalNotificationRequest { - id: string; -} - -export interface LocalNotificationPendingList { - notifications: LocalNotificationRequest[]; -} - -export type LocalNotificationScheduleResult = LocalNotificationPendingList; - -export interface LocalNotificationActionType { - id: string; - actions?: LocalNotificationAction[]; - iosHiddenPreviewsBodyPlaceholder?: string; // >= iOS 11 only - iosCustomDismissAction?: boolean; - iosAllowInCarPlay?: boolean; - iosHiddenPreviewsShowTitle?: boolean; // >= iOS 11 only - iosHiddenPreviewsShowSubtitle?: boolean; // >= iOS 11 only -} - -export interface LocalNotificationAction { - id: string; - title: string; - requiresAuthentication?: boolean; - foreground?: boolean; - destructive?: boolean; - input?: boolean; - inputButtonTitle?: string; - inputPlaceholder?: string; -} - -export interface LocalNotificationAttachment { - id: string; - url: string; - options?: LocalNotificationAttachmentOptions; -} - -export interface LocalNotificationAttachmentOptions { - iosUNNotificationAttachmentOptionsTypeHintKey?: string; - iosUNNotificationAttachmentOptionsThumbnailHiddenKey?: string; - iosUNNotificationAttachmentOptionsThumbnailClippingRectKey?: string; - iosUNNotificationAttachmentOptionsThumbnailTimeKey?: string; -} - -export interface LocalNotification { - title: string; - body: string; - id: number; - schedule?: LocalNotificationSchedule; - /** - * Name of the audio file with extension. - * On iOS the file should be in the app bundle. - * On Android the file should be on res/raw folder. - * Doesn't work on Android version 26+ (Android O and newer), for - * Recommended format is .wav because is supported by both platforms. - */ - sound?: string; - /** - * Android-only: set a custom statusbar icon. - * If set, it overrides default icon from capacitor.config.json - */ - smallIcon?: string; - /** - * Android only: set the color of the notification icon - */ - iconColor?: string; - attachments?: LocalNotificationAttachment[]; - actionTypeId?: string; - extra?: any; - /** - * iOS only: set the thread identifier for notification grouping - */ - threadIdentifier?: string; - /** - * iOS 12+ only: set the summary argument for notification grouping - */ - summaryArgument?: string; - /** - * Android only: set the group identifier for notification grouping, like - * threadIdentifier on iOS. - */ - group?: string; - /** - * Android only: designate this notification as the summary for a group - * (should be used with the `group` property). - */ - groupSummary?: boolean; - /** - * Android only: set the notification channel on which local notification - * will generate. If channel with the given name does not exist then the - * notification will not fire. If not provided, it will use the default channel. - */ - channelId?: string; - /** - * Android only: set the notification ongoing. - * If set to true the notification can't be swiped away. - */ - ongoing?: boolean; - /** - * Android only: set the notification to be removed automatically when the user clicks on it - */ - autoCancel?: boolean; -} - -export interface LocalNotificationSchedule { - at?: Date; - repeats?: boolean; - every?: - | 'year' - | 'month' - | 'two-weeks' - | 'week' - | 'day' - | 'hour' - | 'minute' - | 'second'; - count?: number; - on?: { - year?: number; - month?: number; - day?: number; - hour?: number; - minute?: number; - }; -} - -export interface LocalNotificationActionPerformed { - actionId: string; - inputValue?: string; - notification: LocalNotification; -} - -export interface LocalNotificationEnabledResult { - /** - * Whether the device has Local Notifications enabled or not - */ - value: boolean; -} - -export interface NotificationPermissionResponse { - granted: boolean; -} - -export interface LocalNotificationsPlugin extends Plugin { - schedule(options: { - notifications: LocalNotification[]; - }): Promise; - getPending(): Promise; - registerActionTypes(options: { - types: LocalNotificationActionType[]; - }): Promise; - cancel(pending: LocalNotificationPendingList): Promise; - areEnabled(): Promise; - createChannel(channel: NotificationChannel): Promise; - deleteChannel(channel: NotificationChannel): Promise; - listChannels(): Promise; - requestPermission(): Promise; - addListener( - eventName: 'localNotificationReceived', - listenerFunc: (notification: LocalNotification) => void, - ): PluginListenerHandle; - addListener( - eventName: 'localNotificationActionPerformed', - listenerFunc: ( - notificationAction: LocalNotificationActionPerformed, - ) => void, - ): PluginListenerHandle; - - /** - * Remove all native listeners for this plugin - */ - removeAllListeners(): void; -} - -// - -export interface PushNotification { - title?: string; - subtitle?: string; - body?: string; - id: string; - badge?: number; - notification?: any; - data: any; - click_action?: string; - link?: string; - /** - * Android only: set the group identifier for notification grouping, like - * threadIdentifier on iOS. - */ - group?: string; - /** - * Android only: designate this notification as the summary for a group - * (should be used with the `group` property). - */ - groupSummary?: boolean; -} - -export interface PushNotificationActionPerformed { - actionId: string; - inputValue?: string; - notification: PushNotification; -} - -export interface PushNotificationToken { - value: string; -} - -export interface PushNotificationDeliveredList { - notifications: PushNotification[]; -} - -export interface NotificationChannel { - id: string; - name: string; - description?: string; - sound?: string; - importance: 1 | 2 | 3 | 4 | 5; - visibility?: -1 | 0 | 1; - lights?: boolean; - lightColor?: string; - vibration?: boolean; -} - -export interface NotificationChannelList { - channels: NotificationChannel[]; -} - -export interface PushNotificationsPlugin extends Plugin { - /** - * Register the app to receive push notifications. - * Will trigger registration event with the push token - * or registrationError if there was some problem. - * Doesn't prompt the user for notification permissions, use requestPermission() first. - */ - register(): Promise; - /** - * On iOS it prompts the user to allow displaying notifications - * and return if the permission was granted or not. - * On Android there is no such prompt, so just return as granted. - */ - requestPermission(): Promise; - /** - * Returns the notifications that are visible on the notifications screen. - */ - getDeliveredNotifications(): Promise; - /** - * Removes the specified notifications from the notifications screen. - * @param delivered list of delivered notifications. - */ - removeDeliveredNotifications( - delivered: PushNotificationDeliveredList, - ): Promise; - /** - * Removes all the notifications from the notifications screen. - */ - removeAllDeliveredNotifications(): Promise; - /** - * On Android O or newer (SDK 26+) creates a notification channel. - * @param channel to create. - */ - createChannel(channel: NotificationChannel): Promise; - /** - * On Android O or newer (SDK 26+) deletes a notification channel. - * @param channel to delete. - */ - deleteChannel(channel: NotificationChannel): Promise; - /** - * On Android O or newer (SDK 26+) list the available notification channels. - */ - listChannels(): Promise; - /** - * Event called when the push notification registration finished without problems. - * Provides the push notification token. - * @param eventName registration. - * @param listenerFunc callback with the push token. - */ - addListener( - eventName: 'registration', - listenerFunc: (token: PushNotificationToken) => void, - ): PluginListenerHandle; - /** - * Event called when the push notification registration finished with problems. - * Provides an error with the registration problem. - * @param eventName registrationError. - * @param listenerFunc callback with the registration error. - */ - addListener( - eventName: 'registrationError', - listenerFunc: (error: any) => void, - ): PluginListenerHandle; - /** - * Event called when the device receives a push notification. - * @param eventName pushNotificationReceived. - * @param listenerFunc callback with the received notification. - */ - addListener( - eventName: 'pushNotificationReceived', - listenerFunc: (notification: PushNotification) => void, - ): PluginListenerHandle; - /** - * Event called when an action is performed on a push notification. - * @param eventName pushNotificationActionPerformed. - * @param listenerFunc callback with the notification action. - */ - addListener( - eventName: 'pushNotificationActionPerformed', - listenerFunc: (notification: PushNotificationActionPerformed) => void, - ): PluginListenerHandle; - /** - * Remove all native listeners for this plugin. - */ - removeAllListeners(): void; -} - -// - export interface SplashScreenPlugin extends Plugin { /** * Show the splash screen diff --git a/core/src/web-plugins.ts b/core/src/web-plugins.ts index 5dd60ba148..a5d7631708 100644 --- a/core/src/web-plugins.ts +++ b/core/src/web-plugins.ts @@ -1,9 +1,6 @@ import { mergeWebPlugin } from './plugins'; -import { LocalNotifications } from './web/local-notifications'; import { SplashScreen } from './web/splash-screen'; -export * from './web/local-notifications'; export * from './web/splash-screen'; -mergeWebPlugin(LocalNotifications); mergeWebPlugin(SplashScreen); diff --git a/core/src/web/local-notifications.ts b/core/src/web/local-notifications.ts deleted file mode 100644 index e581500a39..0000000000 --- a/core/src/web/local-notifications.ts +++ /dev/null @@ -1,153 +0,0 @@ -/* eslint-disable */ -import { - LocalNotificationsPlugin, - LocalNotificationEnabledResult, - LocalNotificationPendingList, - LocalNotificationActionType, - LocalNotification, - LocalNotificationScheduleResult, - NotificationPermissionResponse, - NotificationChannel, - NotificationChannelList, -} from '../core-plugin-definitions'; -import { PermissionsRequestResult } from '../definitions'; - -import { WebPlugin } from './index'; - -export class LocalNotificationsPluginWeb - extends WebPlugin - implements LocalNotificationsPlugin { - private pending: LocalNotification[] = []; - - constructor() { - super({ name: 'LocalNotifications' }); - } - - createChannel(channel: NotificationChannel): Promise { - throw new Error('Feature not available in the browser. ' + channel.id); - } - - deleteChannel(channel: NotificationChannel): Promise { - throw new Error('Feature not available in the browser. ' + channel.id); - } - - listChannels(): Promise { - throw new Error('Feature not available in the browser'); - } - - sendPending() { - const toRemove: LocalNotification[] = []; - const now = +new Date(); - this.pending.forEach(localNotification => { - if (localNotification.schedule && localNotification.schedule.at) { - if (+localNotification.schedule.at <= now) { - this.buildNotification(localNotification); - toRemove.push(localNotification); - } - } - }); - console.log('Sent pending, removing', toRemove); - - this.pending = this.pending.filter( - localNotification => !toRemove.find(ln => ln === localNotification), - ); - } - - sendNotification(localNotification: LocalNotification): Notification { - const l = localNotification; - - if (localNotification.schedule && localNotification.schedule.at) { - const diff = +localNotification.schedule.at - +new Date(); - this.pending.push(l); - setTimeout(() => { - this.sendPending(); - }, diff); - return; - } - - this.buildNotification(localNotification); - } - - buildNotification(localNotification: LocalNotification) { - const l = localNotification; - return new Notification(l.title, { - body: l.body, - }); - } - - schedule(options: { - notifications: LocalNotification[]; - }): Promise { - const notifications: Notification[] = []; - options.notifications.forEach(notification => { - notifications.push(this.sendNotification(notification)); - }); - - return Promise.resolve({ - notifications: options.notifications.map(notification => { - return { id: '' + notification.id }; - }), - }); - } - - getPending(): Promise { - return Promise.resolve({ - notifications: this.pending.map(localNotification => { - return { - id: '' + localNotification.id, - }; - }), - }); - } - - registerActionTypes(_options: { - types: LocalNotificationActionType[]; - }): Promise { - throw new Error('Method not implemented.'); - } - - cancel(pending: LocalNotificationPendingList): Promise { - console.log('Cancel these', pending); - this.pending = this.pending.filter( - localNotification => - !pending.notifications.find(ln => ln.id === '' + localNotification.id), - ); - return Promise.resolve(); - } - - areEnabled(): Promise { - return Promise.resolve({ - value: Notification.permission === 'granted', - }); - } - - requestPermission(): Promise { - return new Promise(resolve => { - Notification.requestPermission(result => { - let granted = true; - if (result === 'denied' || result === 'default') { - granted = false; - } - resolve({ granted }); - }); - }); - } - - requestPermissions(): Promise { - return new Promise((resolve, reject) => { - Notification.requestPermission(result => { - if (result === 'denied' || result === 'default') { - reject(result); - return; - } - resolve({ - results: [result], - }); - }); - }); - } -} - -const LocalNotifications = new LocalNotificationsPluginWeb(); - -export { LocalNotifications }; diff --git a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj index ee508d803c..601fb45e62 100644 --- a/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj +++ b/ios/Capacitor/Capacitor.xcodeproj/project.pbxproj @@ -24,15 +24,12 @@ 62959B1C2524DA7800A3D7F1 /* CAPPluginMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 62959AE82524DA7700A3D7F1 /* CAPPluginMethod.m */; }; 62959B1D2524DA7800A3D7F1 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959AE92524DA7700A3D7F1 /* UIColor.swift */; }; 62959B222524DA7800A3D7F1 /* Console.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959AEF2524DA7700A3D7F1 /* Console.swift */; }; - 62959B252524DA7800A3D7F1 /* PushNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959AF22524DA7700A3D7F1 /* PushNotifications.swift */; }; 62959B262524DA7800A3D7F1 /* WebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959AF32524DA7700A3D7F1 /* WebView.swift */; }; 62959B292524DA7800A3D7F1 /* SplashScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959AF62524DA7700A3D7F1 /* SplashScreen.swift */; }; - 62959B2C2524DA7800A3D7F1 /* LocalNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959AFA2524DA7700A3D7F1 /* LocalNotifications.swift */; }; 62959B2F2524DA7800A3D7F1 /* DefaultPlugins.m in Sources */ = {isa = PBXBuildFile; fileRef = 62959AFD2524DA7700A3D7F1 /* DefaultPlugins.m */; }; 62959B302524DA7800A3D7F1 /* UIStatusBarManager+CAPHandleTapAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 62959AFE2524DA7700A3D7F1 /* UIStatusBarManager+CAPHandleTapAction.m */; }; 62959B312524DA7800A3D7F1 /* JS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959AFF2524DA7700A3D7F1 /* JS.swift */; }; 62959B332524DA7800A3D7F1 /* CAPPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 62959B012524DA7700A3D7F1 /* CAPPlugin.m */; }; - 62959B342524DA7800A3D7F1 /* CAPUNUserNotificationCenterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959B022524DA7700A3D7F1 /* CAPUNUserNotificationCenterDelegate.swift */; }; 62959B362524DA7800A3D7F1 /* CAPBridgeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959B042524DA7700A3D7F1 /* CAPBridgeViewController.swift */; }; 62959B372524DA7800A3D7F1 /* CAPConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62959B052524DA7700A3D7F1 /* CAPConfig.swift */; }; 62959B382524DA7800A3D7F1 /* CAPPluginCall.m in Sources */ = {isa = PBXBuildFile; fileRef = 62959B062524DA7700A3D7F1 /* CAPPluginCall.m */; }; @@ -127,15 +124,12 @@ 62959AE82524DA7700A3D7F1 /* CAPPluginMethod.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CAPPluginMethod.m; sourceTree = ""; }; 62959AE92524DA7700A3D7F1 /* UIColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; 62959AEF2524DA7700A3D7F1 /* Console.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Console.swift; sourceTree = ""; }; - 62959AF22524DA7700A3D7F1 /* PushNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushNotifications.swift; sourceTree = ""; }; 62959AF32524DA7700A3D7F1 /* WebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebView.swift; sourceTree = ""; }; 62959AF62524DA7700A3D7F1 /* SplashScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplashScreen.swift; sourceTree = ""; }; - 62959AFA2524DA7700A3D7F1 /* LocalNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalNotifications.swift; sourceTree = ""; }; 62959AFD2524DA7700A3D7F1 /* DefaultPlugins.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DefaultPlugins.m; sourceTree = ""; }; 62959AFE2524DA7700A3D7F1 /* UIStatusBarManager+CAPHandleTapAction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIStatusBarManager+CAPHandleTapAction.m"; sourceTree = ""; }; 62959AFF2524DA7700A3D7F1 /* JS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JS.swift; sourceTree = ""; }; 62959B012524DA7700A3D7F1 /* CAPPlugin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CAPPlugin.m; sourceTree = ""; }; - 62959B022524DA7700A3D7F1 /* CAPUNUserNotificationCenterDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAPUNUserNotificationCenterDelegate.swift; sourceTree = ""; }; 62959B042524DA7700A3D7F1 /* CAPBridgeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAPBridgeViewController.swift; sourceTree = ""; }; 62959B052524DA7700A3D7F1 /* CAPConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAPConfig.swift; sourceTree = ""; }; 62959B062524DA7700A3D7F1 /* CAPPluginCall.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CAPPluginCall.m; sourceTree = ""; }; @@ -258,7 +252,6 @@ 62959AFE2524DA7700A3D7F1 /* UIStatusBarManager+CAPHandleTapAction.m */, 62959AFF2524DA7700A3D7F1 /* JS.swift */, 62959B012524DA7700A3D7F1 /* CAPPlugin.m */, - 62959B022524DA7700A3D7F1 /* CAPUNUserNotificationCenterDelegate.swift */, 62959B042524DA7700A3D7F1 /* CAPBridgeViewController.swift */, 62959B052524DA7700A3D7F1 /* CAPConfig.swift */, 62959B062524DA7700A3D7F1 /* CAPPluginCall.m */, @@ -283,8 +276,6 @@ children = ( 62959AEF2524DA7700A3D7F1 /* Console.swift */, 62959AFD2524DA7700A3D7F1 /* DefaultPlugins.m */, - 62959AFA2524DA7700A3D7F1 /* LocalNotifications.swift */, - 62959AF22524DA7700A3D7F1 /* PushNotifications.swift */, 62959AF62524DA7700A3D7F1 /* SplashScreen.swift */, 62959AF32524DA7700A3D7F1 /* WebView.swift */, ); @@ -495,10 +486,8 @@ 62959B1D2524DA7800A3D7F1 /* UIColor.swift in Sources */, 62959B332524DA7800A3D7F1 /* CAPPlugin.m in Sources */, 62959B1C2524DA7800A3D7F1 /* CAPPluginMethod.m in Sources */, - 62959B342524DA7800A3D7F1 /* CAPUNUserNotificationCenterDelegate.swift in Sources */, 62959B472524DA7800A3D7F1 /* CAPNotifications.swift in Sources */, 62959B312524DA7800A3D7F1 /* JS.swift in Sources */, - 62959B252524DA7800A3D7F1 /* PushNotifications.swift in Sources */, 62959B292524DA7800A3D7F1 /* SplashScreen.swift in Sources */, 62959B1A2524DA7800A3D7F1 /* CAPPluginCall.swift in Sources */, 62959B302524DA7800A3D7F1 /* UIStatusBarManager+CAPHandleTapAction.m in Sources */, @@ -508,7 +497,6 @@ 62959B172524DA7800A3D7F1 /* JSExport.swift in Sources */, 62959B3F2524DA7800A3D7F1 /* CAPAssetHandler.swift in Sources */, 62959B3C2524DA7800A3D7F1 /* CAPBridgeDelegate.swift in Sources */, - 62959B2C2524DA7800A3D7F1 /* LocalNotifications.swift in Sources */, 62959B2F2524DA7800A3D7F1 /* DefaultPlugins.m in Sources */, 62959B222524DA7800A3D7F1 /* Console.swift in Sources */, 62959B3A2524DA7800A3D7F1 /* CAPLog.swift in Sources */, diff --git a/ios/Capacitor/Capacitor/CAPBridge.swift b/ios/Capacitor/Capacitor/CAPBridge.swift index b68ff3dc3e..505ccce7bc 100644 --- a/ios/Capacitor/Capacitor/CAPBridge.swift +++ b/ios/Capacitor/Capacitor/CAPBridge.swift @@ -46,19 +46,15 @@ enum BridgeError: Error { // Background dispatch queue for plugin calls public var dispatchQueue = DispatchQueue(label: "bridge") - public var notificationDelegationHandler: CAPUNUserNotificationCenterDelegate - public init(_ bridgeDelegate: CAPBridgeDelegate, _ messageHandlerWrapper: CAPMessageHandlerWrapper, _ config: CAPConfig, _ scheme: String) { self.bridgeDelegate = bridgeDelegate self.messageHandlerWrapper = messageHandlerWrapper - self.notificationDelegationHandler = CAPUNUserNotificationCenterDelegate() self.config = config self.scheme = scheme super.init() self.messageHandlerWrapper.bridge = self - self.notificationDelegationHandler.bridge = self localUrl = "\(self.scheme)://\(config.getString("server.hostname") ?? "localhost")" exportCoreJS(localUrl: localUrl!) registerPlugins() diff --git a/ios/Capacitor/Capacitor/CAPUNUserNotificationCenterDelegate.swift b/ios/Capacitor/Capacitor/CAPUNUserNotificationCenterDelegate.swift deleted file mode 100644 index 7a86509fa4..0000000000 --- a/ios/Capacitor/Capacitor/CAPUNUserNotificationCenterDelegate.swift +++ /dev/null @@ -1,168 +0,0 @@ -import UserNotifications - -public class CAPUNUserNotificationCenterDelegate: NSObject, UNUserNotificationCenterDelegate { - - public weak var bridge: CAPBridge? - // Local list of notification id -> JSObject for storing options - // between notification requets - var notificationRequestLookup = [String: JSObject]() - - override public init() { - super.init() - let center = UNUserNotificationCenter.current() - if center.delegate == nil { - center.delegate = self - } - } - - public func setBridge(bridge: CAPBridge) { - self.bridge = bridge - } - /** - * Request permissions to send notifications - */ - public func requestPermissions(with completion: ((Bool, Error?) -> Void)? = nil) { - let center = UNUserNotificationCenter.current() - center.requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in - completion?(granted, error) - } - } - - /** - * Handle delegate willPresent action when the app is in the foreground. - * This controls how a notification is presented when the app is running, such as - * whether it should stay silent, display a badge, play a sound, or show an alert. - */ - public func userNotificationCenter(_ center: UNUserNotificationCenter, - willPresent notification: UNNotification, - withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { - let request = notification.request - var plugin: CAPPlugin - var action = "localNotificationReceived" - var presentationOptions: UNNotificationPresentationOptions = [] - - var notificationData = makeNotificationRequestJSObject(request) - if request.trigger?.isKind(of: UNPushNotificationTrigger.self) ?? false { - plugin = (self.bridge?.getOrLoadPlugin(pluginName: "PushNotifications"))! - let options = plugin.getConfigValue("presentationOptions") as? [String] ?? ["badge"] - - action = "pushNotificationReceived" - if options.contains("alert") { - presentationOptions.update(with: .alert) - } - if options.contains("badge") { - presentationOptions.update(with: .badge) - } - if options.contains("sound") { - presentationOptions.update(with: .sound) - } - notificationData = makePushNotificationRequestJSObject(request) - - } else { - plugin = (self.bridge?.getOrLoadPlugin(pluginName: "LocalNotifications"))! - presentationOptions = [ - .badge, - .sound, - .alert - ] - } - - plugin.notifyListeners(action, data: notificationData) - - if let options = notificationRequestLookup[request.identifier] { - let silent = options["silent"] as? Bool ?? false - if silent { - completionHandler(.init(rawValue:0)) - return - } - } - - completionHandler(presentationOptions) - } - - /** - * Handle didReceive action, called when a notification opens or activates - * the app based on an action. - */ - public func userNotificationCenter(_ center: UNUserNotificationCenter, - didReceive response: UNNotificationResponse, - withCompletionHandler completionHandler: @escaping () -> Void) { - completionHandler() - - var data = JSObject() - - // Get the info for the original notification request - let originalNotificationRequest = response.notification.request - - let actionId = response.actionIdentifier - - // We turn the two default actions (open/dismiss) into generic strings - if actionId == UNNotificationDefaultActionIdentifier { - data["actionId"] = "tap" - } else if actionId == UNNotificationDismissActionIdentifier { - data["actionId"] = "dismiss" - } else { - data["actionId"] = actionId - } - - // If the type of action was for an input type, get the value - if let inputType = response as? UNTextInputNotificationResponse { - data["inputValue"] = inputType.userText - } - - var plugin: CAPPlugin - var action = "localNotificationActionPerformed" - - if originalNotificationRequest.trigger?.isKind(of: UNPushNotificationTrigger.self) ?? false { - plugin = (self.bridge?.getOrLoadPlugin(pluginName: "PushNotifications"))! - data["notification"] = makePushNotificationRequestJSObject(originalNotificationRequest) - action = "pushNotificationActionPerformed" - } else { - data["notification"] = makeNotificationRequestJSObject(originalNotificationRequest) - plugin = (self.bridge?.getOrLoadPlugin(pluginName: "LocalNotifications"))! - } - - plugin.notifyListeners(action, data: data, retainUntilConsumed: true) - } - - /** - * Make a JSObject of pending notifications. - */ - func makePendingNotificationRequestJSObject(_ request: UNNotificationRequest) -> JSObject { - return [ - "id": request.identifier - ] - } - - /** - * Turn a UNNotificationRequest into a JSObject to return back to the client. - */ - func makeNotificationRequestJSObject(_ request: UNNotificationRequest) -> JSObject { - let notificationRequest = notificationRequestLookup[request.identifier] ?? [:] - return [ - "id": request.identifier, - "title": request.content.title, - "sound": notificationRequest["sound"] ?? "", - "body": request.content.body, - "extra": request.content.userInfo as? JSObject ?? [:], - "actionTypeId": request.content.categoryIdentifier, - "attachments": notificationRequest["attachments"] ?? [] - ] - } - - /** - * Turn a UNNotificationRequest into a JSObject to return back to the client. - */ - func makePushNotificationRequestJSObject(_ request: UNNotificationRequest) -> JSObject { - let content = request.content - return [ - "id": request.identifier, - "title": content.title, - "subtitle": content.subtitle, - "body": content.body, - "badge": content.badge ?? 1, - "data": content.userInfo as? JSObject ?? [:] - ] - } - -} diff --git a/ios/Capacitor/Capacitor/Plugins/DefaultPlugins.m b/ios/Capacitor/Capacitor/Plugins/DefaultPlugins.m index ce2664bbe6..28fcf4c06e 100644 --- a/ios/Capacitor/Capacitor/Plugins/DefaultPlugins.m +++ b/ios/Capacitor/Capacitor/Plugins/DefaultPlugins.m @@ -6,31 +6,6 @@ CAP_PLUGIN_METHOD(log, CAPPluginReturnNone); ) -CAP_PLUGIN(CAPLocalNotificationsPlugin, "LocalNotifications", - CAP_PLUGIN_METHOD(schedule, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(requestPermission, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(cancel, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(getPending, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(registerActionTypes, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(areEnabled, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(createChannel, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(deleteChannel, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(listChannels, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(removeAllListeners, CAPPluginReturnNone); -) - -CAP_PLUGIN(CAPPushNotificationsPlugin, "PushNotifications", - CAP_PLUGIN_METHOD(register, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(requestPermission, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(getDeliveredNotifications, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(removeDeliveredNotifications, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(removeAllDeliveredNotifications, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(createChannel, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(deleteChannel, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(listChannels, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(removeAllListeners, CAPPluginReturnNone); -) - CAP_PLUGIN(CAPSplashScreenPlugin, "SplashScreen", CAP_PLUGIN_METHOD(show, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(hide, CAPPluginReturnPromise); diff --git a/ios/Capacitor/Capacitor/Plugins/LocalNotifications.swift b/ios/Capacitor/Capacitor/Plugins/LocalNotifications.swift deleted file mode 100644 index f950f390a7..0000000000 --- a/ios/Capacitor/Capacitor/Plugins/LocalNotifications.swift +++ /dev/null @@ -1,520 +0,0 @@ -import Foundation -import UserNotifications - -enum LocalNotificationError: LocalizedError { - case contentNoId - case contentNoTitle - case contentNoBody - case triggerConstructionFailed - case triggerRepeatIntervalTooShort - case attachmentNoId - case attachmentNoUrl - case attachmentFileNotFound(path: String) - case attachmentUnableToCreate(String) - - var errorDescription: String? { - switch self { - case .attachmentFileNotFound(path: let path): - return "Unable to find file \(path) for attachment" - default: - return "" - } - } -} - -/** - * Implement Local Notifications - */ -@objc(CAPLocalNotificationsPlugin) -public class CAPLocalNotificationsPlugin: CAPPlugin { - - /** - * Schedule a notification. - */ - @objc func schedule(_ call: CAPPluginCall) { - guard let notifications = call.getArray("notifications", JSObject.self) else { - call.error("Must provide notifications array as notifications option") - return - } - var ids = [String]() - - for notification in notifications { - guard let identifier = notification["id"] as? Int else { - call.error("Notification missing identifier") - return - } - - // let extra = notification["options"] as? JSObject ?? [:] - - var content: UNNotificationContent - do { - content = try makeNotificationContent(notification) - } catch { - CAPLog.print(error.localizedDescription) - call.error("Unable to make notification", error) - return - } - - var trigger: UNNotificationTrigger? - - do { - if let schedule = notification["schedule"] as? JSObject { - try trigger = handleScheduledNotification(call, schedule) - } - } catch { - call.error("Unable to create notification, trigger failed", error) - return - } - - // Schedule the request. - let request = UNNotificationRequest(identifier: "\(identifier)", content: content, trigger: trigger) - - self.bridge?.notificationDelegationHandler.notificationRequestLookup[request.identifier] = notification - - let center = UNUserNotificationCenter.current() - center.add(request) { (error: Error?) in - if let theError = error { - CAPLog.print(theError.localizedDescription) - call.error(theError.localizedDescription) - } - } - - ids.append(request.identifier) - } - - let ret = ids.map({ (id) -> JSObject in - return [ - "id": id - ] - }) - call.success([ - "notifications": ret - ]) - } - - /** - * Request notification permission - */ - @objc func requestPermission(_ call: CAPPluginCall) { - self.bridge?.notificationDelegationHandler.requestPermissions { granted, error in - guard error == nil else { - call.error(error!.localizedDescription) - return - } - call.success(["granted": granted]) - } - } - - /** - * Cancel notifications by id - */ - @objc func cancel(_ call: CAPPluginCall) { - guard let notifications = call.getArray("notifications", JSObject.self), notifications.count > 0 else { - call.error("Must supply notifications to cancel") - return - } - - let ids = notifications.map { $0["id"] as? String ?? "" } - - UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ids) - call.success() - } - - /** - * Get all pending notifications. - */ - @objc func getPending(_ call: CAPPluginCall) { - UNUserNotificationCenter.current().getPendingNotificationRequests(completionHandler: { (notifications) in - CAPLog.print("num of pending notifications \(notifications.count)") - CAPLog.print(notifications) - - let ret = notifications.compactMap({ [weak self] (notification) -> JSObject? in - return self?.bridge?.notificationDelegationHandler.makePendingNotificationRequestJSObject(notification) - }) - call.success([ - "notifications": ret - ]) - }) - } - - /** - * Register allowed action types that a notification may present. - */ - @objc func registerActionTypes(_ call: CAPPluginCall) { - guard let types = call.getArray("types", JSObject.self) else { - return - } - - makeActionTypes(types) - - call.success() - } - - /** - * Check if Local Notifications are authorized and enabled - */ - @objc func areEnabled(_ call: CAPPluginCall) { - let center = UNUserNotificationCenter.current() - center.getNotificationSettings { (settings) in - let authorized = settings.authorizationStatus == UNAuthorizationStatus.authorized - let enabled = settings.notificationCenterSetting == UNNotificationSetting.enabled - call.success([ - "value": enabled && authorized - ]) - } - } - - /** - * Build the content for a notification. - */ - func makeNotificationContent(_ notification: JSObject) throws -> UNNotificationContent { - guard let title = notification["title"] as? String else { - throw LocalNotificationError.contentNoTitle - } - guard let body = notification["body"] as? String else { - throw LocalNotificationError.contentNoBody - } - - let extra = notification["extra"] as? JSObject ?? [:] - let content = UNMutableNotificationContent() - content.title = NSString.localizedUserNotificationString(forKey: title, arguments: nil) - content.body = NSString.localizedUserNotificationString(forKey: body, - arguments: nil) - - content.userInfo = extra - if let actionTypeId = notification["actionTypeId"] as? String { - content.categoryIdentifier = actionTypeId - } - - if let threadIdentifier = notification["threadIdentifier"] as? String { - content.threadIdentifier = threadIdentifier - } - - if #available(iOS 12, *), let summaryArgument = notification["summaryArgument"] as? String { - content.summaryArgument = summaryArgument - } - - if let sound = notification["sound"] as? String { - content.sound = UNNotificationSound(named: UNNotificationSoundName(sound)) - } - - if let attachments = notification["attachments"] as? [JSObject] { - content.attachments = try makeAttachments(attachments) - } - - return content - } - - /** - * Build a notification trigger, such as triggering each N seconds, or - * on a certain date "shape" (such as every first of the month) - */ - func handleScheduledNotification(_ call: CAPPluginCall, _ schedule: JSObject) throws -> UNNotificationTrigger? { - var at: Date? - if let dateString = schedule["at"] as? String, let date = CAPPluginCall.jsDateFormatter.date(from: dateString) { - at = date - } - let every = schedule["every"] as? String - let count = schedule["count"] as? Int ?? 1 - let on = schedule["on"] as? JSObject - let repeats = schedule["repeats"] as? Bool ?? false - - // If there's a specific date for this notificiation - if let at = at { - let dateInfo = Calendar.current.dateComponents(in: TimeZone.current, from: at) - - if dateInfo.date! < Date() { - call.error("Scheduled time must be *after* current time") - return nil - } - - let dateInterval = DateInterval(start: Date(), end: dateInfo.date!) - - // Notifications that repeat have to be at least a minute between each other - if repeats && dateInterval.duration < 60 { - throw LocalNotificationError.triggerRepeatIntervalTooShort - } - - return UNTimeIntervalNotificationTrigger(timeInterval: dateInterval.duration, repeats: repeats) - } - - // If this notification should repeat every count of day/month/week/etc. or on a certain - // matching set of date components - if let on = on { - let dateComponents = getDateComponents(on) - return UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true) - } - - if let every = every { - if let repeatDateInterval = getRepeatDateInterval(every, count) { - return UNTimeIntervalNotificationTrigger(timeInterval: repeatDateInterval.duration, repeats: true) - } - } - - return nil - } - - /** - * Given our schedule format, return a DateComponents object - * that only contains the components passed in. - */ - func getDateComponents(_ at: JSObject) -> DateComponents { - //var dateInfo = Calendar.current.dateComponents(in: TimeZone.current, from: Date()) - //dateInfo.calendar = Calendar.current - var dateInfo = DateComponents() - - if let year = at["year"] as? Int { - dateInfo.year = year - } - if let month = at["month"] as? Int { - dateInfo.month = month - } - if let day = at["day"] as? Int { - dateInfo.day = day - } - if let hour = at["hour"] as? Int { - dateInfo.hour = hour - } - if let minute = at["minute"] as? Int { - dateInfo.minute = minute - } - if let second = at["second"] as? Int { - dateInfo.second = second - } - return dateInfo - } - - /** - * Compute the difference between the string representation of a date - * interval and today. For example, if every is "month", then we - * return the interval between today and a month from today. - */ - func getRepeatDateInterval(_ every: String, _ count: Int) -> DateInterval? { - let cal = Calendar.current - let now = Date() - switch every { - case "year": - let newDate = cal.date(byAdding: .year, value: count, to: now)! - return DateInterval(start: now, end: newDate) - case "month": - let newDate = cal.date(byAdding: .month, value: count, to: now)! - return DateInterval(start: now, end: newDate) - case "two-weeks": - let newDate = cal.date(byAdding: .weekOfYear, value: 2 * count, to: now)! - return DateInterval(start: now, end: newDate) - case "week": - let newDate = cal.date(byAdding: .weekOfYear, value: count, to: now)! - return DateInterval(start: now, end: newDate) - case "day": - let newDate = cal.date(byAdding: .day, value: count, to: now)! - return DateInterval(start: now, end: newDate) - case "hour": - let newDate = cal.date(byAdding: .hour, value: count, to: now)! - return DateInterval(start: now, end: newDate) - case "minute": - let newDate = cal.date(byAdding: .minute, value: count, to: now)! - return DateInterval(start: now, end: newDate) - case "second": - let newDate = cal.date(byAdding: .second, value: count, to: now)! - return DateInterval(start: now, end: newDate) - default: - return nil - } - } - - /** - * Make required UNNotificationCategory entries for action types - */ - func makeActionTypes(_ actionTypes: [JSObject]) { - var createdCategories = [UNNotificationCategory]() - - let generalCategory = UNNotificationCategory(identifier: "GENERAL", - actions: [], - intentIdentifiers: [], - options: .customDismissAction) - - createdCategories.append(generalCategory) - for type in actionTypes { - guard let id = type["id"] as? String else { - bridge?.modulePrint(self, "Action type must have an id field") - continue - } - let hiddenBodyPlaceholder = type["iosHiddenPreviewsBodyPlaceholder"] as? String ?? "" - let actions = type["actions"] as? [JSObject] ?? [] - - let newActions = makeActions(actions) - - // Create the custom actions for the TIMER_EXPIRED category. - var newCategory: UNNotificationCategory? - - newCategory = UNNotificationCategory(identifier: id, - actions: newActions, - intentIdentifiers: [], - hiddenPreviewsBodyPlaceholder: hiddenBodyPlaceholder, - options: makeCategoryOptions(type)) - - createdCategories.append(newCategory!) - } - - let center = UNUserNotificationCenter.current() - center.setNotificationCategories(Set(createdCategories)) - } - - /** - * Build the required UNNotificationAction objects for each action type registered. - */ - func makeActions(_ actions: [JSObject]) -> [UNNotificationAction] { - var createdActions = [UNNotificationAction]() - - for action in actions { - guard let id = action["id"] as? String else { - bridge?.modulePrint(self, "Action must have an id field") - continue - } - let title = action["title"] as? String ?? "" - let input = action["input"] as? Bool ?? false - - var newAction: UNNotificationAction - if input { - let inputButtonTitle = action["inputButtonTitle"] as? String - let inputPlaceholder = action["inputPlaceholder"] as? String ?? "" - - if inputButtonTitle != nil { - newAction = UNTextInputNotificationAction(identifier: id, - title: title, - options: makeActionOptions(action), - textInputButtonTitle: inputButtonTitle!, - textInputPlaceholder: inputPlaceholder) - } else { - newAction = UNTextInputNotificationAction(identifier: id, title: title, options: makeActionOptions(action)) - } - } else { - // Create the custom actions for the TIMER_EXPIRED category. - newAction = UNNotificationAction(identifier: id, - title: title, - options: makeActionOptions(action)) - } - createdActions.append(newAction) - } - - return createdActions - } - - /** - * Make options for UNNotificationActions - */ - func makeActionOptions(_ action: JSObject) -> UNNotificationActionOptions { - let foreground = action["foreground"] as? Bool ?? false - let destructive = action["destructive"] as? Bool ?? false - let requiresAuthentication = action["requiresAuthentication"] as? Bool ?? false - - if foreground { - return .foreground - } - if destructive { - return .destructive - } - if requiresAuthentication { - return .authenticationRequired - } - return UNNotificationActionOptions(rawValue: 0) - } - - /** - * Make options for UNNotificationCategoryActions - */ - func makeCategoryOptions(_ type: JSObject) -> UNNotificationCategoryOptions { - let customDismiss = type["iosCustomDismissAction"] as? Bool ?? false - let carPlay = type["iosAllowInCarPlay"] as? Bool ?? false - let hiddenPreviewsShowTitle = type["iosHiddenPreviewsShowTitle"] as? Bool ?? false - let hiddenPreviewsShowSubtitle = type["iosHiddenPreviewsShowSubtitle"] as? Bool ?? false - - if customDismiss { - return .customDismissAction - } - if carPlay { - return .allowInCarPlay - } - - if hiddenPreviewsShowTitle { - return .hiddenPreviewsShowTitle - } - if hiddenPreviewsShowSubtitle { - return .hiddenPreviewsShowSubtitle - } - - return UNNotificationCategoryOptions(rawValue: 0) - } - - /** - * Build the UNNotificationAttachment object for each attachment supplied. - */ - func makeAttachments(_ attachments: [JSObject]) throws -> [UNNotificationAttachment] { - var createdAttachments = [UNNotificationAttachment]() - - for attachment in attachments { - guard let id = attachment["id"] as? String else { - throw LocalNotificationError.attachmentNoId - } - guard let url = attachment["url"] as? String else { - throw LocalNotificationError.attachmentNoUrl - } - guard let urlObject = makeAttachmentUrl(url) else { - throw LocalNotificationError.attachmentFileNotFound(path: url) - } - - let options = attachment["options"] as? JSObject ?? [:] - - do { - let newAttachment = try UNNotificationAttachment(identifier: id, url: urlObject, options: makeAttachmentOptions(options)) - createdAttachments.append(newAttachment) - } catch { - throw LocalNotificationError.attachmentUnableToCreate(error.localizedDescription) - } - } - - return createdAttachments - } - - /** - * Get the internal URL for the attachment URL - */ - func makeAttachmentUrl(_ path: String) -> URL? { - let file = CAPFileManager.get(path: path) - return file?.url - } - - /** - * Build the options for the attachment, if any. (For example: the clipping rectangle to use - * for image attachments) - */ - func makeAttachmentOptions(_ options: JSObject) -> JSObject { - var opts: JSObject = [:] - - if let iosUNNotificationAttachmentOptionsTypeHintKey = options["iosUNNotificationAttachmentOptionsTypeHintKey"] as? String { - opts[UNNotificationAttachmentOptionsTypeHintKey] = iosUNNotificationAttachmentOptionsTypeHintKey - } - if let iosUNNotificationAttachmentOptionsThumbnailHiddenKey = options["iosUNNotificationAttachmentOptionsThumbnailHiddenKey"] as? String { - opts[UNNotificationAttachmentOptionsThumbnailHiddenKey] = iosUNNotificationAttachmentOptionsThumbnailHiddenKey - } - if let iosUNNotificationAttachmentOptionsThumbnailClippingRectKey = options["iosUNNotificationAttachmentOptionsThumbnailClippingRectKey"] as? String { - opts[UNNotificationAttachmentOptionsThumbnailClippingRectKey] = iosUNNotificationAttachmentOptionsThumbnailClippingRectKey - } - if let iosUNNotificationAttachmentOptionsThumbnailTimeKey = options["iosUNNotificationAttachmentOptionsThumbnailTimeKey"] as? String { - opts[UNNotificationAttachmentOptionsThumbnailTimeKey] = iosUNNotificationAttachmentOptionsThumbnailTimeKey - } - return opts - } - - @objc func createChannel(_ call: CAPPluginCall) { - call.unimplemented() - } - - @objc func deleteChannel(_ call: CAPPluginCall) { - call.unimplemented() - } - - @objc func listChannels(_ call: CAPPluginCall) { - call.unimplemented() - } -} diff --git a/ios/Capacitor/Capacitor/Plugins/PushNotifications.swift b/ios/Capacitor/Capacitor/Plugins/PushNotifications.swift deleted file mode 100644 index 43aca31238..0000000000 --- a/ios/Capacitor/Capacitor/Plugins/PushNotifications.swift +++ /dev/null @@ -1,129 +0,0 @@ -import Foundation -import UserNotifications - -enum PushNotificationError: Error { - case tokenParsingFailed -} - -/** - * Implement Push Notifications - */ -@objc(CAPPushNotificationsPlugin) -public class CAPPushNotificationsPlugin: CAPPlugin { - // Local list of notification id -> JSObject for storing options - // between notification requets - var notificationRequestLookup = [String: JSObject]() - - override public func load() { - NotificationCenter.default.addObserver(self, - selector: #selector(self.didRegisterForRemoteNotificationsWithDeviceToken(notification:)), - name: Notification.Name(CAPNotifications.DidRegisterForRemoteNotificationsWithDeviceToken.name()), - object: nil) - NotificationCenter.default.addObserver(self, - selector: #selector(self.didFailToRegisterForRemoteNotificationsWithError(notification:)), - name: Notification.Name(CAPNotifications.DidFailToRegisterForRemoteNotificationsWithError.name()), - object: nil) - } - - /** - * Register for push notifications - */ - @objc func register(_ call: CAPPluginCall) { - DispatchQueue.main.async { - UIApplication.shared.registerForRemoteNotifications() - } - call.success() - } - - /** - * Request notification permission - */ - @objc func requestPermission(_ call: CAPPluginCall) { - self.bridge?.notificationDelegationHandler.requestPermissions { granted, error in - guard error == nil else { - call.error(error!.localizedDescription) - return - } - call.success(["granted": granted]) - } - } - - /** - * Get notifications in Notification Center - */ - @objc func getDeliveredNotifications(_ call: CAPPluginCall) { - UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { [weak self] (notifications) in - let ret = notifications.compactMap({ (notification) -> [String: Any]? in - return self?.bridge?.notificationDelegationHandler.makePushNotificationRequestJSObject(notification.request) - }) - call.success([ - "notifications": ret - ]) - }) - } - - /** - * Remove specified notifications from Notification Center - */ - @objc func removeDeliveredNotifications(_ call: CAPPluginCall) { - guard let notifications = call.getArray("notifications", JSObject.self) else { - call.error("Must supply notifications to remove") - return - } - - let ids = notifications.map { $0["id"] as? String ?? "" } - - UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: ids) - call.success() - } - - /** - * Remove all notifications from Notification Center - */ - @objc func removeAllDeliveredNotifications(_ call: CAPPluginCall) { - UNUserNotificationCenter.current().removeAllDeliveredNotifications() - DispatchQueue.main.async(execute: { - UIApplication.shared.applicationIconBadgeNumber = 0 - }) - call.success() - } - - @objc func createChannel(_ call: CAPPluginCall) { - call.unimplemented() - } - - @objc func deleteChannel(_ call: CAPPluginCall) { - call.unimplemented() - } - - @objc func listChannels(_ call: CAPPluginCall) { - call.unimplemented() - } - - @objc public func didRegisterForRemoteNotificationsWithDeviceToken(notification: NSNotification) { - if let deviceToken = notification.object as? Data { - let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)}) - notifyListeners("registration", data: [ - "value": deviceTokenString - ]) - } else if let stringToken = notification.object as? String { - notifyListeners("registration", data: [ - "value": stringToken - ]) - } else { - notifyListeners("registrationError", data: [ - "error": PushNotificationError.tokenParsingFailed.localizedDescription - ]) - } - } - - @objc public func didFailToRegisterForRemoteNotificationsWithError(notification: NSNotification) { - guard let error = notification.object as? Error else { - return - } - notifyListeners("registrationError", data: [ - "error": error.localizedDescription - ]) - } - -}