Skip to content

Commit

Permalink
Add getActiveNotificationMessagingStyle
Browse files Browse the repository at this point in the history
This allows fetching an active notification's MessagingStyleInformation.
A typical use-case is appending a new Message to a MessagingStyle
when receiving a push message.
  • Loading branch information
emersion committed May 2, 2022
1 parent 659508f commit d8db25a
Show file tree
Hide file tree
Showing 5 changed files with 284 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public class FlutterLocalNotificationsPlugin
private static final String CREATE_NOTIFICATION_CHANNEL_METHOD = "createNotificationChannel";
private static final String DELETE_NOTIFICATION_CHANNEL_METHOD = "deleteNotificationChannel";
private static final String GET_ACTIVE_NOTIFICATIONS_METHOD = "getActiveNotifications";
private static final String GET_ACTIVE_NOTIFICATION_MESSAGING_STYLE_METHOD = "getActiveNotificationMessagingStyle";
private static final String GET_NOTIFICATION_CHANNELS_METHOD = "getNotificationChannels";
private static final String START_FOREGROUND_SERVICE = "startForegroundService";
private static final String STOP_FOREGROUND_SERVICE = "stopForegroundService";
Expand All @@ -126,6 +127,8 @@ public class FlutterLocalNotificationsPlugin
"GET_ACTIVE_NOTIFICATIONS_ERROR_CODE";
private static final String GET_ACTIVE_NOTIFICATIONS_ERROR_MESSAGE =
"Android version must be 6.0 or newer to use getActiveNotifications";
private static final String GET_ACTIVE_MESSAGING_STYLE_ERROR_CODE =
"GET_ACTIVE_MESSAGING_STYLE_ERROR_CODE";
private static final String GET_NOTIFICATION_CHANNELS_ERROR_CODE =
"GET_NOTIFICATION_CHANNELS_ERROR_CODE";
private static final String INVALID_LED_DETAILS_ERROR_MESSAGE =
Expand Down Expand Up @@ -1270,6 +1273,9 @@ public void onMethodCall(MethodCall call, Result result) {
case GET_ACTIVE_NOTIFICATIONS_METHOD:
getActiveNotifications(result);
break;
case GET_ACTIVE_NOTIFICATION_MESSAGING_STYLE_METHOD:
getActiveNotificationMessagingStyle(call, result);
break;
case GET_NOTIFICATION_CHANNELS_METHOD:
getNotificationChannels(result);
break;
Expand Down Expand Up @@ -1607,6 +1613,103 @@ private void getActiveNotifications(Result result) {
}
}

private void getActiveNotificationMessagingStyle(MethodCall call, Result result) {
if (VERSION.SDK_INT < VERSION_CODES.M) {
result.error(
GET_ACTIVE_MESSAGING_STYLE_ERROR_CODE,
"Android version must be 6.0 or newer to use getActiveNotificationMessagingStyle", null);
return;
}
NotificationManager notificationManager =
(NotificationManager) applicationContext.getSystemService(Context.NOTIFICATION_SERVICE);
try {
Map<String, Object> arguments = call.arguments();
int id = (int) arguments.get("id");
String tag = (String) arguments.get("tag");

StatusBarNotification[] activeNotifications = notificationManager.getActiveNotifications();
Notification notification = null;
for (StatusBarNotification activeNotification : activeNotifications) {
if (activeNotification.getId() == id && (tag == null || activeNotification.getTag() == tag)) {
notification = activeNotification.getNotification();
break;
}
}

if (notification == null) {
result.success(null);
return;
}

NotificationCompat.MessagingStyle messagingStyle = NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(notification);
if (messagingStyle == null) {
result.success(null);
return;
}

HashMap<String, Object> stylePayload = new HashMap<>();
stylePayload.put("groupConversation", messagingStyle.isGroupConversation());
stylePayload.put("person", describePerson(messagingStyle.getUser()));
stylePayload.put("conversationTitle", messagingStyle.getConversationTitle());

List<Map<String, Object>> messagesPayload = new ArrayList<>();
for (NotificationCompat.MessagingStyle.Message msg : messagingStyle.getMessages()) {
Map<String, Object> msgPayload = new HashMap<>();
msgPayload.put("text", msg.getText());
msgPayload.put("timestamp", msg.getTimestamp());
msgPayload.put("person", describePerson(msg.getPerson()));
messagesPayload.add(msgPayload);
}
stylePayload.put("messages", messagesPayload);

result.success(stylePayload);
} catch (Throwable e) {
result.error(GET_ACTIVE_MESSAGING_STYLE_ERROR_CODE, e.getMessage(), e.getStackTrace());
}
}

private Map<String, Object> describePerson(Person person) {
if (person == null) {
return null;
}
Map<String, Object> payload = new HashMap<>();
payload.put("key", person.getKey());
payload.put("name", person.getName());
payload.put("uri", person.getUri());
payload.put("bot", person.isBot());
payload.put("important", person.isImportant());
payload.put("icon", describeIcon(person.getIcon()));
return payload;
}

private Map<String, Object> describeIcon(IconCompat icon) {
if (icon == null) {
return null;
}
IconSource source;
Object data;
switch (icon.getType()) {
case IconCompat.TYPE_RESOURCE:
source = IconSource.DrawableResource;
int resId = icon.getResId();
Context context = applicationContext;
assert(context.getResources().getResourceTypeName(resId).equals(DRAWABLE));
assert(context.getResources().getResourcePackageName(resId).equals(context.getPackageName()));
data = context.getResources().getResourceEntryName(resId);
break;
case IconCompat.TYPE_URI:
source = IconSource.ContentUri;
data = icon.getUri().toString();
break;
default:
return null;
}
Map<String, Object> payload = new HashMap<>();
payload.put("source", source.ordinal());
payload.put("data", data);
return payload;
}

private void getNotificationChannels(Result result) {
try {
NotificationManagerCompat notificationManagerCompat =
Expand Down
109 changes: 109 additions & 0 deletions flutter_local_notifications/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2024,6 +2024,12 @@ class _HomePageState extends State<HomePage> {
'title: ${activeNotification.title}\n'
'body: ${activeNotification.body}',
),
TextButton(
child: const Text('Get messaging style'),
onPressed: () {
_getActiveNotificationMessagingStyle(activeNotification.id, activeNotification.tag);
},
),
const Divider(color: Colors.black),
],
),
Expand All @@ -2038,6 +2044,109 @@ class _HomePageState extends State<HomePage> {
}
}

Future<void> _getActiveNotificationMessagingStyle(int id, String? tag) async {
Widget dialogContent;
try {
final messagingStyle = await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()!
.getActiveNotificationMessagingStyle(id, tag: tag);
if (messagingStyle == null) {
dialogContent = const Text('No messaging style');
} else {
dialogContent = SingleChildScrollView(child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'person: ${_formatPerson(messagingStyle.person)}\n'
'conversationTitle: ${messagingStyle.conversationTitle}\n'
'groupConversation: ${messagingStyle.groupConversation}'
),
const Divider(color: Colors.black),
if (messagingStyle.messages == null)
const Text('No messages'),
if (messagingStyle.messages != null)
for (final msg in messagingStyle.messages!)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'text: ${msg.text}\n'
'timestamp: ${msg.timestamp}\n'
'person: ${_formatPerson(msg.person)}'
),
const Divider(color: Colors.black),
],
),
],
));
}
} on PlatformException catch (error) {
dialogContent = Text(
'Error calling "getActiveNotificationMessagingStyle"\n'
'code: ${error.code}\n'
'message: ${error.message}',
);
}

await showDialog<void>(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text('Messaging style'),
content: dialogContent,
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('OK'),
),
],
),
);
}

String _formatPerson(Person? person) {
if (person == null) {
return 'null';
}

List<String> attrs = [];
if (person.name != null) {
attrs.add('name: "${person.name}"');
}
if (person.uri != null) {
attrs.add('uri: "${person.uri}"');
}
if (person.key != null) {
attrs.add('key: "${person.key}"');
}
if (person.important) {
attrs.add('important: true');
}
if (person.bot) {
attrs.add('bot: true');
}
if (person.icon != null) {
attrs.add('icon: ${_formatAndroidIcon(person.icon)}');
}
return 'Person(${attrs.join(', ')})';
}

String _formatAndroidIcon(Object? icon) {
if (icon == null) {
return 'null';
}
if (icon is DrawableResourceAndroidIcon) {
return 'DrawableResourceAndroidIcon("${icon.data}")';
} else if (icon is ContentUriAndroidIcon) {
return 'ContentUriAndroidIcon("${icon.data}")';
} else {
return 'AndroidIcon()';
}
}

Future<void> _getNotificationChannels() async {
final Widget notificationChannelsDialogContent =
await _getNotificationChannelsDialogContent();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Generated file. Do not edit.
//

// clang-format off

#include "generated_plugin_registrant.h"


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Generated file. Do not edit.
//

// clang-format off

#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@ import 'helpers.dart';
import 'platform_specifics/android/active_notification.dart';
import 'platform_specifics/android/enums.dart';
import 'platform_specifics/android/initialization_settings.dart';
import 'platform_specifics/android/icon.dart';
import 'platform_specifics/android/message.dart';
import 'platform_specifics/android/method_channel_mappers.dart';
import 'platform_specifics/android/notification_channel.dart';
import 'platform_specifics/android/notification_channel_group.dart';
import 'platform_specifics/android/notification_details.dart';
import 'platform_specifics/android/notification_sound.dart';
import 'platform_specifics/android/person.dart';
import 'platform_specifics/android/styles/messaging_style_information.dart';
import 'platform_specifics/android/styles/style_information.dart';
import 'platform_specifics/ios/enums.dart';
import 'platform_specifics/ios/initialization_settings.dart';
import 'platform_specifics/ios/method_channel_mappers.dart';
Expand Down Expand Up @@ -408,6 +413,69 @@ class AndroidFlutterLocalNotificationsPlugin
.toList();
}

/// Returns the messaging style information of an active notification shown
/// by the application that hasn't been dismissed/removed.
///
/// This method is only applicable to Android 6.0 or newer and will throw an
/// [PlatformException] when called on a device with an incompatible Android
/// version.
///
/// Only [DrawableResourceAndroidIcon] and [ContentUriAndroidIcon] are
/// supported for [AndroidIcon] fields.
Future<MessagingStyleInformation?> getActiveNotificationMessagingStyle(int id, { String? tag }) async {
final Map<dynamic, dynamic>? m =
await _channel.invokeMethod('getActiveNotificationMessagingStyle', {
'id': id,
'tag': tag,
});
if (m == null) {
return null;
}

return MessagingStyleInformation(
_personFromMap(m['person'])!,
conversationTitle: m['conversationTitle'],
groupConversation: m['groupConversation'],
messages: m['messages']?.map<Message>((m) => _messageFromMap(m))?.toList(),
);
}

Person? _personFromMap(Map<dynamic, dynamic>? m) {
if (m == null) {
return null;
}
return Person(
bot: m['bot'],
icon: _iconFromMap(m['icon']),
important: m['important'],
key: m['key'],
name: m['name'],
uri: m['uri'],
);
}

Message _messageFromMap(Map<dynamic, dynamic> m) {
return Message(
m['text'],
DateTime.fromMillisecondsSinceEpoch(m['timestamp']),
_personFromMap(m['person']),
);
}

AndroidIcon<Object>? _iconFromMap(Map<dynamic, dynamic>? m) {
if (m == null) {
return null;
}
switch (AndroidIconSource.values[m['source']]) {
case AndroidIconSource.drawableResource:
return DrawableResourceAndroidIcon(m['data']);
case AndroidIconSource.contentUri:
return ContentUriAndroidIcon(m['data']);
default:
return null;
}
}

/// Returns the list of all notification channels.
///
/// This method is only applicable on Android 8.0 or newer. On older versions,
Expand Down

0 comments on commit d8db25a

Please sign in to comment.