diff --git a/apps/demo/src/app.ts b/apps/demo/src/app.ts index 211a2533..57acd792 100644 --- a/apps/demo/src/app.ts +++ b/apps/demo/src/app.ts @@ -1,12 +1,37 @@ -import { Application } from '@nativescript/core'; +import { Application, AndroidActivityNewIntentEventData } from '@nativescript/core'; // import { Inquiry } from '@nstudio/nativescript-persona'; // Inquiry.init(); // Uncomment to test AppCues: -// import { AppcuesSDK } from '@nstudio/nativescript-appcues'; -// Application.on(Application.launchEvent, () => { -// AppcuesSDK.init('', ''); -// }) +import { AppcuesSDK } from '@nstudio/nativescript-appcues'; + +AppcuesSDK.on('analytics', (event) => { + console.log(event); +}); + +AppcuesSDK.on('experience', (event) => { + console.log(event); +}); + +if (global.isAndroid) { + Application.android.on('activityNewIntent', (args: AndroidActivityNewIntentEventData) => { + const didHandleURL = AppcuesSDK.didHandleURL(args.intent); + if (didHandleURL) { + const uri = args.intent.getData?.() as android.net.Uri; + console.log(uri?.toString?.()); + } + }); + Application.android.on('activityCreated', (args) => { + console.log('activityCreated'); + AppcuesSDK.debug(); + }); + AppcuesSDK.init('69047', 'a2355571-cbd5-4a8e-a0ed-a9ea09f0b904'); +} else { + Application.on('launch', (args) => { + AppcuesSDK.init('69047', 'a2355571-cbd5-4a8e-a0ed-a9ea09f0b904'); + }); + AppcuesSDK.debug(); +} Application.run({ moduleName: 'app-root' }); diff --git a/packages/nativescript-appcues/index.android.ts b/packages/nativescript-appcues/index.android.ts index 3ec3df39..6af6cb9b 100644 --- a/packages/nativescript-appcues/index.android.ts +++ b/packages/nativescript-appcues/index.android.ts @@ -1,39 +1,140 @@ -import { Utils } from '@nativescript/core'; +import { Utils, fromObject, Application } from '@nativescript/core'; import { AppcuesSDKCommon } from './common'; let appcuesInstance: com.appcues.Appcues; -declare const io; +declare const io, kotlin; + +let events = fromObject({}); + +function parseType(type: com.appcues.AnalyticType) { + switch (type) { + case com.appcues.AnalyticType.IDENTIFY: + return 'identify'; + case com.appcues.AnalyticType.GROUP: + return 'group'; + case com.appcues.AnalyticType.EVENT: + return 'event'; + case com.appcues.AnalyticType.SCREEN: + return 'screen'; + } +} export class AppcuesSDK extends AppcuesSDKCommon { - static init(accountId: string, appId: string) { - appcuesInstance = new io.nstudio.plugins.appcues.Appcues.init(Utils.android.getApplicationContext(), accountId, appId); - } - - static identify(userId: string, properties?: any) { - if (appcuesInstance) { - appcuesInstance.identify(userId, Utils.dataSerialize(properties || {})); - } - } - - static track(name: string, properties?: any) { - if (appcuesInstance) { - appcuesInstance.track(name, Utils.dataSerialize(properties || {})) - } - } - - static trackScreens() { - if (appcuesInstance) { - appcuesInstance.trackScreens(); - } - } - - static screen(title: string, properties?: any) { - if (appcuesInstance) { - appcuesInstance.screen(title, Utils.dataSerialize(properties || {})) - } - } - - static nativeInstance() { - return appcuesInstance; - } + static on = events.on.bind(events); + + static once = events.once.bind(events); + + static off = events.off.bind(events); + + static init(accountId: string, appId: string) { + appcuesInstance = new io.nstudio.plugins.appcues.Appcues.init(Utils.android.getApplicationContext(), accountId, appId); + appcuesInstance.setAnalyticsListener( + new com.appcues.AnalyticsListener({ + trackedAnalytic(type: com.appcues.AnalyticType, value: string, properties: java.util.Map, isInternal: boolean): void { + events.notify({ + eventName: 'analytics', + type: parseType(type), + value, + properties: Utils.dataDeserialize(properties) ?? {}, + isInternal, + }); + }, + }) + ); + appcuesInstance.setExperienceListener( + new com.appcues.ExperienceListener({ + experienceStarted(experienceId: java.util.UUID): void { + events.notify({ + eventName: 'experience', + started: true, + id: experienceId.toString(), + }); + }, + experienceFinished(experienceId: java.util.UUID): void { + events.notify({ + eventName: 'experience', + started: false, + id: experienceId.toString(), + }); + }, + }) + ); + } + + static identify(userId: string, properties?: any) { + if (appcuesInstance) { + appcuesInstance.identify(userId, Utils.dataSerialize(properties || {})); + } + } + + static track(name: string, properties?: any) { + if (appcuesInstance) { + appcuesInstance.track(name, Utils.dataSerialize(properties || {})); + } + } + + static trackScreens() { + if (appcuesInstance) { + appcuesInstance.trackScreens(); + } + } + + static screen(title: string, properties?: any) { + if (appcuesInstance) { + appcuesInstance.screen(title, Utils.dataSerialize(properties || {})); + } + } + + static show(experienceId: string) { + return new Promise((resolve, reject) => { + if (appcuesInstance) { + io.nstudio.plugins.appcues.Appcues.show( + appcuesInstance, + experienceId, + new kotlin.jvm.functions.Function1({ + invoke(done) { + resolve(done); + }, + }) + ); + } else { + reject('SDK unintialized'); + } + }); + } + + static reset() { + if (appcuesInstance) { + appcuesInstance.reset(); + } + } + + static anonymous(properties?: any) { + if (appcuesInstance) { + appcuesInstance.anonymous(Utils.dataSerialize(properties || {})); + } + } + + static group(groupId?: string, properties?: any) { + if (appcuesInstance) { + appcuesInstance.group(groupId, Utils.dataSerialize(properties || {})); + } + } + + static debug() { + if (appcuesInstance) { + appcuesInstance.debug(Application.android.foregroundActivity || Application.android.startActivity); + } + } + + static didHandleURL(value: any): boolean { + if (appcuesInstance) { + return appcuesInstance.onNewIntent(Application.android.foregroundActivity || Application.android.startActivity, value); + } + return false; + } + + static nativeInstance() { + return appcuesInstance; + } } diff --git a/packages/nativescript-appcues/index.d.ts b/packages/nativescript-appcues/index.d.ts index df9c71d4..fa138c23 100644 --- a/packages/nativescript-appcues/index.d.ts +++ b/packages/nativescript-appcues/index.d.ts @@ -1,4 +1,5 @@ import { AppcuesSDKCommon } from './common'; +import { EventData } from '@nativescript/core'; export declare class AppcuesSDK extends AppcuesSDKCommon { static nativeInstance(): any /* iOS or Android SDK instance */ @@ -7,4 +8,20 @@ export declare class AppcuesSDK extends AppcuesSDKCommon { static track(name: string, properties?: any): void; static trackScreens(): void; static screen(title: string, properties?: any): void; + static show(experienceId: string): Promise; + static reset(): void; + static anonymous(properties?: any); + static group(groupId?: string, properties?: any); + static debug(); + static didHandleURL(value: any): boolean; + static on(event: 'analytics', callback: (args: EventData & { + type: 'identify' | 'group' | 'event' | 'screen', + value: string, + properties: Record, + isInternal: boolean, + }) => void, thisArg?: any): void; + static on(event: 'experience', callback: (args: EventData & { + started: boolean, + id: string + }) => void, thisArg?: any): void; } diff --git a/packages/nativescript-appcues/index.ios.ts b/packages/nativescript-appcues/index.ios.ts index fe6799a4..9c9facea 100644 --- a/packages/nativescript-appcues/index.ios.ts +++ b/packages/nativescript-appcues/index.ios.ts @@ -1,39 +1,152 @@ -import { Utils } from '@nativescript/core'; +import { Utils, fromObject } from '@nativescript/core'; import { AppcuesSDKCommon } from './common'; let appcuesInstance: Appcues; +let events = fromObject({}); + +function parseType(type: AppcuesAnalytic) { + switch (type) { + case AppcuesAnalytic.Identify: + return 'identify'; + case AppcuesAnalytic.Group: + return 'group'; + case AppcuesAnalytic.Event: + return 'event'; + case AppcuesAnalytic.Screen: + return 'screen'; + } +} + +@NativeClass() +@ObjCClass(AppcuesAnalyticsDelegate) +class AppcuesAnalyticsDelegateImpl extends NSObject implements AppcuesAnalyticsDelegate { + didTrackWithAnalyticValuePropertiesIsInternal(analytic: AppcuesAnalytic, value: string, properties: NSDictionary, isInternal: boolean): void { + events.notify({ + eventName: 'analytics', + type: parseType(analytic), + value, + properties: Utils.dataDeserialize(properties) ?? {}, + isInternal, + }); + } +} + +@NativeClass() +@ObjCClass(AppcuesExperienceDelegate) +class AppcuesExperienceDelegateImpl extends NSObject implements AppcuesExperienceDelegate { + experienceID: string | null = null; + + canDisplayExperienceWithExperienceID(experienceID: string): boolean { + this.experienceID = experienceID; + return true; + } + experienceDidAppear(): void { + events.notify({ + eventName: 'experience', + started: true, + id: this.experienceID, + }); + } + experienceDidDisappear(): void { + events.notify({ + eventName: 'experience', + started: false, + id: this.experienceID, + }); + this.experienceID = null; + } + experienceWillAppear(): void {} + experienceWillDisappear(): void {} +} + export class AppcuesSDK extends AppcuesSDKCommon { - static init(accountId: string, appId: string) { - const config = Config.alloc().initWithAccountIDApplicationID(accountId, appId); - appcuesInstance = Appcues.alloc().initWithConfig(config); - } - - static identify(userId: string, properties?: any) { - if (appcuesInstance) { - appcuesInstance.identifyWithUserIDProperties(userId, Utils.dataSerialize(properties || {})); - } - } - - static track(name: string, properties?: any) { - if (appcuesInstance) { - appcuesInstance.trackWithNameProperties(name, Utils.dataSerialize(properties || {})) - } - } - - static trackScreens() { - if (appcuesInstance) { - appcuesInstance.trackScreens(); - } - } - - static screen(title: string, properties?: any) { - if (appcuesInstance) { - appcuesInstance.screenWithTitleProperties(title, Utils.dataSerialize(properties || {})) - } - } - - static nativeInstance() { - return appcuesInstance; - } + private static _appcuesAnalyticsDelegate = AppcuesAnalyticsDelegateImpl.new(); + private static _appcuesExperienceDelegate = AppcuesExperienceDelegateImpl.new(); + + static init(accountId: string, appId: string) { + if (appcuesInstance) { + appcuesInstance.analyticsDelegate = null; + appcuesInstance.experienceDelegate = null; + } + const config = Config.alloc().initWithAccountIDApplicationID(accountId, appId); + appcuesInstance = Appcues.alloc().initWithConfig(config); + appcuesInstance.analyticsDelegate = AppcuesSDK._appcuesAnalyticsDelegate; + appcuesInstance.experienceDelegate = AppcuesSDK._appcuesExperienceDelegate; + } + + static identify(userId: string, properties?: any) { + if (appcuesInstance) { + appcuesInstance.identifyWithUserIDProperties(userId, Utils.dataSerialize(properties || {})); + } + } + + static track(name: string, properties?: any) { + if (appcuesInstance) { + appcuesInstance.trackWithNameProperties(name, Utils.dataSerialize(properties || {})); + } + } + + static trackScreens() { + if (appcuesInstance) { + appcuesInstance.trackScreens(); + } + } + + static screen(title: string, properties?: any) { + if (appcuesInstance) { + appcuesInstance.screenWithTitleProperties(title, Utils.dataSerialize(properties || {})); + } + } + + static show(experienceId: string) { + return new Promise((resolve, reject) => { + if (appcuesInstance) { + appcuesInstance.showWithExperienceIDCompletion(experienceId, (done, error) => { + if (error) { + reject(new Error(error.localizedDescription)); + } else { + resolve(done); + } + }); + } else { + reject('SDK unintialized'); + } + }); + } + + static reset() { + if (appcuesInstance) { + appcuesInstance.reset(); + } + } + + static anonymous(properties?: any) { + if (appcuesInstance) { + appcuesInstance.anonymousWithProperties(Utils.dataSerialize(properties || {})); + } + } + + static group(groupId?: string, properties?: any) { + if (appcuesInstance) { + appcuesInstance.groupWithGroupIDProperties(groupId ?? null, Utils.dataSerialize(properties || {})); + } + } + + static debug() { + if (appcuesInstance) { + appcuesInstance.debug(); + } + } + + static didHandleURL(value: any): boolean { + if (appcuesInstance) { + return appcuesInstance.didHandleURL(value); + } + return false; + } + + static nativeInstance() { + return appcuesInstance; + } } diff --git a/packages/nativescript-appcues/package.json b/packages/nativescript-appcues/package.json index 2b284159..4f442e83 100644 --- a/packages/nativescript-appcues/package.json +++ b/packages/nativescript-appcues/package.json @@ -1,6 +1,6 @@ { "name": "@nstudio/nativescript-appcues", - "version": "1.0.0", + "version": "1.0.1", "description": "Appcues SDK for NativeScript", "main": "index", "typings": "index.d.ts", diff --git a/packages/nativescript-appcues/platforms/android/include.gradle b/packages/nativescript-appcues/platforms/android/include.gradle index fc260721..e988c7b6 100644 --- a/packages/nativescript-appcues/platforms/android/include.gradle +++ b/packages/nativescript-appcues/platforms/android/include.gradle @@ -1,3 +1,6 @@ dependencies { - implementation "com.appcues:appcues:1.1.2" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1" + + implementation "com.appcues:appcues:2.1.3" } \ No newline at end of file diff --git a/packages/nativescript-appcues/platforms/android/java/io/nstudio/plugins/appcues/Appcues.kt b/packages/nativescript-appcues/platforms/android/java/io/nstudio/plugins/appcues/Appcues.kt index c4b5a066..aa77a576 100644 --- a/packages/nativescript-appcues/platforms/android/java/io/nstudio/plugins/appcues/Appcues.kt +++ b/packages/nativescript-appcues/platforms/android/java/io/nstudio/plugins/appcues/Appcues.kt @@ -2,9 +2,15 @@ package io.nstudio.plugins.appcues import android.content.Context import com.appcues.AppcuesConfig +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch class Appcues { companion object { + + private val mainScope = CoroutineScope(Dispatchers.Main) + @JvmOverloads @JvmStatic fun init( @@ -15,6 +21,19 @@ class Appcues { ): com.appcues.Appcues { return com.appcues.Appcues(context, accountId, applicationId, config) } + + + @JvmStatic + fun show( + appcues: com.appcues.Appcues, + experienceId: String, + callback: (Boolean) -> Unit + ) { + mainScope.launch { + val showed = appcues.show(experienceId) + callback(showed) + } + } } -} \ No newline at end of file +} diff --git a/packages/nativescript-appcues/platforms/android/nativescript_appcues.aar b/packages/nativescript-appcues/platforms/android/nativescript_appcues.aar index a5ebba9b..c9d14a91 100644 Binary files a/packages/nativescript-appcues/platforms/android/nativescript_appcues.aar and b/packages/nativescript-appcues/platforms/android/nativescript_appcues.aar differ diff --git a/tools/assets/App_Resources/Android/src/main/AndroidManifest.xml b/tools/assets/App_Resources/Android/src/main/AndroidManifest.xml index df97a703..bdf6ced1 100644 --- a/tools/assets/App_Resources/Android/src/main/AndroidManifest.xml +++ b/tools/assets/App_Resources/Android/src/main/AndroidManifest.xml @@ -44,6 +44,13 @@ + + + + + + + diff --git a/tools/demo/nativescript-appcues/index.ts b/tools/demo/nativescript-appcues/index.ts index 843ef99f..8c164dc9 100644 --- a/tools/demo/nativescript-appcues/index.ts +++ b/tools/demo/nativescript-appcues/index.ts @@ -4,7 +4,13 @@ import { AppcuesSDK } from '@nstudio/nativescript-appcues'; export class DemoSharedNativescriptAppcues extends DemoSharedBase { init() { - AppcuesSDK.init('', ''); + //AppcuesSDK.init('69047', 'a2355571-cbd5-4a8e-a0ed-a9ea09f0b904'); + AppcuesSDK.show('11111').then(shown =>{ + console.log('shown', shown); + }) + .catch(err =>{ + console.log('error', err); + }) } track() {