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

Add LiveActivities and LiveUpdate support, push and message center updates #220

Merged
merged 11 commits into from
Oct 21, 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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ name: CI
on: [pull_request]

env:
DEVELOPER_DIR: /Applications/Xcode_15.3.app/Contents/Developer
FLUTTER_VERSION: 3.22.0
DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer
FLUTTER_VERSION: 3.24.3
JAVA_VERSION: "17.x"
JAVA_DISTRIBUTION: 'zulu'
ANDROID_SDK_ROOT: ${{ github.workspace }}/android-sdk
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ on:
- "[0-9]+.[0-9]+.[0-9]+*"

env:
DEVELOPER_DIR: /Applications/Xcode_15.3.app/Contents/Developer
FLUTTER_VERSION: 3.22.0
DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer
FLUTTER_VERSION: 3.24.3
JAVA_VERSION: "17.x"
JAVA_DISTRIBUTION: 'zulu'

Expand Down
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# Flutter Plugin Changelog

## Version 7.9.0 - October 20, 2024

Minor version release with several new features including: iOS Live Activities, Android Live Updates, Message Center improvements, and iOS notification service extension support in the iOS example project.

### Changes

- Updated Airship Android SDK to [18.3.3](https://github.com/urbanairship/android-library/releases/tag/18.3.3)
- Updated Airship iOS SDK to [18.11.1](https://github.com/urbanairship/ios-library/releases/tag/18.11.1)
- Added `notificationPermissionStatus` to `PushNotificationStatus`
- Added options to `enableUserNotifications` to specify the `PromptPermissionFallback` when enabling user notifications
- Added new `showMessageCenter(messageId?: string)` and `showMessageView(messageId: string)` to `MessageCenter` to display the OOTB UI even if `autoLaunchDefaultMessageCenter` is disabled
- Added new APIs to manage [iOS Live Activities](https://docs.airship.com/platform/mobile/ios-live-activities/)
- Added new APIs to manage [Android Live Updates](https://docs.airship.com/platform/mobile/android-live-updates/)
- Added a new [iOS plugin extender]() to modify the native Airship SDK after takeOff
- Added new [Android plugin extender]() to modify the native Airship SDK after takeOff

## Version 7.8.2 - September 13, 2024

Patch release that fixes a potential Swift compile error.

### Changes

- Fixed a potential Swift compile error.

## Version 7.8.2 - September 13, 2024

Patch release that fixes a potential Swift compile error.
Expand Down
3 changes: 2 additions & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ include: package:lints/recommended.yaml

linter:
rules:
constant_identifier_names: false
constant_identifier_names: false
prefer_const_constructors: false
14 changes: 5 additions & 9 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

group 'com.airship.airship'
version '1.0-SNAPSHOT'

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.3.0'
ext.airship_framework_proxy_version = '10.1.1'


repositories {
Expand Down Expand Up @@ -45,19 +43,17 @@ android {
lintOptions {
disable 'InvalidPackage'
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}

namespace = 'com.airship.airship'
}

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
jvmTarget = "17"
jvmTarget = '17'
}

namespace = 'com.airship.airship'
}

dependencies {
Expand Down
31 changes: 31 additions & 0 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,36 @@
<application>
<meta-data android:name="com.urbanairship.autopilot"
android:value="com.airship.flutter.FlutterAutopilot"/>

<activity
android:name="com.urbanairship.android.framework.proxy.CustomMessageCenterActivity"
android:label="@string/ua_message_center_title"
android:theme="@style/Theme.AppCompat.DayNight"
android:launchMode="singleTask"
android:exported="false">

<intent-filter>
<action android:name="com.urbanairship.VIEW_RICH_PUSH_INBOX" />
<data android:scheme="message" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>

<intent-filter>
<action android:name="com.urbanairship.VIEW_RICH_PUSH_INBOX" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

<activity
android:name="com.urbanairship.android.framework.proxy.CustomMessageActivity"
android:theme="@style/Theme.AppCompat.DayNight"
android:exported="false">
<intent-filter>
<action android:name="com.urbanairship.VIEW_RICH_PUSH_MESSAGE" />
<data android:scheme="message" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

</application>
</manifest>
153 changes: 150 additions & 3 deletions android/src/main/kotlin/com/airship/flutter/AirshipPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ package com.airship.flutter
import android.app.Activity
import android.content.Context
import android.os.Build
import android.util.Log
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
import com.urbanairship.android.framework.proxy.proxies.FeatureFlagProxy
import com.urbanairship.android.framework.proxy.proxies.LiveUpdateRequest
import com.urbanairship.android.framework.proxy.proxies.EnableUserNotificationsArgs
import com.urbanairship.json.JsonValue
import com.urbanairship.json.toJsonList
import io.flutter.embedding.engine.FlutterShellArgs
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
Expand All @@ -25,6 +28,8 @@ import io.flutter.plugin.platform.PlatformViewRegistry
import kotlinx.coroutines.*
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
import kotlinx.coroutines.Dispatchers


class AirshipPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {

Expand Down Expand Up @@ -109,13 +114,15 @@ class AirshipPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {

override fun onMethodCall(call: MethodCall, result: Result) {
val proxy = AirshipProxy.shared(context)
val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())

when (call.method) {
// Flutter
"startBackgroundIsolate" -> startBackgroundIsolate(call, result)

// Airship
"takeOff" -> result.resolveResult(call) { proxy.takeOff(call.jsonArgs()) }

"isFlying" -> result.resolveResult(call) { proxy.isFlying() }

// Channel
Expand Down Expand Up @@ -153,9 +160,32 @@ class AirshipPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {

// Push
"push#setUserNotificationsEnabled" -> result.resolveResult(call) { proxy.push.setUserNotificationsEnabled(call.booleanArg()) }
"push#enableUserNotifications" -> result.resolvePending(call) { proxy.push.enableUserPushNotifications() }
"push#enableUserNotifications" -> {
val args = call.jsonArgs()

val enableArgs = args?.let {
try {
EnableUserNotificationsArgs.fromJson(it)
} catch (e: Exception) {
null
}
}

coroutineScope.launch {
try {
val enableResult = proxy.push.enableUserPushNotifications(enableArgs)
result.success(enableResult)
} catch (e: Exception) {
result.error("ERROR", "Failed to enable user notifications", e.localizedMessage)
}
}
}
"push#isUserNotificationsEnabled" -> result.resolveResult(call) { proxy.push.isUserNotificationsEnabled() }
"push#getNotificationStatus" -> result.resolveResult(call) { proxy.push.getNotificationStatus() }
"push#getNotificationStatus" -> result.resolveResult(call) {
coroutineScope.launch {
proxy.push.getNotificationStatus()
}
}
"push#getActiveNotifications" -> result.resolveResult(call) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
proxy.push.getActiveNotifications()
Expand Down Expand Up @@ -197,6 +227,8 @@ class AirshipPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
"messageCenter#getMessages" -> result.resolveResult(call) {
JsonValue.wrapOpt(proxy.messageCenter.getMessages())
}
"messageCenter#showMessageCenter" -> result.resolveResult(call) { proxy.messageCenter.showMessageCenter(call.optStringArg()) }
"messageCenter#showMessageView" -> result.resolveResult(call) { proxy.messageCenter.showMessageView(call.stringArg()) }
"messageCenter#display" -> result.resolveResult(call) { proxy.messageCenter.display(call.optStringArg()) }
"messageCenter#markMessageRead" -> result.resolveResult(call) { proxy.messageCenter.markMessageRead(call.stringArg()) }
"messageCenter#deleteMessage" -> result.resolveResult(call) { proxy.messageCenter.deleteMessage(call.stringArg()) }
Expand Down Expand Up @@ -248,6 +280,121 @@ class AirshipPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
}
}
}
// Live Activities

"featureFlagManager#trackInteraction" -> {
result.resolveDeferred(call) { callback ->
scope.launch {
try {
val args = call.jsonArgs()

val wrapped = JsonValue.wrap(args)
val featureFlagProxy = FeatureFlagProxy(wrapped)
proxy.featureFlagManager.trackInteraction(flag = featureFlagProxy)
callback(null, null)
} catch (e: Exception) {
callback(null, e)
}
}
}
}

"liveUpdate#start" -> result.resolveResult(call) {
try {
val args = call.jsonArgs()
Log.d("AirshipPlugin", "Received args for liveUpdate#start: $args")

val startRequest = LiveUpdateRequest.Start.fromJson(args)

Log.d("AirshipPlugin", "Created LiveUpdateRequest.Start: $startRequest")

proxy.liveUpdateManager.start(startRequest)
Log.d("AirshipPlugin", "LiveUpdate start method called successfully")

null // Return null as the start function doesn't return anything
} catch (e: Exception) {
throw e
}
}

"liveUpdate#update" -> result.resolveResult(call) {
try {
val args = call.jsonArgs()
Log.d("AirshipPlugin", "Received args for liveUpdate#update: $args")

val updateRequest = LiveUpdateRequest.Update.fromJson(args)

proxy.liveUpdateManager.update(updateRequest)
Log.d("AirshipPlugin", "LiveUpdate update method called successfully")
null
} catch (e: Exception) {
Log.e("AirshipPlugin", "Error processing liveUpdate#update request", e)
throw e
}
}

"liveUpdate#list" -> result.resolveDeferred(call) { callback ->
try {
val args = call.jsonArgs()
Log.d("AirshipPlugin", "Received args for liveUpdate#list: $args")

val listRequest = LiveUpdateRequest.List.fromJson(args)

coroutineScope.launch {
try {
val liveUpdates = proxy.liveUpdateManager.list(listRequest)
Log.d("AirshipPlugin", "LiveUpdate list method completed successfully")
callback(liveUpdates.toJsonList(), null)
} catch (e: Exception) {
Log.e("AirshipPlugin", "Error listing LiveUpdates", e)
callback(null, e)
}
}
} catch (e: Exception) {
Log.e("AirshipPlugin", "Error processing liveUpdate#list request", e)
callback(null, e)
}
}

"liveUpdate#listAll" -> result.resolveDeferred(call) { callback ->
coroutineScope.launch {
try {
val liveUpdates = proxy.liveUpdateManager.listAll()
Log.d("AirshipPlugin", "LiveUpdate listAll method completed successfully")
callback(JsonValue.wrap(liveUpdates), null)
} catch (e: Exception) {
Log.e("AirshipPlugin", "Error listing all LiveUpdates", e)
callback(null, e)
}
}
}

"liveUpdate#end" -> result.resolveResult(call) {
try {
val args = call.jsonArgs()
Log.d("AirshipPlugin", "Received args for liveUpdate#end: $args")

val endRequest = LiveUpdateRequest.End.fromJson(args)

proxy.liveUpdateManager.end(endRequest)
Log.d("AirshipPlugin", "LiveUpdate end method called successfully")
null
} catch (e: Exception) {
Log.e("AirshipPlugin", "Error processing liveUpdate#end request", e)
throw e
}
}

"liveUpdate#clearAll" -> result.resolveResult(call) {
try {
proxy.liveUpdateManager.clearAll()
Log.d("AirshipPlugin", "LiveUpdate clearAll method called successfully")
null
} catch (e: Exception) {
Log.e("AirshipPlugin", "Error processing liveUpdate#clearAll request", e)
throw e
}
}

// Feature Flag
"featureFlagManager#flag" -> result.resolveDeferred(call) { callback ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* Copyright Urban Airship and Contributors */

package com.urbanairship.reactnative

import android.content.Context
import com.urbanairship.UAirship

/**
* Extender that will be called during takeOff to customize the airship instance.
* Register the extender fully qualified class name in the manifest under the key
* `com.urbanairship.flutter.AIRSHIP_EXTENDER`.
*/
@Deprecated("Use com.urbanairship.android.framework.proxy.AirshipPluginExtender instead and register it under the manifest key `com.urbanairship.plugin.extender`")
crow marked this conversation as resolved.
Show resolved Hide resolved
interface AirshipExtender {
fun onAirshipReady(context: Context, airship: UAirship)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ package com.airship.flutter

class AirshipPluginVersion {
companion object {
const val AIRSHIP_PLUGIN_VERSION = "7.8.2"
const val AIRSHIP_PLUGIN_VERSION = "7.9.0"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ class FlutterAutopilot : BaseAutopilot() {
private val appContext: Context
get() = UAirship.getApplicationContext()

override fun onAirshipReady(airship: UAirship) {
super.onAirshipReady(airship)

override fun onReady(context: Context, airship: UAirship) {
Log.i("FlutterAutopilot", "onAirshipReady")

// If running in the background, start the background Isolate
Expand Down Expand Up @@ -66,7 +64,6 @@ class FlutterAutopilot : BaseAutopilot() {
}
}


companion object {
private val APP_KEY = stringPreferencesKey("app_key")
private val APP_SECRET = stringPreferencesKey("app_secret")
Expand Down
Loading
Loading