diff --git a/scgateway/android/build.gradle b/scgateway/android/build.gradle index 4a06427..2ef41ed 100644 --- a/scgateway/android/build.gradle +++ b/scgateway/android/build.gradle @@ -64,5 +64,5 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'com.smallcase.gateway:sdk:6.0.5' + implementation 'com.smallcase.gateway:sdk:6.0.6' } diff --git a/scgateway/android/src/main/kotlin/com/example/scgateway_flutter_plugin/ScgatewayFlutterPlugin.kt b/scgateway/android/src/main/kotlin/com/example/scgateway_flutter_plugin/ScgatewayFlutterPlugin.kt index 16fd480..3f8121c 100644 --- a/scgateway/android/src/main/kotlin/com/example/scgateway_flutter_plugin/ScgatewayFlutterPlugin.kt +++ b/scgateway/android/src/main/kotlin/com/example/scgateway_flutter_plugin/ScgatewayFlutterPlugin.kt @@ -14,9 +14,13 @@ import com.smallcase.gateway.data.models.* import com.smallcase.gateway.data.requests.InitRequest import com.smallcase.gateway.portal.SmallcaseGatewaySdk import com.smallcase.gateway.portal.SmallplugPartnerProps +import com.smallcase.gateway.data.listeners.Notification +import com.smallcase.gateway.data.listeners.NotificationCenter +import com.smallcase.gateway.portal.ScgNotification import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler @@ -46,13 +50,43 @@ class ScgatewayFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { /// 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 lateinit var eventChannel: EventChannel + private var eventSink: EventChannel.EventSink? = null + private var notificationObserver: ((Notification) -> Unit)? = null override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { this.context = flutterPluginBinding.applicationContext channel = MethodChannel(flutterPluginBinding.binaryMessenger, "scgateway_flutter_plugin") channel.setMethodCallHandler(this) + + eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "scgateway_flutter_plugin/smallplug_events") + eventChannel.setStreamHandler(object : EventChannel.StreamHandler { + override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { + eventSink = events + notificationObserver = { notification -> + processSmallplugNotification(notification) + } + notificationObserver?.let { NotificationCenter.addObserver(it) } + } + override fun onCancel(arguments: Any?) { + notificationObserver?.let { NotificationCenter.removeObserver(it) } + notificationObserver = null + eventSink = null + } + }) + } + + private fun processSmallplugNotification(notification: Notification) { + val jsonString = notification.userInfo?.get(ScgNotification.STRINGIFIED_PAYLOAD_KEY) as? String ?: return + try { + val parsed = org.json.JSONObject(jsonString) + if (parsed.optString("type") != "smallplug_analytics_event") return + val eventData = parsed.optJSONObject("data") ?: return + uiThreadHandler.post { + eventSink?.success(eventData.toString()) + } + } catch (_: Exception) {} } override fun onMethodCall(@NonNull call: MethodCall, @NonNull rawResult: Result) { @@ -495,6 +529,9 @@ class ScgatewayFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { channel.setMethodCallHandler(null) + notificationObserver?.let { NotificationCenter.removeObserver(it) } + notificationObserver = null + eventSink = null } override fun onAttachedToActivity(binding: ActivityPluginBinding) { diff --git a/scgateway/ios/Classes/SwiftScgatewayFlutterPlugin.swift b/scgateway/ios/Classes/SwiftScgatewayFlutterPlugin.swift index 91e5c81..0299f23 100644 --- a/scgateway/ios/Classes/SwiftScgatewayFlutterPlugin.swift +++ b/scgateway/ios/Classes/SwiftScgatewayFlutterPlugin.swift @@ -11,20 +11,57 @@ enum IntentType: String { case authoriseHoldings = "AUTHORISE_HOLDINGS" } -public class SwiftScgatewayFlutterPlugin: NSObject, FlutterPlugin { - +public class SwiftScgatewayFlutterPlugin: NSObject, FlutterPlugin, FlutterStreamHandler { + @MainActor let currentViewController: UIViewController = (UIApplication.shared.delegate?.window??.rootViewController)! - + + private var eventSink: FlutterEventSink? + private var notificationObserver: NSObjectProtocol? + public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "scgateway_flutter_plugin", binaryMessenger: registrar.messenger()) let instance = SwiftScgatewayFlutterPlugin() - - // let scLoansChannel = FlutterMethodChannel(name: "scloans", binaryMessenger: registrar.messenger()) - // let scLoansInstance = ScLoanFlutterPlugin() - + + let eventChannel = FlutterEventChannel(name: "scgateway_flutter_plugin/smallplug_events", binaryMessenger: registrar.messenger()) + eventChannel.setStreamHandler(instance) + registrar.addMethodCallDelegate(instance, channel: channel) - // registrar.addMethodCallDelegate(scLoansInstance, channel: scLoansChannel) + } + + // MARK: - FlutterStreamHandler (SmallPlug event streaming) + + public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { + self.eventSink = events + notificationObserver = NotificationCenter.default.addObserver( + forName: SCGateway.scgNotificationName, + object: nil, + queue: .main + ) { [weak self] notification in + self?.handleScgNotification(notification) + } + return nil + } + + public func onCancel(withArguments arguments: Any?) -> FlutterError? { + if let observer = notificationObserver { + NotificationCenter.default.removeObserver(observer) + notificationObserver = nil + } + eventSink = nil + return nil + } + + private func handleScgNotification(_ notification: Notification) { + guard let jsonString = notification.userInfo?[SCGNotification.strigifiedPayloadKey] as? String, + let data = jsonString.data(using: .utf8), + let parsed = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + parsed["type"] as? String == NotificationTypes.smallplugAnalyticsEvent, + let eventData = parsed["data"] as? [String: Any] else { return } + guard let sink = eventSink, + let eventJson = try? JSONSerialization.data(withJSONObject: eventData), + let eventString = String(data: eventJson, encoding: .utf8) else { return } + sink(eventString) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { diff --git a/scgateway/ios/scgateway_flutter_plugin.podspec b/scgateway/ios/scgateway_flutter_plugin.podspec index deafa0a..4590987 100644 --- a/scgateway/ios/scgateway_flutter_plugin.podspec +++ b/scgateway/ios/scgateway_flutter_plugin.podspec @@ -15,7 +15,7 @@ Scgateway Flutter plugin. s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'SCGateway', '7.1.4' + s.dependency 'SCGateway', '7.1.7' s.xcconfig = {'BUILD_LIBRARY_FOR_DISTRIBUTION' => 'YES'} s.vendored_frameworks = 'SCGateway.xcframework' s.platform = :ios, '13.0' diff --git a/scgateway/lib/scgateway_flutter_plugin.dart b/scgateway/lib/scgateway_flutter_plugin.dart index df4efab..6479d0f 100644 --- a/scgateway/lib/scgateway_flutter_plugin.dart +++ b/scgateway/lib/scgateway_flutter_plugin.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:flutter/services.dart'; import './color_ext.dart'; @@ -46,10 +47,43 @@ class SmallplugUiConfig { }; } +class SmallplugAnalyticsEvent { + final String eventName; + final Map data; + final double timestamp; + + SmallplugAnalyticsEvent({ + required this.eventName, + required this.data, + required this.timestamp, + }); + + factory SmallplugAnalyticsEvent.fromJson(Map json) { + return SmallplugAnalyticsEvent( + eventName: json['eventName'] as String? ?? '', + data: (json['data'] as Map?)?.cast() ?? {}, + timestamp: (json['timestamp'] as num?)?.toDouble() ?? 0, + ); + } +} + class ScgatewayFlutterPlugin { static const MethodChannel _channel = const MethodChannel('scgateway_flutter_plugin'); + static const EventChannel _smallplugEventChannel = + EventChannel('scgateway_flutter_plugin/smallplug_events'); + + /// Stream of SmallPlug / DM analytics events. + /// Subscribe before calling [launchSmallplug] or [launchSmallplugWithBranding]. + static Stream get smallplugEventStream { + return _smallplugEventChannel.receiveBroadcastStream().map((event) { + final Map json = + (event is String ? jsonDecode(event) : event) as Map; + return SmallplugAnalyticsEvent.fromJson(json); + }); + } + static const String _flutterPluginVersion = "4.0.0"; static Future getSdkVersion() async { diff --git a/scgateway/pubspec.yaml b/scgateway/pubspec.yaml index 2ad17fa..db560ad 100644 --- a/scgateway/pubspec.yaml +++ b/scgateway/pubspec.yaml @@ -1,6 +1,6 @@ name: scgateway_flutter_plugin description: Scgateway Flutter plugin. -version: 7.0.5 +version: 7.0.6 homepage: https://github.com/smallcase/gw-mob-sdk-flutter # The following line prevents the package from being accidentally published to diff --git a/smart_investing/lib/main.dart b/smart_investing/lib/main.dart index d68dbbc..9fb8022 100644 --- a/smart_investing/lib/main.dart +++ b/smart_investing/lib/main.dart @@ -1,6 +1,9 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:go_router/go_router.dart'; +import 'package:scgateway_flutter_plugin/scgateway_flutter_plugin.dart'; import 'app/SIGatewayPage.dart'; import 'app/SILoansPage.dart'; @@ -20,6 +23,7 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { late Brightness _brightness; + StreamSubscription? _smallplugEventSub; @override void initState() { @@ -31,11 +35,17 @@ class _MyAppState extends State { _brightness = dispatcher.platformBrightness; }); }; + _smallplugEventSub = + ScgatewayFlutterPlugin.smallplugEventStream.listen((event) { + print( + '[SmallplugEvent] ${event.eventName} | data: ${event.data} | ts: ${event.timestamp}'); + }); super.initState(); } @override void dispose() { + _smallplugEventSub?.cancel(); repository.dispose(); super.dispose(); } diff --git a/smart_investing/pubspec.yaml b/smart_investing/pubspec.yaml index ce8a433..0844e18 100644 --- a/smart_investing/pubspec.yaml +++ b/smart_investing/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 3.0.7 +version: 3.0.8 environment: sdk: ^3.8.1