From e9443dc4b7540fc1edcc2119dec1a91c374ab13f Mon Sep 17 00:00:00 2001 From: Ryan Lepinski Date: Fri, 30 Aug 2024 12:49:37 -0700 Subject: [PATCH] Wire up new listeners --- android/gradle.properties | 2 +- .../urbanairship/reactnative/AirshipModule.kt | 5 ++ .../reactnative/ReactAutopilot.kt | 22 +++++- .../com/urbanairship/reactnative/Utils.kt | 3 +- .../urbanairship/reactnative/AirshipSpec.kt | 4 + example/android/settings.gradle | 1 - example/ios/Podfile.lock | 12 +-- example/src/App.tsx | 6 ++ example/src/screens/HomeScreen.tsx | 40 ++++++++-- ios/AirshipReactNative.swift | 18 ++++- ios/RTNAirship.mm | 12 ++- package.json | 2 +- react-native-airship.podspec | 2 +- src/AirshipInApp.ts | 76 ++++++++++++++++++- src/AirshipRoot.ts | 5 +- src/NativeRTNAirship.ts | 1 + 16 files changed, 182 insertions(+), 29 deletions(-) diff --git a/android/gradle.properties b/android/gradle.properties index 73e271e6..ef8605fd 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -3,4 +3,4 @@ Airship_minSdkVersion=21 Airship_targetSdkVersion=34 Airship_compileSdkVersion=34 Airship_ndkversion=26.1.10909125 -Airship_airshipProxyVersion=7.1.2 +Airship_airshipProxyVersion=7.3.0 diff --git a/android/src/main/java/com/urbanairship/reactnative/AirshipModule.kt b/android/src/main/java/com/urbanairship/reactnative/AirshipModule.kt index 1432ed94..44b447c9 100644 --- a/android/src/main/java/com/urbanairship/reactnative/AirshipModule.kt +++ b/android/src/main/java/com/urbanairship/reactnative/AirshipModule.kt @@ -546,6 +546,11 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) : } } + @ReactMethod + override fun inAppResendPendingEmbeddedEvent() { + proxy.inApp.resendLastEmbeddedEvent() + } + @ReactMethod override fun messageCenterGetUnreadCount(promise: Promise) { promise.resolveResult { diff --git a/android/src/main/java/com/urbanairship/reactnative/ReactAutopilot.kt b/android/src/main/java/com/urbanairship/reactnative/ReactAutopilot.kt index 25b9f6ec..194ac959 100644 --- a/android/src/main/java/com/urbanairship/reactnative/ReactAutopilot.kt +++ b/android/src/main/java/com/urbanairship/reactnative/ReactAutopilot.kt @@ -8,9 +8,15 @@ import android.content.pm.PackageManager import com.urbanairship.UAirship import com.urbanairship.analytics.Extension import com.urbanairship.android.framework.proxy.BaseAutopilot +import com.urbanairship.android.framework.proxy.Event +import com.urbanairship.android.framework.proxy.EventType import com.urbanairship.android.framework.proxy.ProxyLogger import com.urbanairship.android.framework.proxy.ProxyStore import com.urbanairship.android.framework.proxy.events.EventEmitter +import com.urbanairship.embedded.AirshipEmbeddedInfo +import com.urbanairship.embedded.AirshipEmbeddedObserver +import com.urbanairship.json.JsonMap +import com.urbanairship.json.jsonMapOf import kotlinx.coroutines.MainScope import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch @@ -35,7 +41,13 @@ class ReactAutopilot : BaseAutopilot() { } } - // Set our custom notification provider + MainScope().launch { + AirshipEmbeddedObserver(filter = { true }).embeddedViewInfoFlow.collect { + EventEmitter.shared().addEvent(PendingEmbeddedUpdated(it)) + } + } + + // Set our custom notification providerr val notificationProvider = ReactNotificationProvider(context, airship.airshipConfigOptions) airship.pushManager.notificationProvider = notificationProvider @@ -75,4 +87,12 @@ class ReactAutopilot : BaseAutopilot() { companion object { const val EXTENDER_MANIFEST_KEY = "com.urbanairship.reactnative.AIRSHIP_EXTENDER" } +} + +internal class PendingEmbeddedUpdated(pending: List) : Event { + override val type = EventType.PENDING_EMBEDDED_UPDATED + + override val body: JsonMap = jsonMapOf( + "pending" to pending.map { jsonMapOf( "embeddedId" to it.embeddedId ) } + ) } \ No newline at end of file diff --git a/android/src/main/java/com/urbanairship/reactnative/Utils.kt b/android/src/main/java/com/urbanairship/reactnative/Utils.kt index d4ea6da2..8f5714f0 100644 --- a/android/src/main/java/com/urbanairship/reactnative/Utils.kt +++ b/android/src/main/java/com/urbanairship/reactnative/Utils.kt @@ -27,7 +27,8 @@ object Utils { EventType.FOREGROUND_PUSH_RECEIVED, EventType.BACKGROUND_PUSH_RECEIVED ), - "com.airship.notification_status_changed" to listOf(EventType.NOTIFICATION_STATUS_CHANGED) + "com.airship.notification_status_changed" to listOf(EventType.NOTIFICATION_STATUS_CHANGED), + "com.airship.pending_embedded_updated" to listOf(EventType.PENDING_EMBEDDED_UPDATED) ) fun convertArray(array: ReadableArray?): JsonValue { diff --git a/android/src/oldarch/java/com/urbanairship/reactnative/AirshipSpec.kt b/android/src/oldarch/java/com/urbanairship/reactnative/AirshipSpec.kt index 99de0ebd..be2a7518 100644 --- a/android/src/oldarch/java/com/urbanairship/reactnative/AirshipSpec.kt +++ b/android/src/oldarch/java/com/urbanairship/reactnative/AirshipSpec.kt @@ -302,6 +302,10 @@ abstract class AirshipSpec internal constructor(context: ReactApplicationContext @com.facebook.proguard.annotations.DoNotStrip abstract fun inAppIsPaused(promise: Promise) + @ReactMethod + @com.facebook.proguard.annotations.DoNotStrip + abstract fun inAppResendPendingEmbeddedEvent() + @ReactMethod @com.facebook.proguard.annotations.DoNotStrip abstract fun messageCenterGetUnreadCount(promise: Promise) diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 4ceda33f..124e144b 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -2,4 +2,3 @@ rootProject.name = 'AirshipExample' apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app' includeBuild('../node_modules/@react-native/gradle-plugin') -includeBuild('./../../../android-library-dev') \ No newline at end of file diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 48107121..b72a85f7 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -17,7 +17,7 @@ PODS: - Airship/Core - Airship/PreferenceCenter (18.7.2): - Airship/Core - - AirshipFrameworkProxy (7.1.2): + - AirshipFrameworkProxy (7.3.0): - Airship (= 18.7.2) - AirshipServiceExtension (18.7.2) - boost (1.83.0) @@ -907,8 +907,8 @@ PODS: - React-Mapbuffer (0.73.4): - glog - React-debug - - react-native-airship (19.2.0): - - AirshipFrameworkProxy (= 7.1.2) + - react-native-airship (19.3.0): + - AirshipFrameworkProxy (= 7.3.0) - glog - RCT-Folly (= 2022.05.16.00) - React-Core @@ -1280,7 +1280,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Airship: bb32ff2c5a811352da074480357d9f02dbb8f327 - AirshipFrameworkProxy: dbd862dc6fb21b13e8b196458d626123e2a43a50 + AirshipFrameworkProxy: 88a5e374efb5841e8cd84e63983c1ded123fe073 AirshipServiceExtension: 9c73369f426396d9fb9ff222d86d842fac76ba46 boost: d3f49c53809116a5d38da093a8aa78bf551aed09 DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 @@ -1311,7 +1311,7 @@ SPEC CHECKSUMS: React-jsinspector: 9ac353eccf6ab54d1e0a33862ba91221d1e88460 React-logger: 0a57b68dd2aec7ff738195f081f0520724b35dab React-Mapbuffer: 63913773ed7f96b814a2521e13e6d010282096ad - react-native-airship: 54a39240587b06b2f683769a2ab838b7ee25b2e6 + react-native-airship: 7fcefeebfac490aab222b93521e08ebd5b3d4581 react-native-safe-area-context: b97eb6f9e3b7f437806c2ce5983f479f8eb5de4b React-nativeconfig: d7af5bae6da70fa15ce44f045621cf99ed24087c React-NativeModulesApple: 0123905d5699853ac68519607555a9a4f5c7b3ac @@ -1342,4 +1342,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: dbd88b0cf2e018eeff600431486ded3ce68933fb -COCOAPODS: 1.12.1 +COCOAPODS: 1.15.2 diff --git a/example/src/App.tsx b/example/src/App.tsx index d31cf057..9bad8bb4 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -13,6 +13,12 @@ import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; const Tab = createBottomTabNavigator(); const MessageCenterStack = createStackNavigator(); +Airship.takeOff({ + default: { + appKey: "VWDwdOFjRTKLRxCeXTVP6g", + appSecret: "5Ifi5rYgTm2QHey9JkP0WA" + } +}) Airship.addListener(EventType.NotificationResponse, (event) => { console.log('NotificationResponse:', JSON.stringify(event)); }); diff --git a/example/src/screens/HomeScreen.tsx b/example/src/screens/HomeScreen.tsx index 22d305e7..4e3ddec3 100644 --- a/example/src/screens/HomeScreen.tsx +++ b/example/src/screens/HomeScreen.tsx @@ -14,7 +14,7 @@ import { Platform, TouchableOpacity } from 'react-native'; -import Airship, { EventType, AirshipEmbeddedView } from '@ua/react-native-airship'; +import Airship, { EventType, AirshipEmbeddedView} from '@ua/react-native-airship'; import styles from '../Styles'; import NamedUserManagerCell from './Home Elements/NamedUserManagerCell'; @@ -46,6 +46,7 @@ export default function HomeScreen() { const [tags, setTags] = useState([]); const [tagText, setTagText] = useState(''); const [notificationsEnabled, setNotificationsEnabled] = useState(false); + const [isEmbeddedReady, setEmbeddedReady] = useState(false) const refreshTags = useCallback(async () => { const fetchedTags = await Airship.channel.getTags(); @@ -95,6 +96,8 @@ export default function HomeScreen() { console.error('Error getting notification status:', error); }); + setEmbeddedReady(Airship.inApp.isEmbeddedReady("test")) + Airship.push.iOS .getAuthorizedNotificationSettings() .then((id) => { @@ -141,6 +144,11 @@ export default function HomeScreen() { } ); + Airship.inApp.addEmbeddedReadyListener("test", (isReady) => { + console.log("Test " + isReady) + setEmbeddedReady(isReady) + }); + return () => { subscription.remove(); }; @@ -153,15 +161,31 @@ export default function HomeScreen() { keyboardVerticalOffset={Platform.OS === 'ios' ? 200 : 0} > - - + + {isEmbeddedReady ? + ( + + - - + ) + : ( + + + )} + diff --git a/ios/AirshipReactNative.swift b/ios/AirshipReactNative.swift index bdfcdbc9..8e71d4e4 100644 --- a/ios/AirshipReactNative.swift +++ b/ios/AirshipReactNative.swift @@ -14,7 +14,9 @@ public class AirshipReactNative: NSObject { @objc public static let overridePresentationOptionsEventName = "com.airship.ios.override_presentation_options" - + @objc + public static let pendingEmbeddedUpdated = "com.airship.iax.pending_embedded_updated" + var lock = AirshipLock() var pendingPresentationRequests: [String: PresentationOptionsOverridesRequest] = [:] @@ -56,6 +58,7 @@ public class AirshipReactNative: NSObject { } + AirshipProxy.shared.push.presentationOptionOverrides = { request in guard self.overridePresentationOptionsEnabled else { request.result(options: nil) @@ -409,7 +412,7 @@ public extension AirshipReactNative { // InApp @objc public extension AirshipReactNative { - + @objc @MainActor func inAppIsPaused() throws -> NSNumber { return try NSNumber( @@ -417,22 +420,30 @@ public extension AirshipReactNative { ) } + @objc @MainActor func inAppSetPaused(_ paused: Bool) throws { try AirshipProxy.shared.inApp.setPaused(paused) } + @objc @MainActor func inAppSetDisplayInterval(milliseconds: Double) throws { try AirshipProxy.shared.inApp.setDisplayInterval(Int(milliseconds)) } + @objc @MainActor func inAppGetDisplayInterval() throws -> NSNumber { return try NSNumber( value: AirshipProxy.shared.inApp.getDisplayInterval() ) } + + @objc + func inAppResendPendingEmbeddedEvent() { + AirshipProxy.shared.inApp.resendLastEmbeddedEvent() + } } // Locale @@ -624,7 +635,8 @@ extension AirshipProxyEventType { "com.airship.notification_response": .notificationResponseReceived, "com.airship.push_received": .pushReceived, "com.airship.notification_status_changed": .notificationStatusChanged, - "com.airship.authorized_notification_settings_changed": .authorizedNotificationSettingsChanged + "com.airship.authorized_notification_settings_changed": .authorizedNotificationSettingsChanged, + "com.airship.pending_embedded_updated": .pendingEmbeddedUpdated ] public static func fromReactName(_ name: String) throws -> AirshipProxyEventType { diff --git a/ios/RTNAirship.mm b/ios/RTNAirship.mm index 7c562546..0832d0c1 100644 --- a/ios/RTNAirship.mm +++ b/ios/RTNAirship.mm @@ -12,7 +12,11 @@ @implementation RTNAirship RCT_EXPORT_MODULE() - (NSArray *)supportedEvents { - return @[AirshipReactNative.pendingEventsEventName, AirshipReactNative.overridePresentationOptionsEventName]; + return @[ + AirshipReactNative.pendingEventsEventName, + AirshipReactNative.overridePresentationOptionsEventName, + AirshipReactNative.pendingEmbeddedUpdated + ]; } -(void)startObserving { @@ -239,8 +243,6 @@ + (BOOL)requiresMainQueueSetup { pushIosSetBadgeNumber:(double)badgeNumber resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { - NSError *error; - [AirshipReactNative.shared pushSetBadgeNumber:badgeNumber completionHandler:^(NSError *error) { [self handleResult:nil error:error @@ -514,6 +516,10 @@ + (BOOL)requiresMainQueueSetup { [self handleResult:nil error:error resolve:resolve reject:reject]; } +RCT_EXPORT_METHOD(inAppResendPendingEmbeddedEvent) { + [AirshipReactNative.shared inAppResendPendingEmbeddedEvent]; +} + RCT_REMAP_METHOD(localeClearLocaleOverride, localeClearLocaleOverride:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { diff --git a/package.json b/package.json index 07c84750..50d0a853 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ua/react-native-airship", - "version": "19.2.1", + "version": "19.3.0", "description": "Airship plugin for React Native apps.", "main": "lib/commonjs/index", "module": "lib/module/index", diff --git a/react-native-airship.podspec b/react-native-airship.podspec index 8a36a13b..4fe23cff 100644 --- a/react-native-airship.podspec +++ b/react-native-airship.podspec @@ -22,5 +22,5 @@ Pod::Spec.new do |s| s.dependency "React-Core" end - s.dependency "AirshipFrameworkProxy", "7.1.2" + s.dependency "AirshipFrameworkProxy", "7.3.0" end diff --git a/src/AirshipInApp.ts b/src/AirshipInApp.ts index e8d200f0..811bc830 100644 --- a/src/AirshipInApp.ts +++ b/src/AirshipInApp.ts @@ -1,8 +1,82 @@ +import { Subscription, UAEventEmitter } from './UAEventEmitter'; + + +interface PendingEmbedded { + embeddedId: string +} + /** * Airship InApp Experiences. */ export class AirshipInApp { - constructor(private readonly module: any) {} + private pendingEmbedded: Map = new Map() + private pendingEmbeddedListeners: Map any)[]> = new Map(); + + constructor(private readonly module: any, private readonly eventEmitter: UAEventEmitter) { + this.eventEmitter.addListener("com.airship.pending_embedded_updated", (event) => { + let pending = event["pending"] as PendingEmbedded[]; + + this.pendingEmbedded = pending.reduce((map, entry) => { + var embeddedId = entry.embeddedId + if (!map.has(embeddedId)) { + map.set(embeddedId, [entry]) + } else { + map.get(embeddedId)?.push(entry) + } + return map + }, new Map()); + + + this.pendingEmbeddedListeners.forEach((listeners, embeddedId) => { + let pending = this.pendingEmbedded.get(embeddedId); + listeners.forEach((listener) => { listener(pending ?? []) }); + }); + }); + + module.inAppResendPendingEmbeddedEvent(); + } + + /** + * Adds a listener to listen for if an embedded ID is ready to display or not. + * @param embeddedId The embedded ID to check. + * @param listener The listener. + * @returns A subscription that can be used to cancel the listener. + */ + public addEmbeddedReadyListener(embeddedId: string, listener: (isReady: boolean) => void): Subscription { + var currentValue = this.isEmbeddedReady(embeddedId); + listener(currentValue); + + let wrappedListener = (pending: PendingEmbedded[]) => { + console.log("wrappedListener", embeddedId, pending) + + var nextValue = pending.length > 0; + if (currentValue != nextValue) { + listener(nextValue); + console.log("embeddedReadyListener", embeddedId, nextValue) + } + currentValue = nextValue; + } + + if (!this.pendingEmbeddedListeners.has(embeddedId)) { + this.pendingEmbeddedListeners.set(embeddedId, [wrappedListener]); + } else { + this.pendingEmbeddedListeners.get(embeddedId)?.push(wrappedListener); + } + + return new Subscription(() => { + this.pendingEmbeddedListeners.set(embeddedId, this.pendingEmbeddedListeners.get(embeddedId)?.filter((obj) => obj !== wrappedListener) ?? []); + }); + } + + /** + * Checks if embedded message is ready for the given ID. + * @param embeddedId The embedded ID to check. + * @returns `true` if one is ready, otherwise `false`. + */ + public isEmbeddedReady(embeddedId: string): boolean { + console.log("isEmbeddedReady", this.pendingEmbedded.get(embeddedId) ?? []) + return (this.pendingEmbedded.get(embeddedId)?.length ?? 0) > 0; + } /** * Pauses messages. diff --git a/src/AirshipRoot.ts b/src/AirshipRoot.ts index 0327a618..ac19798f 100644 --- a/src/AirshipRoot.ts +++ b/src/AirshipRoot.ts @@ -32,18 +32,19 @@ export class AirshipRoot { private readonly eventEmitter: UAEventEmitter; constructor(private readonly module: any) { + this.eventEmitter = new UAEventEmitter(module); + this.actions = new AirshipActions(module); this.analytics = new AirshipAnalytics(module); this.channel = new AirshipChannel(module); this.contact = new AirshipContact(module); - this.inApp = new AirshipInApp(module); + this.inApp = new AirshipInApp(module, this.eventEmitter); this.locale = new AirshipLocale(module); this.messageCenter = new AirshipMessageCenter(module); this.preferenceCenter = new AirshipPreferenceCenter(module); this.privacyManager = new AirshipPrivacyManager(module); this.push = new AirshipPush(module); this.featureFlagManager = new AirshipFeatureFlagManager(module); - this.eventEmitter = new UAEventEmitter(module); } /** diff --git a/src/NativeRTNAirship.ts b/src/NativeRTNAirship.ts index 36cc625f..67b56f50 100644 --- a/src/NativeRTNAirship.ts +++ b/src/NativeRTNAirship.ts @@ -83,6 +83,7 @@ export interface Spec extends TurboModule { inAppGetDisplayInterval(): Promise; inAppSetPaused(paused: boolean): Promise; inAppIsPaused(): Promise; + inAppResendPendingEmbeddedEvent(): void; // Message Center messageCenterGetUnreadCount(): Promise;