Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions android/app/src/main/kotlin/com/zulip/flutter/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
package com.zulip.flutter

import android.content.Intent
import com.zulip.flutter.notifications.NotificationTapEventListener
import com.zulip.flutter.notifications.NotificationTapEventsStreamHandler
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine

class MainActivity : FlutterActivity() {
private var androidIntentEventListener: AndroidIntentEventListener? = null
private var notificationTapEventListener: NotificationTapEventListener? = null

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)

androidIntentEventListener = AndroidIntentEventListener()
AndroidIntentEventsStreamHandler.register(
flutterEngine.dartExecutor.binaryMessenger,
androidIntentEventListener!!
flutterEngine.dartExecutor.binaryMessenger, androidIntentEventListener!!
)
notificationTapEventListener = NotificationTapEventListener()
NotificationTapEventsStreamHandler.register(
flutterEngine.dartExecutor.binaryMessenger, notificationTapEventListener!!
)

maybeHandleIntent(intent)
}

Expand All @@ -35,6 +42,17 @@ class MainActivity : FlutterActivity() {
return true
}

Intent.ACTION_VIEW -> {
if (notificationTapEventListener!!.maybeHandleViewIntent(intent)) {
// Notification tapped
return true
}

// Let Flutter handle other intents, in particular the web-auth intents
// have ACTION_VIEW, scheme "zulip", and authority "login".
return false
}

// For other intents, let Flutter handle it.
else -> return false
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.zulip.flutter.notifications

import android.content.Intent
import android.net.Uri

class NotificationTapEventListener : NotificationTapEventsStreamHandler() {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The notification taps come to us as Android Intents, right, so I'm curious why our existing class named AndroidIntentEventListener isn't involved at all in this work. Is it helpful or necessary to create this whole separate class? (Maybe it has something to do with the Pigeon limitations you mentioned at #2043 (comment) ?) Or would the new intent-handling code be better organized if we just put it on AndroidIntentEventListener?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

IIRC the problem was that the implementation of AndroidIntentEventListener would need to accommodate for multiple listeners (handling multiple incoming onListen calls), because stream.listen() would be called at two places.

The implementation would then need to handle buffered events specially, basically keep filling the buffer until there are two incoming onListen calls, replay the events to the listener on each call, and then clear the buffer. AndroidIntentEventListener would also need to ensure that there can be only two stream.listen() on the Dart side.

All this seemed like unnecessary complexity, and also a nice self-contained notifications.dart Pigeon API with types like cross-platform variants of NotificationTapEvent seemed like a better approach.

private var eventSink: PigeonEventSink<NotificationTapEvent>? = null
private val buffer = mutableListOf<NotificationTapEvent>()

override fun onListen(p0: Any?, sink: PigeonEventSink<NotificationTapEvent>) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is it important here (and elsewhere in this file) that we use the platform-agnostic base class NotificationTapEvent, instead of AndroidNotificationTapEvent?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hmm, since these are overriden functions we can't change their signatures.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Probably it'd be good to fully implement the protocol, even though we don't currently intend to use this feature: override onCancel too, and have it remove the sink.

That way there isn't a latent bug waiting for us in case we someday do start using that feature of the protocol.

eventSink = sink
if (buffer.isNotEmpty()) {
buffer.forEach { sink.success(it) }
buffer.clear()
}
}

override fun onCancel(p0: Any?) {
if (eventSink != null) {
eventSink!!.endOfStream()
eventSink = null
}
}

private fun onNotificationTapEvent(dataUrl: Uri) {
val event = AndroidNotificationTapEvent(dataUrl.toString())
if (eventSink != null) {
eventSink!!.success(event)
} else {
buffer.add(event)
}
}

/**
* Recognize if the ACTION_VIEW intent came from tapping a notification; handle it if so.
*
* If the intent is recognized, sends a notification tap event via
* the Pigeon event stream to the Dart layer and returns true.
* Else does nothing and returns false.
*
* Do not call if `intent.action` is not ACTION_VIEW.
*/
fun maybeHandleViewIntent(intent: Intent): Boolean {
assert(intent.action == Intent.ACTION_VIEW)

val url = intent.data
if (url?.scheme == "zulip" && url.authority == "notification") {
onNotificationTapEvent(url)
return true
}

return false
}
}
Loading