Skip to content

Commit c2150a5

Browse files
notif android: Migrate to cross-platform Pigeon API for navigation
1 parent d0cf530 commit c2150a5

File tree

12 files changed

+584
-644
lines changed

12 files changed

+584
-644
lines changed

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -104,22 +104,25 @@ data class AndroidIntent (
104104
val action: String,
105105
val dataUrl: String,
106106
/** A combination of flags from [IntentFlag]. */
107-
val flags: Long
107+
val flags: Long,
108+
val extrasData: Map<String, String>
108109
)
109110
{
110111
companion object {
111112
fun fromList(pigeonVar_list: List<Any?>): AndroidIntent {
112113
val action = pigeonVar_list[0] as String
113114
val dataUrl = pigeonVar_list[1] as String
114115
val flags = pigeonVar_list[2] as Long
115-
return AndroidIntent(action, dataUrl, flags)
116+
val extrasData = pigeonVar_list[3] as Map<String, String>
117+
return AndroidIntent(action, dataUrl, flags, extrasData)
116118
}
117119
}
118120
fun toList(): List<Any?> {
119121
return listOf(
120122
action,
121123
dataUrl,
122124
flags,
125+
extrasData,
123126
)
124127
}
125128
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,81 @@
11
package com.zulip.flutter
22

3+
import android.content.Intent
4+
import android.os.Bundle
35
import io.flutter.embedding.android.FlutterActivity
6+
import io.flutter.embedding.engine.FlutterEngine
47

5-
class MainActivity: FlutterActivity() {
8+
class MainActivity : FlutterActivity() {
9+
private var notificationTapEventListener: NotificationTapEventListener? = null
10+
11+
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
12+
super.configureFlutterEngine(flutterEngine)
13+
14+
val maybeNotifPayload = maybeIntentToNotificationPayload(intent)
15+
val api = NotificationHostApiImpl(maybeNotifPayload)
16+
NotificationHostApi.setUp(flutterEngine.dartExecutor.binaryMessenger, api)
17+
18+
notificationTapEventListener = NotificationTapEventListener()
19+
NotificationTapEventsStreamHandler.register(
20+
flutterEngine.dartExecutor.binaryMessenger, notificationTapEventListener!!
21+
)
22+
}
23+
24+
override fun onNewIntent(intent: Intent) {
25+
val maybeNotifData = maybeIntentToNotificationPayload(intent)
26+
if (notificationTapEventListener != null && maybeNotifData != null) {
27+
notificationTapEventListener!!.onNotificationTapEvent(maybeNotifData)
28+
return
29+
}
30+
31+
super.onNewIntent(intent)
32+
}
33+
34+
override fun cleanUpFlutterEngine(flutterEngine: FlutterEngine) {
35+
notificationTapEventListener?.onEventsDone()
36+
notificationTapEventListener = null
37+
38+
super.cleanUpFlutterEngine(flutterEngine)
39+
}
40+
41+
private fun maybeIntentToNotificationPayload(intent: Intent): NotificationPayloadForOpen? {
42+
var notifData: NotificationPayloadForOpen? = null
43+
if (intent.action == Intent.ACTION_VIEW) {
44+
val intentUrl = intent.data
45+
if (intentUrl?.scheme == "zulip" && intentUrl.authority == "notification") {
46+
val bundle = intent.getBundleExtra("data")
47+
if (bundle != null) {
48+
notifData = NotificationPayloadForOpen(payload = bundleToMap(bundle))
49+
}
50+
}
51+
}
52+
return notifData
53+
}
54+
}
55+
56+
fun bundleToMap(bundle: Bundle): Map<Any?, Any?> =
57+
bundle.keySet().mapNotNull { key -> bundle.getString(key)?.let { key to it } }.toMap()
58+
59+
private class NotificationHostApiImpl(val maybeNotifPayload: NotificationPayloadForOpen?) :
60+
NotificationHostApi {
61+
override fun getNotificationDataFromLaunch(): NotificationPayloadForOpen? {
62+
return maybeNotifPayload
63+
}
64+
}
65+
66+
private class NotificationTapEventListener : NotificationTapEventsStreamHandler() {
67+
private var eventSink: PigeonEventSink<NotificationPayloadForOpen>? = null
68+
69+
override fun onListen(p0: Any?, sink: PigeonEventSink<NotificationPayloadForOpen>) {
70+
eventSink = sink
71+
}
72+
73+
fun onNotificationTapEvent(data: NotificationPayloadForOpen) {
74+
eventSink?.success(data)
75+
}
76+
77+
fun onEventsDone() {
78+
eventSink?.endOfStream()
79+
eventSink = null
80+
}
681
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Autogenerated from Pigeon (v24.2.1), do not edit directly.
2+
// See also: https://pub.dev/packages/pigeon
3+
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
4+
5+
package com.zulip.flutter
6+
7+
import android.util.Log
8+
import io.flutter.plugin.common.BasicMessageChannel
9+
import io.flutter.plugin.common.BinaryMessenger
10+
import io.flutter.plugin.common.EventChannel
11+
import io.flutter.plugin.common.MessageCodec
12+
import io.flutter.plugin.common.StandardMethodCodec
13+
import io.flutter.plugin.common.StandardMessageCodec
14+
import java.io.ByteArrayOutputStream
15+
import java.nio.ByteBuffer
16+
17+
private fun wrapResult(result: Any?): List<Any?> {
18+
return listOf(result)
19+
}
20+
21+
private fun wrapError(exception: Throwable): List<Any?> {
22+
return if (exception is FlutterError) {
23+
listOf(
24+
exception.code,
25+
exception.message,
26+
exception.details
27+
)
28+
} else {
29+
listOf(
30+
exception.javaClass.simpleName,
31+
exception.toString(),
32+
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
33+
)
34+
}
35+
}
36+
37+
/** Generated class from Pigeon that represents data sent in messages. */
38+
data class NotificationPayloadForOpen (
39+
val payload: Map<Any?, Any?>
40+
)
41+
{
42+
companion object {
43+
fun fromList(pigeonVar_list: List<Any?>): NotificationPayloadForOpen {
44+
val payload = pigeonVar_list[0] as Map<Any?, Any?>
45+
return NotificationPayloadForOpen(payload)
46+
}
47+
}
48+
fun toList(): List<Any?> {
49+
return listOf(
50+
payload,
51+
)
52+
}
53+
}
54+
private open class NotificationsPigeonCodec : StandardMessageCodec() {
55+
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
56+
return when (type) {
57+
129.toByte() -> {
58+
return (readValue(buffer) as? List<Any?>)?.let {
59+
NotificationPayloadForOpen.fromList(it)
60+
}
61+
}
62+
else -> super.readValueOfType(type, buffer)
63+
}
64+
}
65+
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
66+
when (value) {
67+
is NotificationPayloadForOpen -> {
68+
stream.write(129)
69+
writeValue(stream, value.toList())
70+
}
71+
else -> super.writeValue(stream, value)
72+
}
73+
}
74+
}
75+
76+
val NotificationsPigeonMethodCodec = StandardMethodCodec(NotificationsPigeonCodec());
77+
78+
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
79+
interface NotificationHostApi {
80+
fun getNotificationDataFromLaunch(): NotificationPayloadForOpen?
81+
82+
companion object {
83+
/** The codec used by NotificationHostApi. */
84+
val codec: MessageCodec<Any?> by lazy {
85+
NotificationsPigeonCodec()
86+
}
87+
/** Sets up an instance of `NotificationHostApi` to handle messages through the `binaryMessenger`. */
88+
@JvmOverloads
89+
fun setUp(binaryMessenger: BinaryMessenger, api: NotificationHostApi?, messageChannelSuffix: String = "") {
90+
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
91+
run {
92+
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.zulip.NotificationHostApi.getNotificationDataFromLaunch$separatedMessageChannelSuffix", codec)
93+
if (api != null) {
94+
channel.setMessageHandler { _, reply ->
95+
val wrapped: List<Any?> = try {
96+
listOf(api.getNotificationDataFromLaunch())
97+
} catch (exception: Throwable) {
98+
wrapError(exception)
99+
}
100+
reply.reply(wrapped)
101+
}
102+
} else {
103+
channel.setMessageHandler(null)
104+
}
105+
}
106+
}
107+
}
108+
}
109+
110+
private class NotificationsPigeonStreamHandler<T>(
111+
val wrapper: NotificationsPigeonEventChannelWrapper<T>
112+
) : EventChannel.StreamHandler {
113+
var pigeonSink: PigeonEventSink<T>? = null
114+
115+
override fun onListen(p0: Any?, sink: EventChannel.EventSink) {
116+
pigeonSink = PigeonEventSink<T>(sink)
117+
wrapper.onListen(p0, pigeonSink!!)
118+
}
119+
120+
override fun onCancel(p0: Any?) {
121+
pigeonSink = null
122+
wrapper.onCancel(p0)
123+
}
124+
}
125+
126+
interface NotificationsPigeonEventChannelWrapper<T> {
127+
open fun onListen(p0: Any?, sink: PigeonEventSink<T>) {}
128+
129+
open fun onCancel(p0: Any?) {}
130+
}
131+
132+
class PigeonEventSink<T>(private val sink: EventChannel.EventSink) {
133+
fun success(value: T) {
134+
sink.success(value)
135+
}
136+
137+
fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
138+
sink.error(errorCode, errorMessage, errorDetails)
139+
}
140+
141+
fun endOfStream() {
142+
sink.endOfStream()
143+
}
144+
}
145+
146+
abstract class NotificationTapEventsStreamHandler : NotificationsPigeonEventChannelWrapper<NotificationPayloadForOpen> {
147+
companion object {
148+
fun register(messenger: BinaryMessenger, streamHandler: NotificationTapEventsStreamHandler, instanceName: String = "") {
149+
var channelName: String = "dev.flutter.pigeon.zulip.NotificationHostEvents.notificationTapEvents"
150+
if (instanceName.isNotEmpty()) {
151+
channelName += ".$instanceName"
152+
}
153+
val internalStreamHandler = NotificationsPigeonStreamHandler<NotificationPayloadForOpen>(streamHandler)
154+
EventChannel(messenger, channelName, NotificationsPigeonMethodCodec).setStreamHandler(internalStreamHandler)
155+
}
156+
}
157+
}
158+

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

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import androidx.core.app.NotificationChannelCompat
1818
import androidx.core.app.NotificationCompat
1919
import androidx.core.app.NotificationManagerCompat
2020
import androidx.core.graphics.drawable.IconCompat
21+
import androidx.core.os.bundleOf
2122
import io.flutter.embedding.engine.plugins.FlutterPlugin
2223

2324
private const val TAG = "ZulipPlugin"
@@ -204,6 +205,10 @@ private class AndroidNotificationHost(val context: Context)
204205
MainActivity::class.java
205206
).apply {
206207
flags = intent.flags.toInt()
208+
putExtra(
209+
"data",
210+
bundleOf(*intent.extrasData.toList().toTypedArray())
211+
)
207212
} },
208213
it.flags.toInt())
209214
) }

lib/host/android_notifications.g.dart

+5
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class AndroidIntent {
7878
required this.action,
7979
required this.dataUrl,
8080
this.flags = 0,
81+
required this.extrasData,
8182
});
8283

8384
String action;
@@ -87,11 +88,14 @@ class AndroidIntent {
8788
/// A combination of flags from [IntentFlag].
8889
int flags;
8990

91+
Map<String, String> extrasData;
92+
9093
Object encode() {
9194
return <Object?>[
9295
action,
9396
dataUrl,
9497
flags,
98+
extrasData,
9599
];
96100
}
97101

@@ -101,6 +105,7 @@ class AndroidIntent {
101105
action: result[0]! as String,
102106
dataUrl: result[1]! as String,
103107
flags: result[2]! as int,
108+
extrasData: (result[3] as Map<Object?, Object?>?)!.cast<String, String>(),
104109
);
105110
}
106111
}

0 commit comments

Comments
 (0)