diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml deleted file mode 100644 index 048a74fca..000000000 --- a/.github/workflows/format.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: format - -on: push - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - java_format: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: axel-op/googlejavaformat-action@v3 - with: - args: '--skip-sorting-imports --replace' - - objc_format: - runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - - name: Test - run: | - which clang-format || brew install clang-format - find . -name '*.m' -exec clang-format -i {} \; - find . -path '*/ios/**/*.h' -exec clang-format -i {} \; - find . -path '*/macos/**/*.h' -exec clang-format -i {} \; - git diff --exit-code || (git commit --all -m "Clang Format" && git push) - - swift_format: - runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - - name: Test - run: | - which swiftlint || brew install swiftlint - swiftlint --fix - git diff --exit-code || (git commit --all -m "Swift Format" && git push) diff --git a/.github/workflows/scripts/install-tools.sh b/.github/workflows/scripts/install-tools.sh index 31760cf0d..5c08b7720 100755 --- a/.github/workflows/scripts/install-tools.sh +++ b/.github/workflows/scripts/install-tools.sh @@ -1,2 +1,2 @@ -dart pub global activate melos 2.9.0 +dart pub global activate melos melos bootstrap \ No newline at end of file diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 956d11b1b..a5a64857b 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -2,15 +2,49 @@ name: validate on: pull_request: + paths-ignore: + - '**.md' push: branches: - master + paths-ignore: + - '**.md' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: + java_format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: axel-op/googlejavaformat-action@v3 + with: + args: '--skip-sorting-imports --replace' + + objc_format: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Test + run: | + which clang-format || brew install clang-format + find . -name '*.m' -exec clang-format -i {} \; + find . -path '*/ios/**/*.h' -exec clang-format -i {} \; + find . -path '*/macos/**/*.h' -exec clang-format -i {} \; + git diff --exit-code || (git commit --all -m "Clang Format" && git push) + + swift_format: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Test + run: | + which swiftlint || brew install swiftlint + swiftlint --fix + git diff --exit-code || (git commit --all -m "Swift Format" && git push) + analyze: name: Run Dart Analyzer runs-on: ubuntu-latest @@ -42,29 +76,44 @@ jobs: run: ./.github/workflows/scripts/install-tools.sh - name: Build run: melos run build:example_android - build_example_android_300: - name: Build Android example app (3.0.0) - runs-on: ubuntu-latest + build_example_android_3_13: + name: Build Android example app (3.13) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + channel: stable + flutter-version: 3.13.0 + cache: true + cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' + - name: Install Tools + run: ./.github/workflows/scripts/install-tools.sh + - name: Build + run: melos run build:example_android + build_example_ios_stable: + name: Build iOS example app (stable channel) + runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: - flutter-version: 3.0.0 channel: stable cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools run: ./.github/workflows/scripts/install-tools.sh - name: Build - run: melos run build:example_android - build_example_ios_stable: - name: Build iOS example app (stable channel) + run: melos run build:example_ios + build_example_ios_3_13: + name: Build iOS example app (3.13) runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: channel: stable + flutter-version: 3.13.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -85,6 +134,21 @@ jobs: run: ./.github/workflows/scripts/install-tools.sh - name: Build run: melos run build:example_macos + build_example_macos_3_13: + name: Build macOS example app (3.13) + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + channel: stable + flutter-version: 3.13.0 + cache: true + cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' + - name: Install Tools + run: ./.github/workflows/scripts/install-tools.sh + - name: Build + run: melos run build:example_macos build_example_linux_stable: name: Build Linux example app (stable channel) runs-on: ubuntu-latest @@ -103,15 +167,15 @@ jobs: - run: flutter config --enable-linux-desktop - name: Build run: melos run build:example_linux - build_example_linux_300: - name: Build Linux example app (3.0.0) + build_example_linux_3_13: + name: Build Linux example app (3.13) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: - flutter-version: 3.0.0 channel: stable + flutter-version: 3.13.0 cache: true cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools @@ -152,7 +216,7 @@ jobs: run: melos run test:unit:android integration_tests_android: name: Run integration tests (Android) - runs-on: macos-latest + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v3 @@ -166,6 +230,11 @@ jobs: cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - name: Install Tools run: ./.github/workflows/scripts/install-tools.sh + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm - uses: reactivecircus/android-emulator-runner@v2 with: api-level: 29 @@ -185,7 +254,7 @@ jobs: - uses: futureware-tech/simulator-action@v3 id: simulator-action with: - model: 'iPhone 13' + model: 'iPhone 15' - run: | brew tap wix/brew brew install applesimutils diff --git a/analysis_options.yaml b/analysis_options.yaml index 3605ad5cb..343acd642 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -3,7 +3,6 @@ linter: - always_declare_return_types - always_put_control_body_on_new_line - always_put_required_named_parameters_first - - always_require_non_null_named_parameters - always_specify_types - annotate_overrides - avoid_annotating_with_dynamic @@ -29,8 +28,6 @@ linter: - avoid_relative_lib_imports - avoid_renaming_method_parameters - avoid_return_types_on_setters - - avoid_returning_null - - avoid_returning_null_for_future - avoid_returning_null_for_void - avoid_returning_this - avoid_setters_without_getters @@ -62,7 +59,6 @@ linter: - flutter_style_todos - hash_and_equals - implementation_imports - - invariant_booleans - collection_methods_unrelated_type - join_return_with_assignment - leading_newlines_in_multiline_strings @@ -95,7 +91,6 @@ linter: - prefer_const_literals_to_create_immutables - prefer_constructors_over_static_methods - prefer_contains - - prefer_equal_for_default_values - prefer_expression_function_bodies - prefer_final_fields - prefer_final_in_for_each diff --git a/flutter_local_notifications/CHANGELOG.md b/flutter_local_notifications/CHANGELOG.md index e146be554..02c08011b 100644 --- a/flutter_local_notifications/CHANGELOG.md +++ b/flutter_local_notifications/CHANGELOG.md @@ -1,3 +1,59 @@ +## [vNext] + +* **Breaking change** Bumped minimum Flutter SDK requirement to 3.13 +* [Android] **Breaking change** removed the deprecated `androidAllowWhileIdle` parameter from `zonedSchedule()` and `periodicallyShow()` methods. `androidScheduleMode` is now a required parameter + +## [17.2.1+2] + +* Updated Gradle setup readme section around specifying AGP version to include link to Flutter documentation for apps that are using the declarative Plugin DSL syntax + +## [17.2.1+1] + +* Fixed accidental change done in example app as part of 17.2.0 where it made use of `SCHEDULE_EXACT_ALARM` permission instead of `USE_EXACT_ALARM` + +## [17.2.1] + +* [Android] fixed issue [#2329](https://github.com/MaikuB/flutter_local_notifications/issues/2329) where a compilation issue could occur due to ambiguity between Android APIs being called. Thanks to the PR from [Greg Price](https://github.com/gnprice) + +## [17.2.0] + +* [Android][iOS][macOS] added `periodicallyShowWithDuration()` method that allows for having a notification periodically shown based on a specified duration. The duration will need to be at least a minute. Thanks to the PR from [Mateusz Łuczak](https://github.com/mateuszluczak1996) +* [Android] added the `requestFullScreenIntentPermission()` to the `AndroidFlutterNotificationsPlugin` class. This allows app to request the full-screen intent permission. Updated the documentation around full-screen intent notifications accordingly as well +* Added a comment to the `AndroidManifest.xml` file of the example to state that it requests the `USE_EXACT_ALARM` only for ease of use. Developers will need to check if they should be using the `SCHEDULE_EXACT_ALARM` permission instead + +## [17.1.2] + +* [Android] fixed issue [2318](https://github.com/MaikuB/flutter_local_notifications/issues/2318) where an exception could occur on calling the `getNotificationChannels()` method from the `AndroidFlutterLocalNotificationsPlugin` class. This happened when Android found that the audio attributes associated with the channel was null. The plugin will now coalesce the null value to indicate that the usage of the audio is for notifications as per https://developer.android.com/reference/android/media/AudioAttributes#USAGE_NOTIFICATION. On the Dart side, this would correspond to the [AudioAttributesUsage.notification](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AudioAttributesUsage.html#notification) enum value + +## [17.1.1] + +* [Android] fixes issue [#2299](https://github.com/MaikuB/flutter_local_notifications/issues/2299) where within the range of the max integer value of epoch time passed to a messaging style would result in a casting exception + +## [17.1.0] + +* [Android] `bigText` has added to `ActiveNotification` that allows getting information about the longer text associated with a notification displayed using the big text style. Thanks to the PR from [vulpeep](https://github.com/vulpeep) +* [Android] added `audioAttributesUsage` to `AndroidNotificationChannel`. Thanks to the PR from [Dithesh](https://github.com/ditheshthegreat) +* Fix description of the behaviour iOS pending notifications limit. Thanks to the PR from [Amman Zaman](https://github.com/zamanzamzz) +* Updated link in readme to Gradle desugaring setup. Thanks to the PR from [James Allen](https://github.com/jamesncl) + + +# [17.0.1] + +* [iOS] updated privacy manifest to declare reason the plugin uses the User Defaults API. Thanks to the PR from [Miya49-p0](https://github.com/Miya49-p0) + +# [17.0.0] + +* [Android] **Breaking change** bumped `compileSdk` to 34 and updated readme to mention this +* Updated `compileSdk` and `targetSdkVersion` of example app to 34 +* **Important announcement** given how both quickly both Flutter ecosystem and Android ecosystem evolves, the minimum Flutter SDK version will be bumped to make it easier to maintain the plugin. Note that official plugins already follow a similar approach e.g. have a minimum Flutter SDK version of 3.13. This is being called out as if this affects your applications (e.g. supported OS versions) then you may need to consider maintaining your own fork in the future +* Updated build status badge shown on readme to sync to recent changes on using GitHub Actions +* Fixed code snippet in readme related to handling the `onDidReceiveLocalNotification` callback. Thanks to the PR from [Sanket Patel](https://github.com/s4nk37) + +# [16.3.3] + +* [Android] added missing check on if `SCHEDULE_EXACT_ALARM` permission was granted when using the `alarmClock` as the `AndroidScheduleMode` +* Bumped `device_info_plus` dependency for example app, which means example app requires Flutter SDK version 3.3.0 or higher to run + # [16.3.2] * [Android] fixed how native stack traces were obtained. Relates to issue [2088](https://github.com/MaikuB/flutter_local_notifications/issues/2088). Thanks to the PR from [Jonas Uekötter](https://github.com/ueman) diff --git a/flutter_local_notifications/README.md b/flutter_local_notifications/README.md index 927a05fdd..2619472e8 100644 --- a/flutter_local_notifications/README.md +++ b/flutter_local_notifications/README.md @@ -1,10 +1,13 @@ # flutter_local_notifications [![pub package](https://img.shields.io/pub/v/flutter_local_notifications.svg)](https://pub.dartlang.org/packages/flutter_local_notifications) -[![Build Status](https://api.cirrus-ci.com/github/MaikuB/flutter_local_notifications.svg)](https://cirrus-ci.com/github/MaikuB/flutter_local_notifications/master) +![Build Status](https://github.com/MaikuB/flutter_local_notifications/actions/workflows/validate.yml/badge.svg) A cross platform plugin for displaying local notifications. +>[!IMPORTANT] +> Given how both quickly both Flutter ecosystem and Android ecosystem evolves, the minimum Flutter SDK version will be bumped to make it easier to maintain the plugin. Note that official plugins already follow a similar approach e.g. have a minimum Flutter SDK version of 3.13. This is being called out as if this affects your applications (e.g. supported OS versions) then you may need to consider maintaining your own fork in the future + ## Table of contents - **[📱 Supported platforms](#-supported-platforms)** @@ -52,10 +55,12 @@ A cross platform plugin for displaying local notifications. ## 📱 Supported platforms -* **Android 4.1+**. Uses the [NotificationCompat APIs](https://developer.android.com/reference/androidx/core/app/NotificationCompat) so it can be run older Android devices -* **iOS 8.0+**. On iOS versions older than 10, the plugin will use the UILocalNotification APIs. The [UserNotification APIs](https://developer.apple.com/documentation/usernotifications) (aka the User Notifications Framework) is used on iOS 10 or newer. Notification actions only work on iOS 10 or newer. -* **macOS 10.11+**. On macOS versions older than 10.14, the plugin will use the [NSUserNotification APIs](https://developer.apple.com/documentation/foundation/nsusernotification). The [UserNotification APIs](https://developer.apple.com/documentation/usernotifications) (aka the User Notifications Framework) is used on macOS 10.14 or newer. Notification actions only work on macOS 10.14 or newer -* **Linux**. Uses the [Desktop Notifications Specification](https://specifications.freedesktop.org/notification-spec/). +* **Android+**. Uses the [NotificationCompat APIs](https://developer.android.com/reference/androidx/core/app/NotificationCompat) so it can be run older Android devices +* **iOS**. Uses the [UserNotification APIs](https://developer.apple.com/documentation/usernotifications) (aka the User Notifications Framework) +* **macOS**. On macOS versions older than 10.14, the plugin will use the [NSUserNotification APIs](https://developer.apple.com/documentation/foundation/nsusernotification). The [UserNotification APIs](https://developer.apple.com/documentation/usernotifications) (aka the User Notifications Framework) is used on macOS 10.14 or newer. Notification actions only work on macOS 10.14 or newer +* **Linux**. Uses the [Desktop Notifications Specification](https://specifications.freedesktop.org/notification-spec/) + +Note: the plugin has a requires Flutter SDK 3.13 at a minimum. The list of support platforms for Flutter 3.1.3 itself can be found [here](https://github.com/flutter/website/blob/3d18ab48218101493af84953b71eac0cc6781fdd/src/reference/supported-platforms.md) ## ✨ Features @@ -121,7 +126,7 @@ It has been reported that Samsung's implementation of Android has imposed a maxi ### iOS pending notifications limit -There is a limit imposed by iOS where it will only keep 64 notifications that will fire the soonest. +There is a limit imposed by iOS where it will only keep the 64 notifications that were last set on any iOS versions newer than 9. On iOS versions 9 and older, the 64 notifications that fire soonest are kept. [See here for more details.](http://ileyf.cn.openradar.appspot.com/38065340) ### Scheduled notifications and daylight saving time @@ -177,7 +182,7 @@ Before proceeding, please make sure you are using the latest version of the plug ### Gradle setup -Version 10+ on the plugin now relies on [desugaring](https://developer.android.com/studio/releases/gradle-plugin#j8-library-desugaring) to support scheduled notifications with backwards compatibility on older versions of Android. Developers will need to update their application's Gradle file at `android/app/build.gradle`. Please see the link on desugaring for details but the main parts needed in this Gradle file would be +Version 10+ on the plugin now relies on [desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring) to support scheduled notifications with backwards compatibility on older versions of Android. Developers will need to update their application's Gradle file at `android/app/build.gradle`. Please see the link on desugaring for details but the main parts needed in this Gradle file would be ```gradle android { @@ -199,7 +204,7 @@ dependencies { } ``` -Note that the plugin uses Android Gradle plugin 7.3.1 to leverage this functionality so to errr on the safe side, applications should aim to use the same version at a **minimum**. Using a higher version is also needed as at point, Android Studio bundled a newer version of the Java SDK that will only work with Gradle 7.3 or higher (see [here](https://docs.flutter.dev/release/breaking-changes/android-java-gradle-migration-guide) for more details). For a Flutter project, this is specified in `android/build.gradle` and the main parts would look similar to the following +Note that the plugin uses Android Gradle plugin (AGP) 7.3.1 to leverage this functionality so to errr on the safe side, applications should aim to use the same version at a **minimum**. Using a higher version is also needed as at point, Android Studio bundled a newer version of the Java SDK that will only work with Gradle 7.3 or higher (see [here](https://docs.flutter.dev/release/breaking-changes/android-java-gradle-migration-guide) for more details). For a Flutter app usin the legacy `apply` script syntax, this is specified in `android/build.gradle` and the main parts would look similar to the following ```gradle buildscript { @@ -211,6 +216,8 @@ buildscript { } ``` +If your app is using the new declarative Plugin DSL syntax, please refer to the Flutter documentation [here](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) where they document where the AGP version can be specified + There have been reports that enabling desugaring may result in a Flutter apps crashing on Android 12L and above. This would be an issue with Flutter itself, not the plugin. One possible fix is adding the [WindowManager library](https://developer.android.com/jetpack/androidx/releases/window) as a dependency: ```gradle @@ -223,11 +230,11 @@ dependencies { More information and other proposed solutions can be found in [Flutter issue #110658](https://github.com/flutter/flutter/issues/110658). -The plugin also requires that the `compileSdkVersion` in your application's Gradle file is set to 33: +The plugin also requires that the `compileSdk` in your application's Gradle file is set to 34 at a minimum: ```gradle android { - compileSdkVersion 33 + compileSdk 34 ... } ``` @@ -256,7 +263,7 @@ For apps that need the following functionality please complete the following in ``` -* To use full-screen intent notifications, specify the `` permission between the `` tags. +* To use full-screen intent notifications, specify the `` permission between the `` tags. Developers will also need to follow the instructions documented [here](#full-screen-intent-notifications) * To use notification actions, specify `` between the `` tags so that the plugin can process the actions and trigger the appropriate callback(s) Developers can refer to the example app's `AndroidManifest.xml` to help see what the end result may look like. Do note that the example app covers all the plugin's supported functionality so will request more permissions than your own app may need @@ -298,6 +305,8 @@ For reference, the example app's `AndroidManifest.xml` file can be found [here]( Note that when a full-screen intent notification actually occurs (as opposed to a heads-up notification that the system may decide should occur), the plugin will act as though the user has tapped on a notification so handle those the same way (e.g. `onDidReceiveNotificationResponse` callback) to display the appropriate page for your application. +Developers should also be across Google's requirements on using full-screen intents. Please refer to their documentation [here](https://source.android.com/docs/core/permissions/fsi-limits) for more information. Should you app need request permissions, the `AndroidFlutterNotificationsPlugin` class exposes the `requestFullScreenIntentPermission()` method that can be used to do so. + ### Release build configuration Before creating the release build of your app (which is the default setting when building an APK or app bundle) you will need to customise your ProGuard configuration file as per this [link](https://developer.android.com/studio/build/shrink-code#keep-code). Rules specific to the GSON dependency being used by the plugin will need to be added. These rules can be found [here](https://github.com/google/gson/blob/master/examples/android-proguard-example/proguard.cfg). Whilst the example app has a Proguard rules (`proguard-rules.pro`) [here](https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/example/android/app/proguard-rules.pro), it is recommended that developers refer to the rules on the GSON repository in case they get updated over time. @@ -354,13 +363,13 @@ flutterLocalNotificationsPlugin.initialize(initializationSettings, ... void onDidReceiveLocalNotification( - int id, String title?, String? body, String? payload) async { + int id, String? title, String? body, String? payload) async { // display a dialog with the notification details, tap ok to go to another page showDialog( context: context, builder: (BuildContext context) => CupertinoAlertDialog( - title: Text(title), - content: Text(body), + title: Text(title??''), + content: Text(body??''), actions: [ CupertinoDialogAction( isDefaultAction: true, @@ -744,7 +753,7 @@ const NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); await flutterLocalNotificationsPlugin.periodicallyShow(0, 'repeating title', 'repeating body', RepeatInterval.everyMinute, notificationDetails, - androidAllowWhileIdle: true); + androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle); ``` ### Retrieving pending notification requests diff --git a/flutter_local_notifications/android/build.gradle b/flutter_local_notifications/android/build.gradle index 843a4807d..6d872a171 100644 --- a/flutter_local_notifications/android/build.gradle +++ b/flutter_local_notifications/android/build.gradle @@ -23,7 +23,7 @@ apply plugin: 'com.android.library' android { namespace 'com.dexterous.flutterlocalnotifications' - compileSdkVersion 33 + compileSdk 34 compileOptions { coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_1_8 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 f34dec658..b0968614b 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 @@ -1,5 +1,6 @@ package com.dexterous.flutterlocalnotifications; +import static android.provider.Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT; import static android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM; import android.Manifest; @@ -156,12 +157,16 @@ public class FlutterLocalNotificationsPlugin private static final String CANCEL_ALL_METHOD = "cancelAll"; private static final String ZONED_SCHEDULE_METHOD = "zonedSchedule"; private static final String PERIODICALLY_SHOW_METHOD = "periodicallyShow"; + private static final String PERIODICALLY_SHOW_WITH_DURATION = "periodicallyShowWithDuration"; private static final String GET_NOTIFICATION_APP_LAUNCH_DETAILS_METHOD = "getNotificationAppLaunchDetails"; private static final String REQUEST_NOTIFICATIONS_PERMISSION_METHOD = "requestNotificationsPermission"; private static final String REQUEST_EXACT_ALARMS_PERMISSION_METHOD = "requestExactAlarmsPermission"; + + private static final String REQUEST_FULL_SCREEN_INTENT_PERMISSION_METHOD = + "requestFullScreenIntentPermission"; private static final String METHOD_CHANNEL = "dexterous.com/flutter/local_notifications"; private static final String INVALID_ICON_ERROR_CODE = "invalid_icon"; private static final String INVALID_LARGE_ICON_ERROR_CODE = "invalid_large_icon"; @@ -204,6 +209,8 @@ public class FlutterLocalNotificationsPlugin static final int EXACT_ALARM_PERMISSION_REQUEST_CODE = 2; + static final int FULL_SCREEN_INTENT_PERMISSION_REQUEST_CODE = 3; + private PermissionRequestListener callback; private PermissionRequestProgress permissionRequestProgress = PermissionRequestProgress.None; @@ -212,7 +219,8 @@ static void rescheduleNotifications(Context context) { ArrayList scheduledNotifications = loadScheduledNotifications(context); for (NotificationDetails notificationDetails : scheduledNotifications) { try { - if (notificationDetails.repeatInterval != null) { + if (notificationDetails.repeatInterval != null + || notificationDetails.repeatIntervalMilliseconds != null) { repeatNotification(context, notificationDetails, false); } else if (notificationDetails.timeZoneName != null) { zonedScheduleNotification(context, notificationDetails, false); @@ -232,7 +240,8 @@ static void scheduleNextNotification(Context context, NotificationDetails notifi zonedScheduleNextNotification(context, notificationDetails); } else if (notificationDetails.matchDateTimeComponents != null) { zonedScheduleNextNotificationMatchingDateComponents(context, notificationDetails); - } else if (notificationDetails.repeatInterval != null) { + } else if (notificationDetails.repeatInterval != null + || notificationDetails.repeatIntervalMilliseconds != null) { scheduleNextRepeatingNotification(context, notificationDetails); } else { removeNotificationFromCache(context, notificationDetails.id); @@ -721,6 +730,7 @@ private static void setupAlarm( AlarmManagerCompat.setExact( alarmManager, AlarmManager.RTC_WAKEUP, epochMilli, pendingIntent); } else if (notificationDetails.scheduleMode.useAlarmClock()) { + checkCanScheduleExactAlarms(alarmManager); AlarmManagerCompat.setAlarmClock(alarmManager, epochMilli, pendingIntent, pendingIntent); } else { alarmManager.set(AlarmManager.RTC_WAKEUP, epochMilli, pendingIntent); @@ -738,6 +748,7 @@ private static void setupAllowWhileIdleAlarm( AlarmManagerCompat.setExactAndAllowWhileIdle( alarmManager, AlarmManager.RTC_WAKEUP, epochMilli, pendingIntent); } else if (notificationDetails.scheduleMode.useAlarmClock()) { + checkCanScheduleExactAlarms(alarmManager); AlarmManagerCompat.setAlarmClock(alarmManager, epochMilli, pendingIntent, pendingIntent); } else { AlarmManagerCompat.setAndAllowWhileIdle( @@ -763,6 +774,12 @@ private static long calculateNextNotificationTrigger( private static long calculateRepeatIntervalMilliseconds(NotificationDetails notificationDetails) { long repeatInterval = 0; + + if (notificationDetails.repeatIntervalMilliseconds != null) { + repeatInterval = notificationDetails.repeatIntervalMilliseconds; + return repeatInterval; + } + switch (notificationDetails.repeatInterval) { case EveryMinute: repeatInterval = 60000; @@ -1032,7 +1049,7 @@ private static void setBigPictureStyle( } if (bigPictureStyleInformation.hideExpandedLargeIcon) { - bigPictureStyle.bigLargeIcon(null); + bigPictureStyle.bigLargeIcon((Bitmap) null); } else { if (bigPictureStyleInformation.largeIcon != null) { bigPictureStyle.bigLargeIcon( @@ -1445,9 +1462,26 @@ public void fail(String message) { } }); break; + case REQUEST_FULL_SCREEN_INTENT_PERMISSION_METHOD: + requestFullScreenIntentPermission( + new PermissionRequestListener() { + @Override + public void complete(boolean granted) { + result.success(granted); + } + + @Override + public void fail(String message) { + result.error(PERMISSION_REQUEST_IN_PROGRESS_ERROR_CODE, message, null); + } + }); + break; case PERIODICALLY_SHOW_METHOD: repeat(call, result); break; + case PERIODICALLY_SHOW_WITH_DURATION: + repeat(call, result); + break; case CANCEL_METHOD: cancel(call, result); break; @@ -1536,6 +1570,8 @@ private void getActiveNotifications(Result result) { activeNotificationPayload.put( "title", notification.extras.getCharSequence("android.title")); activeNotificationPayload.put("body", notification.extras.getCharSequence("android.text")); + activeNotificationPayload.put( + "bigText", notification.extras.getCharSequence("android.bigText")); activeNotificationsPayload.add(activeNotificationPayload); } result.success(activeNotificationsPayload); @@ -1822,6 +1858,36 @@ public void requestExactAlarmsPermission(@NonNull PermissionRequestListener call } } + public void requestFullScreenIntentPermission(@NonNull PermissionRequestListener callback) { + if (permissionRequestProgress != PermissionRequestProgress.None) { + callback.fail(PERMISSION_REQUEST_IN_PROGRESS_ERROR_MESSAGE); + return; + } + + this.callback = callback; + + if (Build.VERSION.SDK_INT >= VERSION_CODES.UPSIDE_DOWN_CAKE) { + NotificationManager notificationManager = + (NotificationManager) applicationContext.getSystemService(Context.NOTIFICATION_SERVICE); + AlarmManager alarmManager = getAlarmManager(applicationContext); + boolean permissionGranted = notificationManager.canUseFullScreenIntent(); + + if (!permissionGranted) { + permissionRequestProgress = PermissionRequestProgress.RequestingFullScreenIntentPermission; + mainActivity.startActivityForResult( + new Intent( + ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT, + Uri.parse("package:" + applicationContext.getPackageName())), + FULL_SCREEN_INTENT_PERMISSION_REQUEST_CODE); + } else { + this.callback.complete(true); + permissionRequestProgress = PermissionRequestProgress.None; + } + } else { + this.callback.complete(true); + } + } + @Override public boolean onRequestPermissionsResult( int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { @@ -2080,6 +2146,12 @@ private HashMap getMappedNotificationChannel(NotificationChannel channelPayload.put("vibrationPattern", channel.getVibrationPattern()); channelPayload.put("enableLights", channel.shouldShowLights()); channelPayload.put("ledColor", channel.getLightColor()); + final AudioAttributes audioAttributes = channel.getAudioAttributes(); + channelPayload.put( + "audioAttributesUsage", + audioAttributes == null + ? AudioAttributes.USAGE_NOTIFICATION + : audioAttributes.getUsage()); } return channelPayload; } @@ -2148,7 +2220,8 @@ private void setCanScheduleExactNotifications(Result result) { @Override public boolean onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (requestCode != NOTIFICATION_PERMISSION_REQUEST_CODE - && requestCode != EXACT_ALARM_PERMISSION_REQUEST_CODE) { + && requestCode != EXACT_ALARM_PERMISSION_REQUEST_CODE + && requestCode != FULL_SCREEN_INTENT_PERMISSION_REQUEST_CODE) { return false; } @@ -2160,6 +2233,15 @@ public boolean onActivityResult(int requestCode, int resultCode, @Nullable Inten permissionRequestProgress = PermissionRequestProgress.None; } + if (permissionRequestProgress == PermissionRequestProgress.RequestingFullScreenIntentPermission + && requestCode == FULL_SCREEN_INTENT_PERMISSION_REQUEST_CODE + && VERSION.SDK_INT >= VERSION_CODES.UPSIDE_DOWN_CAKE) { + NotificationManager notificationManager = + (NotificationManager) applicationContext.getSystemService(Context.NOTIFICATION_SERVICE); + this.callback.complete(notificationManager.canUseFullScreenIntent()); + permissionRequestProgress = PermissionRequestProgress.None; + } + return true; } @@ -2181,6 +2263,7 @@ public ExactAlarmPermissionException() { enum PermissionRequestProgress { None, RequestingNotificationPermission, - RequestingExactAlarmsPermission + RequestingExactAlarmsPermission, + RequestingFullScreenIntentPermission } } diff --git a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java index a42c084a1..dcb59648a 100644 --- a/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java +++ b/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/models/NotificationDetails.java @@ -30,6 +30,7 @@ public class NotificationDetails implements Serializable { private static final String MILLISECONDS_SINCE_EPOCH = "millisecondsSinceEpoch"; private static final String CALLED_AT = "calledAt"; private static final String REPEAT_INTERVAL = "repeatInterval"; + private static final String REPEAT_INTERVAL_MILLISECONDS = "repeatIntervalMilliseconds"; private static final String REPEAT_TIME = "repeatTime"; private static final String PLATFORM_SPECIFICS = "platformSpecifics"; private static final String AUTO_CANCEL = "autoCancel"; @@ -145,6 +146,7 @@ public class NotificationDetails implements Serializable { public NotificationStyle style; public StyleInformation styleInformation; public RepeatInterval repeatInterval; + public Integer repeatIntervalMilliseconds; public Time repeatTime; public Long millisecondsSinceEpoch; public Long calledAt; @@ -226,6 +228,10 @@ public static NotificationDetails from(Map arguments) { notificationDetails.repeatInterval = RepeatInterval.values()[(Integer) arguments.get(REPEAT_INTERVAL)]; } + if (arguments.containsKey(REPEAT_INTERVAL_MILLISECONDS)) { + notificationDetails.repeatIntervalMilliseconds = + (Integer) arguments.get(REPEAT_INTERVAL_MILLISECONDS); + } if (arguments.containsKey(REPEAT_TIME)) { @SuppressWarnings("unchecked") Map repeatTimeParams = (Map) arguments.get(REPEAT_TIME); @@ -458,7 +464,7 @@ private static ArrayList readMessages(ArrayList) messageData.get(PERSON)), (String) messageData.get(DATA_MIME_TYPE), (String) messageData.get(DATA_URI))); diff --git a/flutter_local_notifications/example/analysis_options.yaml b/flutter_local_notifications/example/analysis_options.yaml index 6d87f5e9d..fba01321b 100644 --- a/flutter_local_notifications/example/analysis_options.yaml +++ b/flutter_local_notifications/example/analysis_options.yaml @@ -6,7 +6,6 @@ linter: - always_declare_return_types - always_put_control_body_on_new_line - always_put_required_named_parameters_first - - always_require_non_null_named_parameters - always_specify_types - annotate_overrides - avoid_annotating_with_dynamic @@ -32,8 +31,6 @@ linter: - avoid_relative_lib_imports - avoid_renaming_method_parameters - avoid_return_types_on_setters - - avoid_returning_null - - avoid_returning_null_for_future - avoid_returning_null_for_void - avoid_returning_this - avoid_setters_without_getters diff --git a/flutter_local_notifications/example/android/app/build.gradle b/flutter_local_notifications/example/android/app/build.gradle index 16a50de70..cfd0b9d35 100644 --- a/flutter_local_notifications/example/android/app/build.gradle +++ b/flutter_local_notifications/example/android/app/build.gradle @@ -27,7 +27,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { namespace 'com.dexterous.flutter_local_notifications_example' - compileSdkVersion 33 + compileSdk 34 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -43,7 +43,7 @@ android { multiDexEnabled true applicationId "com.dexterous.flutter_local_notifications_example" minSdkVersion flutter.minSdkVersion - targetSdkVersion 33 + targetSdkVersion 34 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/flutter_local_notifications/example/android/app/src/main/AndroidManifest.xml b/flutter_local_notifications/example/android/app/src/main/AndroidManifest.xml index f14f28503..027234c99 100644 --- a/flutter_local_notifications/example/android/app/src/main/AndroidManifest.xml +++ b/flutter_local_notifications/example/android/app/src/main/AndroidManifest.xml @@ -3,6 +3,8 @@ + didReceiveLocalNotificationStream = StreamController.broadcast(); -final StreamController selectNotificationStream = StreamController.broadcast(); +final StreamController selectNotificationStream = + StreamController.broadcast(); -const MethodChannel platform = MethodChannel('dexterx.dev/flutter_local_notifications_example'); +const MethodChannel platform = + MethodChannel('dexterx.dev/flutter_local_notifications_example'); const String portName = 'notification_send_port'; @@ -69,7 +71,8 @@ void notificationTapBackground(NotificationResponse notificationResponse) { ' payload: ${notificationResponse.payload}'); if (notificationResponse.input?.isNotEmpty ?? false) { // ignore: avoid_print - print('notification action tapped with input: ${notificationResponse.input}'); + print( + 'notification action tapped with input: ${notificationResponse.input}'); } } @@ -84,12 +87,14 @@ Future main() async { await _configureLocalTimeZone(); - final NotificationAppLaunchDetails? notificationAppLaunchDetails = !kIsWeb && Platform.isLinux + final NotificationAppLaunchDetails? notificationAppLaunchDetails = !kIsWeb && + Platform.isLinux ? null : await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails(); String initialRoute = HomePage.routeName; if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) { - selectedNotificationPayload = notificationAppLaunchDetails!.notificationResponse?.payload; + selectedNotificationPayload = + notificationAppLaunchDetails!.notificationResponse?.payload; initialRoute = SecondPage.routeName; } @@ -143,11 +148,13 @@ Future main() async { /// Note: permissions aren't requested here just to demonstrate that can be /// done later - final DarwinInitializationSettings initializationSettingsDarwin = DarwinInitializationSettings( + final DarwinInitializationSettings initializationSettingsDarwin = + DarwinInitializationSettings( requestAlertPermission: false, requestBadgePermission: false, requestSoundPermission: false, - onDidReceiveLocalNotification: (int id, String? title, String? body, String? payload) async { + onDidReceiveLocalNotification: + (int id, String? title, String? body, String? payload) async { didReceiveLocalNotificationStream.add( ReceivedNotification( id: id, @@ -159,7 +166,8 @@ Future main() async { }, notificationCategories: darwinNotificationCategories, ); - final LinuxInitializationSettings initializationSettingsLinux = LinuxInitializationSettings( + final LinuxInitializationSettings initializationSettingsLinux = + LinuxInitializationSettings( defaultActionName: 'Open notification', defaultIcon: AssetsLinuxIcon('icons/app_icon.png'), ); @@ -171,7 +179,8 @@ Future main() async { ); await flutterLocalNotificationsPlugin.initialize( initializationSettings, - onDidReceiveNotificationResponse: (NotificationResponse notificationResponse) { + onDidReceiveNotificationResponse: + (NotificationResponse notificationResponse) { switch (notificationResponse.notificationResponseType) { case NotificationResponseType.selectedNotification: selectNotificationStream.add(notificationResponse.payload); @@ -243,7 +252,8 @@ class HomePage extends StatefulWidget { } class _HomePageState extends State { - final TextEditingController _linuxIconPathController = TextEditingController(); + final TextEditingController _linuxIconPathController = + TextEditingController(); bool _notificationsEnabled = false; @@ -259,7 +269,8 @@ class _HomePageState extends State { Future _isAndroidPermissionGranted() async { if (Platform.isAndroid) { final bool granted = await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation() + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() ?.areNotificationsEnabled() ?? false; @@ -272,14 +283,16 @@ class _HomePageState extends State { Future _requestPermissions() async { if (Platform.isIOS || Platform.isMacOS) { await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation() + .resolvePlatformSpecificImplementation< + IOSFlutterLocalNotificationsPlugin>() ?.requestPermissions( alert: true, badge: true, sound: true, ); await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation() + .resolvePlatformSpecificImplementation< + MacOSFlutterLocalNotificationsPlugin>() ?.requestPermissions( alert: true, badge: true, @@ -287,8 +300,8 @@ class _HomePageState extends State { ); } else if (Platform.isAndroid) { final AndroidFlutterLocalNotificationsPlugin? androidImplementation = - flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation(); + flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>(); final bool? grantedNotificationPermission = await androidImplementation?.requestNotificationsPermission(); @@ -304,8 +317,12 @@ class _HomePageState extends State { await showDialog( context: context, builder: (BuildContext context) => CupertinoAlertDialog( - title: receivedNotification.title != null ? Text(receivedNotification.title!) : null, - content: receivedNotification.body != null ? Text(receivedNotification.body!) : null, + title: receivedNotification.title != null + ? Text(receivedNotification.title!) + : null, + content: receivedNotification.body != null + ? Text(receivedNotification.body!) + : null, actions: [ CupertinoDialogAction( isDefaultAction: true, @@ -313,7 +330,8 @@ class _HomePageState extends State { Navigator.of(context, rootNavigator: true).pop(); await Navigator.of(context).push( MaterialPageRoute( - builder: (BuildContext context) => SecondPage(receivedNotification.payload), + builder: (BuildContext context) => + SecondPage(receivedNotification.payload), ), ); }, @@ -353,8 +371,9 @@ class _HomePageState extends State { children: [ const Padding( padding: EdgeInsets.fromLTRB(0, 0, 0, 8), - child: Text('Tap on a notification when it appears to trigger' - ' navigation'), + child: + Text('Tap on a notification when it appears to trigger' + ' navigation'), ), _InfoValueString( title: 'Did notification launch app?', @@ -364,16 +383,20 @@ class _HomePageState extends State { const Text('Launch notification details'), _InfoValueString( title: 'Notification id', - value: widget.notificationAppLaunchDetails!.notificationResponse?.id), + value: widget.notificationAppLaunchDetails! + .notificationResponse?.id), _InfoValueString( title: 'Action id', - value: widget.notificationAppLaunchDetails!.notificationResponse?.actionId), + value: widget.notificationAppLaunchDetails! + .notificationResponse?.actionId), _InfoValueString( title: 'Input', - value: widget.notificationAppLaunchDetails!.notificationResponse?.input), + value: widget.notificationAppLaunchDetails! + .notificationResponse?.input), _InfoValueString( title: 'Payload:', - value: widget.notificationAppLaunchDetails!.notificationResponse?.payload, + value: widget.notificationAppLaunchDetails! + .notificationResponse?.payload, ), ], PaddedElevatedButton( @@ -383,7 +406,8 @@ class _HomePageState extends State { }, ), PaddedElevatedButton( - buttonText: 'Show plain notification that has no title with ' + buttonText: + 'Show plain notification that has no title with ' 'payload', onPressed: () async { await _showNotificationWithNoTitle(); @@ -404,14 +428,16 @@ class _HomePageState extends State { ), if (kIsWeb || !Platform.isLinux) ...[ PaddedElevatedButton( - buttonText: 'Schedule notification to appear in 5 seconds ' + buttonText: + 'Schedule notification to appear in 5 seconds ' 'based on local time zone', onPressed: () async { await _zonedScheduleNotification(); }, ), PaddedElevatedButton( - buttonText: 'Schedule notification to appear in 5 seconds ' + buttonText: + 'Schedule notification to appear in 5 seconds ' 'based on local time zone using alarm clock', onPressed: () async { await _zonedScheduleAlarmClockNotification(); @@ -424,28 +450,38 @@ class _HomePageState extends State { }, ), PaddedElevatedButton( - buttonText: 'Schedule daily 10:00:00 am notification in your ' + buttonText: 'Repeat notification every 5 minutes', + onPressed: () async { + await _repeatPeriodicallyWithDurationNotification(); + }, + ), + PaddedElevatedButton( + buttonText: + 'Schedule daily 10:00:00 am notification in your ' 'local time zone', onPressed: () async { await _scheduleDailyTenAMNotification(); }, ), PaddedElevatedButton( - buttonText: 'Schedule daily 10:00:00 am notification in your ' + buttonText: + 'Schedule daily 10:00:00 am notification in your ' "local time zone using last year's date", onPressed: () async { await _scheduleDailyTenAMLastYearNotification(); }, ), PaddedElevatedButton( - buttonText: 'Schedule weekly 10:00:00 am notification in your ' + buttonText: + 'Schedule weekly 10:00:00 am notification in your ' 'local time zone', onPressed: () async { await _scheduleWeeklyTenAMNotification(); }, ), PaddedElevatedButton( - buttonText: 'Schedule weekly Monday 10:00:00 am notification ' + buttonText: + 'Schedule weekly Monday 10:00:00 am notification ' 'in your local time zone', onPressed: () async { await _scheduleWeeklyMondayTenAMNotification(); @@ -465,14 +501,16 @@ class _HomePageState extends State { ), ], PaddedElevatedButton( - buttonText: 'Schedule monthly Monday 10:00:00 am notification in ' + buttonText: + 'Schedule monthly Monday 10:00:00 am notification in ' 'your local time zone', onPressed: () async { await _scheduleMonthlyMondayTenAMNotification(); }, ), PaddedElevatedButton( - buttonText: 'Schedule yearly Monday 10:00:00 am notification in ' + buttonText: + 'Schedule yearly Monday 10:00:00 am notification in ' 'your local time zone', onPressed: () async { await _scheduleYearlyMondayTenAMNotification(); @@ -516,7 +554,8 @@ class _HomePageState extends State { ), if (Platform.isLinux) PaddedElevatedButton( - buttonText: 'Show notification with icon action (if supported)', + buttonText: + 'Show notification with icon action (if supported)', onPressed: () async { await _showNotificationWithIconAction(); }, @@ -543,7 +582,8 @@ class _HomePageState extends State { ), Text('notifications enabled: $_notificationsEnabled'), PaddedElevatedButton( - buttonText: 'Check if notifications are enabled for this app', + buttonText: + 'Check if notifications are enabled for this app', onPressed: _areNotifcationsEnabledOnAndroid, ), PaddedElevatedButton( @@ -551,7 +591,8 @@ class _HomePageState extends State { onPressed: () => _requestPermissions(), ), PaddedElevatedButton( - buttonText: 'Show plain notification with payload and update ' + buttonText: + 'Show plain notification with payload and update ' 'channel description', onPressed: () async { await _showNotificationUpdateChannelDescription(); @@ -565,7 +606,8 @@ class _HomePageState extends State { }, ), PaddedElevatedButton( - buttonText: 'Show notification with custom vibration pattern, ' + buttonText: + 'Show notification with custom vibration pattern, ' 'red LED and red icon', onPressed: () async { await _showNotificationCustomVibrationIconLed(); @@ -578,7 +620,8 @@ class _HomePageState extends State { }, ), PaddedElevatedButton( - buttonText: 'Show notification that times out after 3 seconds', + buttonText: + 'Show notification that times out after 3 seconds', onPressed: () async { await _showTimeoutNotification(); }, @@ -590,27 +633,31 @@ class _HomePageState extends State { }, ), PaddedElevatedButton( - buttonText: 'Show big picture notification using local images', + buttonText: + 'Show big picture notification using local images', onPressed: () async { await _showBigPictureNotification(); }, ), PaddedElevatedButton( - buttonText: 'Show big picture notification using base64 String ' + buttonText: + 'Show big picture notification using base64 String ' 'for images', onPressed: () async { await _showBigPictureNotificationBase64(); }, ), PaddedElevatedButton( - buttonText: 'Show big picture notification using URLs for ' + buttonText: + 'Show big picture notification using URLs for ' 'Images', onPressed: () async { await _showBigPictureNotificationURL(); }, ), PaddedElevatedButton( - buttonText: 'Show big picture notification, hide large icon ' + buttonText: + 'Show big picture notification, hide large icon ' 'on expand', onPressed: () async { await _showBigPictureNotificationHiddenLargeIcon(); @@ -665,13 +712,15 @@ class _HomePageState extends State { }, ), PaddedElevatedButton( - buttonText: 'Show notification with no badge, alert only once', + buttonText: + 'Show notification with no badge, alert only once', onPressed: () async { await _showNotificationWithNoBadge(); }, ), PaddedElevatedButton( - buttonText: 'Show progress notification - updates every second', + buttonText: + 'Show progress notification - updates every second', onPressed: () async { await _showProgressNotification(); }, @@ -706,6 +755,13 @@ class _HomePageState extends State { await _showNotificationWithChronometer(); }, ), + PaddedElevatedButton( + buttonText: + 'Request full-screen intent permission (API 34+)', + onPressed: () async { + await _requestFullScreenIntentPermission(); + }, + ), PaddedElevatedButton( buttonText: 'Show full-screen notification', onPressed: () async { @@ -713,7 +769,8 @@ class _HomePageState extends State { }, ), PaddedElevatedButton( - buttonText: 'Show notification with number if the launcher ' + buttonText: + 'Show notification with number if the launcher ' 'supports', onPressed: () async { await _showNotificationWithNumber(); @@ -763,7 +820,8 @@ class _HomePageState extends State { }, ), PaddedElevatedButton( - buttonText: 'Start foreground service with blue background ' + buttonText: + 'Start foreground service with blue background ' 'notification', onPressed: () async { await _startForegroundServiceWithBlueBackgroundNotification(); @@ -776,7 +834,8 @@ class _HomePageState extends State { }, ), ], - if (!kIsWeb && (Platform.isIOS || Platform.isMacOS)) ...[ + if (!kIsWeb && + (Platform.isIOS || Platform.isMacOS)) ...[ const Text( 'iOS and macOS-specific examples', style: TextStyle(fontWeight: FontWeight.bold), @@ -802,19 +861,24 @@ class _HomePageState extends State { }, ), PaddedElevatedButton( - buttonText: 'Show notification with attachment (with thumbnail)', + buttonText: + 'Show notification with attachment (with thumbnail)', onPressed: () async { - await _showNotificationWithAttachment(hideThumbnail: false); + await _showNotificationWithAttachment( + hideThumbnail: false); }, ), PaddedElevatedButton( - buttonText: 'Show notification with attachment (no thumbnail)', + buttonText: + 'Show notification with attachment (no thumbnail)', onPressed: () async { - await _showNotificationWithAttachment(hideThumbnail: true); + await _showNotificationWithAttachment( + hideThumbnail: true); }, ), PaddedElevatedButton( - buttonText: 'Show notification with attachment (clipped thumbnail)', + buttonText: + 'Show notification with attachment (clipped thumbnail)', onPressed: () async { await _showNotificationWithClippedThumbnailAttachment(); }, @@ -826,7 +890,8 @@ class _HomePageState extends State { }, ), PaddedElevatedButton( - buttonText: 'Show notification with time sensitive interruption ' + buttonText: + 'Show notification with time sensitive interruption ' 'level', onPressed: () async { await _showNotificationWithTimeSensitiveInterruptionLevel(); @@ -840,7 +905,8 @@ class _HomePageState extends State { }, ), PaddedElevatedButton( - buttonText: 'Show notification in notification centre only', + buttonText: + 'Show notification in notification centre only', onPressed: () async { await _showNotificationInNotificationCentreOnly(); }, @@ -1033,20 +1099,22 @@ class _HomePageState extends State { ); Future _showNotification() async { - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'your channel id', 'your channel name', - channelDescription: 'your channel description', - importance: Importance.max, - priority: Priority.high, - ticker: 'ticker'); + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('your channel id', 'your channel name', + channelDescription: 'your channel description', + importance: Importance.max, + priority: Priority.high, + ticker: 'ticker'); const NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); - await flutterLocalNotificationsPlugin - .show(id++, 'plain title', 'plain body', notificationDetails, payload: 'item x'); + await flutterLocalNotificationsPlugin.show( + id++, 'plain title', 'plain body', notificationDetails, + payload: 'item x'); } Future _showNotificationWithActions() async { - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( 'your channel id', 'your channel name', channelDescription: 'your channel description', @@ -1078,15 +1146,18 @@ class _HomePageState extends State { ], ); - const DarwinNotificationDetails iosNotificationDetails = DarwinNotificationDetails( + const DarwinNotificationDetails iosNotificationDetails = + DarwinNotificationDetails( categoryIdentifier: darwinNotificationCategoryPlain, ); - const DarwinNotificationDetails macOSNotificationDetails = DarwinNotificationDetails( + const DarwinNotificationDetails macOSNotificationDetails = + DarwinNotificationDetails( categoryIdentifier: darwinNotificationCategoryPlain, ); - const LinuxNotificationDetails linuxNotificationDetails = LinuxNotificationDetails( + const LinuxNotificationDetails linuxNotificationDetails = + LinuxNotificationDetails( actions: [ LinuxNotificationAction( key: urlLaunchActionId, @@ -1105,12 +1176,14 @@ class _HomePageState extends State { macOS: macOSNotificationDetails, linux: linuxNotificationDetails, ); - await flutterLocalNotificationsPlugin - .show(id++, 'plain title', 'plain body', notificationDetails, payload: 'item z'); + await flutterLocalNotificationsPlugin.show( + id++, 'plain title', 'plain body', notificationDetails, + payload: 'item z'); } Future _showNotificationWithTextAction() async { - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( 'your channel id', 'your channel name', channelDescription: 'your channel description', @@ -1131,7 +1204,8 @@ class _HomePageState extends State { ], ); - const DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails( + const DarwinNotificationDetails darwinNotificationDetails = + DarwinNotificationDetails( categoryIdentifier: darwinNotificationCategoryText, ); @@ -1141,13 +1215,14 @@ class _HomePageState extends State { macOS: darwinNotificationDetails, ); - await flutterLocalNotificationsPlugin.show( - id++, 'Text Input Notification', 'Expand to see input action', notificationDetails, + await flutterLocalNotificationsPlugin.show(id++, 'Text Input Notification', + 'Expand to see input action', notificationDetails, payload: 'item x'); } Future _showNotificationWithIconAction() async { - const LinuxNotificationDetails linuxNotificationDetails = LinuxNotificationDetails( + const LinuxNotificationDetails linuxNotificationDetails = + LinuxNotificationDetails( actions: [ LinuxNotificationAction( key: 'media-eject', @@ -1159,12 +1234,14 @@ class _HomePageState extends State { const NotificationDetails notificationDetails = NotificationDetails( linux: linuxNotificationDetails, ); - await flutterLocalNotificationsPlugin - .show(id++, 'plain title', 'plain body', notificationDetails, payload: 'item z'); + await flutterLocalNotificationsPlugin.show( + id++, 'plain title', 'plain body', notificationDetails, + payload: 'item z'); } Future _showNotificationWithTextChoice() async { - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( 'your channel id', 'your channel name', channelDescription: 'your channel description', @@ -1187,7 +1264,8 @@ class _HomePageState extends State { ], ); - const DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails( + const DarwinNotificationDetails darwinNotificationDetails = + DarwinNotificationDetails( categoryIdentifier: darwinNotificationCategoryText, ); @@ -1196,8 +1274,31 @@ class _HomePageState extends State { iOS: darwinNotificationDetails, macOS: darwinNotificationDetails, ); - await flutterLocalNotificationsPlugin - .show(id++, 'plain title', 'plain body', notificationDetails, payload: 'item x'); + await flutterLocalNotificationsPlugin.show( + id++, 'plain title', 'plain body', notificationDetails, + payload: 'item x'); + } + + Future _requestFullScreenIntentPermission() async { + final bool permissionGranted = await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.requestFullScreenIntentPermission() ?? + false; + await showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + content: Text( + 'Full screen intent permission granted: $permissionGranted'), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('OK'), + ), + ], + )); } Future _showFullScreenNotification() async { @@ -1205,8 +1306,10 @@ class _HomePageState extends State { context: context, builder: (_) => AlertDialog( title: const Text('Turn off your screen'), - content: const Text('to see the full-screen intent in 5 seconds, press OK and TURN ' - 'OFF your screen'), + content: const Text( + 'to see the full-screen intent in 5 seconds, press OK and TURN ' + 'OFF your screen. Note that the full-screen intent permission must ' + 'be granted for this to work too'), actions: [ TextButton( onPressed: () { @@ -1242,31 +1345,32 @@ class _HomePageState extends State { } Future _showNotificationWithNoBody() async { - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'your channel id', 'your channel name', - channelDescription: 'your channel description', - importance: Importance.max, - priority: Priority.high, - ticker: 'ticker'); + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('your channel id', 'your channel name', + channelDescription: 'your channel description', + importance: Importance.max, + priority: Priority.high, + ticker: 'ticker'); const NotificationDetails notificationDetails = NotificationDetails( android: androidNotificationDetails, ); - await flutterLocalNotificationsPlugin.show(id++, 'plain title', null, notificationDetails, + await flutterLocalNotificationsPlugin.show( + id++, 'plain title', null, notificationDetails, payload: 'item x'); } Future _showNotificationWithNoTitle() async { - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'your channel id', 'your channel name', - channelDescription: 'your channel description', - importance: Importance.max, - priority: Priority.high, - ticker: 'ticker'); + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('your channel id', 'your channel name', + channelDescription: 'your channel description', + importance: Importance.max, + priority: Priority.high, + ticker: 'ticker'); const NotificationDetails notificationDetails = NotificationDetails( android: androidNotificationDetails, ); - await flutterLocalNotificationsPlugin.show(id++, null, 'plain body', notificationDetails, - payload: 'item x'); + await flutterLocalNotificationsPlugin + .show(id++, null, 'plain body', notificationDetails, payload: 'item x'); } Future _cancelNotification() async { @@ -1278,16 +1382,19 @@ class _HomePageState extends State { } Future _showNotificationCustomSound() async { - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( 'your other channel id', 'your other channel name', channelDescription: 'your other channel description', sound: RawResourceAndroidNotificationSound('slow_spring_board'), ); - const DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails( + const DarwinNotificationDetails darwinNotificationDetails = + DarwinNotificationDetails( sound: 'slow_spring_board.aiff', ); - final LinuxNotificationDetails linuxPlatformChannelSpecifics = LinuxNotificationDetails( + final LinuxNotificationDetails linuxPlatformChannelSpecifics = + LinuxNotificationDetails( sound: AssetsLinuxSound('sound/slow_spring_board.mp3'), ); final NotificationDetails notificationDetails = NotificationDetails( @@ -1311,17 +1418,18 @@ class _HomePageState extends State { vibrationPattern[2] = 5000; vibrationPattern[3] = 2000; - final AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'other custom channel id', 'other custom channel name', - channelDescription: 'other custom channel description', - icon: 'secondary_icon', - largeIcon: const DrawableResourceAndroidBitmap('sample_large_icon'), - vibrationPattern: vibrationPattern, - enableLights: true, - color: const Color.fromARGB(255, 255, 0, 0), - ledColor: const Color.fromARGB(255, 255, 0, 0), - ledOnMs: 1000, - ledOffMs: 500); + final AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( + 'other custom channel id', 'other custom channel name', + channelDescription: 'other custom channel description', + icon: 'secondary_icon', + largeIcon: const DrawableResourceAndroidBitmap('sample_large_icon'), + vibrationPattern: vibrationPattern, + enableLights: true, + color: const Color.fromARGB(255, 255, 0, 0), + ledColor: const Color.fromARGB(255, 255, 0, 0), + ledOnMs: 1000, + ledOffMs: 500); final NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); @@ -1339,10 +1447,12 @@ class _HomePageState extends State { 'scheduled body', tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)), const NotificationDetails( - android: AndroidNotificationDetails('your channel id', 'your channel name', + android: AndroidNotificationDetails( + 'your channel id', 'your channel name', channelDescription: 'your channel description')), androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, - uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime); + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime); } Future _zonedScheduleAlarmClockNotification() async { @@ -1352,19 +1462,22 @@ class _HomePageState extends State { 'scheduled alarm clock body', tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)), const NotificationDetails( - android: AndroidNotificationDetails('alarm_clock_channel', 'Alarm Clock Channel', + android: AndroidNotificationDetails( + 'alarm_clock_channel', 'Alarm Clock Channel', channelDescription: 'Alarm Clock Notification')), androidScheduleMode: AndroidScheduleMode.alarmClock, - uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime); + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime); } Future _showNotificationWithNoSound() async { - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'silent channel id', 'silent channel name', - channelDescription: 'silent channel description', - playSound: false, - styleInformation: DefaultStyleInformation(true, true)); - const DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails( + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('silent channel id', 'silent channel name', + channelDescription: 'silent channel description', + playSound: false, + styleInformation: DefaultStyleInformation(true, true)); + const DarwinNotificationDetails darwinNotificationDetails = + DarwinNotificationDetails( presentSound: false, ); const NotificationDetails notificationDetails = NotificationDetails( @@ -1400,12 +1513,13 @@ class _HomePageState extends State { /// example app to return the Uri for the default alarm sound and uses /// as the notification sound final String? alarmUri = await platform.invokeMethod('getAlarmUri'); - final UriAndroidNotificationSound uriSound = UriAndroidNotificationSound(alarmUri!); - final AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'uri channel id', 'uri channel name', - channelDescription: 'uri channel description', - sound: uriSound, - styleInformation: const DefaultStyleInformation(true, true)); + final UriAndroidNotificationSound uriSound = + UriAndroidNotificationSound(alarmUri!); + final AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('uri channel id', 'uri channel name', + channelDescription: 'uri channel description', + sound: uriSound, + styleInformation: const DefaultStyleInformation(true, true)); final NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); await flutterLocalNotificationsPlugin.show( @@ -1413,31 +1527,32 @@ class _HomePageState extends State { } Future _showTimeoutNotification() async { - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'silent channel id', 'silent channel name', - channelDescription: 'silent channel description', - timeoutAfter: 3000, - styleInformation: DefaultStyleInformation(true, true)); + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('silent channel id', 'silent channel name', + channelDescription: 'silent channel description', + timeoutAfter: 3000, + styleInformation: DefaultStyleInformation(true, true)); const NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); - await flutterLocalNotificationsPlugin.show( - id++, 'timeout notification', 'Times out after 3 seconds', notificationDetails); + await flutterLocalNotificationsPlugin.show(id++, 'timeout notification', + 'Times out after 3 seconds', notificationDetails); } Future _showInsistentNotification() async { // This value is from: https://developer.android.com/reference/android/app/Notification.html#FLAG_INSISTENT const int insistentFlag = 4; - final AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'your channel id', 'your channel name', - channelDescription: 'your channel description', - importance: Importance.max, - priority: Priority.high, - ticker: 'ticker', - additionalFlags: Int32List.fromList([insistentFlag])); + final AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('your channel id', 'your channel name', + channelDescription: 'your channel description', + importance: Importance.max, + priority: Priority.high, + ticker: 'ticker', + additionalFlags: Int32List.fromList([insistentFlag])); final NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); - await flutterLocalNotificationsPlugin - .show(id++, 'insistent title', 'insistent body', notificationDetails, payload: 'item x'); + await flutterLocalNotificationsPlugin.show( + id++, 'insistent title', 'insistent body', notificationDetails, + payload: 'item x'); } Future _downloadAndSaveFile(String url, String fileName) async { @@ -1452,19 +1567,20 @@ class _HomePageState extends State { Future _showBigPictureNotification() async { final String largeIconPath = await _downloadAndSaveFile('https://dummyimage.com/48x48', 'largeIcon'); - final String bigPicturePath = - await _downloadAndSaveFile('https://dummyimage.com/400x800', 'bigPicture'); - final BigPictureStyleInformation bigPictureStyleInformation = BigPictureStyleInformation( - FilePathAndroidBitmap(bigPicturePath), - largeIcon: FilePathAndroidBitmap(largeIconPath), - contentTitle: 'overridden big content title', - htmlFormatContentTitle: true, - summaryText: 'summary text', - htmlFormatSummaryText: true); - final AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'big text channel id', 'big text channel name', - channelDescription: 'big text channel description', - styleInformation: bigPictureStyleInformation); + final String bigPicturePath = await _downloadAndSaveFile( + 'https://dummyimage.com/400x800', 'bigPicture'); + final BigPictureStyleInformation bigPictureStyleInformation = + BigPictureStyleInformation(FilePathAndroidBitmap(bigPicturePath), + largeIcon: FilePathAndroidBitmap(largeIconPath), + contentTitle: 'overridden big content title', + htmlFormatContentTitle: true, + summaryText: 'summary text', + htmlFormatSummaryText: true); + final AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( + 'big text channel id', 'big text channel name', + channelDescription: 'big text channel description', + styleInformation: bigPictureStyleInformation); final NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); await flutterLocalNotificationsPlugin.show( @@ -1478,20 +1594,25 @@ class _HomePageState extends State { } Future _showBigPictureNotificationBase64() async { - final String largeIcon = await _base64encodedImage('https://dummyimage.com/48x48'); - final String bigPicture = await _base64encodedImage('https://dummyimage.com/400x800'); - - final BigPictureStyleInformation bigPictureStyleInformation = BigPictureStyleInformation( - ByteArrayAndroidBitmap.fromBase64String(bigPicture), //Base64AndroidBitmap(bigPicture), - largeIcon: ByteArrayAndroidBitmap.fromBase64String(largeIcon), - contentTitle: 'overridden big content title', - htmlFormatContentTitle: true, - summaryText: 'summary text', - htmlFormatSummaryText: true); - final AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'big text channel id', 'big text channel name', - channelDescription: 'big text channel description', - styleInformation: bigPictureStyleInformation); + final String largeIcon = + await _base64encodedImage('https://dummyimage.com/48x48'); + final String bigPicture = + await _base64encodedImage('https://dummyimage.com/400x800'); + + final BigPictureStyleInformation bigPictureStyleInformation = + BigPictureStyleInformation( + ByteArrayAndroidBitmap.fromBase64String( + bigPicture), //Base64AndroidBitmap(bigPicture), + largeIcon: ByteArrayAndroidBitmap.fromBase64String(largeIcon), + contentTitle: 'overridden big content title', + htmlFormatContentTitle: true, + summaryText: 'summary text', + htmlFormatSummaryText: true); + final AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( + 'big text channel id', 'big text channel name', + channelDescription: 'big text channel description', + styleInformation: bigPictureStyleInformation); final NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); await flutterLocalNotificationsPlugin.show( @@ -1504,22 +1625,23 @@ class _HomePageState extends State { } Future _showBigPictureNotificationURL() async { - final ByteArrayAndroidBitmap largeIcon = - ByteArrayAndroidBitmap(await _getByteArrayFromUrl('https://dummyimage.com/48x48')); - final ByteArrayAndroidBitmap bigPicture = - ByteArrayAndroidBitmap(await _getByteArrayFromUrl('https://dummyimage.com/400x800')); - - final BigPictureStyleInformation bigPictureStyleInformation = BigPictureStyleInformation( - bigPicture, - largeIcon: largeIcon, - contentTitle: 'overridden big content title', - htmlFormatContentTitle: true, - summaryText: 'summary text', - htmlFormatSummaryText: true); - final AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'big text channel id', 'big text channel name', - channelDescription: 'big text channel description', - styleInformation: bigPictureStyleInformation); + final ByteArrayAndroidBitmap largeIcon = ByteArrayAndroidBitmap( + await _getByteArrayFromUrl('https://dummyimage.com/48x48')); + final ByteArrayAndroidBitmap bigPicture = ByteArrayAndroidBitmap( + await _getByteArrayFromUrl('https://dummyimage.com/400x800')); + + final BigPictureStyleInformation bigPictureStyleInformation = + BigPictureStyleInformation(bigPicture, + largeIcon: largeIcon, + contentTitle: 'overridden big content title', + htmlFormatContentTitle: true, + summaryText: 'summary text', + htmlFormatSummaryText: true); + final AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( + 'big text channel id', 'big text channel name', + channelDescription: 'big text channel description', + styleInformation: bigPictureStyleInformation); final NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); await flutterLocalNotificationsPlugin.show( @@ -1529,20 +1651,21 @@ class _HomePageState extends State { Future _showBigPictureNotificationHiddenLargeIcon() async { final String largeIconPath = await _downloadAndSaveFile('https://dummyimage.com/48x48', 'largeIcon'); - final String bigPicturePath = - await _downloadAndSaveFile('https://dummyimage.com/400x800', 'bigPicture'); - final BigPictureStyleInformation bigPictureStyleInformation = BigPictureStyleInformation( - FilePathAndroidBitmap(bigPicturePath), - hideExpandedLargeIcon: true, - contentTitle: 'overridden big content title', - htmlFormatContentTitle: true, - summaryText: 'summary text', - htmlFormatSummaryText: true); - final AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'big text channel id', 'big text channel name', - channelDescription: 'big text channel description', - largeIcon: FilePathAndroidBitmap(largeIconPath), - styleInformation: bigPictureStyleInformation); + final String bigPicturePath = await _downloadAndSaveFile( + 'https://dummyimage.com/400x800', 'bigPicture'); + final BigPictureStyleInformation bigPictureStyleInformation = + BigPictureStyleInformation(FilePathAndroidBitmap(bigPicturePath), + hideExpandedLargeIcon: true, + contentTitle: 'overridden big content title', + htmlFormatContentTitle: true, + summaryText: 'summary text', + htmlFormatSummaryText: true); + final AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( + 'big text channel id', 'big text channel name', + channelDescription: 'big text channel description', + largeIcon: FilePathAndroidBitmap(largeIconPath), + styleInformation: bigPictureStyleInformation); final NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); await flutterLocalNotificationsPlugin.show( @@ -1550,9 +1673,10 @@ class _HomePageState extends State { } Future _showNotificationMediaStyle() async { - final String largeIconPath = - await _downloadAndSaveFile('https://dummyimage.com/128x128/00FF00/000000', 'largeIcon'); - final AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( + final String largeIconPath = await _downloadAndSaveFile( + 'https://dummyimage.com/128x128/00FF00/000000', 'largeIcon'); + final AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( 'media channel id', 'media channel name', channelDescription: 'media channel description', @@ -1566,7 +1690,8 @@ class _HomePageState extends State { } Future _showBigTextNotification() async { - const BigTextStyleInformation bigTextStyleInformation = BigTextStyleInformation( + const BigTextStyleInformation bigTextStyleInformation = + BigTextStyleInformation( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', htmlFormatBigText: true, contentTitle: 'overridden big content title', @@ -1574,10 +1699,11 @@ class _HomePageState extends State { summaryText: 'summary text', htmlFormatSummaryText: true, ); - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'big text channel id', 'big text channel name', - channelDescription: 'big text channel description', - styleInformation: bigTextStyleInformation); + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( + 'big text channel id', 'big text channel name', + channelDescription: 'big text channel description', + styleInformation: bigTextStyleInformation); const NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); await flutterLocalNotificationsPlugin.show( @@ -1586,15 +1712,17 @@ class _HomePageState extends State { Future _showInboxNotification() async { final List lines = ['line 1', 'line 2']; - final InboxStyleInformation inboxStyleInformation = InboxStyleInformation(lines, + final InboxStyleInformation inboxStyleInformation = InboxStyleInformation( + lines, htmlFormatLines: true, contentTitle: 'overridden inbox context title', htmlFormatContentTitle: true, summaryText: 'summary text', htmlFormatSummaryText: true); - final AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'inbox channel id', 'inboxchannel name', - channelDescription: 'inbox channel description', styleInformation: inboxStyleInformation); + final AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('inbox channel id', 'inboxchannel name', + channelDescription: 'inbox channel description', + styleInformation: inboxStyleInformation); final NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); await flutterLocalNotificationsPlugin.show( @@ -1605,7 +1733,8 @@ class _HomePageState extends State { // use a platform channel to resolve an Android drawable resource to a URI. // This is NOT part of the notifications plugin. Calls made over this /// channel is handled by the app - final String? imageUri = await platform.invokeMethod('drawableToUri', 'food'); + final String? imageUri = + await platform.invokeMethod('drawableToUri', 'food'); /// First two person objects will use icons that part of the Android app's /// drawable resources @@ -1640,7 +1769,8 @@ class _HomePageState extends State { final List messages = [ Message('Hi', DateTime.now(), null), - Message("What's up?", DateTime.now().add(const Duration(minutes: 5)), coworker), + Message("What's up?", DateTime.now().add(const Duration(minutes: 5)), + coworker), Message('Lunch?', DateTime.now().add(const Duration(minutes: 10)), null, dataMimeType: 'image/png', dataUri: imageUri), Message('What kind of food would you prefer?', @@ -1648,17 +1778,18 @@ class _HomePageState extends State { Message('You do not have time eat! Keep working!', DateTime.now().add(const Duration(minutes: 11)), chef), ]; - final MessagingStyleInformation messagingStyle = MessagingStyleInformation(me, + final MessagingStyleInformation messagingStyle = MessagingStyleInformation( + me, groupConversation: true, conversationTitle: 'Team lunch', htmlFormatContent: true, htmlFormatTitle: true, messages: messages); - final AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'message channel id', 'message channel name', - channelDescription: 'message channel description', - category: AndroidNotificationCategory.message, - styleInformation: messagingStyle); + final AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('message channel id', 'message channel name', + channelDescription: 'message channel description', + category: AndroidNotificationCategory.message, + styleInformation: messagingStyle); final NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); await flutterLocalNotificationsPlugin.show( @@ -1679,16 +1810,16 @@ class _HomePageState extends State { const String groupChannelName = 'grouped channel name'; const String groupChannelDescription = 'grouped channel description'; // example based on https://developer.android.com/training/notify-user/group.html - const AndroidNotificationDetails firstNotificationAndroidSpecifics = AndroidNotificationDetails( - groupChannelId, groupChannelName, - channelDescription: groupChannelDescription, - importance: Importance.max, - priority: Priority.high, - groupKey: groupKey); + const AndroidNotificationDetails firstNotificationAndroidSpecifics = + AndroidNotificationDetails(groupChannelId, groupChannelName, + channelDescription: groupChannelDescription, + importance: Importance.max, + priority: Priority.high, + groupKey: groupKey); const NotificationDetails firstNotificationPlatformSpecifics = NotificationDetails(android: firstNotificationAndroidSpecifics); - await flutterLocalNotificationsPlugin.show( - id++, 'Alex Faarborg', 'You will not believe...', firstNotificationPlatformSpecifics); + await flutterLocalNotificationsPlugin.show(id++, 'Alex Faarborg', + 'You will not believe...', firstNotificationPlatformSpecifics); const AndroidNotificationDetails secondNotificationAndroidSpecifics = AndroidNotificationDetails(groupChannelId, groupChannelName, channelDescription: groupChannelDescription, @@ -1697,8 +1828,11 @@ class _HomePageState extends State { groupKey: groupKey); const NotificationDetails secondNotificationPlatformSpecifics = NotificationDetails(android: secondNotificationAndroidSpecifics); - await flutterLocalNotificationsPlugin.show(id++, 'Jeff Chang', - 'Please join us to celebrate the...', secondNotificationPlatformSpecifics); + await flutterLocalNotificationsPlugin.show( + id++, + 'Jeff Chang', + 'Please join us to celebrate the...', + secondNotificationPlatformSpecifics); // Create the summary notification to support older devices that pre-date /// Android 7.0 (API level 24). @@ -1709,14 +1843,16 @@ class _HomePageState extends State { 'Alex Faarborg Check this out', 'Jeff Chang Launch Party' ]; - const InboxStyleInformation inboxStyleInformation = InboxStyleInformation(lines, - contentTitle: '2 messages', summaryText: 'janedoe@example.com'); - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - groupChannelId, groupChannelName, - channelDescription: groupChannelDescription, - styleInformation: inboxStyleInformation, - groupKey: groupKey, - setAsGroupSummary: true); + const InboxStyleInformation inboxStyleInformation = InboxStyleInformation( + lines, + contentTitle: '2 messages', + summaryText: 'janedoe@example.com'); + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails(groupChannelId, groupChannelName, + channelDescription: groupChannelDescription, + styleInformation: inboxStyleInformation, + groupKey: groupKey, + setAsGroupSummary: true); const NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); await flutterLocalNotificationsPlugin.show( @@ -1724,12 +1860,12 @@ class _HomePageState extends State { } Future _showNotificationWithTag() async { - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'your channel id', 'your channel name', - channelDescription: 'your channel description', - importance: Importance.max, - priority: Priority.high, - tag: 'tag'); + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('your channel id', 'your channel name', + channelDescription: 'your channel description', + importance: Importance.max, + priority: Priority.high, + tag: 'tag'); const NotificationDetails notificationDetails = NotificationDetails( android: androidNotificationDetails, ); @@ -1743,8 +1879,9 @@ class _HomePageState extends State { return showDialog( context: context, builder: (BuildContext context) => AlertDialog( - content: Text('${pendingNotificationRequests.length} pending notification ' - 'requests'), + content: + Text('${pendingNotificationRequests.length} pending notification ' + 'requests'), actions: [ TextButton( onPressed: () { @@ -1762,23 +1899,27 @@ class _HomePageState extends State { } Future _showOngoingNotification() async { - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'your channel id', 'your channel name', - channelDescription: 'your channel description', - importance: Importance.max, - priority: Priority.high, - ongoing: true, - autoCancel: false); + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('your channel id', 'your channel name', + channelDescription: 'your channel description', + importance: Importance.max, + priority: Priority.high, + ongoing: true, + autoCancel: false); const NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); await flutterLocalNotificationsPlugin.show( - id++, 'ongoing notification title', 'ongoing notification body', notificationDetails); + id++, + 'ongoing notification title', + 'ongoing notification body', + notificationDetails); } Future _repeatNotification() async { - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'repeating channel id', 'repeating channel name', - channelDescription: 'repeating description'); + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( + 'repeating channel id', 'repeating channel name', + channelDescription: 'repeating description'); const NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); await flutterLocalNotificationsPlugin.periodicallyShow( @@ -1791,6 +1932,23 @@ class _HomePageState extends State { ); } + Future _repeatPeriodicallyWithDurationNotification() async { + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( + 'repeating channel id', 'repeating channel name', + channelDescription: 'repeating description'); + const NotificationDetails notificationDetails = + NotificationDetails(android: androidNotificationDetails); + await flutterLocalNotificationsPlugin.periodicallyShowWithDuration( + id++, + 'repeating period title', + 'repeating period body', + const Duration(minutes: 5), + notificationDetails, + androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, + ); + } + Future _scheduleDailyTenAMNotification() async { await flutterLocalNotificationsPlugin.zonedSchedule( 0, @@ -1798,12 +1956,13 @@ class _HomePageState extends State { 'daily scheduled notification body', _nextInstanceOfTenAM(), const NotificationDetails( - android: AndroidNotificationDetails( - 'daily notification channel id', 'daily notification channel name', + android: AndroidNotificationDetails('daily notification channel id', + 'daily notification channel name', channelDescription: 'daily notification description'), ), androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, - uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime, + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime, matchDateTimeComponents: DateTimeComponents.time); } @@ -1815,12 +1974,13 @@ class _HomePageState extends State { 'daily scheduled notification body', _nextInstanceOfTenAMLastYear(), const NotificationDetails( - android: AndroidNotificationDetails( - 'daily notification channel id', 'daily notification channel name', + android: AndroidNotificationDetails('daily notification channel id', + 'daily notification channel name', channelDescription: 'daily notification description'), ), androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, - uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime, + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime, matchDateTimeComponents: DateTimeComponents.time); } @@ -1831,12 +1991,13 @@ class _HomePageState extends State { 'weekly scheduled notification body', _nextInstanceOfTenAM(), const NotificationDetails( - android: AndroidNotificationDetails( - 'weekly notification channel id', 'weekly notification channel name', + android: AndroidNotificationDetails('weekly notification channel id', + 'weekly notification channel name', channelDescription: 'weekly notificationdescription'), ), androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, - uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime, + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime, matchDateTimeComponents: DateTimeComponents.dayOfWeekAndTime); } @@ -1847,12 +2008,13 @@ class _HomePageState extends State { 'weekly scheduled notification body', _nextInstanceOfMondayTenAM(), const NotificationDetails( - android: AndroidNotificationDetails( - 'weekly notification channel id', 'weekly notification channel name', + android: AndroidNotificationDetails('weekly notification channel id', + 'weekly notification channel name', channelDescription: 'weekly notificationdescription'), ), androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, - uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime, + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime, matchDateTimeComponents: DateTimeComponents.dayOfWeekAndTime); } @@ -1863,12 +2025,13 @@ class _HomePageState extends State { 'monthly scheduled notification body', _nextInstanceOfMondayTenAM(), const NotificationDetails( - android: AndroidNotificationDetails( - 'monthly notification channel id', 'monthly notification channel name', + android: AndroidNotificationDetails('monthly notification channel id', + 'monthly notification channel name', channelDescription: 'monthly notificationdescription'), ), androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, - uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime, + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime, matchDateTimeComponents: DateTimeComponents.dayOfMonthAndTime); } @@ -1879,18 +2042,20 @@ class _HomePageState extends State { 'yearly scheduled notification body', _nextInstanceOfMondayTenAM(), const NotificationDetails( - android: AndroidNotificationDetails( - 'yearly notification channel id', 'yearly notification channel name', + android: AndroidNotificationDetails('yearly notification channel id', + 'yearly notification channel name', channelDescription: 'yearly notification description'), ), androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, - uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime, + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime, matchDateTimeComponents: DateTimeComponents.dateAndTime); } tz.TZDateTime _nextInstanceOfTenAM() { final tz.TZDateTime now = tz.TZDateTime.now(tz.local); - tz.TZDateTime scheduledDate = tz.TZDateTime(tz.local, now.year, now.month, now.day, 10); + tz.TZDateTime scheduledDate = + tz.TZDateTime(tz.local, now.year, now.month, now.day, 10); if (scheduledDate.isBefore(now)) { scheduledDate = scheduledDate.add(const Duration(days: 1)); } @@ -1911,17 +2076,18 @@ class _HomePageState extends State { } Future _showNotificationWithNoBadge() async { - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'no badge channel', 'no badge name', - channelDescription: 'no badge description', - channelShowBadge: false, - importance: Importance.max, - priority: Priority.high, - onlyAlertOnce: true); + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('no badge channel', 'no badge name', + channelDescription: 'no badge description', + channelShowBadge: false, + importance: Importance.max, + priority: Priority.high, + onlyAlertOnce: true); const NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); - await flutterLocalNotificationsPlugin - .show(id++, 'no badge title', 'no badge body', notificationDetails, payload: 'item x'); + await flutterLocalNotificationsPlugin.show( + id++, 'no badge title', 'no badge body', notificationDetails, + payload: 'item x'); } Future _showProgressNotification() async { @@ -1930,99 +2096,119 @@ class _HomePageState extends State { const int maxProgress = 5; for (int i = 0; i <= maxProgress; i++) { await Future.delayed(const Duration(seconds: 1), () async { - final AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'progress channel', 'progress channel', - channelDescription: 'progress channel description', - channelShowBadge: false, - importance: Importance.max, - priority: Priority.high, - onlyAlertOnce: true, - showProgress: true, - maxProgress: maxProgress, - progress: i); + final AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('progress channel', 'progress channel', + channelDescription: 'progress channel description', + channelShowBadge: false, + importance: Importance.max, + priority: Priority.high, + onlyAlertOnce: true, + showProgress: true, + maxProgress: maxProgress, + progress: i); final NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); - await flutterLocalNotificationsPlugin.show(progressId, 'progress notification title', - 'progress notification body', notificationDetails, + await flutterLocalNotificationsPlugin.show( + progressId, + 'progress notification title', + 'progress notification body', + notificationDetails, payload: 'item x'); }); } } Future _showIndeterminateProgressNotification() async { - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'indeterminate progress channel', 'indeterminate progress channel', - channelDescription: 'indeterminate progress channel description', - channelShowBadge: false, - importance: Importance.max, - priority: Priority.high, - onlyAlertOnce: true, - showProgress: true, - indeterminate: true); + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( + 'indeterminate progress channel', 'indeterminate progress channel', + channelDescription: 'indeterminate progress channel description', + channelShowBadge: false, + importance: Importance.max, + priority: Priority.high, + onlyAlertOnce: true, + showProgress: true, + indeterminate: true); const NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); - await flutterLocalNotificationsPlugin.show(id++, 'indeterminate progress notification title', - 'indeterminate progress notification body', notificationDetails, + await flutterLocalNotificationsPlugin.show( + id++, + 'indeterminate progress notification title', + 'indeterminate progress notification body', + notificationDetails, payload: 'item x'); } Future _showNotificationUpdateChannelDescription() async { - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'your channel id', 'your channel name', - channelDescription: 'your updated channel description', - importance: Importance.max, - priority: Priority.high, - channelAction: AndroidNotificationChannelAction.update); + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('your channel id', 'your channel name', + channelDescription: 'your updated channel description', + importance: Importance.max, + priority: Priority.high, + channelAction: AndroidNotificationChannelAction.update); const NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); - await flutterLocalNotificationsPlugin.show(id++, 'updated notification channel', - 'check settings to see updated channel description', notificationDetails, + await flutterLocalNotificationsPlugin.show( + id++, + 'updated notification channel', + 'check settings to see updated channel description', + notificationDetails, payload: 'item x'); } Future _showPublicNotification() async { - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'your channel id', 'your channel name', - channelDescription: 'your channel description', - importance: Importance.max, - priority: Priority.high, - ticker: 'ticker', - visibility: NotificationVisibility.public); + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('your channel id', 'your channel name', + channelDescription: 'your channel description', + importance: Importance.max, + priority: Priority.high, + ticker: 'ticker', + visibility: NotificationVisibility.public); const NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); await flutterLocalNotificationsPlugin.show( - id++, 'public notification title', 'public notification body', notificationDetails, + id++, + 'public notification title', + 'public notification body', + notificationDetails, payload: 'item x'); } Future _showNotificationWithSubtitle() async { - const DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails( + const DarwinNotificationDetails darwinNotificationDetails = + DarwinNotificationDetails( subtitle: 'the subtitle', ); - const NotificationDetails notificationDetails = - NotificationDetails(iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); - await flutterLocalNotificationsPlugin.show(id++, 'title of notification with a subtitle', - 'body of notification with a subtitle', notificationDetails, + const NotificationDetails notificationDetails = NotificationDetails( + iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); + await flutterLocalNotificationsPlugin.show( + id++, + 'title of notification with a subtitle', + 'body of notification with a subtitle', + notificationDetails, payload: 'item x'); } Future _showNotificationWithIconBadge() async { const DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails(badgeNumber: 1); - const NotificationDetails notificationDetails = - NotificationDetails(iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); - await flutterLocalNotificationsPlugin - .show(id++, 'icon badge title', 'icon badge body', notificationDetails, payload: 'item x'); + const NotificationDetails notificationDetails = NotificationDetails( + iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); + await flutterLocalNotificationsPlugin.show( + id++, 'icon badge title', 'icon badge body', notificationDetails, + payload: 'item x'); } Future _showNotificationsWithThreadIdentifier() async { NotificationDetails buildNotificationDetailsForThread( String threadIdentifier, ) { - final DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails( + final DarwinNotificationDetails darwinNotificationDetails = + DarwinNotificationDetails( threadIdentifier: threadIdentifier, ); - return NotificationDetails(iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); + return NotificationDetails( + iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); } final NotificationDetails thread1PlatformChannelSpecifics = @@ -2030,51 +2216,60 @@ class _HomePageState extends State { final NotificationDetails thread2PlatformChannelSpecifics = buildNotificationDetailsForThread('thread2'); - await flutterLocalNotificationsPlugin.show( - id++, 'thread 1', 'first notification', thread1PlatformChannelSpecifics); - await flutterLocalNotificationsPlugin.show( - id++, 'thread 1', 'second notification', thread1PlatformChannelSpecifics); - await flutterLocalNotificationsPlugin.show( - id++, 'thread 1', 'third notification', thread1PlatformChannelSpecifics); + await flutterLocalNotificationsPlugin.show(id++, 'thread 1', + 'first notification', thread1PlatformChannelSpecifics); + await flutterLocalNotificationsPlugin.show(id++, 'thread 1', + 'second notification', thread1PlatformChannelSpecifics); + await flutterLocalNotificationsPlugin.show(id++, 'thread 1', + 'third notification', thread1PlatformChannelSpecifics); - await flutterLocalNotificationsPlugin.show( - id++, 'thread 2', 'first notification', thread2PlatformChannelSpecifics); - await flutterLocalNotificationsPlugin.show( - id++, 'thread 2', 'second notification', thread2PlatformChannelSpecifics); - await flutterLocalNotificationsPlugin.show( - id++, 'thread 2', 'third notification', thread2PlatformChannelSpecifics); + await flutterLocalNotificationsPlugin.show(id++, 'thread 2', + 'first notification', thread2PlatformChannelSpecifics); + await flutterLocalNotificationsPlugin.show(id++, 'thread 2', + 'second notification', thread2PlatformChannelSpecifics); + await flutterLocalNotificationsPlugin.show(id++, 'thread 2', + 'third notification', thread2PlatformChannelSpecifics); } Future _showNotificationWithTimeSensitiveInterruptionLevel() async { - const DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails( + const DarwinNotificationDetails darwinNotificationDetails = + DarwinNotificationDetails( interruptionLevel: InterruptionLevel.timeSensitive, ); - const NotificationDetails notificationDetails = - NotificationDetails(iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); - await flutterLocalNotificationsPlugin.show(id++, 'title of time sensitive notification', - 'body of time sensitive notification', notificationDetails, + const NotificationDetails notificationDetails = NotificationDetails( + iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); + await flutterLocalNotificationsPlugin.show( + id++, + 'title of time sensitive notification', + 'body of time sensitive notification', + notificationDetails, payload: 'item x'); } Future _showNotificationWithBannerNotInNotificationCentre() async { - const DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails( + const DarwinNotificationDetails darwinNotificationDetails = + DarwinNotificationDetails( presentBanner: true, presentList: false, ); - const NotificationDetails notificationDetails = - NotificationDetails(iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); + const NotificationDetails notificationDetails = NotificationDetails( + iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); await flutterLocalNotificationsPlugin.show( - id++, 'title of banner notification', 'body of banner notification', notificationDetails, + id++, + 'title of banner notification', + 'body of banner notification', + notificationDetails, payload: 'item x'); } Future _showNotificationInNotificationCentreOnly() async { - const DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails( + const DarwinNotificationDetails darwinNotificationDetails = + DarwinNotificationDetails( presentBanner: false, presentList: true, ); - const NotificationDetails notificationDetails = - NotificationDetails(iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); + const NotificationDetails notificationDetails = NotificationDetails( + iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); await flutterLocalNotificationsPlugin.show( id++, 'title of notification shown only in notification centre', @@ -2084,20 +2279,22 @@ class _HomePageState extends State { } Future _showNotificationWithoutTimestamp() async { - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'your channel id', 'your channel name', - channelDescription: 'your channel description', - importance: Importance.max, - priority: Priority.high, - showWhen: false); + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('your channel id', 'your channel name', + channelDescription: 'your channel description', + importance: Importance.max, + priority: Priority.high, + showWhen: false); const NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); - await flutterLocalNotificationsPlugin - .show(id++, 'plain title', 'plain body', notificationDetails, payload: 'item x'); + await flutterLocalNotificationsPlugin.show( + id++, 'plain title', 'plain body', notificationDetails, + payload: 'item x'); } Future _showNotificationWithCustomTimestamp() async { - final AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( + final AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( 'your channel id', 'your channel name', channelDescription: 'your channel description', @@ -2107,12 +2304,14 @@ class _HomePageState extends State { ); final NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); - await flutterLocalNotificationsPlugin - .show(id++, 'plain title', 'plain body', notificationDetails, payload: 'item x'); + await flutterLocalNotificationsPlugin.show( + id++, 'plain title', 'plain body', notificationDetails, + payload: 'item x'); } Future _showNotificationWithCustomSubText() async { - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( 'your channel id', 'your channel name', channelDescription: 'your channel description', @@ -2123,12 +2322,14 @@ class _HomePageState extends State { ); const NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); - await flutterLocalNotificationsPlugin - .show(id++, 'plain title', 'plain body', notificationDetails, payload: 'item x'); + await flutterLocalNotificationsPlugin.show( + id++, 'plain title', 'plain body', notificationDetails, + payload: 'item x'); } Future _showNotificationWithChronometer() async { - final AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( + final AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( 'your channel id', 'your channel name', channelDescription: 'your channel description', @@ -2140,16 +2341,18 @@ class _HomePageState extends State { ); final NotificationDetails notificationDetails = NotificationDetails(android: androidNotificationDetails); - await flutterLocalNotificationsPlugin - .show(id++, 'plain title', 'plain body', notificationDetails, payload: 'item x'); + await flutterLocalNotificationsPlugin.show( + id++, 'plain title', 'plain body', notificationDetails, + payload: 'item x'); } Future _showNotificationWithAttachment({ required bool hideThumbnail, }) async { - final String bigPicturePath = - await _downloadAndSaveFile('https://dummyimage.com/600x200', 'bigPicture.jpg'); - final DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails( + final String bigPicturePath = await _downloadAndSaveFile( + 'https://dummyimage.com/600x200', 'bigPicture.jpg'); + final DarwinNotificationDetails darwinNotificationDetails = + DarwinNotificationDetails( attachments: [ DarwinNotificationAttachment( bigPicturePath, @@ -2157,16 +2360,20 @@ class _HomePageState extends State { ) ], ); - final NotificationDetails notificationDetails = - NotificationDetails(iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); - await flutterLocalNotificationsPlugin.show(id++, 'notification with attachment title', - 'notification with attachment body', notificationDetails); + final NotificationDetails notificationDetails = NotificationDetails( + iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); + await flutterLocalNotificationsPlugin.show( + id++, + 'notification with attachment title', + 'notification with attachment body', + notificationDetails); } Future _showNotificationWithClippedThumbnailAttachment() async { - final String bigPicturePath = - await _downloadAndSaveFile('https://dummyimage.com/600x200', 'bigPicture.jpg'); - final DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails( + final String bigPicturePath = await _downloadAndSaveFile( + 'https://dummyimage.com/600x200', 'bigPicture.jpg'); + final DarwinNotificationDetails darwinNotificationDetails = + DarwinNotificationDetails( attachments: [ DarwinNotificationAttachment( bigPicturePath, @@ -2181,34 +2388,43 @@ class _HomePageState extends State { ) ], ); - final NotificationDetails notificationDetails = - NotificationDetails(iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); - await flutterLocalNotificationsPlugin.show(id++, 'notification with attachment title', - 'notification with attachment body', notificationDetails); + final NotificationDetails notificationDetails = NotificationDetails( + iOS: darwinNotificationDetails, macOS: darwinNotificationDetails); + await flutterLocalNotificationsPlugin.show( + id++, + 'notification with attachment title', + 'notification with attachment body', + notificationDetails); } Future _createNotificationChannelGroup() async { const String channelGroupId = 'your channel group id'; // create the group first const AndroidNotificationChannelGroup androidNotificationChannelGroup = - AndroidNotificationChannelGroup(channelGroupId, 'your channel group name', + AndroidNotificationChannelGroup( + channelGroupId, 'your channel group name', description: 'your channel group description'); await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation()! + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>()! .createNotificationChannelGroup(androidNotificationChannelGroup); // create channels associated with the group await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation()! + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>()! .createNotificationChannel(const AndroidNotificationChannel( 'grouped channel id 1', 'grouped channel name 1', - description: 'grouped channel description 1', groupId: channelGroupId)); + description: 'grouped channel description 1', + groupId: channelGroupId)); await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation()! + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>()! .createNotificationChannel(const AndroidNotificationChannel( 'grouped channel id 2', 'grouped channel name 2', - description: 'grouped channel description 2', groupId: channelGroupId)); + description: 'grouped channel description 2', + groupId: channelGroupId)); await showDialog( context: context, @@ -2229,7 +2445,8 @@ class _HomePageState extends State { Future _deleteNotificationChannelGroup() async { const String channelGroupId = 'your channel group id'; await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation() + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() ?.deleteNotificationChannelGroup(channelGroupId); await showDialog( @@ -2249,20 +2466,22 @@ class _HomePageState extends State { } Future _startForegroundService() async { - const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails( - 'your channel id', 'your channel name', - channelDescription: 'your channel description', - importance: Importance.max, - priority: Priority.high, - ticker: 'ticker'); + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('your channel id', 'your channel name', + channelDescription: 'your channel description', + importance: Importance.max, + priority: Priority.high, + ticker: 'ticker'); await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation() + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() ?.startForegroundService(1, 'plain title', 'plain body', notificationDetails: androidNotificationDetails, payload: 'item x'); } Future _startForegroundServiceWithBlueBackgroundNotification() async { - const AndroidNotificationDetails androidPlatformChannelSpecifics = AndroidNotificationDetails( + const AndroidNotificationDetails androidPlatformChannelSpecifics = + AndroidNotificationDetails( 'your channel id', 'your channel name', channelDescription: 'color background channel description', @@ -2275,32 +2494,39 @@ class _HomePageState extends State { /// only using foreground service can color the background await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation() - ?.startForegroundService(1, 'colored background text title', 'colored background text body', - notificationDetails: androidPlatformChannelSpecifics, payload: 'item x'); + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.startForegroundService( + 1, 'colored background text title', 'colored background text body', + notificationDetails: androidPlatformChannelSpecifics, + payload: 'item x'); } Future _stopForegroundService() async { await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation() + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() ?.stopForegroundService(); } Future _createNotificationChannel() async { - const AndroidNotificationChannel androidNotificationChannel = AndroidNotificationChannel( + const AndroidNotificationChannel androidNotificationChannel = + AndroidNotificationChannel( 'your channel id 2', 'your channel name 2', description: 'your channel description 2', ); await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation() + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannel(androidNotificationChannel); await showDialog( context: context, builder: (BuildContext context) => AlertDialog( - content: Text('Channel with name ${androidNotificationChannel.name} ' - 'created'), + content: + Text('Channel with name ${androidNotificationChannel.name} ' + 'created'), actions: [ TextButton( onPressed: () { @@ -2314,14 +2540,17 @@ class _HomePageState extends State { Future _areNotifcationsEnabledOnAndroid() async { final bool? areEnabled = await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation() + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() ?.areNotificationsEnabled(); await showDialog( context: context, builder: (BuildContext context) => AlertDialog( content: Text(areEnabled == null ? 'ERROR: received null' - : (areEnabled ? 'Notifications are enabled' : 'Notifications are NOT enabled')), + : (areEnabled + ? 'Notifications are enabled' + : 'Notifications are NOT enabled')), actions: [ TextButton( onPressed: () { @@ -2371,7 +2600,8 @@ class _HomePageState extends State { Future _deleteNotificationChannel() async { const String channelId = 'your channel id 2'; await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation() + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() ?.deleteNotificationChannel(channelId); await showDialog( @@ -2391,7 +2621,8 @@ class _HomePageState extends State { } Future _getActiveNotifications() async { - final Widget activeNotificationsDialogContent = await _getActiveNotificationsDialogContent(); + final Widget activeNotificationsDialogContent = + await _getActiveNotificationsDialogContent(); await showDialog( context: context, builder: (BuildContext context) => AlertDialog( @@ -2420,7 +2651,7 @@ class _HomePageState extends State { } else if (Platform.isIOS) { final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); final IosDeviceInfo iosInfo = await deviceInfo.iosInfo; - final List fullVersion = iosInfo.systemVersion!.split('.'); + final List fullVersion = iosInfo.systemVersion.split('.'); if (fullVersion.isNotEmpty) { final int? version = int.tryParse(fullVersion[0]); if (version != null && version < 10) { @@ -2444,9 +2675,11 @@ class _HomePageState extends State { style: TextStyle(fontWeight: FontWeight.bold), ), const Divider(color: Colors.black), - if (activeNotifications!.isEmpty) const Text('No active notifications'), + if (activeNotifications!.isEmpty) + const Text('No active notifications'), if (activeNotifications.isNotEmpty) - for (final ActiveNotification activeNotification in activeNotifications) + for (final ActiveNotification activeNotification + in activeNotifications) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -2458,7 +2691,9 @@ class _HomePageState extends State { 'title: ${activeNotification.title}\n' 'body: ${activeNotification.body}', ), - if (Platform.isAndroid && activeNotification.id != null) + if (Platform.isAndroid && + activeNotification.id != null) ...[ + Text('bigText: ${activeNotification.bigText}'), TextButton( child: const Text('Get messaging style'), onPressed: () { @@ -2466,6 +2701,7 @@ class _HomePageState extends State { activeNotification.id!, activeNotification.tag); }, ), + ], const Divider(color: Colors.black), ], ), @@ -2483,9 +2719,11 @@ class _HomePageState extends State { Future _getActiveNotificationMessagingStyle(int id, String? tag) async { Widget dialogContent; try { - final MessagingStyleInformation? messagingStyle = await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation()! - .getActiveNotificationMessagingStyle(id, tag: tag); + final MessagingStyleInformation? messagingStyle = + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>()! + .getActiveNotificationMessagingStyle(id, tag: tag); if (messagingStyle == null) { dialogContent = const Text('No messaging style'); } else { @@ -2579,7 +2817,8 @@ class _HomePageState extends State { } Future _getNotificationChannels() async { - final Widget notificationChannelsDialogContent = await _getNotificationChannelsDialogContent(); + final Widget notificationChannelsDialogContent = + await _getNotificationChannelsDialogContent(); await showDialog( context: context, builder: (BuildContext context) => AlertDialog( @@ -2598,9 +2837,11 @@ class _HomePageState extends State { Future _getNotificationChannelsDialogContent() async { try { - final List? channels = await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation()! - .getNotificationChannels(); + final List? channels = + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>()! + .getNotificationChannels(); return Container( width: double.maxFinite, @@ -2629,7 +2870,8 @@ class _HomePageState extends State { 'vibrationPattern: ${channel.vibrationPattern}\n' 'showBadge: ${channel.showBadge}\n' 'enableLights: ${channel.enableLights}\n' - 'ledColor: ${channel.ledColor}\n'), + 'ledColor: ${channel.ledColor}\n' + 'audioAttributesUsage: ${channel.audioAttributesUsage}\n'), const Divider(color: Colors.black), ], ), @@ -2646,12 +2888,12 @@ class _HomePageState extends State { } Future _showNotificationWithNumber() async { - const AndroidNotificationDetails androidPlatformChannelSpecifics = AndroidNotificationDetails( - 'your channel id', 'your channel name', - channelDescription: 'your channel description', - importance: Importance.max, - priority: Priority.high, - number: 1); + const AndroidNotificationDetails androidPlatformChannelSpecifics = + AndroidNotificationDetails('your channel id', 'your channel name', + channelDescription: 'your channel description', + importance: Importance.max, + priority: Priority.high, + number: 1); const NotificationDetails platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics); await flutterLocalNotificationsPlugin.show( @@ -2660,7 +2902,8 @@ class _HomePageState extends State { } Future _showNotificationWithAudioAttributeAlarm() async { - const AndroidNotificationDetails androidPlatformChannelSpecifics = AndroidNotificationDetails( + const AndroidNotificationDetails androidPlatformChannelSpecifics = + AndroidNotificationDetails( 'your alarm channel id', 'your alarm channel name', channelDescription: 'your alarm channel description', @@ -2693,7 +2936,8 @@ Future _showLinuxNotificationWithBodyMarkup() async { } Future _showLinuxNotificationWithCategory() async { - const LinuxNotificationDetails linuxPlatformChannelSpecifics = LinuxNotificationDetails( + const LinuxNotificationDetails linuxPlatformChannelSpecifics = + LinuxNotificationDetails( category: LinuxNotificationCategory.emailArrived, ); const NotificationDetails notificationDetails = NotificationDetails( @@ -2715,7 +2959,8 @@ Future _showLinuxNotificationWithByteDataIcon() async { assetIcon.buffer.asUint8List().toList(), ); final Uint8List iconBytes = iconData!.getBytes(); - final LinuxNotificationDetails linuxPlatformChannelSpecifics = LinuxNotificationDetails( + final LinuxNotificationDetails linuxPlatformChannelSpecifics = + LinuxNotificationDetails( icon: ByteDataLinuxIcon( LinuxRawIconData( data: iconBytes, @@ -2753,7 +2998,8 @@ Future _showLinuxNotificationWithPathIcon(String path) async { } Future _showLinuxNotificationWithThemeIcon() async { - final LinuxNotificationDetails linuxPlatformChannelSpecifics = LinuxNotificationDetails( + final LinuxNotificationDetails linuxPlatformChannelSpecifics = + LinuxNotificationDetails( icon: ThemeLinuxIcon('media-eject'), ); final NotificationDetails notificationDetails = NotificationDetails( @@ -2768,7 +3014,8 @@ Future _showLinuxNotificationWithThemeIcon() async { } Future _showLinuxNotificationWithThemeSound() async { - final LinuxNotificationDetails linuxPlatformChannelSpecifics = LinuxNotificationDetails( + final LinuxNotificationDetails linuxPlatformChannelSpecifics = + LinuxNotificationDetails( sound: ThemeLinuxSound('message-new-email'), ); final NotificationDetails notificationDetails = NotificationDetails( @@ -2783,7 +3030,8 @@ Future _showLinuxNotificationWithThemeSound() async { } Future _showLinuxNotificationWithCriticalUrgency() async { - const LinuxNotificationDetails linuxPlatformChannelSpecifics = LinuxNotificationDetails( + const LinuxNotificationDetails linuxPlatformChannelSpecifics = + LinuxNotificationDetails( urgency: LinuxNotificationUrgency.critical, ); const NotificationDetails notificationDetails = NotificationDetails( @@ -2798,7 +3046,8 @@ Future _showLinuxNotificationWithCriticalUrgency() async { } Future _showLinuxNotificationWithTimeout() async { - final LinuxNotificationDetails linuxPlatformChannelSpecifics = LinuxNotificationDetails( + final LinuxNotificationDetails linuxPlatformChannelSpecifics = + LinuxNotificationDetails( timeout: LinuxNotificationTimeout.fromDuration( const Duration(seconds: 1), ), @@ -2815,7 +3064,8 @@ Future _showLinuxNotificationWithTimeout() async { } Future _showLinuxNotificationSuppressSound() async { - const LinuxNotificationDetails linuxPlatformChannelSpecifics = LinuxNotificationDetails( + const LinuxNotificationDetails linuxPlatformChannelSpecifics = + LinuxNotificationDetails( suppressSound: true, ); const NotificationDetails notificationDetails = NotificationDetails( @@ -2830,7 +3080,8 @@ Future _showLinuxNotificationSuppressSound() async { } Future _showLinuxNotificationTransient() async { - const LinuxNotificationDetails linuxPlatformChannelSpecifics = LinuxNotificationDetails( + const LinuxNotificationDetails linuxPlatformChannelSpecifics = + LinuxNotificationDetails( transient: true, ); const NotificationDetails notificationDetails = NotificationDetails( @@ -2845,7 +3096,8 @@ Future _showLinuxNotificationTransient() async { } Future _showLinuxNotificationResident() async { - const LinuxNotificationDetails linuxPlatformChannelSpecifics = LinuxNotificationDetails( + const LinuxNotificationDetails linuxPlatformChannelSpecifics = + LinuxNotificationDetails( resident: true, ); const NotificationDetails notificationDetails = NotificationDetails( @@ -2873,9 +3125,11 @@ Future _showLinuxNotificationDifferentLocation() async { ); } -Future getLinuxCapabilities() => flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation()! - .getCapabilities(); +Future getLinuxCapabilities() => + flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + LinuxFlutterLocalNotificationsPlugin>()! + .getCapabilities(); class SecondPage extends StatefulWidget { const SecondPage( diff --git a/flutter_local_notifications/example/pubspec.yaml b/flutter_local_notifications/example/pubspec.yaml index ea091aef6..57dfa6023 100644 --- a/flutter_local_notifications/example/pubspec.yaml +++ b/flutter_local_notifications/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: none dependencies: cupertino_icons: ^1.0.2 - device_info_plus: ^8.0.0 + device_info_plus: ^9.1.2 flutter: sdk: flutter flutter_local_notifications: @@ -22,14 +22,6 @@ dev_dependencies: integration_test: sdk: flutter -# The following overrides exist to ensure the example app builds with the latest code -# of these packages as part of CI -dependency_overrides: - flutter_local_notifications_linux: - path: ../../flutter_local_notifications_linux - flutter_local_notifications_platform_interface: - path: ../../flutter_local_notifications_platform_interface - flutter: uses-material-design: true assets: @@ -37,5 +29,5 @@ flutter: - sound/ environment: - sdk: ">=2.15.0 <3.0.0" - flutter: ">=2.8.0" + sdk: ^3.1.0 + flutter: ">=3.1.3" diff --git a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m index da6ffd87a..67813dae1 100644 --- a/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m +++ b/flutter_local_notifications/ios/Classes/FlutterLocalNotificationsPlugin.m @@ -22,6 +22,8 @@ @implementation FlutterLocalNotificationsPlugin { NSString *const SHOW_METHOD = @"show"; NSString *const ZONED_SCHEDULE_METHOD = @"zonedSchedule"; NSString *const PERIODICALLY_SHOW_METHOD = @"periodicallyShow"; +NSString *const PERIODICALLY_SHOW_WITH_DURATION_METHOD = + @"periodicallyShowWithDuration"; NSString *const CANCEL_METHOD = @"cancel"; NSString *const CANCEL_ALL_METHOD = @"cancelAll"; NSString *const PENDING_NOTIFICATIONS_REQUESTS_METHOD = @@ -79,6 +81,7 @@ @implementation FlutterLocalNotificationsPlugin { NSString *const BADGE_NUMBER = @"badgeNumber"; NSString *const MILLISECONDS_SINCE_EPOCH = @"millisecondsSinceEpoch"; NSString *const REPEAT_INTERVAL = @"repeatInterval"; +NSString *const REPEAT_INTERVAL_MILLISECODNS = @"repeatIntervalMilliseconds"; NSString *const SCHEDULED_DATE_TIME = @"scheduledDateTimeISO8601"; NSString *const TIME_ZONE_NAME = @"timeZoneName"; NSString *const MATCH_DATE_TIME_COMPONENTS = @"matchDateTimeComponents"; @@ -176,6 +179,9 @@ - (void)handleMethodCall:(FlutterMethodCall *)call [self zonedSchedule:call.arguments result:result]; } else if ([PERIODICALLY_SHOW_METHOD isEqualToString:call.method]) { [self periodicallyShow:call.arguments result:result]; + } else if ([PERIODICALLY_SHOW_WITH_DURATION_METHOD + isEqualToString:call.method]) { + [self periodicallyShow:call.arguments result:result]; } else if ([REQUEST_PERMISSIONS_METHOD isEqualToString:call.method]) { [self requestPermissions:call.arguments result:result]; } else if ([CHECK_PERMISSIONS_METHOD isEqualToString:call.method]) { @@ -815,6 +821,13 @@ - (void)periodicallyShow:(NSDictionary *_Nonnull)arguments [self buildStandardUILocalNotification:arguments]; #pragma clang diagnostic pop NSTimeInterval timeInterval = 0; + if ([self containsKey:REPEAT_INTERVAL_MILLISECODNS + forDictionary:arguments]) { + NSInteger repeatIntervalMilliseconds = + [arguments[REPEAT_INTERVAL_MILLISECODNS] integerValue]; + timeInterval = repeatIntervalMilliseconds / 1000.0; + notification.repeatInterval = NSCalendarUnitSecond; + } switch ([arguments[REPEAT_INTERVAL] integerValue]) { case EveryMinute: timeInterval = 60; @@ -1074,6 +1087,14 @@ - (UNCalendarNotificationTrigger *)buildUserNotificationCalendarTrigger: - (UNTimeIntervalNotificationTrigger *)buildUserNotificationTimeIntervalTrigger: (id)arguments API_AVAILABLE(ios(10.0)) { + + if ([self containsKey:REPEAT_INTERVAL_MILLISECODNS forDictionary:arguments]) { + NSInteger repeatIntervalMilliseconds = + [arguments[REPEAT_INTERVAL_MILLISECODNS] integerValue]; + return [UNTimeIntervalNotificationTrigger + triggerWithTimeInterval:repeatIntervalMilliseconds / 1000.0 + repeats:YES]; + } switch ([arguments[REPEAT_INTERVAL] integerValue]) { case EveryMinute: return [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:60 diff --git a/flutter_local_notifications/ios/Resources/PrivacyInfo.xcprivacy b/flutter_local_notifications/ios/Resources/PrivacyInfo.xcprivacy index a34b7e2e6..9bdc821a1 100644 --- a/flutter_local_notifications/ios/Resources/PrivacyInfo.xcprivacy +++ b/flutter_local_notifications/ios/Resources/PrivacyInfo.xcprivacy @@ -5,7 +5,16 @@ NSPrivacyTrackingDomains NSPrivacyAccessedAPITypes - + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + NSPrivacyCollectedDataTypes NSPrivacyTracking 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 49118a4c4..6a39f1c41 100644 --- a/flutter_local_notifications/lib/src/flutter_local_notifications_plugin.dart +++ b/flutter_local_notifications/lib/src/flutter_local_notifications_plugin.dart @@ -301,13 +301,6 @@ class FlutterLocalNotificationsPlugin { /// platform channel in yyyy-mm-dd hh:mm:ss format. Therefore, the precision /// is at the best to the second. /// - /// The [androidAllowWhileIdle] parameter determines if the notification - /// should still be shown at the exact time even when the device is in a - /// low-power idle mode. This parameter has been deprecated and will removed - /// in a future major release in favour of the [androidScheduledMode] - /// parameter that provides the same functionality in addition to being able - /// to schedule notifications with inexact timings. - /// /// The [uiLocalNotificationDateInterpretation] is for iOS versions older /// than 10 as the APIs have limited support for time zones. With this /// parameter, it is used to determine if the scheduled date should be @@ -339,9 +332,7 @@ class FlutterLocalNotificationsPlugin { NotificationDetails notificationDetails, { required UILocalNotificationDateInterpretation uiLocalNotificationDateInterpretation, - @Deprecated('Deprecated in favor of the androidScheduleMode parameter') - bool androidAllowWhileIdle = false, - AndroidScheduleMode? androidScheduleMode, + required AndroidScheduleMode androidScheduleMode, String? payload, DateTimeComponents? matchDateTimeComponents, }) async { @@ -352,14 +343,9 @@ class FlutterLocalNotificationsPlugin { await resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>()! .zonedSchedule( - id, - title, - body, - scheduledDate, - notificationDetails.android, + id, title, body, scheduledDate, notificationDetails.android, payload: payload, - scheduleMode: _chooseScheduleMode( - androidScheduleMode, androidAllowWhileIdle), + scheduleMode: androidScheduleMode, matchDateTimeComponents: matchDateTimeComponents); } else if (defaultTargetPlatform == TargetPlatform.iOS) { await resolvePlatformSpecificImplementation< @@ -388,18 +374,6 @@ class FlutterLocalNotificationsPlugin { /// notification will be an hour after the method has been called and /// then every hour after that. /// - /// If [androidAllowWhileIdle] is `false`, the Android `AlarmManager` APIs - /// are used to set a recurring inexact alarm that would present the - /// notification. This means that there may be delay in on when - /// notifications are displayed. If [androidAllowWhileIdle] is `true`, the - /// Android `AlarmManager` APIs are used to schedule a single notification - /// to be shown at the exact time even when the device is in a low-power idle - /// mode. After it is shown, the next one would be scheduled and this would - /// repeat. Note that this parameter has been deprecated and will removed in - /// future majorrelease in favour of the [androidScheduledMode] parameter that - /// provides the same functionality in addition to being able to schedule - /// notifications with inexact timings. - /// /// On Android, this will also require additional setup for the app, /// especially in the app's `AndroidManifest.xml` file. Please see check the /// readme for further details. @@ -409,10 +383,8 @@ class FlutterLocalNotificationsPlugin { String? body, RepeatInterval repeatInterval, NotificationDetails notificationDetails, { + required AndroidScheduleMode androidScheduleMode, String? payload, - @Deprecated('Deprecated in favor of the androidScheduleMode parameter') - bool androidAllowWhileIdle = false, - AndroidScheduleMode? androidScheduleMode, }) async { if (kIsWeb) { return; @@ -423,8 +395,7 @@ class FlutterLocalNotificationsPlugin { ?.periodicallyShow(id, title, body, repeatInterval, notificationDetails: notificationDetails.android, payload: payload, - scheduleMode: _chooseScheduleMode( - androidScheduleMode, androidAllowWhileIdle)); + scheduleMode: androidScheduleMode); } else if (defaultTargetPlatform == TargetPlatform.iOS) { await resolvePlatformSpecificImplementation< IOSFlutterLocalNotificationsPlugin>() @@ -441,12 +412,54 @@ class FlutterLocalNotificationsPlugin { } } - AndroidScheduleMode _chooseScheduleMode( - AndroidScheduleMode? scheduleMode, bool allowWhileIdle) => - scheduleMode ?? - (allowWhileIdle - ? AndroidScheduleMode.exactAllowWhileIdle - : AndroidScheduleMode.exact); + /// Periodically show a notification using the specified custom duration + /// interval. + /// + /// For example, specifying a 5 minutes repeat duration interval means + /// the first time the notification will be an 5 minutes after the method + /// has been called and then every 5 minutes after that. + /// + /// On Android, this will also require additional setup for the app, + /// especially in the app's `AndroidManifest.xml` file. Please see check the + /// readme for further details. + Future periodicallyShowWithDuration( + int id, + String? title, + String? body, + Duration repeatDurationInterval, + NotificationDetails notificationDetails, { + AndroidScheduleMode androidScheduleMode = AndroidScheduleMode.exact, + String? payload, + }) async { + if (kIsWeb) { + return; + } + if (defaultTargetPlatform == TargetPlatform.android) { + await resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.periodicallyShowWithDuration( + id, title, body, repeatDurationInterval, + notificationDetails: notificationDetails.android, + payload: payload, + scheduleMode: androidScheduleMode); + } else if (defaultTargetPlatform == TargetPlatform.iOS) { + await resolvePlatformSpecificImplementation< + IOSFlutterLocalNotificationsPlugin>() + ?.periodicallyShowWithDuration( + id, title, body, repeatDurationInterval, + notificationDetails: notificationDetails.iOS, payload: payload); + } else if (defaultTargetPlatform == TargetPlatform.macOS) { + await resolvePlatformSpecificImplementation< + MacOSFlutterLocalNotificationsPlugin>() + ?.periodicallyShowWithDuration( + id, title, body, repeatDurationInterval, + notificationDetails: notificationDetails.macOS, payload: payload); + } else { + await FlutterLocalNotificationsPlatform.instance + .periodicallyShowWithDuration( + id, title, body, repeatDurationInterval); + } + } /// Returns a list of notifications pending to be delivered/shown. Future> pendingNotificationRequests() => 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 c0e4a9863..595fd78c4 100644 --- a/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart +++ b/flutter_local_notifications/lib/src/platform_flutter_local_notifications.dart @@ -106,6 +106,7 @@ class MethodChannelFlutterLocalNotificationsPlugin title: p['title'], body: p['body'], payload: p['payload'], + bigText: p['bigText'], )) .toList() ?? []; @@ -162,6 +163,20 @@ class AndroidFlutterLocalNotificationsPlugin Future requestExactAlarmsPermission() async => _channel.invokeMethod('requestExactAlarmsPermission'); + /// Requests the permission to send/use full-screen intents. + /// + /// Returns whether the permission was granted. + /// + /// Use this when your application requires the [`USE_FULL_SCREEN_INTENT`](https://developer.android.com/reference/android/Manifest.permission#SCHEDULE_EXACT_ALARM) + /// permission and targets Android 14 or higher. The reason for this is that + /// the permission is granted by default. However, applications that do not + /// have calling or alarm functionalities have the permission revoked by the + /// Google Play Store. See + /// [here](https://source.android.com/docs/core/permissions/fsi-limits) + /// for official Android documentation. + Future requestFullScreenIntentPermission() async => + _channel.invokeMethod('requestFullScreenIntentPermission'); + /// Requests the permission for sending notifications. Returns whether the /// permission was granted. /// @@ -354,6 +369,31 @@ class AndroidFlutterLocalNotificationsPlugin }); } + @override + Future periodicallyShowWithDuration( + int id, + String? title, + String? body, + Duration repeatDurationInterval, { + AndroidNotificationDetails? notificationDetails, + String? payload, + AndroidScheduleMode scheduleMode = AndroidScheduleMode.exact, + }) async { + validateId(id); + validateRepeatDurationInterval(repeatDurationInterval); + await _channel + .invokeMethod('periodicallyShowWithDuration', { + 'id': id, + 'title': title, + 'body': body, + 'calledAt': clock.now().millisecondsSinceEpoch, + 'repeatIntervalMilliseconds': repeatDurationInterval.inMilliseconds, + 'platformSpecifics': + _buildPlatformSpecifics(notificationDetails, scheduleMode), + 'payload': payload ?? '', + }); + } + Map _buildPlatformSpecifics( AndroidNotificationDetails? notificationDetails, AndroidScheduleMode scheduleMode, @@ -499,6 +539,11 @@ class AndroidFlutterLocalNotificationsPlugin enableVibration: a['enableVibration'], vibrationPattern: a['vibrationPattern'], ledColor: Color(a['ledColor']), + audioAttributesUsage: AudioAttributesUsage.values.firstWhere( + // ignore: always_specify_types + (e) => e.value == a['audioAttributesUsage'], + orElse: () => AudioAttributesUsage.notification, + ), )) .toList(); } @@ -735,6 +780,29 @@ class IOSFlutterLocalNotificationsPlugin }); } + @override + Future periodicallyShowWithDuration( + int id, + String? title, + String? body, + Duration repeatDurationInterval, { + DarwinNotificationDetails? notificationDetails, + String? payload, + }) async { + validateId(id); + validateRepeatDurationInterval(repeatDurationInterval); + await _channel + .invokeMethod('periodicallyShowWithDuration', { + 'id': id, + 'title': title, + 'body': body, + 'calledAt': clock.now().millisecondsSinceEpoch, + 'repeatIntervalMilliseconds': repeatDurationInterval.inMilliseconds, + 'platformSpecifics': notificationDetails?.toMap(), + 'payload': payload ?? '' + }); + } + Future _handleMethod(MethodCall call) async { switch (call.method) { case 'didReceiveNotificationResponse': @@ -907,6 +975,29 @@ class MacOSFlutterLocalNotificationsPlugin }); } + @override + Future periodicallyShowWithDuration( + int id, + String? title, + String? body, + Duration repeatDurationInterval, { + DarwinNotificationDetails? notificationDetails, + String? payload, + }) async { + validateId(id); + validateRepeatDurationInterval(repeatDurationInterval); + await _channel + .invokeMethod('periodicallyShowWithDuration', { + 'id': id, + 'title': title, + 'body': body, + 'calledAt': clock.now().millisecondsSinceEpoch, + 'repeatIntervalMilliseconds': repeatDurationInterval.inMilliseconds, + 'platformSpecifics': notificationDetails?.toMap(), + 'payload': payload ?? '' + }); + } + Future _handleMethod(MethodCall call) async { switch (call.method) { case 'didReceiveNotificationResponse': diff --git a/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart b/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart index 0c7db4f91..cc1fa1994 100644 --- a/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart +++ b/flutter_local_notifications/lib/src/platform_specifics/android/method_channel_mappers.dart @@ -53,6 +53,7 @@ extension AndroidNotificationChannelMapper on AndroidNotificationChannel { 'ledColorRed': ledColor?.red, 'ledColorGreen': ledColor?.green, 'ledColorBlue': ledColor?.blue, + 'audioAttributesUsage': audioAttributesUsage.value, 'channelAction': AndroidNotificationChannelAction.createIfNotExists.index, }..addAll(_convertNotificationSoundToMap(sound)); diff --git a/flutter_local_notifications/lib/src/platform_specifics/android/notification_channel.dart b/flutter_local_notifications/lib/src/platform_specifics/android/notification_channel.dart index f580efca0..a240d8c86 100644 --- a/flutter_local_notifications/lib/src/platform_specifics/android/notification_channel.dart +++ b/flutter_local_notifications/lib/src/platform_specifics/android/notification_channel.dart @@ -20,6 +20,7 @@ class AndroidNotificationChannel { this.showBadge = true, this.enableLights = false, this.ledColor, + this.audioAttributesUsage = AudioAttributesUsage.notification, }); /// The channel's id. @@ -80,4 +81,9 @@ class AndroidNotificationChannel { /// Whether notifications posted to this channel can appear as application /// icon badges in a Launcher final bool showBadge; + + /// The attribute describing what is the intended use of the audio signal, + /// such as alarm or ringtone set in [`AudioAttributes.Builder`](https://developer.android.com/reference/android/media/AudioAttributes.Builder#setUsage(int)) + /// https://developer.android.com/reference/android/media/AudioAttributes + final AudioAttributesUsage audioAttributesUsage; } diff --git a/flutter_local_notifications/lib/src/platform_specifics/android/notification_details.dart b/flutter_local_notifications/lib/src/platform_specifics/android/notification_details.dart index 820395fa4..c3a23f1d2 100644 --- a/flutter_local_notifications/lib/src/platform_specifics/android/notification_details.dart +++ b/flutter_local_notifications/lib/src/platform_specifics/android/notification_details.dart @@ -334,8 +334,9 @@ class AndroidNotificationDetails { /// soon as it triggers. /// /// Note: The system UI may choose to display a heads-up notification, - /// instead of launching your full-screen intent, while the user is using the - /// device. When the full-screen intent occurs, the plugin will act as though + /// instead of launching your full-screen intent. This can occur while the + /// user is using the device or due the full-screen intent not being granted. + /// When the full-screen intent occurs, the plugin will act as though /// the user has tapped on a notification so handle it the same way /// (e.g. via `onSelectNotification` callback) to display the appropriate /// page for your application. diff --git a/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift b/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift index 8da38dd8e..29dee49c7 100644 --- a/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift +++ b/flutter_local_notifications/macos/Classes/FlutterLocalNotificationsPlugin.swift @@ -38,6 +38,7 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot static let platformSpecifics = "platformSpecifics" static let badgeNumber = "badgeNumber" static let repeatInterval = "repeatInterval" + static let repeatIntervalMilliseconds = "repeatIntervalMilliseconds" static let attachments = "attachments" static let identifier = "identifier" static let filePath = "filePath" @@ -217,6 +218,8 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot zonedSchedule(call, result) case "periodicallyShow": periodicallyShow(call, result) + case "periodicallyShowWithDuration": + periodicallyShow(call, result) default: result(FlutterMethodNotImplemented) } @@ -491,21 +494,29 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot } else { let arguments = call.arguments as! [String: AnyObject] let notification = buildNSUserNotification(fromArguments: arguments) - let rawRepeatInterval = arguments[MethodCallArguments.repeatInterval] as! Int - let repeatInterval = RepeatInterval.init(rawValue: rawRepeatInterval)! - switch repeatInterval { - case .everyMinute: - notification.deliveryDate = Date.init(timeIntervalSinceNow: 60) - notification.deliveryRepeatInterval = DateComponents.init(minute: 1) - case .hourly: - notification.deliveryDate = Date.init(timeIntervalSinceNow: 60 * 60) - notification.deliveryRepeatInterval = DateComponents.init(hour: 1) - case .daily: - notification.deliveryDate = Date.init(timeIntervalSinceNow: 60 * 60 * 24) - notification.deliveryRepeatInterval = DateComponents.init(day: 1) - case .weekly: - notification.deliveryDate = Date.init(timeIntervalSinceNow: 60 * 60 * 24 * 7) - notification.deliveryRepeatInterval = DateComponents.init(weekOfYear: 1) + let rawRepeatInterval = arguments[MethodCallArguments.repeatInterval] as? Int + let repeatIntervalMilliseconds = arguments[MethodCallArguments.repeatIntervalMilliseconds] as? Int + + if rawRepeatInterval != nil { + let repeatInterval = RepeatInterval.init(rawValue: rawRepeatInterval!)! + switch repeatInterval { + case .everyMinute: + notification.deliveryDate = Date.init(timeIntervalSinceNow: 60) + notification.deliveryRepeatInterval = DateComponents.init(minute: 1) + case .hourly: + notification.deliveryDate = Date.init(timeIntervalSinceNow: 60 * 60) + notification.deliveryRepeatInterval = DateComponents.init(hour: 1) + case .daily: + notification.deliveryDate = Date.init(timeIntervalSinceNow: 60 * 60 * 24) + notification.deliveryRepeatInterval = DateComponents.init(day: 1) + case .weekly: + notification.deliveryDate = Date.init(timeIntervalSinceNow: 60 * 60 * 24 * 7) + notification.deliveryRepeatInterval = DateComponents.init(weekOfYear: 1) + } + } else if repeatIntervalMilliseconds != nil { + let repeatIntervalSeconds = repeatIntervalMilliseconds! / 1000 + notification.deliveryDate = Date.init(timeIntervalSinceNow: TimeInterval(repeatIntervalSeconds)) + notification.deliveryRepeatInterval = DateComponents.init(second: repeatIntervalSeconds) } NSUserNotificationCenter.default.scheduleNotification(notification) result(nil) @@ -632,19 +643,25 @@ public class FlutterLocalNotificationsPlugin: NSObject, FlutterPlugin, UNUserNot @available(macOS 10.14, *) func buildUserNotificationTimeIntervalTrigger(fromArguments arguments: [String: AnyObject]) -> UNTimeIntervalNotificationTrigger { - let rawRepeatInterval = arguments[MethodCallArguments.repeatInterval] as! Int - let repeatInterval = RepeatInterval.init(rawValue: rawRepeatInterval)! - switch repeatInterval { - case .everyMinute: - return UNTimeIntervalNotificationTrigger.init(timeInterval: 60, repeats: true) - case .hourly: - return UNTimeIntervalNotificationTrigger.init(timeInterval: 60 * 60, repeats: true) - case .daily: - return UNTimeIntervalNotificationTrigger.init(timeInterval: 60 * 60 * 24, repeats: true) - case .weekly: - return UNTimeIntervalNotificationTrigger.init(timeInterval: 60 * 60 * 24 * 7, repeats: true) - } + let rawRepeatInterval = arguments[MethodCallArguments.repeatInterval] as? Int + let repeatIntervalMilliseconds = arguments[MethodCallArguments.repeatIntervalMilliseconds] as? Int + if rawRepeatInterval != nil { + let repeatInterval = RepeatInterval.init(rawValue: rawRepeatInterval!)! + switch repeatInterval { + case .everyMinute: + return UNTimeIntervalNotificationTrigger.init(timeInterval: 60, repeats: true) + case .hourly: + return UNTimeIntervalNotificationTrigger.init(timeInterval: 60 * 60, repeats: true) + case .daily: + return UNTimeIntervalNotificationTrigger.init(timeInterval: 60 * 60 * 24, repeats: true) + case .weekly: + return UNTimeIntervalNotificationTrigger.init(timeInterval: 60 * 60 * 24 * 7, repeats: true) + } + } else { + let repeatIntervalSeconds = repeatIntervalMilliseconds! / 1000 + return UNTimeIntervalNotificationTrigger.init(timeInterval: TimeInterval(repeatIntervalSeconds), repeats: true) + } } @available(macOS 10.14, *) diff --git a/flutter_local_notifications/pubspec.yaml b/flutter_local_notifications/pubspec.yaml index c262ea63c..86a3df5ce 100644 --- a/flutter_local_notifications/pubspec.yaml +++ b/flutter_local_notifications/pubspec.yaml @@ -2,7 +2,7 @@ name: flutter_local_notifications description: A cross platform plugin for displaying and scheduling local notifications for Flutter applications with the ability to customise for each platform. -version: 16.3.2 +version: 17.2.1+2 homepage: https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications issue_tracker: https://github.com/MaikuB/flutter_local_notifications/issues @@ -12,11 +12,11 @@ dependencies: sdk: flutter flutter_local_notifications_linux: git: - url: https://github.com/remcoanker/flutter_local_notifications.git + url: https://github.com/Bundeling/flutter_local_notifications.git path: flutter_local_notifications_linux flutter_local_notifications_platform_interface: git: - url: https://github.com/remcoanker/flutter_local_notifications.git + url: https://github.com/Bundeling/flutter_local_notifications.git path: flutter_local_notifications_platform_interface timezone: ^0.9.0 @@ -42,5 +42,5 @@ flutter: default_package: flutter_local_notifications_linux environment: - sdk: ">=2.17.0 <4.0.0" - flutter: ">=3.0.0" + sdk: ^3.1.0 + flutter: ">=3.1.3" diff --git a/flutter_local_notifications/test/android_flutter_local_notifications_test.dart b/flutter_local_notifications/test/android_flutter_local_notifications_test.dart index 9af7cbf64..3c97c1c11 100644 --- a/flutter_local_notifications/test/android_flutter_local_notifications_test.dart +++ b/flutter_local_notifications/test/android_flutter_local_notifications_test.dart @@ -1954,6 +1954,7 @@ void main() { 'notification body', repeatInterval, const NotificationDetails(android: androidNotificationDetails), + androidScheduleMode: AndroidScheduleMode.exact, ); expect( @@ -2029,6 +2030,140 @@ void main() { } }); + group('periodicallyShowWithDuration', () { + final DateTime now = DateTime(2023, 12, 29); + + const Duration thirtySeconds = Duration(seconds: 30); + test('$thirtySeconds', () async { + await withClock(Clock.fixed(now), () async { + const AndroidInitializationSettings androidInitializationSettings = + AndroidInitializationSettings('app_icon'); + const InitializationSettings initializationSettings = + InitializationSettings(android: androidInitializationSettings); + await flutterLocalNotificationsPlugin + .initialize(initializationSettings); + + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('channelId', 'channelName', + channelDescription: 'channelDescription'); + + expect( + () async => await flutterLocalNotificationsPlugin + .periodicallyShowWithDuration( + 1, + 'notification title', + 'notification body', + thirtySeconds, + const NotificationDetails( + android: androidNotificationDetails), + ), + throwsA(isA())); + }); + }); + + final List repeatDurationIntervals = [ + const Duration(minutes: 1), + const Duration(minutes: 15), + const Duration(hours: 5), + const Duration(days: 30) + ]; + + for (final Duration repeatDurationInterval in repeatDurationIntervals) { + test('$repeatDurationInterval', () async { + await withClock(Clock.fixed(now), () async { + const AndroidInitializationSettings androidInitializationSettings = + AndroidInitializationSettings('app_icon'); + const InitializationSettings initializationSettings = + InitializationSettings(android: androidInitializationSettings); + await flutterLocalNotificationsPlugin + .initialize(initializationSettings); + + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('channelId', 'channelName', + channelDescription: 'channelDescription'); + await flutterLocalNotificationsPlugin.periodicallyShowWithDuration( + 1, + 'notification title', + 'notification body', + repeatDurationInterval, + const NotificationDetails(android: androidNotificationDetails), + ); + + expect( + log.last, + isMethodCall('periodicallyShowWithDuration', + arguments: { + 'id': 1, + 'title': 'notification title', + 'body': 'notification body', + 'payload': '', + 'calledAt': now.millisecondsSinceEpoch, + 'repeatIntervalMilliseconds': + repeatDurationInterval.inMilliseconds, + 'platformSpecifics': { + 'scheduleMode': 'exact', + 'icon': null, + 'channelId': 'channelId', + 'channelName': 'channelName', + 'channelDescription': 'channelDescription', + 'channelShowBadge': true, + 'channelAction': AndroidNotificationChannelAction + .createIfNotExists.index, + 'importance': Importance.defaultImportance.value, + 'priority': Priority.defaultPriority.value, + 'playSound': true, + 'enableVibration': true, + 'vibrationPattern': null, + 'groupKey': null, + 'setAsGroupSummary': false, + 'groupAlertBehavior': GroupAlertBehavior.all.index, + 'autoCancel': true, + 'ongoing': false, + 'silent': false, + 'colorAlpha': null, + 'colorRed': null, + 'colorGreen': null, + 'colorBlue': null, + 'onlyAlertOnce': false, + 'showWhen': true, + 'when': null, + 'usesChronometer': false, + 'chronometerCountDown': false, + 'showProgress': false, + 'maxProgress': 0, + 'progress': 0, + 'indeterminate': false, + 'enableLights': false, + 'ledColorAlpha': null, + 'ledColorRed': null, + 'ledColorGreen': null, + 'ledColorBlue': null, + 'ledOnMs': null, + 'ledOffMs': null, + 'ticker': null, + 'visibility': null, + 'timeoutAfter': null, + 'category': null, + 'additionalFlags': null, + 'fullScreenIntent': false, + 'shortcutId': null, + 'subText': null, + 'style': AndroidNotificationStyle.defaultStyle.index, + 'styleInformation': { + 'htmlFormatContent': false, + 'htmlFormatTitle': false, + }, + 'tag': null, + 'colorized': false, + 'number': null, + 'audioAttributesUsage': 5, + }, + })); + }); + }); + } + }); + group('zonedSchedule', () { test('no repeat frequency', () async { const AndroidInitializationSettings androidInitializationSettings = @@ -2359,8 +2494,10 @@ void main() { .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>()! .createNotificationChannel(const AndroidNotificationChannel( - 'channelId', 'channelName', - description: 'channelDescription')); + 'channelId', + 'channelName', + description: 'channelDescription', + )); expect(log, [ isMethodCall('createNotificationChannel', arguments: { 'id': 'channelId', @@ -2377,6 +2514,7 @@ void main() { 'ledColorRed': null, 'ledColorGreen': null, 'ledColorBlue': null, + 'audioAttributesUsage': AudioAttributesUsage.notification.value, 'channelAction': AndroidNotificationChannelAction.createIfNotExists.index, }) @@ -2398,6 +2536,7 @@ void main() { enableLights: true, enableVibration: false, ledColor: Color.fromARGB(255, 255, 0, 0), + audioAttributesUsage: AudioAttributesUsage.alarm, )); expect(log, [ isMethodCall('createNotificationChannel', arguments: { @@ -2415,6 +2554,7 @@ void main() { 'ledColorRed': 255, 'ledColorGreen': 0, 'ledColorBlue': 0, + 'audioAttributesUsage': AudioAttributesUsage.alarm.value, 'channelAction': AndroidNotificationChannelAction.createIfNotExists.index, }) diff --git a/flutter_local_notifications/test/ios_flutter_local_notifications_test.dart b/flutter_local_notifications/test/ios_flutter_local_notifications_test.dart index f4f2af86f..c788ce55b 100644 --- a/flutter_local_notifications/test/ios_flutter_local_notifications_test.dart +++ b/flutter_local_notifications/test/ios_flutter_local_notifications_test.dart @@ -304,24 +304,151 @@ void main() { ); await flutterLocalNotificationsPlugin.periodicallyShow( + 1, + 'notification title', + 'notification body', + repeatInterval, + notificationDetails, + androidScheduleMode: AndroidScheduleMode.exact); + + expect( + log.last, + isMethodCall( + 'periodicallyShow', + arguments: { + 'id': 1, + 'title': 'notification title', + 'body': 'notification body', + 'payload': '', + 'calledAt': now.millisecondsSinceEpoch, + 'repeatInterval': repeatInterval.index, + 'platformSpecifics': { + 'presentAlert': true, + 'presentBadge': true, + 'presentSound': true, + 'presentBanner': true, + 'presentList': true, + 'subtitle': null, + 'sound': 'sound.mp3', + 'badgeNumber': 1, + 'threadIdentifier': null, + 'attachments': >[ + { + 'filePath': 'video.mp4', + 'identifier': '2b3f705f-a680-4c9f-8075-a46a70e28373', + 'hideThumbnail': null, + 'thumbnailClippingRect': null, + } + ], + 'categoryIdentifier': null, + 'interruptionLevel': null, + }, + }, + ), + ); + }); + }); + } + }); + + group('periodicallyShowWithDuration', () { + final DateTime now = DateTime(2023, 12, 29); + + const Duration thirtySeconds = Duration(seconds: 30); + test('$thirtySeconds', () async { + await withClock(Clock.fixed(now), () async { + const DarwinInitializationSettings iosInitializationSettings = + DarwinInitializationSettings(); + const InitializationSettings initializationSettings = + InitializationSettings(iOS: iosInitializationSettings); + await flutterLocalNotificationsPlugin + .initialize(initializationSettings); + + const NotificationDetails notificationDetails = NotificationDetails( + iOS: DarwinNotificationDetails( + presentAlert: true, + presentBadge: true, + presentSound: true, + presentBanner: true, + presentList: true, + sound: 'sound.mp3', + badgeNumber: 1, + attachments: [ + DarwinNotificationAttachment( + 'video.mp4', + identifier: '2b3f705f-a680-4c9f-8075-a46a70e28373', + ) + ], + ), + ); + + expect( + () async => await flutterLocalNotificationsPlugin + .periodicallyShowWithDuration( + 1, + 'notification title', + 'notification body', + thirtySeconds, + notificationDetails, + ), + throwsA(isA())); + }); + }); + + final List repeatDurationIntervals = [ + const Duration(minutes: 1), + const Duration(minutes: 15), + const Duration(hours: 5), + const Duration(days: 30) + ]; + for (final Duration repeatDurationInterval in repeatDurationIntervals) { + test('$repeatDurationInterval', () async { + await withClock(Clock.fixed(now), () async { + const DarwinInitializationSettings iosInitializationSettings = + DarwinInitializationSettings(); + const InitializationSettings initializationSettings = + InitializationSettings(iOS: iosInitializationSettings); + await flutterLocalNotificationsPlugin + .initialize(initializationSettings); + + const NotificationDetails notificationDetails = NotificationDetails( + iOS: DarwinNotificationDetails( + presentAlert: true, + presentBadge: true, + presentSound: true, + presentBanner: true, + presentList: true, + sound: 'sound.mp3', + badgeNumber: 1, + attachments: [ + DarwinNotificationAttachment( + 'video.mp4', + identifier: '2b3f705f-a680-4c9f-8075-a46a70e28373', + ) + ], + ), + ); + + await flutterLocalNotificationsPlugin.periodicallyShowWithDuration( 1, 'notification title', 'notification body', - repeatInterval, + repeatDurationInterval, notificationDetails, ); expect( log.last, isMethodCall( - 'periodicallyShow', + 'periodicallyShowWithDuration', arguments: { 'id': 1, 'title': 'notification title', 'body': 'notification body', 'payload': '', 'calledAt': now.millisecondsSinceEpoch, - 'repeatInterval': repeatInterval.index, + 'repeatIntervalMilliseconds': + repeatDurationInterval.inMilliseconds, 'platformSpecifics': { 'presentAlert': true, 'presentBadge': true, diff --git a/flutter_local_notifications/test/macos_flutter_local_notifications_test.dart b/flutter_local_notifications/test/macos_flutter_local_notifications_test.dart index 08ce44f62..9db86bc42 100644 --- a/flutter_local_notifications/test/macos_flutter_local_notifications_test.dart +++ b/flutter_local_notifications/test/macos_flutter_local_notifications_test.dart @@ -217,6 +217,7 @@ void main() { 'notification body', repeatInterval, notificationDetails, + androidScheduleMode: AndroidScheduleMode.exact, ); expect( @@ -255,6 +256,132 @@ void main() { } }); + group('periodicallyShowWithDuration', () { + final DateTime now = DateTime(2023, 12, 29); + + const Duration thirtySeconds = Duration(seconds: 30); + test('$thirtySeconds', () async { + await withClock(Clock.fixed(now), () async { + const DarwinInitializationSettings macOSInitializationSettings = + DarwinInitializationSettings(); + const InitializationSettings initializationSettings = + InitializationSettings(macOS: macOSInitializationSettings); + await flutterLocalNotificationsPlugin + .initialize(initializationSettings); + + const NotificationDetails notificationDetails = NotificationDetails( + macOS: DarwinNotificationDetails( + presentAlert: true, + presentBadge: true, + presentSound: true, + presentBanner: true, + presentList: true, + sound: 'sound.mp3', + badgeNumber: 1, + attachments: [ + DarwinNotificationAttachment( + 'video.mp4', + identifier: '2b3f705f-a680-4c9f-8075-a46a70e28373', + ) + ], + ), + ); + + expect( + () async => await flutterLocalNotificationsPlugin + .periodicallyShowWithDuration( + 1, + 'notification title', + 'notification body', + thirtySeconds, + notificationDetails, + ), + throwsA(isA())); + }); + }); + + final List repeatDurationIntervals = [ + const Duration(minutes: 1), + const Duration(minutes: 15), + const Duration(hours: 5), + const Duration(days: 30) + ]; + + for (final Duration repeatDurationInterval in repeatDurationIntervals) { + test('$repeatDurationInterval', () async { + await withClock(Clock.fixed(now), () async { + const DarwinInitializationSettings macOSInitializationSettings = + DarwinInitializationSettings(); + const InitializationSettings initializationSettings = + InitializationSettings(macOS: macOSInitializationSettings); + await flutterLocalNotificationsPlugin + .initialize(initializationSettings); + + const NotificationDetails notificationDetails = NotificationDetails( + macOS: DarwinNotificationDetails( + presentAlert: true, + presentBadge: true, + presentSound: true, + presentBanner: true, + presentList: true, + sound: 'sound.mp3', + badgeNumber: 1, + attachments: [ + DarwinNotificationAttachment( + 'video.mp4', + identifier: '2b3f705f-a680-4c9f-8075-a46a70e28373', + ) + ], + ), + ); + + await flutterLocalNotificationsPlugin.periodicallyShowWithDuration( + 1, + 'notification title', + 'notification body', + repeatDurationInterval, + notificationDetails, + ); + + expect( + log.last, + isMethodCall('periodicallyShowWithDuration', + arguments: { + 'id': 1, + 'title': 'notification title', + 'body': 'notification body', + 'payload': '', + 'calledAt': now.millisecondsSinceEpoch, + 'repeatIntervalMilliseconds': + repeatDurationInterval.inMilliseconds, + 'platformSpecifics': { + 'presentAlert': true, + 'presentBadge': true, + 'presentSound': true, + 'presentBanner': true, + 'presentList': true, + 'subtitle': null, + 'sound': 'sound.mp3', + 'badgeNumber': 1, + 'threadIdentifier': null, + 'attachments': >[ + { + 'filePath': 'video.mp4', + 'identifier': + '2b3f705f-a680-4c9f-8075-a46a70e28373', + 'hideThumbnail': null, + 'thumbnailClippingRect': null, + } + ], + 'categoryIdentifier': null, + 'interruptionLevel': null, + }, + })); + }); + }); + } + }); + group('zonedSchedule', () { test('no repeat frequency', () async { const DarwinInitializationSettings macOSInitializationSettings = diff --git a/flutter_local_notifications_linux/CHANGELOG.md b/flutter_local_notifications_linux/CHANGELOG.md index 631d78ab0..9574ac2e5 100644 --- a/flutter_local_notifications_linux/CHANGELOG.md +++ b/flutter_local_notifications_linux/CHANGELOG.md @@ -1,3 +1,11 @@ +## [vNext] + +* **Breaking change** Bumped minimum Flutter SDK requirement to 3.13 + +## [4.0.1] + +* Fixed issue [#2368](https://github.com/MaikuB/flutter_local_notifications/issues/2368). This involved updating pubspec so it defines that it implements the Linux implementation of `flutter_local_notifications` and updating the code so it registers the Linux implementation + ## [4.0.0+1] * Bumped maximum Dart SDK constraint diff --git a/flutter_local_notifications_linux/lib/src/flutter_local_notifications.dart b/flutter_local_notifications_linux/lib/src/flutter_local_notifications.dart index 6bd185b6f..a7c89362d 100644 --- a/flutter_local_notifications_linux/lib/src/flutter_local_notifications.dart +++ b/flutter_local_notifications_linux/lib/src/flutter_local_notifications.dart @@ -20,6 +20,12 @@ class LinuxFlutterLocalNotificationsPlugin LinuxNotificationManager manager, ) : _manager = manager; + /// Registers the Linux implementation. + static void registerWith() { + FlutterLocalNotificationsPlatform.instance = + LinuxFlutterLocalNotificationsPlugin(); + } + final LinuxNotificationManager _manager; /// Initializes the plugin. diff --git a/flutter_local_notifications_linux/pubspec.yaml b/flutter_local_notifications_linux/pubspec.yaml index a91f2fbcb..5c724291e 100644 --- a/flutter_local_notifications_linux/pubspec.yaml +++ b/flutter_local_notifications_linux/pubspec.yaml @@ -1,8 +1,16 @@ name: flutter_local_notifications_linux description: Linux implementation of the flutter_local_notifications plugin -version: 4.0.0+1 +version: 4.0.1 homepage: https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications +flutter: + plugin: + implements: flutter_local_notifications + platforms: + linux: + dartPluginClass: LinuxFlutterLocalNotificationsPlugin + + dependencies: dbus: ^0.7.8 ffi: ^2.0.1 @@ -22,5 +30,5 @@ dev_dependencies: mockito: ^5.3.2 environment: - sdk: ">=2.17.0 <4.0.0" - flutter: ">=3.0.0" + sdk: ^3.1.0 + flutter: ">=3.1.3" diff --git a/flutter_local_notifications_linux/test/notifications_manager_test.dart b/flutter_local_notifications_linux/test/notifications_manager_test.dart index a67aff759..4e89c0620 100644 --- a/flutter_local_notifications_linux/test/notifications_manager_test.dart +++ b/flutter_local_notifications_linux/test/notifications_manager_test.dart @@ -65,13 +65,13 @@ void main() { ).thenAnswer((_) async => platformInfo); when( mockStorage.forceReloadCache(), - ).thenAnswer((_) async => {}); + ).thenAnswer((_) async {}); when( mockDbus.build( destination: 'org.freedesktop.Notifications', path: '/org/freedesktop/Notifications', ), - ).thenAnswer((_) => {}); + ).thenAnswer((_) {}); when( mockDbus.subscribeSignal('ActionInvoked'), ).thenAnswer((_) => mockActionInvokedSignal); diff --git a/flutter_local_notifications_linux/test/storage_test.dart b/flutter_local_notifications_linux/test/storage_test.dart index 634de78f6..61ead7273 100644 --- a/flutter_local_notifications_linux/test/storage_test.dart +++ b/flutter_local_notifications_linux/test/storage_test.dart @@ -75,10 +75,10 @@ void main() { when(mockStorageFile.existsSync()).thenReturn(false); when( mockStorageFile.createSync(recursive: true), - ).thenAnswer((_) => {}); + ).thenAnswer((_) {}); when( mockStorageFile.writeAsStringSync(any), - ).thenAnswer((_) => {}); + ).thenAnswer((_) {}); when(mockStorageFile.readAsStringSync()).thenReturn(''); expect(await storage.insert(notifications[0]), isTrue); @@ -111,7 +111,7 @@ void main() { when(mockStorageFile.existsSync()).thenReturn(true); when( mockStorageFile.writeAsStringSync(any), - ).thenAnswer((_) => {}); + ).thenAnswer((_) {}); when(mockStorageFile.readAsStringSync()).thenReturn(''); await storage.insert(notifications[0]); @@ -153,7 +153,7 @@ void main() { when(mockStorageFile.existsSync()).thenReturn(true); when( mockStorageFile.writeAsStringSync(any), - ).thenAnswer((_) => {}); + ).thenAnswer((_) {}); when(mockStorageFile.readAsStringSync()).thenReturn(''); expect(await storage.getAll(), []); @@ -185,7 +185,7 @@ void main() { when(mockStorageFile.existsSync()).thenReturn(true); when( mockStorageFile.writeAsStringSync(any), - ).thenAnswer((_) => {}); + ).thenAnswer((_) {}); when(mockStorageFile.readAsStringSync()).thenReturn(''); expect(await storage.getAll(), []); @@ -213,7 +213,7 @@ void main() { when(mockStorageFile.existsSync()).thenReturn(true); when( mockStorageFile.writeAsStringSync(any), - ).thenAnswer((_) => {}); + ).thenAnswer((_) {}); when( mockStorageFile.readAsStringSync(), @@ -241,7 +241,7 @@ void main() { when(mockStorageFile.existsSync()).thenReturn(true); when( mockStorageFile.writeAsStringSync(any), - ).thenAnswer((_) => {}); + ).thenAnswer((_) {}); when(mockStorageFile.readAsStringSync()).thenReturn(''); await storage.insert(notifications[0]); @@ -271,7 +271,7 @@ void main() { when(mockStorageFile.existsSync()).thenReturn(true); when( mockStorageFile.writeAsStringSync(any), - ).thenAnswer((_) => {}); + ).thenAnswer((_) {}); when(mockStorageFile.readAsStringSync()).thenReturn(''); await storage.insert(notification); diff --git a/flutter_local_notifications_platform_interface/CHANGELOG.md b/flutter_local_notifications_platform_interface/CHANGELOG.md index 735877cf3..847f58abe 100644 --- a/flutter_local_notifications_platform_interface/CHANGELOG.md +++ b/flutter_local_notifications_platform_interface/CHANGELOG.md @@ -1,3 +1,15 @@ +## [vNext] + +* **Breaking change** Bumped minimum Flutter SDK requirement to 3.13 + +## [7.2.0] + +* Added `periodicallyShowWithDuration()` and corresponding `validateRepeatDurationInterval()` helper method to ensure duration is at least a minute + +## [7.1.0] + +* [Android] `bigText` has added to `ActiveNotification` that allows getting information about the longer text associated with a notification displayed using the big text style. Thanks to the PR from [vulpeep](https://github.com/vulpeep) + ## [7.0.0+1] * Bumped maximum Dart SDK constraint 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 d25c74655..5256e12ce 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 @@ -42,6 +42,7 @@ abstract class FlutterLocalNotificationsPlatform extends PlatformInterface { } /// Periodically show a notification using the specified interval. + /// /// For example, specifying a hourly interval means the first time the /// notification will be an hour after the method has been called and then /// every hour after that. @@ -50,7 +51,21 @@ abstract class FlutterLocalNotificationsPlatform extends PlatformInterface { throw UnimplementedError('periodicallyShow() has not been implemented'); } - /// Cancel/remove the notification with the specified id. + /// Periodically show a notification using the specified custom duration + /// interval. + /// + /// For example, specifying a 5 minutes repeat duration interval means + /// the first time the notification will be an 5 minutes after the method + /// has been called and then every 5 minutes after that. + /// + /// [repeatDurationInterval] must be at least one minute. + Future periodicallyShowWithDuration( + int id, String? title, String? body, Duration repeatDurationInterval) { + throw UnimplementedError( + 'periodicallyShowWithDuration() has not been implemented'); + } + + /// Cancels/removes the notification with the specified id. /// /// This applies to notifications that have been scheduled and those that /// have already been presented. diff --git a/flutter_local_notifications_platform_interface/lib/src/helpers.dart b/flutter_local_notifications_platform_interface/lib/src/helpers.dart index d30df5864..7c17a7946 100644 --- a/flutter_local_notifications_platform_interface/lib/src/helpers.dart +++ b/flutter_local_notifications_platform_interface/lib/src/helpers.dart @@ -1,4 +1,5 @@ /// Helper method for validating notification IDs. +/// /// Ensures IDs are valid 32-bit integers. void validateId(int id) { ArgumentError.checkNotNull(id, 'id'); @@ -7,3 +8,15 @@ void validateId(int id) { 'must fit within the size of a 32-bit integer i.e. in the range [-2^31, 2^31 - 1]'); // ignore: lines_longer_than_80_chars } } + +/// Helper method for validation repeat interval duration used passed +/// to periodicallyShowWithDuration(). +/// +/// Ensures the duration is at least one minute. +void validateRepeatDurationInterval(Duration repeatDurationInterval) { + ArgumentError.checkNotNull(repeatDurationInterval, 'repeatDurationInterval'); + if (repeatDurationInterval.inMinutes < 1) { + throw ArgumentError.value(repeatDurationInterval, 'repeatDurationInterval', + 'must be at one minute or more'); + } +} diff --git a/flutter_local_notifications_platform_interface/lib/src/types.dart b/flutter_local_notifications_platform_interface/lib/src/types.dart index 643a470f3..d4b94a25e 100644 --- a/flutter_local_notifications_platform_interface/lib/src/types.dart +++ b/flutter_local_notifications_platform_interface/lib/src/types.dart @@ -43,6 +43,7 @@ class ActiveNotification { this.body, this.payload, this.tag, + this.bigText, }); /// The notification's id. @@ -76,6 +77,11 @@ class ActiveNotification { /// /// Returned only on Android. final String? tag; + + /// The notification's longer text displayed using big text style. + /// + /// Returned only on Android. + final String? bigText; } /// Details of a Notification Action that was triggered. diff --git a/flutter_local_notifications_platform_interface/pubspec.yaml b/flutter_local_notifications_platform_interface/pubspec.yaml index eb1340100..5807c29bc 100644 --- a/flutter_local_notifications_platform_interface/pubspec.yaml +++ b/flutter_local_notifications_platform_interface/pubspec.yaml @@ -1,11 +1,11 @@ name: flutter_local_notifications_platform_interface description: A common platform interface for the flutter_local_notifications plugin. -version: 7.0.0+1 +version: 7.2.0 homepage: https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications_platform_interface environment: - sdk: ">=2.17.0 <4.0.0" - flutter: ">=3.0.0" + sdk: ^3.1.0 + flutter: ">=3.1.3" dependencies: flutter: diff --git a/melos.yaml b/melos.yaml index 16853911a..5b4a6bc4b 100644 --- a/melos.yaml +++ b/melos.yaml @@ -1,10 +1,10 @@ name: flutter_local_notifications repository: https://github.com/MaikuB/flutter_local_notifications packages: - - flutter_local_notifications/* - - flutter_local_notifications_linux/* - - flutter_local_notifications_platform_interface/* - - "**/example/*" + - flutter_local_notifications + - flutter_local_notifications_linux + - flutter_local_notifications_platform_interface + - flutter_local_notifications/example/ command: bootstrap: @@ -20,19 +20,19 @@ scripts: test:unit: description: Run unit tests in a specific package. run: melos exec -c 1 -- "flutter test" - select-package: - dir-exists: + packageFilters: + dirExists: - test test:unit:android: description: Runs java unit tests run: melos exec -c 1 -- "flutter build apk --debug && cd android && ./gradlew flutter_local_notifications:testDebug" - select-package: + packageFilters: scope: "*example*" test:integration: run: melos exec -c 1 -- "flutter test integration_test" description: Run integration tests - select-package: - dir-exists: + packageFilters: + dirExists: - integration_test scope: "*example*" build:example_android: @@ -40,8 +40,8 @@ scripts: melos exec -c 1 -- \ "flutter build apk --debug" description: Build a specific example app for Android. - select-package: - dir-exists: + packageFilters: + dirExists: - android scope: "*example*" build:example_ios: @@ -49,8 +49,8 @@ scripts: melos exec -c 1 -- \ "flutter build ios --no-codesign --debug" description: Build a specific example app for iOS. - select-package: - dir-exists: + packageFilters: + dirExists: - ios scope: "*example*" build:example_macos: @@ -58,8 +58,8 @@ scripts: melos exec -c 1 -- \ "flutter build macos" description: Build a specific example app for macOS. - select-package: - dir-exists: + packageFilters: + dirExists: - macos scope: "*example*" build:example_linux: @@ -67,8 +67,8 @@ scripts: melos exec -c 1 -- \ "flutter build linux" description: Build a specific example app for Linux. - select-package: - dir-exists: + packageFilters: + dirExists: - linux scope: "*example*" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 000000000..f913619b0 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,6 @@ +name: flutter_local_notifications_workspace + +environment: + sdk: '>=3.0.0 <4.0.0' +dev_dependencies: + melos: ^6.1.0