Skip to content

Commit

Permalink
Merge pull request #108 from MaikuB/dev
Browse files Browse the repository at this point in the history
Android only: Added ability to show progress notifications and updated example app to demonstrate how to display them
  • Loading branch information
MaikuB committed Oct 3, 2018
2 parents bd31d30 + b0980e6 commit e09443a
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 47 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,6 @@
# [0.3.9]
* [Android] Added ability to show progress notifications and updated example app to demonstrate how to display them

# [0.3.8]
* Added `getNotificationAppLaunchDetails()` method that could be used to determine if the app was launched via notification (raised in issue [100])
* Added documentation around ProGuard configuration to Android Integration section of the README
Expand Down
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -19,6 +19,7 @@ A cross platform plugin for displaying local notifications.
* Cancelling/removing notification by id or all of them
* Specify a custom notification sound
* Ability to handle when a user has tapped on a notification, when the app is the foreground, background or terminated
* Determine if an app was launched due to tapping on a notification
* [Android] Configuring the importance level
* [Android] Configuring the priority
* [Android] Customising the vibration pattern for notifications
Expand All @@ -31,6 +32,7 @@ A cross platform plugin for displaying local notifications.
* Big text
* Inbox
* [Android] Group notifications
* [Android] Show progress notifications
* [iOS] Customise the permissions to be requested around displaying notifications

Note that this plugin aims to provide abstractions for all platforms as opposed to having methods that only work on specific platforms. However, each method allows passing in "platform-specifics" that contains data that is specific for customising notifications on each platform. It is still under development so expect the API surface to change over time.
Expand Down
Expand Up @@ -128,6 +128,7 @@ public static Notification createNotification(Context context, NotificationDetai
setSound(context, notificationDetails, builder);
setVibrationPattern(notificationDetails, builder);
setStyle(context, notificationDetails, builder);
setProgress(notificationDetails, builder);
return builder.build();
}

Expand Down Expand Up @@ -364,6 +365,12 @@ private static void setStyle(Context context, NotificationDetails notificationDe
}
}

private static void setProgress(NotificationDetails notificationDetails, NotificationCompat.Builder builder) {
if(BooleanUtils.getValue(notificationDetails.showProgress)) {
builder.setProgress(notificationDetails.maxProgress, notificationDetails.progress, notificationDetails.indeterminate);
}
}

private static void setBigPictureStyle(Context context, NotificationDetails notificationDetails, NotificationCompat.Builder builder) {
BigPictureStyleInformation bigPictureStyleInformation = (BigPictureStyleInformation) notificationDetails.styleInformation;
NotificationCompat.BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle();
Expand Down Expand Up @@ -473,75 +480,98 @@ public void onMethodCall(MethodCall call, Result result) {
switch (call.method) {

case INITIALIZE_METHOD: {
Map<String, Object> arguments = call.arguments();
String defaultIcon = (String) arguments.get(DEFAULT_ICON);
defaultIconResourceId = registrar.context().getResources().getIdentifier(defaultIcon, "drawable", registrar.context().getPackageName());
if (defaultIconResourceId == 0) {
result.error(INVALID_ICON_ERROR_CODE, String.format(INVALID_DRAWABLE_RESOURCE_ERROR_MESSAGE, defaultIcon), null);
break;
}
if (registrar.activity() != null) {
sendNotificationPayloadMessage(registrar.activity().getIntent());
}
result.success(true);
initialize(call, result);
break;
}
case GET_NOTIFICATION_APP_LAUNCH_DETAILS_METHOD: {
Map<String, Object> notificationAppLaunchDetails = new HashMap<>();
String payload = null;
Boolean notificationLaunchedApp = (registrar.activity() != null && SELECT_NOTIFICATION.equals(registrar.activity().getIntent().getAction()));
notificationAppLaunchDetails.put(NOTIFICATION_LAUNCHED_APP, notificationLaunchedApp);
if(notificationLaunchedApp) {
payload = registrar.activity().getIntent().getStringExtra(PAYLOAD);
}
notificationAppLaunchDetails.put(PAYLOAD, payload);
result.success(notificationAppLaunchDetails);
getNotificationAppLaunchDetails(result);
break;
}
case SHOW_METHOD: {
Map<String, Object> arguments = call.arguments();
NotificationDetails notificationDetails = extractNotificationDetails(result, arguments);
if (notificationDetails != null) {
showNotification(notificationDetails);
result.success(null);
}
show(call, result);
break;
}
case SCHEDULE_METHOD: {
Map<String, Object> arguments = call.arguments();
NotificationDetails notificationDetails = extractNotificationDetails(result, arguments);
if (notificationDetails != null) {
scheduleNotification(registrar.context(), notificationDetails, true);
result.success(null);
}
schedule(call, result);
break;
}
case PERIODICALLY_SHOW_METHOD:
case SHOW_DAILY_AT_TIME_METHOD:
case SHOW_WEEKLY_AT_DAY_AND_TIME_METHOD: {
Map<String, Object> arguments = call.arguments();
NotificationDetails notificationDetails = extractNotificationDetails(result, arguments);
if (notificationDetails != null) {
repeatNotification(registrar.context(), notificationDetails, true);
result.success(null);
}
repeat(call, result);
break;
}
case CANCEL_METHOD:
Integer id = call.arguments();
cancelNotification(id);
result.success(null);
cancel(call, result);
break;
case CANCEL_ALL_METHOD:
cancelAllNotifications();
result.success(null);
cancelAllNotifications(result);
break;
default:
result.notImplemented();
break;
}
}

private void cancel(MethodCall call, Result result) {
Integer id = call.arguments();
cancelNotification(id);
result.success(null);
}

private void repeat(MethodCall call, Result result) {
Map<String, Object> arguments = call.arguments();
NotificationDetails notificationDetails = extractNotificationDetails(result, arguments);
if (notificationDetails != null) {
repeatNotification(registrar.context(), notificationDetails, true);
result.success(null);
}
}

private void schedule(MethodCall call, Result result) {
Map<String, Object> arguments = call.arguments();
NotificationDetails notificationDetails = extractNotificationDetails(result, arguments);
if (notificationDetails != null) {
scheduleNotification(registrar.context(), notificationDetails, true);
result.success(null);
}
}

private void show(MethodCall call, Result result) {
Map<String, Object> arguments = call.arguments();
NotificationDetails notificationDetails = extractNotificationDetails(result, arguments);
if (notificationDetails != null) {
showNotification(notificationDetails);
result.success(null);
}
}

private void getNotificationAppLaunchDetails(Result result) {
Map<String, Object> notificationAppLaunchDetails = new HashMap<>();
String payload = null;
Boolean notificationLaunchedApp = (registrar.activity() != null && SELECT_NOTIFICATION.equals(registrar.activity().getIntent().getAction()));
notificationAppLaunchDetails.put(NOTIFICATION_LAUNCHED_APP, notificationLaunchedApp);
if(notificationLaunchedApp) {
payload = registrar.activity().getIntent().getStringExtra(PAYLOAD);
}
notificationAppLaunchDetails.put(PAYLOAD, payload);
result.success(notificationAppLaunchDetails);
}

private void initialize(MethodCall call, Result result) {
Map<String, Object> arguments = call.arguments();
String defaultIcon = (String) arguments.get(DEFAULT_ICON);
defaultIconResourceId = registrar.context().getResources().getIdentifier(defaultIcon, "drawable", registrar.context().getPackageName());
if (defaultIconResourceId == 0) {
result.error(INVALID_ICON_ERROR_CODE, String.format(INVALID_DRAWABLE_RESOURCE_ERROR_MESSAGE, defaultIcon), null);
return;
}
if (registrar.activity() != null) {
sendNotificationPayloadMessage(registrar.activity().getIntent());
}
result.success(true);
}

/// Extracts the details of the notifications passed from the Flutter side and also validates that any specified drawable/raw resources exist
private NotificationDetails extractNotificationDetails(Result result, Map<String, Object> arguments) {
NotificationDetails notificationDetails = NotificationDetails.from(arguments);
Expand Down Expand Up @@ -590,12 +620,13 @@ private void cancelNotification(Integer id) {
removeNotificationFromCache(id, context);
}

private void cancelAllNotifications() {
private void cancelAllNotifications(Result result) {
NotificationManagerCompat notificationManager = getNotificationManager();
notificationManager.cancelAll();
Context context = registrar.context();
ArrayList<NotificationDetails> scheduledNotifications = loadScheduledNotifications(context);
if (scheduledNotifications == null || scheduledNotifications.isEmpty()) {
result.success(null);
return;
}

Expand All @@ -608,6 +639,7 @@ private void cancelAllNotifications() {
}

saveScheduledNotifications(context, new ArrayList<NotificationDetails>());
result.success(null);
}

private void showNotification(NotificationDetails notificationDetails) {
Expand Down
Expand Up @@ -63,6 +63,10 @@ public class NotificationDetails {
private static final String LARGE_ICON_BITMAP_SOURCE = "largeIconBitmapSource";
private static final String BIG_PICTURE = "bigPicture";
private static final String BIG_PICTURE_BITMAP_SOURCE = "bigPictureBitmapSource";
private static final String SHOW_PROGRESS = "showProgress";
private static final String MAX_PROGRESS = "maxProgress";
private static final String PROGRESS = "progress";
private static final String INDETERMINATE = "indeterminate";


public Integer id;
Expand Down Expand Up @@ -96,6 +100,11 @@ public class NotificationDetails {
public String largeIcon;
public BitmapSource largeIconBitmapSource;
public Boolean onlyAlertOnce;
public Boolean showProgress;
public Integer maxProgress;
public Integer progress;
public Boolean indeterminate;


// Note: this is set on the Android to save details about the icon that should be used when re-hydrating scheduled notifications when a device has been restarted
public Integer iconResourceId;
Expand Down Expand Up @@ -140,6 +149,19 @@ public static NotificationDetails from(Map<String, Object> arguments) {
notificationDetails.setAsGroupSummary = (Boolean) platformChannelSpecifics.get(SET_AS_GROUP_SUMMARY);
notificationDetails.groupAlertBehavior = (Integer) platformChannelSpecifics.get(GROUP_ALERT_BEHAVIOR);
notificationDetails.onlyAlertOnce = (Boolean) platformChannelSpecifics.get(ONLY_ALERT_ONCE);
notificationDetails.showProgress = (Boolean) platformChannelSpecifics.get(SHOW_PROGRESS);
if (platformChannelSpecifics.containsKey(MAX_PROGRESS)) {
notificationDetails.maxProgress = (Integer) platformChannelSpecifics.get(MAX_PROGRESS);
}

if (platformChannelSpecifics.containsKey(PROGRESS)) {
notificationDetails.progress = (Integer) platformChannelSpecifics.get(PROGRESS);
}

if (platformChannelSpecifics.containsKey(INDETERMINATE)) {
notificationDetails.indeterminate = (Boolean) platformChannelSpecifics.get(INDETERMINATE);
}

readColor(notificationDetails, platformChannelSpecifics);
readChannelInformation(notificationDetails, platformChannelSpecifics);
notificationDetails.largeIcon = (String) platformChannelSpecifics.get(LARGE_ICON);
Expand Down
70 changes: 70 additions & 0 deletions example/lib/main.dart
Expand Up @@ -182,6 +182,26 @@ class _HomePageState extends State<HomePage> {
},
),
),
new Padding(
padding: new EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 8.0),
child: new RaisedButton(
child: new Text(
'Show progress notification - updates every second [Android]'),
onPressed: () async {
await _showProgressNotification();
},
),
),
new Padding(
padding: new EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 8.0),
child: new RaisedButton(
child: new Text(
'Show indeterminate progress notification [Android]'),
onPressed: () async {
await _showIndeterminateProgressNotification();
},
),
),
new Padding(
padding: new EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 8.0),
child: new RaisedButton(
Expand Down Expand Up @@ -471,6 +491,56 @@ class _HomePageState extends State<HomePage> {
payload: 'item x');
}

Future _showProgressNotification() async {
var maxProgress = 5;
for (var i = 0; i <= maxProgress; i++) {
await Future.delayed(Duration(seconds: 1), () async {
var androidPlatformChannelSpecifics = new AndroidNotificationDetails(
'progress channel',
'progress channel',
'progress channel description',
channelShowBadge: false,
importance: Importance.Max,
priority: Priority.High,
onlyAlertOnce: true,
showProgress: true,
maxProgress: maxProgress,
progress: i);
var iOSPlatformChannelSpecifics = new IOSNotificationDetails();
var platformChannelSpecifics = new NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.show(
0,
'progress notification title',
'progress notification body',
platformChannelSpecifics,
payload: 'item x');
});
}
}

Future _showIndeterminateProgressNotification() async {
var androidPlatformChannelSpecifics = new AndroidNotificationDetails(
'indeterminate progress channel',
'indeterminate progress channel',
'indeterminate progress channel description',
channelShowBadge: false,
importance: Importance.Max,
priority: Priority.High,
onlyAlertOnce: true,
showProgress: true,
indeterminate: true);
var iOSPlatformChannelSpecifics = new IOSNotificationDetails();
var platformChannelSpecifics = new NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.show(
0,
'indeterminate progress notification title',
'indeterminate progress notification body',
platformChannelSpecifics,
payload: 'item x');
}

String _toTwoDigitString(int value) {
return value.toString().padLeft(2, '0');
}
Expand Down
24 changes: 22 additions & 2 deletions lib/src/platform_specifics/android/notification_details.dart
Expand Up @@ -68,6 +68,18 @@ class AndroidNotificationDetails {
/// Specifies if you would only like the sound, vibrate and ticker to be played if the notification is not already showing.
bool onlyAlertOnce;

/// Specifies if the notification will be used to show progress
bool showProgress;

/// The maximum progress value
int maxProgress;

/// The current progress value
int progress;

/// Specifies if an indeterminate progress bar will be shown
bool indeterminate;

AndroidNotificationDetails(
this.channelId, this.channelName, this.channelDescription,
{this.icon,
Expand All @@ -88,7 +100,11 @@ class AndroidNotificationDetails {
this.largeIcon,
this.largeIconBitmapSource,
this.onlyAlertOnce,
this.channelShowBadge = true});
this.channelShowBadge = true,
this.showProgress = false,
this.maxProgress = 0,
this.progress = 0,
this.indeterminate = false});

Map<String, dynamic> toMap() {
return <String, dynamic>{
Expand Down Expand Up @@ -118,7 +134,11 @@ class AndroidNotificationDetails {
'colorBlue': color?.blue,
'largeIcon': largeIcon,
'largeIconBitmapSource': largeIconBitmapSource?.index,
'onlyAlertOnce': onlyAlertOnce
'onlyAlertOnce': onlyAlertOnce,
'showProgress': showProgress,
'maxProgress': maxProgress,
'progress': progress,
'indeterminate': indeterminate
};
}
}
2 changes: 1 addition & 1 deletion pubspec.yaml
@@ -1,6 +1,6 @@
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: 0.3.8
version: 0.3.9
author: Michael Bui <maikub84@gmail.com>
homepage: https://github.com/MaikuB/flutter_local_notifications

Expand Down

0 comments on commit e09443a

Please sign in to comment.