diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java index 1ce44bdc7..45d2f66c0 100644 --- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java +++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java @@ -30,6 +30,7 @@ import android.util.Log; import androidx.annotation.Keep; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.core.app.AlarmManagerCompat; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat.Action.Builder; @@ -38,6 +39,7 @@ import androidx.core.app.RemoteInput; import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.IconCompat; + import com.dexterous.flutterlocalnotifications.isolate.IsolatePreferences; import com.dexterous.flutterlocalnotifications.models.BitmapSource; import com.dexterous.flutterlocalnotifications.models.DateTimeComponents; @@ -64,17 +66,9 @@ import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import com.jakewharton.threetenabp.AndroidThreeTen; -import io.flutter.FlutterInjector; -import io.flutter.embedding.engine.loader.FlutterLoader; -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.embedding.engine.plugins.activity.ActivityAware; -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.common.PluginRegistry; + +import org.w3c.dom.Node; + import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Type; @@ -86,10 +80,21 @@ import java.util.Arrays; import java.util.Calendar; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; +import io.flutter.FlutterInjector; +import io.flutter.embedding.engine.loader.FlutterLoader; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugin.common.PluginRegistry; + /** FlutterLocalNotificationsPlugin */ @Keep public class FlutterLocalNotificationsPlugin @@ -101,7 +106,9 @@ public class FlutterLocalNotificationsPlugin private static final String DRAWABLE = "drawable"; private static final String DEFAULT_ICON = "defaultIcon"; private static final String SELECT_NOTIFICATION = "SELECT_NOTIFICATION"; - private static final String SCHEDULED_NOTIFICATIONS = "scheduled_notifications"; + private static final String SCHEDULED_NOTIFICATIONS_FILE = "scheduled_notifications_file"; + private static final String SCHEDULED_NOTIFICATIONS_STRING = "scheduled_notifications"; + private static final String SCHEDULED_NOTIFICATIONS_ID = "snid"; private static final String INITIALIZE_METHOD = "initialize"; private static final String GET_CALLBACK_HANDLE_METHOD = "getCallbackHandle"; private static final String ARE_NOTIFICATIONS_ENABLED_METHOD = "areNotificationsEnabled"; @@ -119,8 +126,10 @@ public class FlutterLocalNotificationsPlugin private static final String SHOW_METHOD = "show"; private static final String CANCEL_METHOD = "cancel"; private static final String CANCEL_ALL_METHOD = "cancelAll"; + private static final String CANCEL_ALL_PENDING_METHOD = "cancelAllPending"; private static final String SCHEDULE_METHOD = "schedule"; private static final String ZONED_SCHEDULE_METHOD = "zonedSchedule"; + private static final String ZONED_SCHEDULE_BATCH_METHOD = "zonedScheduleBatch"; private static final String PERIODICALLY_SHOW_METHOD = "periodicallyShow"; private static final String SHOW_DAILY_AT_TIME_METHOD = "showDailyAtTime"; private static final String SHOW_WEEKLY_AT_DAY_AND_TIME_METHOD = "showWeeklyAtDayAndTime"; @@ -151,6 +160,7 @@ public class FlutterLocalNotificationsPlugin + " your Android head project."; private static final String CANCEL_ID = "id"; private static final String CANCEL_TAG = "tag"; + private static final String REPLACE = "replace"; static String NOTIFICATION_DETAILS = "notificationDetails"; static Gson gson; private MethodChannel channel; @@ -407,38 +417,58 @@ static Gson buildGson() { } private static ArrayList loadScheduledNotifications(Context context) { + migrateScheduledNotificationsIfNeeded(context); ArrayList scheduledNotifications = new ArrayList<>(); SharedPreferences sharedPreferences = - context.getSharedPreferences(SCHEDULED_NOTIFICATIONS, Context.MODE_PRIVATE); - String json = sharedPreferences.getString(SCHEDULED_NOTIFICATIONS, null); - if (json != null) { - Gson gson = buildGson(); - Type type = new TypeToken>() {}.getType(); - scheduledNotifications = gson.fromJson(json, type); + context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_FILE, Context.MODE_PRIVATE); + Map jsonNotifications = sharedPreferences.getAll(); + Log.v("FLN", "Got all " + jsonNotifications.size() + " notification JSONs"); + Gson gson = buildGson(); + Type type = new TypeToken() {}.getType(); + for (Object value : jsonNotifications.values()) { + if (value instanceof String) { + String json = (String) value; + scheduledNotifications.add(gson.fromJson(json, type)); + } else { + Log.v("FLN", "Found non-string value: " + value); + } } return scheduledNotifications; } - private static void saveScheduledNotifications( - Context context, ArrayList scheduledNotifications) { - Gson gson = buildGson(); - String json = gson.toJson(scheduledNotifications); + private static void migrateScheduledNotificationsIfNeeded(Context context) { SharedPreferences sharedPreferences = - context.getSharedPreferences(SCHEDULED_NOTIFICATIONS, Context.MODE_PRIVATE); + context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_STRING, Context.MODE_PRIVATE); + String json = sharedPreferences.getString(SCHEDULED_NOTIFICATIONS_STRING, null); + if (json == null) { + Log.i("FLN", "No old notifications to migrate"); + return; + } + Gson gson = buildGson(); + Type type = new TypeToken>(){}.getType(); + ArrayList oldNotifications = gson.fromJson(json, type); + Log.i("FLN", "Migrating " + oldNotifications.size() + " notifications"); + // Deleting existing new notifications prevents duplicates + context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_FILE, Context.MODE_PRIVATE) + .edit() + .clear() + .apply(); + Log.i("FLN", "Deleted any currently existing new notifications"); + for (NotificationDetails notification : oldNotifications) { + saveScheduledNotification(context, notification); + } SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString(SCHEDULED_NOTIFICATIONS, json).apply(); + editor.remove(SCHEDULED_NOTIFICATIONS_STRING); + editor.apply(); + Log.i("FLN", "Migrated " + oldNotifications.size() + " notifications"); } static void removeNotificationFromCache(Context context, Integer notificationId) { - ArrayList scheduledNotifications = loadScheduledNotifications(context); - for (Iterator it = scheduledNotifications.iterator(); it.hasNext(); ) { - NotificationDetails notificationDetails = it.next(); - if (notificationDetails.id.equals(notificationId)) { - it.remove(); - break; - } - } - saveScheduledNotifications(context, scheduledNotifications); + SharedPreferences sharedPreferences = + context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_FILE, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.remove(SCHEDULED_NOTIFICATIONS_ID + notificationId); + editor.apply(); } @SuppressWarnings("deprecation") @@ -457,51 +487,17 @@ private static void scheduleNotification( Context context, final NotificationDetails notificationDetails, Boolean updateScheduledNotificationsCache) { - Gson gson = buildGson(); - String notificationDetailsJson = gson.toJson(notificationDetails); - Intent notificationIntent = new Intent(context, ScheduledNotificationReceiver.class); - notificationIntent.putExtra(NOTIFICATION_DETAILS, notificationDetailsJson); - PendingIntent pendingIntent = - getBroadcastPendingIntent(context, notificationDetails.id, notificationIntent); - - AlarmManager alarmManager = getAlarmManager(context); - if (BooleanUtils.getValue(notificationDetails.allowWhileIdle)) { - // Workaround for Android 12 issue regarding extra com.android.systemui alarms that - // are not cancelled by AlarmManager. - if (VERSION.SDK_INT >= VERSION_CODES.S) { - AlarmManagerCompat.setExactAndAllowWhileIdle( - alarmManager, - AlarmManager.RTC_WAKEUP, - notificationDetails.millisecondsSinceEpoch, - pendingIntent); - } else { - AlarmManagerCompat.setAlarmClock( - alarmManager, notificationDetails.millisecondsSinceEpoch, pendingIntent, pendingIntent); - } - } else { - AlarmManagerCompat.setExact( - alarmManager, - AlarmManager.RTC_WAKEUP, - notificationDetails.millisecondsSinceEpoch, - pendingIntent); - } - - if (updateScheduledNotificationsCache) { - saveScheduledNotification(context, notificationDetails); - } + scheduleNotificationAtTime( + context, + notificationDetails, + notificationDetails.millisecondsSinceEpoch, + updateScheduledNotificationsCache); } private static void zonedScheduleNotification( Context context, final NotificationDetails notificationDetails, Boolean updateScheduledNotificationsCache) { - Gson gson = buildGson(); - String notificationDetailsJson = gson.toJson(notificationDetails); - Intent notificationIntent = new Intent(context, ScheduledNotificationReceiver.class); - notificationIntent.putExtra(NOTIFICATION_DETAILS, notificationDetailsJson); - PendingIntent pendingIntent = - getBroadcastPendingIntent(context, notificationDetails.id, notificationIntent); - AlarmManager alarmManager = getAlarmManager(context); long epochMilli = VERSION.SDK_INT >= VERSION_CODES.O ? ZonedDateTime.of( @@ -514,6 +510,23 @@ private static void zonedScheduleNotification( org.threeten.bp.ZoneId.of(notificationDetails.timeZoneName)) .toInstant() .toEpochMilli(); + scheduleNotificationAtTime( + context, notificationDetails, epochMilli, updateScheduledNotificationsCache); + } + + private static void scheduleNotificationAtTime( + Context context, + NotificationDetails notificationDetails, + long epochMilli, + Boolean updateScheduledNotificationsCache) { + Gson gson = buildGson(); + String notificationDetailsJson = gson.toJson(notificationDetails); + Intent notificationIntent = new Intent(context, ScheduledNotificationReceiver.class); + notificationIntent.putExtra(NOTIFICATION_DETAILS, notificationDetailsJson); + PendingIntent pendingIntent = + getBroadcastPendingIntent(context, notificationDetails.id, notificationIntent); + + AlarmManager alarmManager = getAlarmManager(context); if (BooleanUtils.getValue(notificationDetails.allowWhileIdle)) { // Workaround for Android 12 issue regarding extra com.android.systemui alarms that // are not cancelled by AlarmManager. @@ -648,16 +661,13 @@ private static long calculateRepeatIntervalMilliseconds(NotificationDetails noti private static void saveScheduledNotification( Context context, NotificationDetails notificationDetails) { - ArrayList scheduledNotifications = loadScheduledNotifications(context); - ArrayList scheduledNotificationsToSave = new ArrayList<>(); - for (NotificationDetails scheduledNotification : scheduledNotifications) { - if (scheduledNotification.id.equals(notificationDetails.id)) { - continue; - } - scheduledNotificationsToSave.add(scheduledNotification); - } - scheduledNotificationsToSave.add(notificationDetails); - saveScheduledNotifications(context, scheduledNotificationsToSave); + SharedPreferences sharedPreferences = + context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_FILE, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + Gson gson = buildGson(); + String jsonNotificationToSave = gson.toJson(notificationDetails); + editor.putString(SCHEDULED_NOTIFICATIONS_ID + notificationDetails.id, jsonNotificationToSave); + editor.apply(); } private static int getDrawableResourceId(Context context, String name) { @@ -1342,6 +1352,11 @@ public void onMethodCall(MethodCall call, @NonNull Result result) { zonedSchedule(call, result); break; } + case ZONED_SCHEDULE_BATCH_METHOD: + { + zonedScheduleBatch(call, result); + break; + } case PERIODICALLY_SHOW_METHOD: case SHOW_DAILY_AT_TIME_METHOD: case SHOW_WEEKLY_AT_DAY_AND_TIME_METHOD: @@ -1353,7 +1368,10 @@ public void onMethodCall(MethodCall call, @NonNull Result result) { cancel(call, result); break; case CANCEL_ALL_METHOD: - cancelAllNotifications(result); + cancelAllNotifications(result, applicationContext); + break; + case CANCEL_ALL_PENDING_METHOD: + cancelAllPending(result, applicationContext); break; case PENDING_NOTIFICATION_REQUESTS_METHOD: pendingNotificationRequests(result); @@ -1468,6 +1486,11 @@ private void schedule(MethodCall call, Result result) { private void zonedSchedule(MethodCall call, Result result) { Map arguments = call.arguments(); NotificationDetails notificationDetails = extractNotificationDetails(result, arguments); + zonedScheduleDetails(result, notificationDetails, applicationContext); + } + + private static void zonedScheduleDetails( + Result result, NotificationDetails notificationDetails, Context applicationContext) { if (notificationDetails != null) { if (notificationDetails.matchDateTimeComponents != null) { notificationDetails.scheduledDateTime = @@ -1478,6 +1501,28 @@ private void zonedSchedule(MethodCall call, Result result) { } } + private void zonedScheduleBatch(MethodCall call, Result result) { + ArrayList> arguments = call.arguments(); + ArrayList requests = new ArrayList<>(); + + for (Map arg : arguments) { + NotificationDetails details = extractNotificationDetails(result, arg); + if (details == null) { + return; + } + requests.add(details); + } + + for (NotificationDetails details : requests) { + if (details.matchDateTimeComponents != null) { + details.scheduledDateTime = + getNextFireDateMatchingDateTimeComponents(details); + } + zonedScheduleNotification(applicationContext, details, true); + } + result.success(null); + } + private void show(MethodCall call, Result result) { Map arguments = call.arguments(); NotificationDetails notificationDetails = extractNotificationDetails(result, arguments); @@ -1639,6 +1684,12 @@ private boolean hasInvalidIcon(Result result, String icon) { && !isValidDrawableResource(applicationContext, icon, result, INVALID_ICON_ERROR_CODE); } + /** + * Cancels a single notification by ID + * + * @param id + * @param tag + */ private void cancelNotification(Integer id, String tag) { Intent intent = new Intent(applicationContext, ScheduledNotificationReceiver.class); PendingIntent pendingIntent = getBroadcastPendingIntent(applicationContext, id, intent); @@ -1653,26 +1704,42 @@ private void cancelNotification(Integer id, String tag) { removeNotificationFromCache(applicationContext, id); } - private void cancelAllNotifications(Result result) { - NotificationManagerCompat notificationManager = getNotificationManager(applicationContext); - notificationManager.cancelAll(); - ArrayList scheduledNotifications = - loadScheduledNotifications(applicationContext); - if (scheduledNotifications == null || scheduledNotifications.isEmpty()) { - result.success(null); + /** Cancels only all pending notifications, leaving active ones. */ + private static void cancelAllPending(Result result, Context context) { + cancelAllPendingNotifications(context); + result.success(null); + } + + /** Cancels all pending notifications without parsing any JSON */ + private static void cancelAllPendingNotifications(Context context) { + SharedPreferences sharedPreferences = + context.getSharedPreferences(SCHEDULED_NOTIFICATIONS_FILE, Context.MODE_PRIVATE); + Map jsonNotifications = (Map) sharedPreferences.getAll(); + + if (jsonNotifications.isEmpty()) { return; } - Intent intent = new Intent(applicationContext, ScheduledNotificationReceiver.class); - for (NotificationDetails scheduledNotification : scheduledNotifications) { - PendingIntent pendingIntent = - getBroadcastPendingIntent(applicationContext, scheduledNotification.id, intent); - AlarmManager alarmManager = getAlarmManager(applicationContext); + Intent intent = new Intent(context, ScheduledNotificationReceiver.class); + AlarmManager alarmManager = getAlarmManager(context); + for (String id : jsonNotifications.keySet()) { + // TODO filter out non-matching IDs + int intId = Integer.parseInt(id.substring(SCHEDULED_NOTIFICATIONS_ID.length())); + PendingIntent pendingIntent = getBroadcastPendingIntent(context, intId, intent); alarmManager.cancel(pendingIntent); } - saveScheduledNotifications(applicationContext, new ArrayList<>()); - result.success(null); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.clear(); + editor.apply(); + } + + /** Cancels all notifications, both scheduled and active */ + private static void cancelAllNotifications(Result result, Context context) { + NotificationManagerCompat notificationManager = getNotificationManager(context); + notificationManager.cancelAll(); + + cancelAllPending(result, context); } @Override diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/ScheduledNotificationReceiver.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/ScheduledNotificationReceiver.java index 7e4b55e30..a52b3005d 100644 --- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/ScheduledNotificationReceiver.java +++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/ScheduledNotificationReceiver.java @@ -16,7 +16,6 @@ import java.lang.reflect.Type; - /** Created by michaelbui on 24/3/18. */ @Keep public class ScheduledNotificationReceiver extends BroadcastReceiver { diff --git a/flutter_local_notifications/example/android/app/build.gradle b/flutter_local_notifications/example/android/app/build.gradle index b77e3da89..5d859e141 100644 --- a/flutter_local_notifications/example/android/app/build.gradle +++ b/flutter_local_notifications/example/android/app/build.gradle @@ -54,11 +54,11 @@ android { signingConfig signingConfigs.debug } } - - // Temporary workaround as per https://issuetracker.google.com/issues/158060799 - lintOptions { + lint { checkReleaseBuilds false } + + // Temporary workaround as per https://issuetracker.google.com/issues/158060799 } flutter { diff --git a/flutter_local_notifications/example/android/build.gradle b/flutter_local_notifications/example/android/build.gradle index df7a3ddd8..7f0bb8f1c 100644 --- a/flutter_local_notifications/example/android/build.gradle +++ b/flutter_local_notifications/example/android/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' + classpath 'com.android.tools.build:gradle:7.1.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/flutter_local_notifications/example/android/gradle/wrapper/gradle-wrapper.properties b/flutter_local_notifications/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58afd..595fb867a 100644 --- a/flutter_local_notifications/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/flutter_local_notifications/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip diff --git a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m index 201934436..e33f21466 100644 --- a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m +++ b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m @@ -12,7 +12,6 @@ @implementation FlutterLocalNotificationsPlugin { bool _launchingAppFromNotification; NSObject *_registrar; NSString *_launchPayload; - UILocalNotification *_launchNotification; FlutterEngineManager *_flutterEngineManager; } @@ -22,13 +21,11 @@ @implementation FlutterLocalNotificationsPlugin { NSString *const INITIALIZE_METHOD = @"initialize"; NSString *const GET_CALLBACK_METHOD = @"getCallbackHandle"; NSString *const SHOW_METHOD = @"show"; -NSString *const SCHEDULE_METHOD = @"schedule"; NSString *const ZONED_SCHEDULE_METHOD = @"zonedSchedule"; NSString *const PERIODICALLY_SHOW_METHOD = @"periodicallyShow"; -NSString *const SHOW_DAILY_AT_TIME_METHOD = @"showDailyAtTime"; -NSString *const SHOW_WEEKLY_AT_DAY_AND_TIME_METHOD = @"showWeeklyAtDayAndTime"; NSString *const CANCEL_METHOD = @"cancel"; NSString *const CANCEL_ALL_METHOD = @"cancelAll"; +NSString *const CANCEL_ALL_PENDING_METHOD = @"cancelAllPending"; NSString *const PENDING_NOTIFICATIONS_REQUESTS_METHOD = @"pendingNotificationRequests"; NSString *const GET_ACTIVE_NOTIFICATIONS_METHOD = @"getActiveNotifications"; @@ -81,8 +78,6 @@ @implementation FlutterLocalNotificationsPlugin { NSString *const SCHEDULED_DATE_TIME = @"scheduledDateTime"; NSString *const TIME_ZONE_NAME = @"timeZoneName"; NSString *const MATCH_DATE_TIME_COMPONENTS = @"matchDateTimeComponents"; -NSString *const UILOCALNOTIFICATION_DATE_INTERPRETATION = - @"uiLocalNotificationDateInterpretation"; NSString *const NOTIFICATION_ID = @"NotificationId"; NSString *const PAYLOAD = @"payload"; @@ -162,32 +157,22 @@ - (void)handleMethodCall:(FlutterMethodCall *)call [self show:call.arguments result:result]; } else if ([ZONED_SCHEDULE_METHOD isEqualToString:call.method]) { [self zonedSchedule:call.arguments result:result]; - } else if ([SCHEDULE_METHOD isEqualToString:call.method]) { - [self schedule:call.arguments result:result]; } else if ([PERIODICALLY_SHOW_METHOD isEqualToString:call.method]) { [self periodicallyShow:call.arguments result:result]; - } else if ([SHOW_DAILY_AT_TIME_METHOD isEqualToString:call.method]) { - [self showDailyAtTime:call.arguments result:result]; - } else if ([SHOW_WEEKLY_AT_DAY_AND_TIME_METHOD isEqualToString:call.method]) { - [self showWeeklyAtDayAndTime:call.arguments result:result]; } else if ([REQUEST_PERMISSIONS_METHOD isEqualToString:call.method]) { [self requestPermissions:call.arguments result:result]; } else if ([CANCEL_METHOD isEqualToString:call.method]) { [self cancel:((NSNumber *)call.arguments) result:result]; } else if ([CANCEL_ALL_METHOD isEqualToString:call.method]) { [self cancelAll:result]; + } else if ([CANCEL_ALL_PENDING_METHOD isEqualToString:call.method]) { + [self cancelAllPending:result]; } else if ([GET_NOTIFICATION_APP_LAUNCH_DETAILS_METHOD isEqualToString:call.method]) { - NSString *payload; - if (_launchNotification != nil) { - payload = _launchNotification.userInfo[PAYLOAD]; - } else { - payload = _launchPayload; - } NSDictionary *notificationAppLaunchDetails = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:_launchingAppFromNotification], - NOTIFICATION_LAUNCHED_APP, payload, PAYLOAD, nil]; + NOTIFICATION_LAUNCHED_APP, _launchPayload, PAYLOAD, nil]; result(notificationAppLaunchDetails); } else if ([PENDING_NOTIFICATIONS_REQUESTS_METHOD isEqualToString:call.method]) { @@ -460,7 +445,6 @@ - (void)requestPermissionsImpl:(bool)soundPermission result(@NO); return; } - if (@available(iOS 10.0, *)) { UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; @@ -484,79 +468,8 @@ - (void)requestPermissionsImpl:(bool)soundPermission NSError *_Nullable error) { result(@(granted)); }]; - } else { - UIUserNotificationType notificationTypes = 0; - if (soundPermission) { - notificationTypes |= UIUserNotificationTypeSound; - } - if (alertPermission) { - notificationTypes |= UIUserNotificationTypeAlert; - } - if (badgePermission) { - notificationTypes |= UIUserNotificationTypeBadge; - } - UIUserNotificationSettings *settings = - [UIUserNotificationSettings settingsForTypes:notificationTypes - categories:nil]; - [[UIApplication sharedApplication] - registerUserNotificationSettings:settings]; - result(@YES); - } } -- (UILocalNotification *)buildStandardUILocalNotification: - (NSDictionary *)arguments { - UILocalNotification *notification = [[UILocalNotification alloc] init]; - if ([self containsKey:BODY forDictionary:arguments]) { - notification.alertBody = arguments[BODY]; - } - - NSString *title; - if ([self containsKey:TITLE forDictionary:arguments]) { - title = arguments[TITLE]; - if (@available(iOS 8.2, *)) { - notification.alertTitle = title; - } - } - - bool presentAlert = _displayAlert; - bool presentSound = _playSound; - bool presentBadge = _updateBadge; - if (arguments[PLATFORM_SPECIFICS] != [NSNull null]) { - NSDictionary *platformSpecifics = arguments[PLATFORM_SPECIFICS]; - - if ([self containsKey:PRESENT_ALERT forDictionary:platformSpecifics]) { - presentAlert = [[platformSpecifics objectForKey:PRESENT_ALERT] boolValue]; - } - if ([self containsKey:PRESENT_SOUND forDictionary:platformSpecifics]) { - presentSound = [[platformSpecifics objectForKey:PRESENT_SOUND] boolValue]; - } - if ([self containsKey:PRESENT_BADGE forDictionary:platformSpecifics]) { - presentBadge = [[platformSpecifics objectForKey:PRESENT_BADGE] boolValue]; - } - - if ([self containsKey:BADGE_NUMBER forDictionary:platformSpecifics]) { - notification.applicationIconBadgeNumber = - [platformSpecifics[BADGE_NUMBER] integerValue]; - } - - if ([self containsKey:SOUND forDictionary:platformSpecifics]) { - notification.soundName = [platformSpecifics[SOUND] stringValue]; - } - } - - if (presentSound && notification.soundName == nil) { - notification.soundName = UILocalNotificationDefaultSoundName; - } - - notification.userInfo = [self buildUserDict:arguments[ID] - title:title - presentAlert:presentAlert - presentSound:presentSound - presentBadge:presentBadge - payload:arguments[PAYLOAD]]; - return notification; -} - (NSString *)getIdentifier:(id)arguments { return [arguments[ID] stringValue]; @@ -564,215 +477,66 @@ - (NSString *)getIdentifier:(id)arguments { - (void)show:(NSDictionary *_Nonnull)arguments result:(FlutterResult _Nonnull)result { - if (@available(iOS 10.0, *)) { UNMutableNotificationContent *content = [self buildStandardNotificationContent:arguments result:result]; - [self addNotificationRequest:[self getIdentifier:arguments] - content:content - result:result - trigger:nil]; - } else { - UILocalNotification *notification = - [self buildStandardUILocalNotification:arguments]; - [[UIApplication sharedApplication] - presentLocalNotificationNow:notification]; - result(nil); - } + UNNotificationRequest *notificationRequest = + [UNNotificationRequest requestWithIdentifier:[self getIdentifier:arguments] + content:content + trigger:nil]; + UNUserNotificationCenter *center = + [UNUserNotificationCenter currentNotificationCenter]; + [center addNotificationRequest:notificationRequest + withCompletionHandler:^(NSError *_Nullable error) { + if (error == nil) { + result(nil); + return; + } + result(getFlutterError(error)); + }]; } - (void)zonedSchedule:(NSDictionary *_Nonnull)arguments result:(FlutterResult _Nonnull)result { - if (@available(iOS 10.0, *)) { UNMutableNotificationContent *content = [self buildStandardNotificationContent:arguments result:result]; UNCalendarNotificationTrigger *trigger = [self buildUserNotificationCalendarTrigger:arguments]; - [self addNotificationRequest:[self getIdentifier:arguments] - content:content - result:result - trigger:trigger]; - - } else { - UILocalNotification *notification = - [self buildStandardUILocalNotification:arguments]; - NSString *scheduledDateTime = arguments[SCHEDULED_DATE_TIME]; - NSString *timeZoneName = arguments[TIME_ZONE_NAME]; - NSNumber *matchDateComponents = arguments[MATCH_DATE_TIME_COMPONENTS]; - NSNumber *uiLocalNotificationDateInterpretation = - arguments[UILOCALNOTIFICATION_DATE_INTERPRETATION]; - NSTimeZone *timezone = [NSTimeZone timeZoneWithName:timeZoneName]; - NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; - [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss"]; - [dateFormatter setTimeZone:timezone]; - NSDate *date = [dateFormatter dateFromString:scheduledDateTime]; - notification.fireDate = date; - if (uiLocalNotificationDateInterpretation != nil) { - if ([uiLocalNotificationDateInterpretation integerValue] == - AbsoluteGMTTime) { - notification.timeZone = nil; - } else if ([uiLocalNotificationDateInterpretation integerValue] == - WallClockTime) { - notification.timeZone = timezone; - } - } - if (matchDateComponents != nil) { - if ([matchDateComponents integerValue] == Time) { - notification.repeatInterval = NSCalendarUnitDay; - } else if ([matchDateComponents integerValue] == DayOfWeekAndTime) { - notification.repeatInterval = NSCalendarUnitWeekOfYear; - } else if ([matchDateComponents integerValue] == DayOfMonthAndTime) { - notification.repeatInterval = NSCalendarUnitMonth; - } else if ([matchDateComponents integerValue] == DateAndTime) { - notification.repeatInterval = NSCalendarUnitYear; - } - } - [[UIApplication sharedApplication] scheduleLocalNotification:notification]; - result(nil); - } -} - -- (void)schedule:(NSDictionary *_Nonnull)arguments - result:(FlutterResult _Nonnull)result { - NSNumber *secondsSinceEpoch = - @([arguments[MILLISECONDS_SINCE_EPOCH] longLongValue] / 1000); - if (@available(iOS 10.0, *)) { - UNMutableNotificationContent *content = - [self buildStandardNotificationContent:arguments result:result]; - NSDate *date = [NSDate - dateWithTimeIntervalSince1970:[secondsSinceEpoch longLongValue]]; - NSCalendar *calendar = [NSCalendar currentCalendar]; - NSDateComponents *dateComponents = - [calendar components:(NSCalendarUnitYear | NSCalendarUnitMonth | - NSCalendarUnitDay | NSCalendarUnitHour | - NSCalendarUnitMinute | NSCalendarUnitSecond) - fromDate:date]; - UNCalendarNotificationTrigger *trigger = [UNCalendarNotificationTrigger - triggerWithDateMatchingComponents:dateComponents - repeats:false]; - [self addNotificationRequest:[self getIdentifier:arguments] - content:content - result:result - trigger:trigger]; - } else { - UILocalNotification *notification = - [self buildStandardUILocalNotification:arguments]; - notification.fireDate = [NSDate - dateWithTimeIntervalSince1970:[secondsSinceEpoch longLongValue]]; - [[UIApplication sharedApplication] scheduleLocalNotification:notification]; - result(nil); - } + UNNotificationRequest *notificationRequest = + [UNNotificationRequest requestWithIdentifier:[self getIdentifier:arguments] + content:content + trigger:trigger]; + UNUserNotificationCenter *center = + [UNUserNotificationCenter currentNotificationCenter]; + [center addNotificationRequest:notificationRequest + withCompletionHandler:^(NSError *_Nullable error) { + if (error == nil) { + result(nil); + return; + } + result(getFlutterError(error)); + }]; } - (void)periodicallyShow:(NSDictionary *_Nonnull)arguments result:(FlutterResult _Nonnull)result { - if (@available(iOS 10.0, *)) { UNMutableNotificationContent *content = [self buildStandardNotificationContent:arguments result:result]; UNTimeIntervalNotificationTrigger *trigger = [self buildUserNotificationTimeIntervalTrigger:arguments]; - [self addNotificationRequest:[self getIdentifier:arguments] - content:content - result:result - trigger:trigger]; - } else { - UILocalNotification *notification = - [self buildStandardUILocalNotification:arguments]; - NSTimeInterval timeInterval = 0; - switch ([arguments[REPEAT_INTERVAL] integerValue]) { - case EveryMinute: - timeInterval = 60; - notification.repeatInterval = NSCalendarUnitMinute; - break; - case Hourly: - timeInterval = 60 * 60; - notification.repeatInterval = NSCalendarUnitHour; - break; - case Daily: - timeInterval = 60 * 60 * 24; - notification.repeatInterval = NSCalendarUnitDay; - break; - case Weekly: - timeInterval = 60 * 60 * 24 * 7; - notification.repeatInterval = NSCalendarUnitWeekOfYear; - break; - } - notification.fireDate = [NSDate dateWithTimeIntervalSinceNow:timeInterval]; - [[UIApplication sharedApplication] scheduleLocalNotification:notification]; - result(nil); - } -} - -- (void)showDailyAtTime:(NSDictionary *_Nonnull)arguments - result:(FlutterResult _Nonnull)result { - NSDictionary *timeArguments = (NSDictionary *)arguments[REPEAT_TIME]; - NSNumber *hourComponent = timeArguments[HOUR]; - NSNumber *minutesComponent = timeArguments[MINUTE]; - NSNumber *secondsComponent = timeArguments[SECOND]; - if (@available(iOS 10.0, *)) { - UNMutableNotificationContent *content = - [self buildStandardNotificationContent:arguments result:result]; - NSDateComponents *dateComponents = [[NSDateComponents alloc] init]; - [dateComponents setHour:[hourComponent integerValue]]; - [dateComponents setMinute:[minutesComponent integerValue]]; - [dateComponents setSecond:[secondsComponent integerValue]]; - UNCalendarNotificationTrigger *trigger = [UNCalendarNotificationTrigger - triggerWithDateMatchingComponents:dateComponents - repeats:YES]; - [self addNotificationRequest:[self getIdentifier:arguments] - content:content - result:result - trigger:trigger]; - } else { - UILocalNotification *notification = - [self buildStandardUILocalNotification:arguments]; - notification.repeatInterval = NSCalendarUnitDay; - NSDateComponents *dateComponents = [[NSDateComponents alloc] init]; - [dateComponents setHour:[hourComponent integerValue]]; - [dateComponents setMinute:[minutesComponent integerValue]]; - [dateComponents setSecond:[secondsComponent integerValue]]; - NSCalendar *calendar = [NSCalendar currentCalendar]; - notification.fireDate = [calendar dateFromComponents:dateComponents]; - [[UIApplication sharedApplication] scheduleLocalNotification:notification]; - result(nil); - } -} - -- (void)showWeeklyAtDayAndTime:(NSDictionary *_Nonnull)arguments - result:(FlutterResult _Nonnull)result { - NSDictionary *timeArguments = (NSDictionary *)arguments[REPEAT_TIME]; - NSNumber *dayOfWeekComponent = arguments[DAY]; - NSNumber *hourComponent = timeArguments[HOUR]; - NSNumber *minutesComponent = timeArguments[MINUTE]; - NSNumber *secondsComponent = timeArguments[SECOND]; - if (@available(iOS 10.0, *)) { - UNMutableNotificationContent *content = - [self buildStandardNotificationContent:arguments result:result]; - NSDateComponents *dateComponents = [[NSDateComponents alloc] init]; - [dateComponents setHour:[hourComponent integerValue]]; - [dateComponents setMinute:[minutesComponent integerValue]]; - [dateComponents setSecond:[secondsComponent integerValue]]; - [dateComponents setWeekday:[dayOfWeekComponent integerValue]]; - UNCalendarNotificationTrigger *trigger = [UNCalendarNotificationTrigger - triggerWithDateMatchingComponents:dateComponents - repeats:YES]; - [self addNotificationRequest:[self getIdentifier:arguments] - content:content - result:result - trigger:trigger]; - } else { - UILocalNotification *notification = - [self buildStandardUILocalNotification:arguments]; - notification.repeatInterval = NSCalendarUnitWeekOfYear; - NSDateComponents *dateComponents = [[NSDateComponents alloc] init]; - [dateComponents setHour:[hourComponent integerValue]]; - [dateComponents setMinute:[minutesComponent integerValue]]; - [dateComponents setSecond:[secondsComponent integerValue]]; - [dateComponents setWeekday:[dayOfWeekComponent integerValue]]; - NSCalendar *calendar = [NSCalendar currentCalendar]; - notification.fireDate = [calendar dateFromComponents:dateComponents]; - [[UIApplication sharedApplication] scheduleLocalNotification:notification]; - result(nil); - } + UNNotificationRequest *notificationRequest = + [UNNotificationRequest requestWithIdentifier:[self getIdentifier:arguments] + content:content + trigger:trigger]; + UNUserNotificationCenter *center = + [UNUserNotificationCenter currentNotificationCenter]; + [center addNotificationRequest:notificationRequest + withCompletionHandler:^(NSError *_Nullable error) { + if (error == nil) { + result(nil); + return; + } + result(getFlutterError(error)); + }]; } - (void)cancel:(NSNumber *)id result:(FlutterResult _Nonnull)result { @@ -800,6 +564,17 @@ - (void)cancel:(NSNumber *)id result:(FlutterResult _Nonnull)result { result(nil); } +- (void)cancelAllPending:(FlutterResult _Nonnull)result { + if (@available(iOS 10.0, *)) { + UNUserNotificationCenter *center = + [UNUserNotificationCenter currentNotificationCenter]; + [center removeAllPendingNotificationRequests]; + } else { + [[UIApplication sharedApplication] cancelAllLocalNotifications]; + } + result(nil); +} + - (void)cancelAll:(FlutterResult _Nonnull)result { if (@available(iOS 10.0, *)) { UNUserNotificationCenter *center = @@ -1013,27 +788,6 @@ - (NSDictionary *)buildUserDict:(NSNumber *)id return userDict; } -- (void)addNotificationRequest:(NSString *)identifier - content:(UNMutableNotificationContent *)content - result:(FlutterResult _Nonnull)result - trigger:(UNNotificationTrigger *)trigger - API_AVAILABLE(ios(10.0)) { - UNNotificationRequest *notificationRequest = - [UNNotificationRequest requestWithIdentifier:identifier - content:content - trigger:trigger]; - UNUserNotificationCenter *center = - [UNUserNotificationCenter currentNotificationCenter]; - [center addNotificationRequest:notificationRequest - withCompletionHandler:^(NSError *_Nullable error) { - if (error == nil) { - result(nil); - return; - } - result(getFlutterError(error)); - }]; -} - - (BOOL)isAFlutterLocalNotification:(NSDictionary *)userInfo { return userInfo != nil && userInfo[NOTIFICATION_ID] && userInfo[PRESENT_ALERT] && userInfo[PRESENT_SOUND] && @@ -1123,45 +877,4 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center } } -#pragma mark - AppDelegate -- (BOOL)application:(UIApplication *)application - didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - if (launchOptions != nil) { - UILocalNotification *launchNotification = - (UILocalNotification *)[launchOptions - objectForKey:UIApplicationLaunchOptionsLocalNotificationKey]; - _launchingAppFromNotification = - launchNotification != nil && - [self isAFlutterLocalNotification:launchNotification.userInfo]; - if (_launchingAppFromNotification) { - _launchNotification = launchNotification; - } - } - - return YES; -} - -- (void)application:(UIApplication *)application - didReceiveLocalNotification:(UILocalNotification *)notification { - if (@available(iOS 10.0, *)) { - return; - } - if (![self isAFlutterLocalNotification:notification.userInfo]) { - return; - } - - NSMutableDictionary *arguments = [[NSMutableDictionary alloc] init]; - arguments[ID] = notification.userInfo[NOTIFICATION_ID]; - if (notification.userInfo[TITLE] != [NSNull null]) { - arguments[TITLE] = notification.userInfo[TITLE]; - } - if (notification.alertBody != nil) { - arguments[BODY] = notification.alertBody; - } - if (notification.userInfo[PAYLOAD] != [NSNull null]) { - arguments[PAYLOAD] = notification.userInfo[PAYLOAD]; - } - [_channel invokeMethod:DID_RECEIVE_LOCAL_NOTIFICATION arguments:arguments]; -} - @end diff --git a/flutter_local_notifications/ios/flutter_local_notifications.podspec b/flutter_local_notifications/ios/flutter_local_notifications.podspec index ddc954e1f..42d80b605 100644 --- a/flutter_local_notifications/ios/flutter_local_notifications.podspec +++ b/flutter_local_notifications/ios/flutter_local_notifications.podspec @@ -16,6 +16,6 @@ Flutter plugin for displaying local notifications. s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' - s.ios.deployment_target = '8.0' + s.ios.deployment_target = '10.0' end diff --git a/flutter_local_notifications/lib/src/flutter_local_notifications_plugin.dart b/flutter_local_notifications/lib/src/flutter_local_notifications_plugin.dart index dd1caf4dd..e6cdaa262 100644 --- a/flutter_local_notifications/lib/src/flutter_local_notifications_plugin.dart +++ b/flutter_local_notifications/lib/src/flutter_local_notifications_plugin.dart @@ -360,6 +360,21 @@ class FlutterLocalNotificationsPlugin { } } + Future zonedScheduleBatch(List requests) async { + if (kIsWeb) { + return; + } + if (defaultTargetPlatform == TargetPlatform.android) { + await resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>()! + .zonedScheduleBatch(requests); + } else if (defaultTargetPlatform == TargetPlatform.iOS) { + await resolvePlatformSpecificImplementation< + IOSFlutterLocalNotificationsPlugin>()! + .zonedScheduleBatch(requests); + } + } + /// Periodically show a notification using the specified interval. /// /// For example, specifying a hourly interval means the first time the @@ -432,13 +447,7 @@ class FlutterLocalNotificationsPlugin { ?.showDailyAtTime( id, title, body, notificationTime, notificationDetails.android, payload: payload); - } else if (defaultTargetPlatform == TargetPlatform.iOS) { - await resolvePlatformSpecificImplementation< - IOSFlutterLocalNotificationsPlugin>() - ?.showDailyAtTime( - id, title, body, notificationTime, notificationDetails.iOS, - payload: payload); - } else if (defaultTargetPlatform == TargetPlatform.macOS) { + } else { throw UnimplementedError(); } } @@ -467,13 +476,7 @@ class FlutterLocalNotificationsPlugin { ?.showWeeklyAtDayAndTime(id, title, body, day, notificationTime, notificationDetails.android, payload: payload); - } else if (defaultTargetPlatform == TargetPlatform.iOS) { - await resolvePlatformSpecificImplementation< - IOSFlutterLocalNotificationsPlugin>() - ?.showWeeklyAtDayAndTime( - id, title, body, day, notificationTime, notificationDetails.iOS, - payload: payload); - } else if (defaultTargetPlatform == TargetPlatform.macOS) { + } else { throw UnimplementedError(); } } diff --git a/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart b/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart index 6a6c54c84..15c753cae 100644 --- a/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart +++ b/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart @@ -39,6 +39,9 @@ class MethodChannelFlutterLocalNotificationsPlugin @override Future cancelAll() => _channel.invokeMethod('cancelAll'); + @override + Future cancelAllPending() => _channel.invokeMethod('cancelAllPending'); + @override Future getNotificationAppLaunchDetails() async { @@ -187,6 +190,38 @@ class AndroidFlutterLocalNotificationsPlugin })); } + /// Schedules a batch of notifications to be shown at the specified date + /// and time relative to a specific time zone. + Future zonedScheduleBatch(List requests) async { + final List> serializedRequests = + >[]; + for (final NotificationRequest request in requests) { + validateId(request.id); + validateDateIsInTheFuture(request.date, request.matchDateTimeComponents); + final Map serializedPlatformSpecifics = + request.details.android?.toMap() ?? {}; + serializedPlatformSpecifics['allowWhileIdle'] = + request.androidAllowWhileIdle; + serializedRequests.add( + { + 'id': request.id, + 'title': request.title, + 'body': request.body, + 'platformSpecifics': serializedPlatformSpecifics, + 'payload': request.payload ?? '' + } + ..addAll(request.date.toMap()) + ..addAll(request.matchDateTimeComponents == null + ? {} + : { + 'matchDateTimeComponents': + request.matchDateTimeComponents!.index + }), + ); + } + await _channel.invokeMethod('zonedScheduleBatch', serializedRequests); + } + /// Shows a notification on a daily interval at the specified time. @Deprecated( 'Deprecated due to problems with time zones. Use zonedSchedule instead.') @@ -605,54 +640,34 @@ class IOSFlutterLocalNotificationsPlugin })); } - /// Shows a notification on a daily interval at the specified time. - @Deprecated( - 'Deprecated due to problems with time zones. Use zonedSchedule instead.') - Future showDailyAtTime( - int id, - String? title, - String? body, - Time notificationTime, - DarwinNotificationDetails? notificationDetails, { - String? payload, - }) async { - validateId(id); - await _channel.invokeMethod('showDailyAtTime', { - 'id': id, - 'title': title, - 'body': body, - 'calledAt': clock.now().millisecondsSinceEpoch, - 'repeatInterval': RepeatInterval.daily.index, - 'repeatTime': notificationTime.toMap(), - 'platformSpecifics': notificationDetails?.toMap(), - 'payload': payload ?? '' - }); - } - - /// Shows a notification on weekly interval at the specified day and time. - @Deprecated( - 'Deprecated due to problems with time zones. Use zonedSchedule instead.') - Future showWeeklyAtDayAndTime( - int id, - String? title, - String? body, - Day day, - Time notificationTime, - DarwinNotificationDetails? notificationDetails, { - String? payload, - }) async { - validateId(id); - await _channel.invokeMethod('showWeeklyAtDayAndTime', { - 'id': id, - 'title': title, - 'body': body, - 'calledAt': clock.now().millisecondsSinceEpoch, - 'repeatInterval': RepeatInterval.weekly.index, - 'repeatTime': notificationTime.toMap(), - 'day': day.value, - 'platformSpecifics': notificationDetails?.toMap(), - 'payload': payload ?? '' - }); + /// Schedules a batch of notifications to be shown at the specified date + /// and time relative to a specific time zone. + Future zonedScheduleBatch(List requests) async { + final List> serializedRequests = + >[]; + for (final NotificationRequest request in requests) { + validateId(request.id); + validateDateIsInTheFuture(request.date, request.matchDateTimeComponents); + final Map serializedPlatformSpecifics = + request.details.iOS?.toMap() ?? {}; + serializedRequests.add( + { + 'id': request.id, + 'title': request.title, + 'body': request.body, + 'platformSpecifics': serializedPlatformSpecifics, + 'payload': request.payload ?? '', + } + ..addAll(request.date.toMap()) + ..addAll(request.matchDateTimeComponents == null + ? {} + : { + 'matchDateTimeComponents': + request.matchDateTimeComponents!.index + }), + ); + } + await _channel.invokeMethod('zonedScheduleBatch', serializedRequests); } @override diff --git a/flutter_local_notifications/lib/src/types.dart b/flutter_local_notifications/lib/src/types.dart index e0748d417..c8798124d 100644 --- a/flutter_local_notifications/lib/src/types.dart +++ b/flutter_local_notifications/lib/src/types.dart @@ -1,3 +1,7 @@ +import 'package:timezone/timezone.dart'; + +import 'notification_details.dart'; + /// The days of the week. class Day { /// Constructs an instance of [Day]. @@ -73,3 +77,25 @@ enum DateTimeComponents { /// The date and time. dateAndTime, } + +class NotificationRequest { + NotificationRequest({ + required this.id, + required this.title, + required this.body, + required this.details, + required this.date, + required this.androidAllowWhileIdle, + this.payload, + this.matchDateTimeComponents, + }); + + final int id; + final String? title; + final String? body; + final String? payload; + final TZDateTime date; + final NotificationDetails details; + final DateTimeComponents? matchDateTimeComponents; + final bool androidAllowWhileIdle; +} diff --git a/flutter_local_notifications_platform_interface/lib/flutter_local_notifications_platform_interface.dart b/flutter_local_notifications_platform_interface/lib/flutter_local_notifications_platform_interface.dart index 80c3d0612..a87cdbfc0 100644 --- a/flutter_local_notifications_platform_interface/lib/flutter_local_notifications_platform_interface.dart +++ b/flutter_local_notifications_platform_interface/lib/flutter_local_notifications_platform_interface.dart @@ -65,6 +65,11 @@ abstract class FlutterLocalNotificationsPlatform extends PlatformInterface { throw UnimplementedError('cancelAll() has not been implemented'); } + /// Cancels/removes all pending notifications. This applies to notifications that have been scheduled but not those that have already been presented. + Future cancelAllPending() async { + throw UnimplementedError('cancelAllPending() has not been implemented'); + } + /// Returns a list of notifications pending to be delivered/shown Future> pendingNotificationRequests() { throw UnimplementedError(