diff --git a/CHANGELOG.md b/CHANGELOG.md index b9a6924..cf0781c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.5.0 + +- https://github.com/TelemetryDeck/FlutterSDK/releases/tag/0.5.0 +- Add support for navigation signals +- Upgrades to the latest version of the Swift and Kotlin SDKs + ## 0.4.0 - https://github.com/TelemetryDeck/FlutterSDK/releases/tag/0.4.0 diff --git a/README.md b/README.md index 510fc7a..69dee9e 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,25 @@ Telemetrydecksdk.stop() In order to restart sending events, you will need to call the `start` method again. +## Navigation signals + +A navigation signal is a regular TelemetryDeck signal of type `TelemetryDeck.Navigation.pathChanged`. Automatic navigation tracking is available using the `navigate` and `navigateToDestination` methods: + +```dart +Telemetrydecksdk.navigate("screen1", "screen2"); + +Telemetrydecksdk.navigateToDestination("screen3"); +``` + +Both methods allow for a custom `clientUser` to be passed as an optional parameter: + +```dart +Telemetrydecksdk.navigate("screen1", "screen2", + clientUser: "custom_user"); +``` + +For more information, please check [this post](https://telemetrydeck.com/docs/articles/navigation-signals/). + ## Test mode If your app's build configuration is set to "Debug", all signals sent will be marked as testing signals. In the Telemetry Viewer app, activate **Test Mode** to see those. diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..30ab43d --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,25 @@ +# Package Release Notes + +This section refers to the process of maintaining, upgrading and publishing the current library. + +## Releasing a new version + +1. Create a PR to update the CHANGELOG in order to mention the changes made in the new version. This is optional, if this step is skipped, the `setupversion.sh` will create a generic entry. + +2. Merge all changes into `main`. + +3. Navigate to the [Set package version](https://github.com/TelemetryDeck/FlutterSDK/actions/workflows/set-version.yml) action and run it by setting the next `version`. Please note: this must be the same if you manually created a release entry in CHANGELOG.md. + +🏁 + +## Adopting newer versions of the native SDKs + +The Flutter SDK depends on the latest major version of the native SDKs. This is defined in the following locations: + +On Android, the dependency is configured in `android/build.gradle`: + +``` +implementation 'com.github.TelemetryDeck:KotlinSDK:2.+' +``` + +On iOS, the dependency is configured in `ios/telemetrydecksdk.podspec` using the podspect Dependency format `s.dependency 'TelemetryClient', '~> 2.0'`. diff --git a/android/build.gradle b/android/build.gradle index 8a8fe2b..0fad355 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,7 +2,7 @@ group 'com.telemetrydeck.telemetrydecksdk' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '1.9.24' repositories { google() mavenCentral() @@ -52,7 +52,7 @@ android { } dependencies { - implementation 'com.github.TelemetryDeck:KotlinSDK:+' + implementation 'com.github.TelemetryDeck:KotlinSDK:2.+' testImplementation 'org.jetbrains.kotlin:kotlin-test' testImplementation 'org.mockito:mockito-core:5.0.0' } diff --git a/android/src/main/kotlin/com/telemetrydeck/telemetrydecksdk/TelemetrydecksdkPlugin.kt b/android/src/main/kotlin/com/telemetrydeck/telemetrydecksdk/TelemetrydecksdkPlugin.kt index 8be00f5..b5802fc 100644 --- a/android/src/main/kotlin/com/telemetrydeck/telemetrydecksdk/TelemetrydecksdkPlugin.kt +++ b/android/src/main/kotlin/com/telemetrydeck/telemetrydecksdk/TelemetrydecksdkPlugin.kt @@ -17,125 +17,188 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext /** TelemetrydecksdkPlugin */ -class TelemetrydecksdkPlugin: FlutterPlugin, MethodCallHandler { - /// The MethodChannel that will the communication between Flutter and native Android - /// - /// This local reference serves to register the plugin with the Flutter Engine and unregister it - /// when the Flutter Engine is detached from the Activity - private lateinit var channel : MethodChannel - private var applicationContext: Context? = null - private val coroutineScope = CoroutineScope(Dispatchers.IO) // Coroutine scope for background tasks - - override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - applicationContext = flutterPluginBinding.applicationContext - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "telemetrydecksdk") - channel.setMethodCallHandler(this) - } - - override fun onMethodCall(call: MethodCall, result: Result) { - if (call.method == "start") { - nativeInitialize(call, result) - } else if (call.method == "stop") { - nativeStop(call, result) - } else if (call.method == "send") { - // this maps to the queue method which aligns with the behaviour of the iOS SDK - nativeQueue(call, result) - } else if (call.method == "generateNewSession") { - TelemetryManager.newSession() - result.success(null) - } else if (call.method == "updateDefaultUser") { - nativeUpdateDefaultUser(call, result) - } else { - result.notImplemented() +class TelemetrydecksdkPlugin : FlutterPlugin, MethodCallHandler { + /// The MethodChannel that will the communication between Flutter and native Android + /// + /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// when the Flutter Engine is detached from the Activity + private lateinit var channel: MethodChannel + private var applicationContext: Context? = null + private val coroutineScope = + CoroutineScope(Dispatchers.IO) // Coroutine scope for background tasks + + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + applicationContext = flutterPluginBinding.applicationContext + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "telemetrydecksdk") + channel.setMethodCallHandler(this) } - } - private fun nativeStop(call: MethodCall, result: Result) { - coroutineScope.launch { - TelemetryManager.stop() - withContext(Dispatchers.Main) { - result.success(null) + override fun onMethodCall(call: MethodCall, result: Result) { + when (call.method) { + "start" -> { + nativeInitialize(call, result) + } + "stop" -> { + nativeStop(result) + } + "send" -> { + // this maps to the queue method which aligns with the behaviour of the iOS SDK + nativeQueue(call, result) + } + "generateNewSession" -> { + TelemetryManager.newSession() + result.success(null) + } + "updateDefaultUser" -> { + nativeUpdateDefaultUser(call, result) + } + "navigate" -> { + nativeNavigate(call, result) + } + "navigateToDestination" -> { + nativeNavigateDestination(call, result) + } + else -> { + result.notImplemented() + } } } - } - private fun nativeUpdateDefaultUser(call: MethodCall, - result: Result) { - val user = call.arguments() + /** + * Send a signal that represents a navigation event with a source and a destination. + * + * @see Navigation Signals + * */ + private fun nativeNavigate(call: MethodCall, result: Result) { + val sourcePath = call.argument("sourcePath") + val destinationPath = call.argument("destinationPath") + val clientUser = call.argument("clientUser") + + if (sourcePath == null || destinationPath == null) { + result.error("INVALID_ARGUMENT", "sourcePath and destinationPath are required", null) + return + } - coroutineScope.launch { - TelemetryManager.newDefaultUser(user) - withContext(Dispatchers.Main) { - result.success(null) - } - } - } - - private fun nativeQueue( - call: MethodCall, - result: Result - ) { - val signalType = call.argument("signalType") - if (signalType != null) { - val clientUser = call.argument("clientUser") - val additionalPayload = call.argument>("additionalPayload") - - coroutineScope.launch { - TelemetryManager.queue(signalType, clientUser, additionalPayload.orEmpty()) - - withContext(Dispatchers.Main) { - result.success(null) + coroutineScope.launch { + TelemetryManager.navigate(sourcePath, destinationPath, clientUser) + withContext(Dispatchers.Main) { + result.success(null) + } } - } } - } - - private fun nativeInitialize(call: MethodCall, result: Result) { - val arguments = call.arguments as? Map<*, *> // Cast to a Map - if (arguments != null) { - // Extract values using the expected keys - // we extract the required appID parameter - val appID = arguments["appID"] as? String - if (appID == null) { - result.error("INVALID_ARGUMENT", "Expected value appID is not provided.", null) - return - } - // additional optional parameters - val apiBaseURL = arguments["apiBaseURL"] as? String? - val defaultUser = arguments["defaultUser"] as? String? - val debug = arguments["debug"] as? Boolean - val testMode = arguments["testMode"] as? Boolean + /** + * Send a signal that represents a navigation event with a destination and a default source. + * + * @see Navigation Signals + * */ + private fun nativeNavigateDestination(call: MethodCall, result: Result) { + val destinationPath = call.argument("destinationPath") + val clientUser = call.argument("clientUser") + + if (destinationPath == null) { + result.error("INVALID_ARGUMENT", "destinationPath is required", null) + return + } + coroutineScope.launch { + TelemetryManager.navigate(destinationPath, clientUser) + withContext(Dispatchers.Main) { + result.success(null) + } + } + } - // Initialize the client - // Do not activate the lifecycle provider - val builder = TelemetryManager.Builder() - .appID(appID) - .providers(listOf(SessionProvider(), EnvironmentMetadataProvider())) + private fun nativeStop(result: Result) { + coroutineScope.launch { + TelemetryManager.stop() + withContext(Dispatchers.Main) { + result.success(null) + } + } + } - apiBaseURL?.let { - builder.baseURL(it) - } - defaultUser?.let { - builder.defaultUser(it) - } - debug?.let { - builder.showDebugLogs(it) - } - testMode?.let { - builder.testMode(it) - } + private fun nativeUpdateDefaultUser( + call: MethodCall, + result: Result + ) { + val user = call.arguments() + + coroutineScope.launch { + TelemetryManager.newDefaultUser(user) + withContext(Dispatchers.Main) { + result.success(null) + } + } + } - val application = applicationContext as Application - TelemetryManager.start(application, builder) - result.success(null) - } else { - result.error("INVALID_ARGUMENT", "Arguments are not a map", null) + private fun nativeQueue( + call: MethodCall, + result: Result + ) { + val signalType = call.argument("signalType") + if (signalType != null) { + val clientUser = call.argument("clientUser") + val additionalPayload = call.argument>("additionalPayload") + + coroutineScope.launch { + TelemetryManager.queue(signalType, clientUser, additionalPayload.orEmpty()) + + withContext(Dispatchers.Main) { + result.success(null) + } + } + } else { + result.error("INVALID_ARGUMENT", "signalType must be provided", null) + } } - } - override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { - channel.setMethodCallHandler(null) - } + private fun nativeInitialize(call: MethodCall, result: Result) { + val arguments = call.arguments as? Map<*, *> // Cast to a Map + if (arguments != null) { + // Extract values using the expected keys + // we extract the required appID parameter + val appID = arguments["appID"] as? String + if (appID == null) { + result.error("INVALID_ARGUMENT", "Expected value appID is not provided.", null) + return + } + + // additional optional parameters + val apiBaseURL = arguments["apiBaseURL"] as? String? + val defaultUser = arguments["defaultUser"] as? String? + val debug = arguments["debug"] as? Boolean + val testMode = arguments["testMode"] as? Boolean + + + // Initialize the client + // Do not activate the lifecycle provider + val builder = TelemetryManager.Builder() + .appID(appID) + .providers(listOf(SessionProvider(), EnvironmentMetadataProvider())) + + apiBaseURL?.let { + builder.baseURL(it) + } + defaultUser?.let { + builder.defaultUser(it) + } + debug?.let { + builder.showDebugLogs(it) + } + testMode?.let { + builder.testMode(it) + } + + val application = applicationContext as Application + TelemetryManager.start(application, builder) + result.success(null) + } else { + result.error("INVALID_ARGUMENT", "Arguments are not a map", null) + } + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } } diff --git a/example/android/build.gradle b/example/android/build.gradle index 0e5b6c7..74834be 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '1.9.24' repositories { google() mavenCentral() diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 598d13f..53cbe7c 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,3 +1,4 @@ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true +kotlin.code.style=official \ No newline at end of file diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 29cc9a4..396a93f 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2,10 +2,10 @@ PODS: - Flutter (1.0.0) - integration_test (0.0.1): - Flutter - - TelemetryClient (1.5.0) + - TelemetryDeck (2.2.4) - telemetrydecksdk (1.0.0): - Flutter - - TelemetryClient + - TelemetryDeck (~> 2.2.4) DEPENDENCIES: - Flutter (from `Flutter`) @@ -14,7 +14,7 @@ DEPENDENCIES: SPEC REPOS: trunk: - - TelemetryClient + - TelemetryDeck EXTERNAL SOURCES: Flutter: @@ -25,11 +25,11 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/telemetrydecksdk/ios" SPEC CHECKSUMS: - Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - integration_test: 13825b8a9334a850581300559b8839134b124670 - TelemetryClient: efd281e6bbee69b0f5f7afc1eeb44067a5b41944 - telemetrydecksdk: 97305a39cfcc797ff80cecaaa6d815e61343145d + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4 + TelemetryDeck: 0fc448990840a22174112c89e7e9ee484e11ca50 + telemetrydecksdk: 1c83be11619131dffd4ff1c403f809f4469c8806 PODFILE CHECKSUM: 7be2f5f74864d463a8ad433546ed1de7e0f29aef -COCOAPODS: 1.15.0 +COCOAPODS: 1.15.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 9149296..313fdf5 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -215,7 +215,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { @@ -468,7 +468,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = MS6MBBXCFJ; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -647,7 +647,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = MS6MBBXCFJ; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -670,7 +670,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = MS6MBBXCFJ; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 87131a0..8e3ca5d 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ { mainAxisSize: MainAxisSize.min, children: [ const Text('Hello !'), - const SizedBox(height: 10), // Adds some space between the text and the button + const SizedBox( + height: + 10), // Adds some space between the text and the button ElevatedButton( onPressed: () { // Send a signal type to TelemetryDeck @@ -64,6 +66,13 @@ class _MyAppState extends State { "button_clicked", additionalPayload: {"mapKey": "mapValue"}, ); + // Signal navigation + Telemetrydecksdk.navigate("home", "settings"); + + Telemetrydecksdk.navigate("settings", "about", + clientUser: "custom_user"); + + Telemetrydecksdk.navigateToDestination("landing"); }, child: const Text('Press Me'), ), diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 65daba9..6a3abcf 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -1,9 +1,9 @@ PODS: - FlutterMacOS (1.0.0) - - TelemetryClient (1.5.0) + - TelemetryDeck (2.2.4) - telemetrydecksdk (0.0.1): - FlutterMacOS - - TelemetryClient + - TelemetryDeck (~> 2.2.4) DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) @@ -11,7 +11,7 @@ DEPENDENCIES: SPEC REPOS: trunk: - - TelemetryClient + - TelemetryDeck EXTERNAL SOURCES: FlutterMacOS: @@ -21,8 +21,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - TelemetryClient: efd281e6bbee69b0f5f7afc1eeb44067a5b41944 - telemetrydecksdk: 82d2ec492fa1249c0d2e3b1f0226465ba2a06525 + TelemetryDeck: 0fc448990840a22174112c89e7e9ee484e11ca50 + telemetrydecksdk: 08f37e676cf3ce7d3cb8f9d4fd00e2f505433044 PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 diff --git a/example/pubspec.lock b/example/pubspec.lock index a879ecb..9da225b 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -102,26 +102,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.4" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.3" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" lints: dependency: transitive description: @@ -150,10 +150,10 @@ packages: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.0" path: dependency: transitive description: @@ -237,7 +237,7 @@ packages: path: ".." relative: true source: path - version: "0.0.5" + version: "0.4.0" term_glyph: dependency: transitive description: @@ -250,10 +250,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.0" vector_math: dependency: transitive description: @@ -266,10 +266,10 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "14.2.1" webdriver: dependency: transitive description: @@ -279,5 +279,5 @@ packages: source: hosted version: "3.0.3" sdks: - dart: ">=3.2.3 <4.0.0" - flutter: ">=3.3.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/ios/Classes/TelemetrydecksdkPlugin.swift b/ios/Classes/TelemetrydecksdkPlugin.swift index 60c61c5..45e9d75 100644 --- a/ios/Classes/TelemetrydecksdkPlugin.swift +++ b/ios/Classes/TelemetrydecksdkPlugin.swift @@ -1,6 +1,6 @@ import Flutter import UIKit -import TelemetryClient +import TelemetryDeck public class TelemetrydecksdkPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { @@ -18,22 +18,59 @@ public class TelemetrydecksdkPlugin: NSObject, FlutterPlugin { case "send": nativeQueue(call, result: result) case "generateNewSession": - TelemetryManager.generateNewSession() + TelemetryDeck.generateNewSession() result(nil) case "updateDefaultUser": nativeUpdateDefaultUser(call, result: result) + case "navigate": + nativeNavigate(call, result: result) + case "navigateToDestination": + nativeNavigateDestination(call, result: result) default: result(FlutterMethodNotImplemented) } } + /** + * Send a signal that represents a navigation event with a source and a destination. + * + * @see Navigation Signals + * */ + private func nativeNavigate(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let arguments = call.arguments as? [String: Any], + let sourcePath = arguments["sourcePath"] as? String, + let destinationPath = arguments["destinationPath"] as? String else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "sourcePath and destinationPath are required", details: nil)) + return + } + let clientUser = arguments["clientUser"] as? String + TelemetryDeck.navigationPathChanged(from: sourcePath, to: destinationPath, customUserID: clientUser) + result(nil) + } + + /** + * Send a signal that represents a navigation event with a destination and a default source. + * + * @see Navigation Signals + * */ + private func nativeNavigateDestination(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let arguments = call.arguments as? [String: Any], + let destinationPath = arguments["destinationPath"] as? String else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "destinationPath are required", details: nil)) + return + } + let clientUser = arguments["clientUser"] as? String + TelemetryDeck.navigationPathChanged(to: destinationPath, customUserID: clientUser) + result(nil) + } + private func nativeStop(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - TelemetryManager.terminate() + TelemetryDeck.terminate() result(nil) } private func nativeUpdateDefaultUser(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - TelemetryManager.updateDefaultUser(to: call.arguments as? String) + TelemetryDeck.updateDefaultUserID(to: call.arguments as? String) result(nil) } @@ -48,7 +85,7 @@ public class TelemetrydecksdkPlugin: NSObject, FlutterPlugin { // do not attempt to send signals if the client is stopped if TelemetryManager.isInitialized { - TelemetryManager.send(signalType, for: clientUser, with: additionalPayload) + TelemetryDeck.signal(signalType, parameters: additionalPayload, customUserID: clientUser) } result(nil) @@ -91,7 +128,7 @@ public class TelemetrydecksdkPlugin: NSObject, FlutterPlugin { configuration.testMode = arguments["testMode"] as? Bool == true } - TelemetryManager.initialize(with: configuration) + TelemetryDeck.initialize(config: configuration) result(nil) } diff --git a/ios/telemetrydecksdk.podspec b/ios/telemetrydecksdk.podspec index fe97008..3d8e649 100644 --- a/ios/telemetrydecksdk.podspec +++ b/ios/telemetrydecksdk.podspec @@ -15,7 +15,7 @@ Flutter SDK for TelemetryDeck, a privacy-conscious analytics service for apps an s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'TelemetryClient' + s.dependency 'TelemetryDeck', '~> 2.2.4' s.platform = :ios, '12.0' # Flutter.framework does not contain a i386 slice. diff --git a/lib/src/telemetrydecksdk.dart b/lib/src/telemetrydecksdk.dart index 92262eb..af212eb 100644 --- a/lib/src/telemetrydecksdk.dart +++ b/lib/src/telemetrydecksdk.dart @@ -43,4 +43,16 @@ abstract class Telemetrydecksdk { static Future updateDefaultUser(String clientUser) async { await TelemetrydecksdkPlatform.instance.updateDefaultUser(clientUser); } + + static Future navigate(String sourcePath, String destinationPath, + {String? clientUser}) async { + await TelemetrydecksdkPlatform.instance + .navigate(sourcePath, destinationPath, clientUser: clientUser); + } + + static Future navigateToDestination(String destinationPath, + {String? clientUser}) async { + await TelemetrydecksdkPlatform.instance + .navigateToDestination(destinationPath, clientUser: clientUser); + } } diff --git a/lib/src/telemetrydecksdk_method_channel.dart b/lib/src/telemetrydecksdk_method_channel.dart index e334bd0..462a6e4 100644 --- a/lib/src/telemetrydecksdk_method_channel.dart +++ b/lib/src/telemetrydecksdk_method_channel.dart @@ -45,4 +45,23 @@ class MethodChannelTelemetrydecksdk extends TelemetrydecksdkPlatform { Future updateDefaultUser(String clientUser) async { await methodChannel.invokeMethod('updateDefaultUser', clientUser); } + + @override + Future navigate(String sourcePath, String destinationPath, + {String? clientUser}) async { + await methodChannel.invokeMethod('navigate', { + 'sourcePath': sourcePath, + 'destinationPath': destinationPath, + 'clientUser': clientUser, + }); + } + + @override + Future navigateToDestination(String destinationPath, + {String? clientUser}) async { + await methodChannel.invokeMethod('navigateToDestination', { + 'destinationPath': destinationPath, + 'clientUser': clientUser, + }); + } } diff --git a/lib/src/telemetrydecksdk_platform_interface.dart b/lib/src/telemetrydecksdk_platform_interface.dart index 6fce489..f39fa4e 100644 --- a/lib/src/telemetrydecksdk_platform_interface.dart +++ b/lib/src/telemetrydecksdk_platform_interface.dart @@ -47,4 +47,15 @@ abstract class TelemetrydecksdkPlatform extends PlatformInterface { Future updateDefaultUser(String clientUser) async { throw UnimplementedError('updateDefaultUser() has not been implemented.'); } + + Future navigate(String sourcePath, String destinationPath, + {String? clientUser}) async { + throw UnimplementedError('navigate() has not been implemented.'); + } + + Future navigateToDestination(String destinationPath, + {String? clientUser}) async { + throw UnimplementedError( + 'navigateToDestination() has not been implemented.'); + } } diff --git a/macos/Classes/TelemetrydecksdkPlugin.swift b/macos/Classes/TelemetrydecksdkPlugin.swift index b888c2a..ea92538 100644 --- a/macos/Classes/TelemetrydecksdkPlugin.swift +++ b/macos/Classes/TelemetrydecksdkPlugin.swift @@ -1,6 +1,6 @@ import Cocoa import FlutterMacOS -import TelemetryClient +import TelemetryDeck public class TelemetrydecksdkPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { @@ -19,41 +19,74 @@ public class TelemetrydecksdkPlugin: NSObject, FlutterPlugin { case "send": nativeQueue(call, result: result) case "generateNewSession": - TelemetryManager.generateNewSession() + TelemetryDeck.generateNewSession() result(nil) case "updateDefaultUser": nativeUpdateDefaultUser(call, result: result) + case "navigate": + nativeNavigate(call, result: result) + case "navigateToDestination": + nativeNavigateDestination(call, result: result) default: result(FlutterMethodNotImplemented) } } + /** + * Send a signal that represents a navigation event with a source and a destination. + * + * @see Navigation Signals + * */ + private func nativeNavigate(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let arguments = call.arguments as? [String: Any], + let sourcePath = arguments["sourcePath"] as? String, + let destinationPath = arguments["destinationPath"] as? String else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "sourcePath and destinationPath are required", details: nil)) + return + } + let clientUser = arguments["clientUser"] as? String + TelemetryDeck.navigationPathChanged(from: sourcePath, to: destinationPath, customUserID: clientUser) + result(nil) + } + + /** + * Send a signal that represents a navigation event with a destination and a default source. + * + * @see Navigation Signals + * */ + private func nativeNavigateDestination(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + guard let arguments = call.arguments as? [String: Any], + let destinationPath = arguments["destinationPath"] as? String else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "destinationPath are required", details: nil)) + return + } + let clientUser = arguments["clientUser"] as? String + TelemetryDeck.navigationPathChanged(to: destinationPath, customUserID: clientUser) + result(nil) + } + private func nativeStop(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - TelemetryManager.terminate() + TelemetryDeck.terminate() result(nil) } private func nativeUpdateDefaultUser(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - TelemetryManager.updateDefaultUser(to: call.arguments as? String) + TelemetryDeck.updateDefaultUserID(to: call.arguments as? String) result(nil) } private func nativeQueue(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - guard let arguments = call.arguments as? [String: Any], - let signalType = arguments["signalType"] as? String - else { - result( - FlutterError( - code: "INVALID_ARGUMENT", message: "Missing required argument signalType", details: nil)) + guard let arguments = call.arguments as? [String: Any], let signalType = arguments["signalType"] as? String else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "Missing required argument signalType", details: nil)) return } let clientUser = arguments["clientUser"] as? String - let additionalPayload = arguments["additionalPayload"] as? [String: String] ?? [:] + let additionalPayload = arguments["additionalPayload"] as? [String : String] ?? [:] // do not attempt to send signals if the client is stopped if TelemetryManager.isInitialized { - TelemetryManager.send(signalType, for: clientUser, with: additionalPayload) + TelemetryDeck.signal(signalType, parameters: additionalPayload, customUserID: clientUser) } result(nil) @@ -61,16 +94,13 @@ public class TelemetrydecksdkPlugin: NSObject, FlutterPlugin { private func nativeInitialize(_ call: FlutterMethodCall, result: @escaping FlutterResult) { guard let arguments = call.arguments as? [String: Any] else { - result( - FlutterError(code: "INVALID_ARGUMENT", message: "Arguments are not a map", details: nil)) + result(FlutterError(code: "INVALID_ARGUMENT", message: "Arguments are not a map", details: nil)) return } // appD is required guard let appID: String = arguments["appID"] as? String else { - result( - FlutterError( - code: "INVALID_ARGUMENT", message: "Expected value appID is not provided.", details: nil)) + result(FlutterError(code: "INVALID_ARGUMENT", message: "Expected value appID is not provided.", details: nil)) return } @@ -80,7 +110,6 @@ public class TelemetrydecksdkPlugin: NSObject, FlutterPlugin { baseURL = url } - let configuration = TelemetryManagerConfiguration.init( appID: appID, salt: nil, baseURL: baseURL) @@ -100,7 +129,7 @@ public class TelemetrydecksdkPlugin: NSObject, FlutterPlugin { configuration.testMode = arguments["testMode"] as? Bool == true } - TelemetryManager.initialize(with: configuration) + TelemetryDeck.initialize(config: configuration) result(nil) } diff --git a/macos/telemetrydecksdk.podspec b/macos/telemetrydecksdk.podspec index b012d71..a7167c3 100644 --- a/macos/telemetrydecksdk.podspec +++ b/macos/telemetrydecksdk.podspec @@ -16,7 +16,7 @@ Flutter SDK for TelemetryDeck, a privacy-conscious analytics service for apps an s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'FlutterMacOS' - s.dependency 'TelemetryClient' + s.dependency 'TelemetryDeck', '~> 2.2.4' s.platform = :osx, '10.11' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } diff --git a/test/telemetrydecksdk_test.dart b/test/telemetrydecksdk_test.dart index d3b3d8d..af6f448 100644 --- a/test/telemetrydecksdk_test.dart +++ b/test/telemetrydecksdk_test.dart @@ -8,7 +8,8 @@ class MockTelemetrydecksdkPlatform with MockPlatformInterfaceMixin implements TelemetrydecksdkPlatform { @override - Future start(TelemetryManagerConfiguration configuration) => Future.value(); + Future start(TelemetryManagerConfiguration configuration) => + Future.value(); @override Future stop() async => (); @@ -26,10 +27,21 @@ class MockTelemetrydecksdkPlatform Map? additionalPayload, }) async => (); + + @override + Future navigate(String sourcePath, String destinationPath, + {String? clientUser}) async => + (); + + @override + Future navigateToDestination(String destinationPath, + {String? clientUser}) async => + (); } void main() { - final TelemetrydecksdkPlatform initialPlatform = TelemetrydecksdkPlatform.instance; + final TelemetrydecksdkPlatform initialPlatform = + TelemetrydecksdkPlatform.instance; test('$MethodChannelTelemetrydecksdk is the default instance', () { expect(initialPlatform, isInstanceOf());