Skip to content

Commit 63714e3

Browse files
draft
1 parent 5c70c76 commit 63714e3

File tree

15 files changed

+470
-389
lines changed

15 files changed

+470
-389
lines changed

android/app/src/main/AndroidManifest.xml

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<category android:name="android.intent.category.DEFAULT" />
3636
<category android:name="android.intent.category.BROWSABLE" />
3737
<data android:scheme="zulip" android:host="login" />
38+
<data android:scheme="zulip" android:host="notification_open" />
3839
</intent-filter>
3940
</activity>
4041
<!-- Don't delete the meta-data below.

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

+52-17
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,36 @@ data class NotificationChannel (
8787
}
8888
}
8989

90+
/**
91+
* Corresponds to `android.content.Intent`
92+
*
93+
* See:
94+
* https://developer.android.com/reference/android/content/Intent
95+
* https://developer.android.com/reference/android/content/Intent#Intent(java.lang.String,%20android.net.Uri,%20android.content.Context,%20java.lang.Class%3C?%3E)
96+
*
97+
* Generated class from Pigeon that represents data sent in messages.
98+
*/
99+
data class AndroidIntent (
100+
val action: String,
101+
val uri: String
102+
103+
) {
104+
companion object {
105+
@Suppress("LocalVariableName")
106+
fun fromList(__pigeon_list: List<Any?>): AndroidIntent {
107+
val action = __pigeon_list[0] as String
108+
val uri = __pigeon_list[1] as String
109+
return AndroidIntent(action, uri)
110+
}
111+
}
112+
fun toList(): List<Any?> {
113+
return listOf(
114+
action,
115+
uri,
116+
)
117+
}
118+
}
119+
90120
/**
91121
* Corresponds to `android.app.PendingIntent`.
92122
*
@@ -96,11 +126,7 @@ data class NotificationChannel (
96126
*/
97127
data class PendingIntent (
98128
val requestCode: Long,
99-
/**
100-
* A value set on an extra on the Intent, and passed to
101-
* the on-notification-opened callback.
102-
*/
103-
val intentPayload: String,
129+
val intent: AndroidIntent,
104130
/**
105131
* A combination of flags from [PendingIntent.flags], and others associated
106132
* with `Intent`; see Android docs for `PendingIntent.getActivity`.
@@ -112,15 +138,15 @@ data class PendingIntent (
112138
@Suppress("LocalVariableName")
113139
fun fromList(__pigeon_list: List<Any?>): PendingIntent {
114140
val requestCode = __pigeon_list[0].let { num -> if (num is Int) num.toLong() else num as Long }
115-
val intentPayload = __pigeon_list[1] as String
141+
val intent = __pigeon_list[1] as AndroidIntent
116142
val flags = __pigeon_list[2].let { num -> if (num is Int) num.toLong() else num as Long }
117-
return PendingIntent(requestCode, intentPayload, flags)
143+
return PendingIntent(requestCode, intent, flags)
118144
}
119145
}
120146
fun toList(): List<Any?> {
121147
return listOf(
122148
requestCode,
123-
intentPayload,
149+
intent,
124150
flags,
125151
)
126152
}
@@ -266,25 +292,30 @@ private object NotificationsPigeonCodec : StandardMessageCodec() {
266292
}
267293
130.toByte() -> {
268294
return (readValue(buffer) as? List<Any?>)?.let {
269-
PendingIntent.fromList(it)
295+
AndroidIntent.fromList(it)
270296
}
271297
}
272298
131.toByte() -> {
273299
return (readValue(buffer) as? List<Any?>)?.let {
274-
InboxStyle.fromList(it)
300+
PendingIntent.fromList(it)
275301
}
276302
}
277303
132.toByte() -> {
278304
return (readValue(buffer) as? List<Any?>)?.let {
279-
Person.fromList(it)
305+
InboxStyle.fromList(it)
280306
}
281307
}
282308
133.toByte() -> {
283309
return (readValue(buffer) as? List<Any?>)?.let {
284-
MessagingStyleMessage.fromList(it)
310+
Person.fromList(it)
285311
}
286312
}
287313
134.toByte() -> {
314+
return (readValue(buffer) as? List<Any?>)?.let {
315+
MessagingStyleMessage.fromList(it)
316+
}
317+
}
318+
135.toByte() -> {
288319
return (readValue(buffer) as? List<Any?>)?.let {
289320
MessagingStyle.fromList(it)
290321
}
@@ -298,26 +329,30 @@ private object NotificationsPigeonCodec : StandardMessageCodec() {
298329
stream.write(129)
299330
writeValue(stream, value.toList())
300331
}
301-
is PendingIntent -> {
332+
is AndroidIntent -> {
302333
stream.write(130)
303334
writeValue(stream, value.toList())
304335
}
305-
is InboxStyle -> {
336+
is PendingIntent -> {
306337
stream.write(131)
307338
writeValue(stream, value.toList())
308339
}
309-
is Person -> {
340+
is InboxStyle -> {
310341
stream.write(132)
311342
writeValue(stream, value.toList())
312343
}
313-
is MessagingStyleMessage -> {
344+
is Person -> {
314345
stream.write(133)
315346
writeValue(stream, value.toList())
316347
}
317-
is MessagingStyle -> {
348+
is MessagingStyleMessage -> {
318349
stream.write(134)
319350
writeValue(stream, value.toList())
320351
}
352+
is MessagingStyle -> {
353+
stream.write(135)
354+
writeValue(stream, value.toList())
355+
}
321356
else -> super.writeValue(stream, value)
322357
}
323358
}

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

+7-7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.zulip.flutter
33
import android.annotation.SuppressLint
44
import android.content.Context
55
import android.content.Intent
6+
import android.net.Uri
67
import android.os.Bundle
78
import android.util.Log
89
import androidx.annotation.Keep
@@ -82,13 +83,12 @@ private class AndroidNotificationHost(val context: Context)
8283
contentIntent?.let { setContentIntent(
8384
android.app.PendingIntent.getActivity(context,
8485
it.requestCode.toInt(),
85-
Intent(context, MainActivity::class.java).apply {
86-
// This action name and extra name are special to
87-
// FlutterLocalNotificationsPlugin, which handles receiving the Intent.
88-
// TODO take care of receiving the notification-opened Intent ourselves
89-
action = "SELECT_NOTIFICATION"
90-
putExtra("payload", it.intentPayload)
91-
},
86+
it.intent.let { intent -> Intent(
87+
intent.action,
88+
Uri.parse(intent.uri),
89+
context,
90+
MainActivity::class.java
91+
) },
9292
it.flags.toInt())
9393
) }
9494
contentText?.let { setContentText(it) }

lib/api/model/notification.dart

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import '../../model/narrow.dart';
2+
3+
class NotificationOpenPayload {
4+
final Uri realm;
5+
final int userId;
6+
final Narrow narrow;
7+
8+
NotificationOpenPayload({
9+
required this.realm,
10+
required this.userId,
11+
required this.narrow,
12+
}): assert((narrow is TopicNarrow) ^ (narrow is DmNarrow));
13+
14+
factory NotificationOpenPayload.parse(Uri url) {
15+
if (url case Uri(
16+
scheme: 'zulip',
17+
host: 'notification_open',
18+
queryParameters: {
19+
'realm': String realmStr,
20+
'user_id': String userIdStr,
21+
'narrow_type': String narrowType,
22+
},
23+
)) {
24+
final realm = Uri.tryParse(realmStr);
25+
if (realm == null) throw const FormatException();
26+
27+
final userId = int.tryParse(userIdStr, radix: 10);
28+
if (userId == null) throw const FormatException();
29+
30+
final Narrow narrow;
31+
switch (narrowType) {
32+
case 'topic':
33+
final streamIdStr = url.queryParameters['stream_id'];
34+
if (streamIdStr == null) throw const FormatException();
35+
final streamId = int.tryParse(streamIdStr, radix: 10);
36+
if (streamId == null) throw const FormatException();
37+
38+
final topic = url.queryParameters['topic'];
39+
if (topic == null) throw const FormatException();
40+
41+
narrow = TopicNarrow(streamId, topic);
42+
case 'dm':
43+
final allRecipientIdsStr = url.queryParameters['all_recipient_ids'];
44+
if (allRecipientIdsStr == null) throw const FormatException();
45+
final List<int> allRecipientIds = allRecipientIdsStr
46+
.split(',')
47+
.map((String idStr) {
48+
final id = int.tryParse(idStr, radix: 10);
49+
if (id == null) throw const FormatException();
50+
return id;
51+
})
52+
.toList();
53+
54+
narrow = DmNarrow(allRecipientIds: allRecipientIds, selfUserId: userId);
55+
default:
56+
throw const FormatException();
57+
}
58+
59+
return NotificationOpenPayload(
60+
realm: realm,
61+
userId: userId,
62+
narrow: narrow,
63+
);
64+
} else {
65+
// TODO(dart): simplify after https://github.com/dart-lang/language/issues/2537
66+
throw const FormatException();
67+
}
68+
}
69+
70+
Uri toUri() {
71+
var queryParameters = <String, String>{
72+
'realm': realm.toString(),
73+
'user_id': userId.toString(),
74+
};
75+
76+
switch (narrow) {
77+
case TopicNarrow(:final streamId, :final topic):
78+
queryParameters = {
79+
...queryParameters,
80+
'narrow_type': 'topic',
81+
'stream_id': streamId.toString(),
82+
'topic': topic.toString(),
83+
};
84+
case DmNarrow(:final allRecipientIds):
85+
queryParameters = {
86+
...queryParameters,
87+
'narrow_type': 'dm',
88+
'all_recipient_ids': allRecipientIds.join(','),
89+
};
90+
default:
91+
throw UnimplementedError();
92+
}
93+
94+
return Uri(
95+
scheme: 'zulip',
96+
host: 'notification_open',
97+
queryParameters: queryParameters,
98+
);
99+
}
100+
}

lib/host/android_notifications.dart

+8
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,11 @@ abstract class PendingIntentFlag {
6060
/// Corresponds to `FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT`.
6161
static const allowUnsafeImplicitIntent = 1 << 24;
6262
}
63+
64+
/// For use in [AndroidIntent.action].
65+
///
66+
/// See: https://developer.android.com/reference/android/content/Intent#constants_1
67+
abstract class IntentAction {
68+
/// Corresponds to `ACTION_VIEW`.
69+
static const view = 'android.intent.action.VIEW';
70+
}

0 commit comments

Comments
 (0)