Skip to content

Commit 4e3e0a4

Browse files
rajveermalviyagnprice
authored andcommitted
notif: Add messaging-style notifications support to Pigeon bindings
Add methods and types for creating messaging style notifications: https://developer.android.com/develop/ui/views/notifications/build-notification#messaging-style
1 parent b0b8a50 commit 4e3e0a4

File tree

5 files changed

+510
-6
lines changed

5 files changed

+510
-6
lines changed

android/app/src/main/kotlin/com/zulip/flutter/Notifications.g.kt

+169-3
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,112 @@ data class InboxStyle (
107107
)
108108
}
109109
}
110+
111+
/**
112+
* Corresponds to `androidx.core.app.Person`
113+
*
114+
* See: https://developer.android.com/reference/androidx/core/app/Person
115+
*
116+
* Generated class from Pigeon that represents data sent in messages.
117+
*/
118+
data class Person (
119+
/**
120+
* An icon for this person.
121+
*
122+
* This should be compressed image data, in a format to be passed
123+
* to `androidx.core.graphics.drawable.IconCompat.createWithData`.
124+
* Supported formats include JPEG, PNG, and WEBP.
125+
*
126+
* See:
127+
* https://developer.android.com/reference/androidx/core/graphics/drawable/IconCompat#createWithData(byte[],int,int)
128+
*/
129+
val iconBitmap: ByteArray? = null,
130+
val key: String,
131+
val name: String
132+
133+
) {
134+
companion object {
135+
@Suppress("LocalVariableName")
136+
fun fromList(__pigeon_list: List<Any?>): Person {
137+
val iconBitmap = __pigeon_list[0] as ByteArray?
138+
val key = __pigeon_list[1] as String
139+
val name = __pigeon_list[2] as String
140+
return Person(iconBitmap, key, name)
141+
}
142+
}
143+
fun toList(): List<Any?> {
144+
return listOf(
145+
iconBitmap,
146+
key,
147+
name,
148+
)
149+
}
150+
}
151+
152+
/**
153+
* Corresponds to `androidx.core.app.NotificationCompat.MessagingStyle.Message`
154+
*
155+
* See: https://developer.android.com/reference/androidx/core/app/NotificationCompat.MessagingStyle.Message
156+
*
157+
* Generated class from Pigeon that represents data sent in messages.
158+
*/
159+
data class MessagingStyleMessage (
160+
val text: String,
161+
val timestampMs: Long,
162+
val person: Person
163+
164+
) {
165+
companion object {
166+
@Suppress("LocalVariableName")
167+
fun fromList(__pigeon_list: List<Any?>): MessagingStyleMessage {
168+
val text = __pigeon_list[0] as String
169+
val timestampMs = __pigeon_list[1].let { num -> if (num is Int) num.toLong() else num as Long }
170+
val person = __pigeon_list[2] as Person
171+
return MessagingStyleMessage(text, timestampMs, person)
172+
}
173+
}
174+
fun toList(): List<Any?> {
175+
return listOf(
176+
text,
177+
timestampMs,
178+
person,
179+
)
180+
}
181+
}
182+
183+
/**
184+
* Corresponds to `androidx.core.app.NotificationCompat.MessagingStyle`
185+
*
186+
* See: https://developer.android.com/reference/androidx/core/app/NotificationCompat.MessagingStyle
187+
*
188+
* Generated class from Pigeon that represents data sent in messages.
189+
*/
190+
data class MessagingStyle (
191+
val user: Person,
192+
val conversationTitle: String? = null,
193+
val messages: List<MessagingStyleMessage?>,
194+
val isGroupConversation: Boolean
195+
196+
) {
197+
companion object {
198+
@Suppress("LocalVariableName")
199+
fun fromList(__pigeon_list: List<Any?>): MessagingStyle {
200+
val user = __pigeon_list[0] as Person
201+
val conversationTitle = __pigeon_list[1] as String?
202+
val messages = __pigeon_list[2] as List<MessagingStyleMessage?>
203+
val isGroupConversation = __pigeon_list[3] as Boolean
204+
return MessagingStyle(user, conversationTitle, messages, isGroupConversation)
205+
}
206+
}
207+
fun toList(): List<Any?> {
208+
return listOf(
209+
user,
210+
conversationTitle,
211+
messages,
212+
isGroupConversation,
213+
)
214+
}
215+
}
110216
private object NotificationsPigeonCodec : StandardMessageCodec() {
111217
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
112218
return when (type) {
@@ -120,6 +226,21 @@ private object NotificationsPigeonCodec : StandardMessageCodec() {
120226
InboxStyle.fromList(it)
121227
}
122228
}
229+
131.toByte() -> {
230+
return (readValue(buffer) as? List<Any?>)?.let {
231+
Person.fromList(it)
232+
}
233+
}
234+
132.toByte() -> {
235+
return (readValue(buffer) as? List<Any?>)?.let {
236+
MessagingStyleMessage.fromList(it)
237+
}
238+
}
239+
133.toByte() -> {
240+
return (readValue(buffer) as? List<Any?>)?.let {
241+
MessagingStyle.fromList(it)
242+
}
243+
}
123244
else -> super.readValueOfType(type, buffer)
124245
}
125246
}
@@ -133,6 +254,18 @@ private object NotificationsPigeonCodec : StandardMessageCodec() {
133254
stream.write(130)
134255
writeValue(stream, value.toList())
135256
}
257+
is Person -> {
258+
stream.write(131)
259+
writeValue(stream, value.toList())
260+
}
261+
is MessagingStyleMessage -> {
262+
stream.write(132)
263+
writeValue(stream, value.toList())
264+
}
265+
is MessagingStyle -> {
266+
stream.write(133)
267+
writeValue(stream, value.toList())
268+
}
136269
else -> super.writeValue(stream, value)
137270
}
138271
}
@@ -159,7 +292,21 @@ interface AndroidNotificationHostApi {
159292
* https://developer.android.com/reference/kotlin/android/app/NotificationManager.html#notify
160293
* https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder
161294
*/
162-
fun notify(tag: String?, id: Long, autoCancel: Boolean?, channelId: String, color: Long?, contentIntent: PendingIntent?, contentText: String?, contentTitle: String?, extras: Map<String?, String?>?, groupKey: String?, inboxStyle: InboxStyle?, isGroupSummary: Boolean?, smallIconResourceName: String?)
295+
fun notify(tag: String?, id: Long, autoCancel: Boolean?, channelId: String, color: Long?, contentIntent: PendingIntent?, contentText: String?, contentTitle: String?, extras: Map<String?, String?>?, groupKey: String?, inboxStyle: InboxStyle?, isGroupSummary: Boolean?, messagingStyle: MessagingStyle?, number: Long?, smallIconResourceName: String?)
296+
/**
297+
* Wraps `androidx.core.app.NotificationManagerCompat.getActiveNotifications`,
298+
* combined with `androidx.core.app.NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification`.
299+
*
300+
* Returns the messaging style, if any, of an active notification
301+
* that has tag `tag`. If there are several such notifications,
302+
* an arbitrary one of them is used.
303+
* Returns null if there are no such notifications.
304+
*
305+
* See:
306+
* https://developer.android.com/reference/kotlin/androidx/core/app/NotificationManagerCompat#getActiveNotifications()
307+
* https://developer.android.com/reference/kotlin/androidx/core/app/NotificationCompat.MessagingStyle#extractMessagingStyleFromNotification(android.app.Notification)
308+
*/
309+
fun getActiveNotificationMessagingStyleByTag(tag: String): MessagingStyle?
163310

164311
companion object {
165312
/** The codec used by AndroidNotificationHostApi. */
@@ -187,9 +334,11 @@ interface AndroidNotificationHostApi {
187334
val groupKeyArg = args[9] as String?
188335
val inboxStyleArg = args[10] as InboxStyle?
189336
val isGroupSummaryArg = args[11] as Boolean?
190-
val smallIconResourceNameArg = args[12] as String?
337+
val messagingStyleArg = args[12] as MessagingStyle?
338+
val numberArg = args[13].let { num -> if (num is Int) num.toLong() else num as Long? }
339+
val smallIconResourceNameArg = args[14] as String?
191340
val wrapped: List<Any?> = try {
192-
api.notify(tagArg, idArg, autoCancelArg, channelIdArg, colorArg, contentIntentArg, contentTextArg, contentTitleArg, extrasArg, groupKeyArg, inboxStyleArg, isGroupSummaryArg, smallIconResourceNameArg)
341+
api.notify(tagArg, idArg, autoCancelArg, channelIdArg, colorArg, contentIntentArg, contentTextArg, contentTitleArg, extrasArg, groupKeyArg, inboxStyleArg, isGroupSummaryArg, messagingStyleArg, numberArg, smallIconResourceNameArg)
193342
listOf(null)
194343
} catch (exception: Throwable) {
195344
wrapError(exception)
@@ -200,6 +349,23 @@ interface AndroidNotificationHostApi {
200349
channel.setMessageHandler(null)
201350
}
202351
}
352+
run {
353+
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.zulip.AndroidNotificationHostApi.getActiveNotificationMessagingStyleByTag$separatedMessageChannelSuffix", codec)
354+
if (api != null) {
355+
channel.setMessageHandler { message, reply ->
356+
val args = message as List<Any?>
357+
val tagArg = args[0] as String
358+
val wrapped: List<Any?> = try {
359+
listOf(api.getActiveNotificationMessagingStyleByTag(tagArg))
360+
} catch (exception: Throwable) {
361+
wrapError(exception)
362+
}
363+
reply.reply(wrapped)
364+
}
365+
} else {
366+
channel.setMessageHandler(null)
367+
}
368+
}
203369
}
204370
}
205371
}

android/app/src/main/kotlin/com/zulip/flutter/ZulipPlugin.kt

+67
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,38 @@ import android.util.Log
88
import androidx.annotation.Keep
99
import androidx.core.app.NotificationCompat
1010
import androidx.core.app.NotificationManagerCompat
11+
import androidx.core.graphics.drawable.IconCompat
1112
import io.flutter.embedding.engine.plugins.FlutterPlugin
1213

1314
private const val TAG = "ZulipPlugin"
1415

16+
fun toAndroidPerson(person: Person): androidx.core.app.Person {
17+
return androidx.core.app.Person.Builder().apply {
18+
person.iconBitmap?.let { setIcon(IconCompat.createWithData(it, 0, it.size)) }
19+
setKey(person.key)
20+
setName(person.name)
21+
}.build()
22+
}
23+
24+
fun toPigeonPerson(person: androidx.core.app.Person): Person {
25+
return Person(
26+
// The API doesn't provide a way to retrieve the icon data,
27+
// so we set this to null.
28+
//
29+
// Notably, Android retains a limited number [1] of messages
30+
// in the messaging style, and it also retains the icon data
31+
// for persons within those messages. Therefore, there's no
32+
// need to include the person's icon data in each message.
33+
// Only one icon data instance is needed for each unique
34+
// person's key in the retained messages.
35+
//
36+
// [1]: https://developer.android.com/reference/androidx/core/app/NotificationCompat.MessagingStyle#MAXIMUM_RETAINED_MESSAGES()
37+
null,
38+
person.key!!,
39+
person.name!!.toString(),
40+
)
41+
}
42+
1543
private class AndroidNotificationHost(val context: Context)
1644
: AndroidNotificationHostApi {
1745
@SuppressLint(
@@ -33,6 +61,8 @@ private class AndroidNotificationHost(val context: Context)
3361
groupKey: String?,
3462
inboxStyle: InboxStyle?,
3563
isGroupSummary: Boolean?,
64+
messagingStyle: MessagingStyle?,
65+
number: Long?,
3666
smallIconResourceName: String?
3767
) {
3868
val notification = NotificationCompat.Builder(context, channelId).apply {
@@ -60,11 +90,48 @@ private class AndroidNotificationHost(val context: Context)
6090
.setSummaryText(it.summaryText)
6191
) }
6292
isGroupSummary?.let { setGroupSummary(it) }
93+
messagingStyle?.let { messagingStyle ->
94+
val style = NotificationCompat.MessagingStyle(toAndroidPerson(messagingStyle.user))
95+
.setConversationTitle(messagingStyle.conversationTitle)
96+
.setGroupConversation(messagingStyle.isGroupConversation)
97+
messagingStyle.messages.forEach { it?.let {
98+
style.addMessage(NotificationCompat.MessagingStyle.Message(
99+
it.text,
100+
it.timestampMs,
101+
toAndroidPerson(it.person),
102+
))
103+
} }
104+
setStyle(style)
105+
}
106+
number?.let { setNumber(it.toInt()) }
63107
smallIconResourceName?.let { setSmallIcon(context.resources.getIdentifier(
64108
it, "drawable", context.packageName)) }
65109
}.build()
66110
NotificationManagerCompat.from(context).notify(tag, id.toInt(), notification)
67111
}
112+
113+
override fun getActiveNotificationMessagingStyleByTag(tag: String): MessagingStyle? {
114+
val activeNotification = NotificationManagerCompat.from(context)
115+
.activeNotifications
116+
.find { it.tag == tag }
117+
activeNotification?.notification?.let { notification ->
118+
NotificationCompat.MessagingStyle
119+
.extractMessagingStyleFromNotification(notification)
120+
?.let { style ->
121+
return MessagingStyle(
122+
toPigeonPerson(style.user),
123+
style.conversationTitle!!.toString(),
124+
style.messages.map { MessagingStyleMessage(
125+
it.text!!.toString(),
126+
it.timestamp,
127+
toPigeonPerson(it.person!!)
128+
) },
129+
style.isGroupConversation,
130+
)
131+
}
132+
}
133+
return null
134+
}
68135
}
69136

70137
/** A Flutter plugin for the Zulip app's ad-hoc needs. */

0 commit comments

Comments
 (0)