Skip to content

Commit

Permalink
draft
Browse files Browse the repository at this point in the history
  • Loading branch information
rajveermalviya committed Aug 1, 2024
1 parent 5c70c76 commit 0747551
Show file tree
Hide file tree
Showing 15 changed files with 470 additions and 389 deletions.
1 change: 1 addition & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="zulip" android:host="login" />
<data android:host="notification_open" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
Expand Down
69 changes: 52 additions & 17 deletions android/app/src/main/kotlin/com/zulip/flutter/Notifications.g.kt
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,36 @@ data class NotificationChannel (
}
}

/**
* Corresponds to `android.content.Intent`
*
* See:
* https://developer.android.com/reference/android/content/Intent
* https://developer.android.com/reference/android/content/Intent#Intent(java.lang.String,%20android.net.Uri,%20android.content.Context,%20java.lang.Class%3C?%3E)
*
* Generated class from Pigeon that represents data sent in messages.
*/
data class AndroidIntent (
val action: String,
val uri: String

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

/**
* Corresponds to `android.app.PendingIntent`.
*
Expand All @@ -96,11 +126,7 @@ data class NotificationChannel (
*/
data class PendingIntent (
val requestCode: Long,
/**
* A value set on an extra on the Intent, and passed to
* the on-notification-opened callback.
*/
val intentPayload: String,
val intent: AndroidIntent,
/**
* A combination of flags from [PendingIntent.flags], and others associated
* with `Intent`; see Android docs for `PendingIntent.getActivity`.
Expand All @@ -112,15 +138,15 @@ data class PendingIntent (
@Suppress("LocalVariableName")
fun fromList(__pigeon_list: List<Any?>): PendingIntent {
val requestCode = __pigeon_list[0].let { num -> if (num is Int) num.toLong() else num as Long }
val intentPayload = __pigeon_list[1] as String
val intent = __pigeon_list[1] as AndroidIntent
val flags = __pigeon_list[2].let { num -> if (num is Int) num.toLong() else num as Long }
return PendingIntent(requestCode, intentPayload, flags)
return PendingIntent(requestCode, intent, flags)
}
}
fun toList(): List<Any?> {
return listOf(
requestCode,
intentPayload,
intent,
flags,
)
}
Expand Down Expand Up @@ -266,25 +292,30 @@ private object NotificationsPigeonCodec : StandardMessageCodec() {
}
130.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
PendingIntent.fromList(it)
AndroidIntent.fromList(it)
}
}
131.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
InboxStyle.fromList(it)
PendingIntent.fromList(it)
}
}
132.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
Person.fromList(it)
InboxStyle.fromList(it)
}
}
133.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
MessagingStyleMessage.fromList(it)
Person.fromList(it)
}
}
134.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
MessagingStyleMessage.fromList(it)
}
}
135.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
MessagingStyle.fromList(it)
}
Expand All @@ -298,26 +329,30 @@ private object NotificationsPigeonCodec : StandardMessageCodec() {
stream.write(129)
writeValue(stream, value.toList())
}
is PendingIntent -> {
is AndroidIntent -> {
stream.write(130)
writeValue(stream, value.toList())
}
is InboxStyle -> {
is PendingIntent -> {
stream.write(131)
writeValue(stream, value.toList())
}
is Person -> {
is InboxStyle -> {
stream.write(132)
writeValue(stream, value.toList())
}
is MessagingStyleMessage -> {
is Person -> {
stream.write(133)
writeValue(stream, value.toList())
}
is MessagingStyle -> {
is MessagingStyleMessage -> {
stream.write(134)
writeValue(stream, value.toList())
}
is MessagingStyle -> {
stream.write(135)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
Expand Down
14 changes: 7 additions & 7 deletions android/app/src/main/kotlin/com/zulip/flutter/ZulipPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.zulip.flutter
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.annotation.Keep
Expand Down Expand Up @@ -82,13 +83,12 @@ private class AndroidNotificationHost(val context: Context)
contentIntent?.let { setContentIntent(
android.app.PendingIntent.getActivity(context,
it.requestCode.toInt(),
Intent(context, MainActivity::class.java).apply {
// This action name and extra name are special to
// FlutterLocalNotificationsPlugin, which handles receiving the Intent.
// TODO take care of receiving the notification-opened Intent ourselves
action = "SELECT_NOTIFICATION"
putExtra("payload", it.intentPayload)
},
it.intent.let { intent -> Intent(
intent.action,
Uri.parse(intent.uri),
context,
MainActivity::class.java
) },
it.flags.toInt())
) }
contentText?.let { setContentText(it) }
Expand Down
100 changes: 100 additions & 0 deletions lib/api/model/notification.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import '../../model/narrow.dart';

class NotificationOpenPayload {
final Uri realm;
final int userId;
final Narrow narrow;

NotificationOpenPayload({
required this.realm,
required this.userId,
required this.narrow,
}): assert((narrow is TopicNarrow) ^ (narrow is DmNarrow));

factory NotificationOpenPayload.parse(Uri url) {
if (url case Uri(
scheme: 'zulip',
host: 'notification_open',
queryParameters: {
'realm': String realmStr,
'user_id': String userIdStr,
'narrow_type': String narrowType,
},
)) {
final realm = Uri.tryParse(realmStr);
if (realm == null) throw const FormatException();

final userId = int.tryParse(userIdStr, radix: 10);
if (userId == null) throw const FormatException();

final Narrow narrow;
switch (narrowType) {
case 'topic':
final streamIdStr = url.queryParameters['stream_id'];
if (streamIdStr == null) throw const FormatException();
final streamId = int.tryParse(streamIdStr, radix: 10);
if (streamId == null) throw const FormatException();

final topic = url.queryParameters['topic'];
if (topic == null) throw const FormatException();

narrow = TopicNarrow(streamId, topic);
case 'dm':
final allRecipientIdsStr = url.queryParameters['all_recipient_ids'];
if (allRecipientIdsStr == null) throw const FormatException();
final List<int> allRecipientIds = allRecipientIdsStr
.split(',')
.map((String idStr) {
final id = int.tryParse(idStr, radix: 10);
if (id == null) throw const FormatException();
return id;
})
.toList();

narrow = DmNarrow(allRecipientIds: allRecipientIds, selfUserId: userId);
default:
throw const FormatException();
}

return NotificationOpenPayload(
realm: realm,
userId: userId,
narrow: narrow,
);
} else {
// TODO(dart): simplify after https://github.com/dart-lang/language/issues/2537
throw const FormatException();
}
}

Uri toUri() {
var queryParameters = <String, String>{
'realm': realm.toString(),
'user_id': userId.toString(),
};

switch (narrow) {
case TopicNarrow(:final streamId, :final topic):
queryParameters = {
...queryParameters,
'narrow_type': 'topic',
'stream_id': streamId.toString(),
'topic': topic.toString(),
};
case DmNarrow(:final allRecipientIds):
queryParameters = {
...queryParameters,
'narrow_type': 'dm',
'all_recipient_ids': allRecipientIds.join(','),
};
default:
throw UnimplementedError();
}

return Uri(
scheme: 'zulip',
host: 'notification_open',
queryParameters: queryParameters,
);
}
}
8 changes: 8 additions & 0 deletions lib/host/android_notifications.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,11 @@ abstract class PendingIntentFlag {
/// Corresponds to `FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT`.
static const allowUnsafeImplicitIntent = 1 << 24;
}

/// For use in [AndroidIntent.action].
///
/// See: https://developer.android.com/reference/android/content/Intent#constants_1
abstract class IntentAction {
/// Corresponds to `ACTION_VIEW`.
static const view = 'android.intent.action.VIEW';
}
Loading

0 comments on commit 0747551

Please sign in to comment.