Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embedded views - Basic wiring with simple views #210

Merged
merged 23 commits into from
Sep 3, 2024
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
6 changes: 6 additions & 0 deletions android/.idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions android/.idea/migrations.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ buildscript {
ext.kotlin_version = '1.9.0'
ext.coroutine_version = '1.5.2'
ext.datastore_preferences_version = '1.1.1'
ext.airship_framework_proxy_version = '7.1.2'
ext.airship_framework_proxy_version = '7.3.0'


repositories {
google()
Expand Down
129 changes: 77 additions & 52 deletions android/src/main/kotlin/com/airship/flutter/AirshipPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.app.Activity
import android.content.Context
import android.os.Build
import com.urbanairship.actions.ActionResult
import com.urbanairship.android.framework.proxy.Event
import com.urbanairship.android.framework.proxy.EventType
import com.urbanairship.android.framework.proxy.events.EventEmitter
import com.urbanairship.android.framework.proxy.proxies.AirshipProxy
Expand All @@ -22,18 +23,16 @@ import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
import io.flutter.plugin.platform.PlatformViewRegistry
import kotlinx.coroutines.*
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock

class AirshipPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {

private lateinit var channel: MethodChannel

private lateinit var context: Context

private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main) + SupervisorJob()

private var mainActivity: Activity? = null

private lateinit var streams : Map<EventType, AirshipEventStream>
private lateinit var streams: Map<EventType, AirshipEventStream>

companion object {
@JvmStatic
Expand All @@ -50,17 +49,18 @@ class AirshipPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
internal const val AIRSHIP_SHARED_PREFS = "com.urbanairship.flutter"

internal val EVENT_NAME_MAP = mapOf(
EventType.BACKGROUND_NOTIFICATION_RESPONSE_RECEIVED to "com.airship.flutter/event/notification_response",
EventType.FOREGROUND_NOTIFICATION_RESPONSE_RECEIVED to "com.airship.flutter/event/notification_response",
EventType.CHANNEL_CREATED to "com.airship.flutter/event/channel_created",
EventType.DEEP_LINK_RECEIVED to "com.airship.flutter/event/deep_link_received",
EventType.DISPLAY_MESSAGE_CENTER to "com.airship.flutter/event/display_message_center",
EventType.DISPLAY_PREFERENCE_CENTER to "com.airship.flutter/event/display_preference_center",
EventType.MESSAGE_CENTER_UPDATED to "com.airship.flutter/event/message_center_updated",
EventType.PUSH_TOKEN_RECEIVED to "com.airship.flutter/event/push_token_received",
EventType.FOREGROUND_PUSH_RECEIVED to "com.airship.flutter/event/push_received",
EventType.BACKGROUND_PUSH_RECEIVED to "com.airship.flutter/event/background_push_received",
EventType.NOTIFICATION_STATUS_CHANGED to "com.airship.flutter/event/notification_status_changed"
EventType.BACKGROUND_NOTIFICATION_RESPONSE_RECEIVED to "com.airship.flutter/event/notification_response",
EventType.FOREGROUND_NOTIFICATION_RESPONSE_RECEIVED to "com.airship.flutter/event/notification_response",
EventType.CHANNEL_CREATED to "com.airship.flutter/event/channel_created",
EventType.DEEP_LINK_RECEIVED to "com.airship.flutter/event/deep_link_received",
EventType.DISPLAY_MESSAGE_CENTER to "com.airship.flutter/event/display_message_center",
EventType.DISPLAY_PREFERENCE_CENTER to "com.airship.flutter/event/display_preference_center",
EventType.MESSAGE_CENTER_UPDATED to "com.airship.flutter/event/message_center_updated",
EventType.PUSH_TOKEN_RECEIVED to "com.airship.flutter/event/push_token_received",
EventType.FOREGROUND_PUSH_RECEIVED to "com.airship.flutter/event/push_received",
EventType.BACKGROUND_PUSH_RECEIVED to "com.airship.flutter/event/background_push_received",
EventType.NOTIFICATION_STATUS_CHANGED to "com.airship.flutter/event/notification_status_changed",
EventType.PENDING_EMBEDDED_UPDATED to "com.airship.flutter/event/pending_embedded_updated"
)
}

Expand All @@ -79,6 +79,7 @@ class AirshipPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
this.streams = generateEventStreams(binaryMessenger)

platformViewRegistry.registerViewFactory("com.airship.flutter/InboxMessageView", InboxMessageViewFactory(binaryMessenger))
platformViewRegistry.registerViewFactory("com.airship.flutter/EmbeddedView", EmbeddedViewFactory(binaryMessenger))

scope.launch {
EventEmitter.shared().pendingEventListener.collect {
Expand All @@ -97,13 +98,15 @@ class AirshipPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
val streamMap = mutableMapOf<EventType, AirshipEventStream>()
streamGroups.forEach { entry ->
val stream = AirshipEventStream(entry.value, entry.key, binaryMessenger)
entry.value.forEach {type ->
stream.register() /// Set up handlers for each stream
entry.value.forEach { type ->
streamMap[type] = stream
}
}
return streamMap
}


override fun onMethodCall(call: MethodCall, result: Result) {
val proxy = AirshipProxy.shared(context)

Expand Down Expand Up @@ -177,15 +180,16 @@ class AirshipPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
"inApp#isPaused" -> result.resolveResult(call) { proxy.inApp.isPaused() }
"inApp#setDisplayInterval" -> result.resolveResult(call) { proxy.inApp.setDisplayInterval(call.longArg()) }
"inApp#getDisplayInterval" -> result.resolveResult(call) { proxy.inApp.getDisplayInterval() }
"inApp#resendLastEmbeddedEvent" -> result.resolveResult(call) { proxy.inApp.resendLastEmbeddedEvent() }

// Analytics
"analytics#trackScreen" -> result.resolveResult(call) { proxy.analytics.trackScreen(call.optStringArg()) }
"analytics#addEvent" -> result.resolveResult(call) { proxy.analytics.addEvent(call.jsonArgs()) }
"analytics#associateIdentifier" -> {
val args = call.stringList()
proxy.analytics.associateIdentifier(
args[0],
args.getOrNull(1)
args[0],
args.getOrNull(1)
)
}

Expand Down Expand Up @@ -214,8 +218,8 @@ class AirshipPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
"preferenceCenter#setAutoLaunch" -> result.resolveResult(call) {
val args = call.jsonArgs().requireList()
proxy.preferenceCenter.setAutoLaunchPreferenceCenter(
args.get(0).requireString(),
args.get(1).getBoolean(false)
args.get(0).requireString(),
args.get(1).getBoolean(false)
)
}

Expand All @@ -236,13 +240,13 @@ class AirshipPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
val args = call.jsonArgs().requireList().list

proxy.actions.runAction(args[0].requireString(), args.getOrNull(1))
.addResultCallback { actionResult ->
if (actionResult != null && actionResult.status == ActionResult.STATUS_COMPLETED) {
callback(actionResult.value, null)
} else {
callback(null, Exception("Action failed ${actionResult?.status}"))
}
.addResultCallback { actionResult ->
if (actionResult != null && actionResult.status == ActionResult.STATUS_COMPLETED) {
callback(actionResult.value, null)
} else {
callback(null, Exception("Action failed ${actionResult?.status}"))
}
}
}

// Feature Flag
Expand Down Expand Up @@ -305,40 +309,61 @@ class AirshipPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
override fun onDetachedFromActivity() {
mainActivity = null
}

internal class AirshipEventStream(
private val eventTypes: List<EventType>,
channelName: String,
binaryMessenger: BinaryMessenger
) {

class AirshipEventStreamHandler : EventChannel.StreamHandler {
private var eventSink: EventChannel.EventSink? = null

init {
val eventChannel = EventChannel(binaryMessenger, channelName)
eventChannel.setStreamHandler(object:EventChannel.StreamHandler {
override fun onListen(arguments: Any?, eventSink: EventChannel.EventSink?) {
[email protected] = eventSink
processPendingEvents()
}

override fun onCancel(p0: Any?) {
[email protected] = null
}
})
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
this.eventSink = events
}

fun processPendingEvents() {
val sink = eventSink ?: return
override fun onCancel(arguments: Any?) {
this.eventSink = null
}

EventEmitter.shared().processPending(eventTypes) { event ->
sink.success(event.body.unwrap())
fun notify(event: Any): Boolean {
val sink = eventSink
return if (sink != null) {
sink.success(event)
true
} else {
false
}
}
}
class AirshipEventStream(
private val eventTypes: List<EventType>,
private val name: String,
private val binaryMessenger: BinaryMessenger
) {
private val handlers = mutableListOf<AirshipEventStreamHandler>()
private val lock = ReentrantLock()
private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())

}

fun register() {
val eventChannel = EventChannel(binaryMessenger, name)
val handler = AirshipEventStreamHandler()
eventChannel.setStreamHandler(handler)

lock.withLock {
handlers.add(handler)
}
}
fun processPendingEvents() {
EventEmitter.shared().processPending(eventTypes) { event ->
val unwrappedEvent = event.body.unwrap()
if (unwrappedEvent != null) {
notify(unwrappedEvent)
} else {
/// If it can't be unwrapped we've processed all we can
true
}
}
}

private fun notify(event: Any): Boolean {
return lock.withLock {
handlers.any { it.notify(event) }
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.airship.flutter

import android.content.Context
import android.graphics.Color
import android.view.View
import android.widget.FrameLayout
import com.urbanairship.embedded.AirshipEmbeddedView
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory

class FlutterEmbeddedView(
private var context: Context,
private val channel: MethodChannel,
private val embeddedId: String
) : PlatformView, MethodChannel.MethodCallHandler {

private val frameLayout: FrameLayout = FrameLayout(context)
private var airshipEmbeddedView: AirshipEmbeddedView? = null

init {
setupAirshipEmbeddedView()
channel.setMethodCallHandler(this)
}
private fun setupAirshipEmbeddedView() {

airshipEmbeddedView = AirshipEmbeddedView(context, embeddedId)
airshipEmbeddedView?.layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
frameLayout.addView(airshipEmbeddedView)
}

override fun getView(): View {
frameLayout.layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
return frameLayout
}

override fun dispose() {
channel.setMethodCallHandler(null)
}

override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
else -> {
result.error("UNAVAILABLE", "Unknown method: ${call.method}", null)
}
}
}
}

class EmbeddedViewFactory(
private val binaryMessenger: BinaryMessenger
) : PlatformViewFactory(StandardMessageCodec.INSTANCE) {

override fun create(context: Context?, viewId: Int, args: Any?): PlatformView {
val channel = MethodChannel(binaryMessenger, "com.airship.flutter/EmbeddedView_$viewId")

// Extracting embeddedId from args
val params = args as? Map<String, Any>
val embeddedId = params?.get("embeddedId") as? String ?: "defaultId"

return FlutterEmbeddedView(checkNotNull(context), channel, embeddedId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@ import com.urbanairship.messagecenter.webkit.MessageWebViewClient
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformViewFactory

class FlutterInboxMessageView(private var context: Context, channel: MethodChannel) : PlatformView, MethodChannel.MethodCallHandler {
class FlutterInboxMessageView(
private var context: Context,
channel: MethodChannel
) : PlatformView, MethodChannel.MethodCallHandler {

private lateinit var webviewResult: MethodChannel.Result

Expand Down Expand Up @@ -75,4 +81,15 @@ class FlutterInboxMessageView(private var context: Context, channel: MethodChann
result.error("InvalidMessage", "Unable to load message: ${call.arguments}", null)
}
}
}

class InboxMessageViewFactory(
private val binaryMessenger: BinaryMessenger
) : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context?, viewId: Int, args: Any?): PlatformView {
val channel = MethodChannel(binaryMessenger, "com.airship.flutter/InboxMessageView_$viewId")
val view = FlutterInboxMessageView(checkNotNull(context), channel)
channel.setMethodCallHandler(view)
return view
}
}

This file was deleted.

3 changes: 3 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
}
});

Airship.inApp.onEmbeddedInfoUpdated
.listen((event) => debugPrint('Embedded info updated $event'));

Airship.messageCenter.onInboxUpdated
.listen((event) => debugPrint('Inbox updated $event'));

Expand Down
3 changes: 3 additions & 0 deletions example/lib/screens/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ class _HomeState extends State<Home> {
child: Container(
alignment: Alignment.center,
child: Wrap(children: <Widget>[
Center(
child: AirshipEmbeddedView(
embeddedId: "test", parentHeight: 200)),
Image.asset(
'assets/airship.png',
),
Expand Down
Loading
Loading