Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

notif: Handle remove notification message #878

Merged
merged 7 commits into from
Aug 21, 2024
130 changes: 130 additions & 0 deletions android/app/src/main/kotlin/com/zulip/flutter/Notifications.g.kt
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,65 @@ data class MessagingStyle (
)
}
}

/**
* Corresponds to `android.app.Notification`
*
* See: https://developer.android.com/reference/kotlin/android/app/Notification
*
* Generated class from Pigeon that represents data sent in messages.
*/
data class Notification (
val group: String,
val extras: Map<String?, String?>

) {
companion object {
@Suppress("LocalVariableName")
fun fromList(__pigeon_list: List<Any?>): Notification {
val group = __pigeon_list[0] as String
val extras = __pigeon_list[1] as Map<String?, String?>
return Notification(group, extras)
}
}
fun toList(): List<Any?> {
return listOf(
group,
extras,
)
}
}

/**
* Corresponds to `android.service.notification.StatusBarNotification`
*
* See: https://developer.android.com/reference/android/service/notification/StatusBarNotification
*
* Generated class from Pigeon that represents data sent in messages.
*/
data class StatusBarNotification (
val id: Long,
val tag: String,
val notification: Notification

) {
companion object {
@Suppress("LocalVariableName")
fun fromList(__pigeon_list: List<Any?>): StatusBarNotification {
val id = __pigeon_list[0].let { num -> if (num is Int) num.toLong() else num as Long }
val tag = __pigeon_list[1] as String
val notification = __pigeon_list[2] as Notification
return StatusBarNotification(id, tag, notification)
}
}
fun toList(): List<Any?> {
return listOf(
id,
tag,
notification,
)
}
}
private object NotificationsPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
Expand Down Expand Up @@ -289,6 +348,16 @@ private object NotificationsPigeonCodec : StandardMessageCodec() {
MessagingStyle.fromList(it)
}
}
135.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
Notification.fromList(it)
}
}
136.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
StatusBarNotification.fromList(it)
}
}
else -> super.readValueOfType(type, buffer)
}
}
Expand Down Expand Up @@ -318,6 +387,14 @@ private object NotificationsPigeonCodec : StandardMessageCodec() {
stream.write(134)
writeValue(stream, value.toList())
}
is Notification -> {
stream.write(135)
writeValue(stream, value.toList())
}
is StatusBarNotification -> {
stream.write(136)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
Expand Down Expand Up @@ -365,6 +442,23 @@ interface AndroidNotificationHostApi {
* https://developer.android.com/reference/kotlin/androidx/core/app/NotificationCompat.MessagingStyle#extractMessagingStyleFromNotification(android.app.Notification)
*/
fun getActiveNotificationMessagingStyleByTag(tag: String): MessagingStyle?
/**
* Corresponds to `androidx.core.app.NotificationManagerCompat.getActiveNotifications`.
*
* The keys of entries to fetch from notification's extras bundle must be
* specified in the [desiredExtras] list. If this list is empty, then
* [Notifications.extras] will also be empty. If value of the matched entry
* is not of type string or is null, then that entry will be skipped.
*
* See: https://developer.android.com/reference/kotlin/androidx/core/app/NotificationManagerCompat?hl=en#getActiveNotifications()
*/
fun getActiveNotifications(desiredExtras: List<String>): List<StatusBarNotification>
/**
* Corresponds to `androidx.core.app.NotificationManagerCompat.cancel`.
*
* See: https://developer.android.com/reference/kotlin/androidx/core/app/NotificationManagerCompat?hl=en#cancel(java.lang.String,int)
*/
fun cancel(tag: String?, id: Long)

companion object {
/** The codec used by AndroidNotificationHostApi. */
Expand Down Expand Up @@ -442,6 +536,42 @@ interface AndroidNotificationHostApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.zulip.AndroidNotificationHostApi.getActiveNotifications$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val desiredExtrasArg = args[0] as List<String>
val wrapped: List<Any?> = try {
listOf(api.getActiveNotifications(desiredExtrasArg))
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.zulip.AndroidNotificationHostApi.cancel$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val tagArg = args[0] as String?
val idArg = args[1].let { num -> if (num is Int) num.toLong() else num as Long }
val wrapped: List<Any?> = try {
api.cancel(tagArg, idArg)
listOf(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
19 changes: 19 additions & 0 deletions android/app/src/main/kotlin/com/zulip/flutter/ZulipPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,25 @@ private class AndroidNotificationHost(val context: Context)
}
return null
}

override fun getActiveNotifications(desiredExtras: List<String>): List<StatusBarNotification> {
return NotificationManagerCompat.from(context).activeNotifications.map {
StatusBarNotification(
it.id.toLong(),
it.tag,
Notification(
it.notification.group,
desiredExtras
.associateWith { key -> it.notification.extras.getString(key) }
.filter { entry -> entry.value != null }
Comment on lines +155 to +156
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This semantics works fine since it covers the one use case we actually have — it's definitely simpler to think about than the one in my draft, and that's a good improvement.

It needs to be described in the method's doc, though (in pigeon/notification.dart). It looks to be:

  • Each requested extra is included if it's present with type string.
  • If it's absent, or not a string, it's skipped.

If not written down explicitly, that's the sort of thing that could lead to some confused time spent debugging when someone in the future tries to add a number or a boolean or something, and then it just doesn't show up — they'd wonder if they typoed the key, or if the extra didn't make it onto the notification for some reason, or what.

),
)
}
}

override fun cancel(tag: String?, id: Long) {
NotificationManagerCompat.from(context).cancel(tag, id.toInt())
}
}

/** A Flutter plugin for the Zulip app's ad-hoc needs. */
Expand Down
133 changes: 133 additions & 0 deletions lib/host/android_notifications.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,69 @@ class MessagingStyle {
}
}

/// Corresponds to `android.app.Notification`
///
/// See: https://developer.android.com/reference/kotlin/android/app/Notification
class Notification {
Notification({
required this.group,
required this.extras,
});

String group;

Map<String?, String?> extras;

Object encode() {
return <Object?>[
group,
extras,
];
}

static Notification decode(Object result) {
result as List<Object?>;
return Notification(
group: result[0]! as String,
extras: (result[1] as Map<Object?, Object?>?)!.cast<String?, String?>(),
);
}
}

/// Corresponds to `android.service.notification.StatusBarNotification`
///
/// See: https://developer.android.com/reference/android/service/notification/StatusBarNotification
class StatusBarNotification {
StatusBarNotification({
required this.id,
required this.tag,
required this.notification,
});

int id;

String tag;

Notification notification;

Object encode() {
return <Object?>[
id,
tag,
notification,
];
}

static StatusBarNotification decode(Object result) {
result as List<Object?>;
return StatusBarNotification(
id: result[0]! as int,
tag: result[1]! as String,
notification: result[2]! as Notification,
);
}
}


class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec();
Expand All @@ -263,6 +326,12 @@ class _PigeonCodec extends StandardMessageCodec {
} else if (value is MessagingStyle) {
buffer.putUint8(134);
writeValue(buffer, value.encode());
} else if (value is Notification) {
buffer.putUint8(135);
writeValue(buffer, value.encode());
} else if (value is StatusBarNotification) {
buffer.putUint8(136);
writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
Expand All @@ -283,6 +352,10 @@ class _PigeonCodec extends StandardMessageCodec {
return MessagingStyleMessage.decode(readValue(buffer)!);
case 134:
return MessagingStyle.decode(readValue(buffer)!);
case 135:
return Notification.decode(readValue(buffer)!);
case 136:
return StatusBarNotification.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
}
Expand Down Expand Up @@ -398,4 +471,64 @@ class AndroidNotificationHostApi {
return (__pigeon_replyList[0] as MessagingStyle?);
}
}

/// Corresponds to `androidx.core.app.NotificationManagerCompat.getActiveNotifications`.
///
/// The keys of entries to fetch from notification's extras bundle must be
/// specified in the [desiredExtras] list. If this list is empty, then
/// [Notifications.extras] will also be empty. If value of the matched entry
/// is not of type string or is null, then that entry will be skipped.
///
/// See: https://developer.android.com/reference/kotlin/androidx/core/app/NotificationManagerCompat?hl=en#getActiveNotifications()
Future<List<StatusBarNotification?>> getActiveNotifications({required List<String?> desiredExtras}) async {
final String __pigeon_channelName = 'dev.flutter.pigeon.zulip.AndroidNotificationHostApi.getActiveNotifications$__pigeon_messageChannelSuffix';
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(<Object?>[desiredExtras]) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else if (__pigeon_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (__pigeon_replyList[0] as List<Object?>?)!.cast<StatusBarNotification?>();
}
}

/// Corresponds to `androidx.core.app.NotificationManagerCompat.cancel`.
///
/// See: https://developer.android.com/reference/kotlin/androidx/core/app/NotificationManagerCompat?hl=en#cancel(java.lang.String,int)
Future<void> cancel({String? tag, required int id}) async {
final String __pigeon_channelName = 'dev.flutter.pigeon.zulip.AndroidNotificationHostApi.cancel$__pigeon_messageChannelSuffix';
final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
__pigeon_channelName,
pigeonChannelCodec,
binaryMessenger: __pigeon_binaryMessenger,
);
final List<Object?>? __pigeon_replyList =
await __pigeon_channel.send(<Object?>[tag, id]) as List<Object?>?;
if (__pigeon_replyList == null) {
throw _createConnectionError(__pigeon_channelName);
} else if (__pigeon_replyList.length > 1) {
throw PlatformException(
code: __pigeon_replyList[0]! as String,
message: __pigeon_replyList[1] as String?,
details: __pigeon_replyList[2],
);
} else {
return;
}
}
}
Loading