diff --git a/example/lib/page/home.dart b/example/lib/page/home.dart index dcfd109..52aa4d2 100644 --- a/example/lib/page/home.dart +++ b/example/lib/page/home.dart @@ -520,4 +520,9 @@ class _HomePageState extends State { void _onPushEvent(dynamic push) { _pushController.value = '$push\nat: ${DateTime.now().toIso8601String()}'; } + + void _onInAppMessageActionEvent(InAppMessageAction action) { + print('received in-app action: $action'); + } + } diff --git a/ios/Classes/Data/InAppMessageAction.swift b/ios/Classes/Data/InAppMessageAction.swift new file mode 100644 index 0000000..3b035d4 --- /dev/null +++ b/ios/Classes/Data/InAppMessageAction.swift @@ -0,0 +1,50 @@ +// +// InAppMessageAction.swift +// exponea +// + +import Foundation +import ExponeaSDK + +final class InAppMessageAction { + let message: InAppMessage + let button: InAppMessageButton? + let interaction: Bool + + init(message: InAppMessage, button: InAppMessageButton?, interaction: Bool) { + self.message = message + self.button = button + self.interaction = interaction + } + + func toMap() -> [String:Any?] { + var data : [String:Any?] = [ + "message": [ + "id": message.id, + "name": message.name, + "messageType": message.messageType.rawValue, + "frequency": message.frequency?.rawValue, + "payload": message.payload != nil ? try? String(data: JSONEncoder().encode(message.payload), encoding: .utf8) : nil, + "variantId": message.variantId, + "variantName": message.variantName, + "trigger": try? String(data: JSONEncoder().encode(message.trigger), encoding: .utf8), + "dateFilter": try? String(data: JSONEncoder().encode(message.dateFilter), encoding: .utf8), + "loadPriority": message.priority, + "loadDelay": message.delayMS, + "closeTimeout": message.timeoutMS, + "payloadHtml": message.payloadHtml, + "isHtml": message.isHtml, + "hasTrackingConsent": message.hasTrackingConsent, + "consentCategoryTracking": message.consentCategoryTracking, + ], + "interaction": interaction, + ] + if let button { + data["button"] = [ + "text": button.text, + "url": button.url + ] + } + return data + } +} diff --git a/ios/Classes/InAppMessageActionStreamHandler.swift b/ios/Classes/InAppMessageActionStreamHandler.swift new file mode 100644 index 0000000..724f393 --- /dev/null +++ b/ios/Classes/InAppMessageActionStreamHandler.swift @@ -0,0 +1,58 @@ +// +// InAppMessageActionStreamHandler.swift +// exponea +// + +import Foundation +import ExponeaSDK +import Flutter + +public class InAppMessageActionStreamHandler: NSObject, FlutterStreamHandler, InAppMessageActionDelegate { + private(set) static var currentInstance: InAppMessageActionStreamHandler = InAppMessageActionStreamHandler() + + // We have to hold inAppMessage until plugin is initialized and listener set + private var pendingData: InAppMessageAction? + + public var overrideDefaultBehavior: Bool = false + + public var trackActions: Bool = true + + private override init() {} + + private var eventSink: FlutterEventSink? + + public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { + eventSink = events + if let data = pendingData { + if handle(action: data) { + pendingData = nil + } + } + return nil + } + + public func onCancel(withArguments arguments: Any?) -> FlutterError? { + eventSink = nil + overrideDefaultBehavior = false + trackActions = true + return nil + } + + public func inAppMessageAction(with message: ExponeaSDK.InAppMessage, button: ExponeaSDK.InAppMessageButton?, interaction: Bool) { + _ = handle(action: InAppMessageAction( + message: message, + button: button, + interaction: interaction + )) + } + + private func handle(action: InAppMessageAction) -> Bool { + guard let sink = eventSink else { + pendingData = action + return false + } + sink(action.toMap()) + return true + } + +} diff --git a/ios/Classes/SwiftExponeaPlugin.swift b/ios/Classes/SwiftExponeaPlugin.swift index 74c966b..e4921a4 100644 --- a/ios/Classes/SwiftExponeaPlugin.swift +++ b/ios/Classes/SwiftExponeaPlugin.swift @@ -8,6 +8,7 @@ import WebKit private let channelName = "com.exponea" private let openedPushStreamName = "\(channelName)/opened_push" private let receivedPushStreamName = "\(channelName)/received_push" +private let inAppMessagesStreamName = "\(channelName)/in_app_messages" enum METHOD_NAME: String { case methodConfigure = "configure" @@ -39,6 +40,7 @@ enum METHOD_NAME: String { case markAppInboxAsRead = "markAppInboxAsRead" case fetchAppInbox = "fetchAppInbox" case fetchAppInboxItem = "fetchAppInboxItem" + case setInAppMessageActionHandler = "setInAppMessageActionHandler" } @@ -232,6 +234,9 @@ public class SwiftExponeaPlugin: NSObject, FlutterPlugin { let receivedPushEventChannel = FlutterEventChannel(name: receivedPushStreamName, binaryMessenger: registrar.messenger()) receivedPushEventChannel.setStreamHandler(ReceivedPushStreamHandler.newInstance()) + + let inAppMessagesChannel = FlutterEventChannel(name: inAppMessagesStreamName, binaryMessenger: registrar.messenger()) + inAppMessagesChannel.setStreamHandler(InAppMessageActionStreamHandler.currentInstance) registrar.register(FluffViewFactory(), withId: "FluffView") registrar.register(FlutterInAppContentBlockPlaceholderFactory(messenger: registrar.messenger()), withId: "InAppContentBlockPlaceholder") @@ -304,6 +309,8 @@ public class SwiftExponeaPlugin: NSObject, FlutterPlugin { fetchAppInbox(with: result) case .fetchAppInboxItem: fetchAppInboxItem(call.arguments, with: result) + case .setInAppMessageActionHandler: + setInAppMessageActionHandler(call.arguments, with: result) } } @@ -518,6 +525,26 @@ public class SwiftExponeaPlugin: NSObject, FlutterPlugin { } } + private func setInAppMessageActionHandler(_ args: Any?, with result: FlutterResult) { + guard requireConfigured(with: result) else { return } + do { + guard let params = args as? NSDictionary, + let overrideDefaultBehavior: Bool = try params.getRequiredSafely(property: "overrideDefaultBehavior"), + let trackActions: Bool = try params.getRequiredSafely(property: "trackActions") else { + result(FlutterError( + code: errorCode, + message: "Requiered function params missing", details: "no overrideDefaultBehavior or trackActions" + )) + return + } + InAppMessageActionStreamHandler.currentInstance.overrideDefaultBehavior = overrideDefaultBehavior + InAppMessageActionStreamHandler.currentInstance.trackActions = trackActions + } catch { + let error = FlutterError(code: errorCode, message: error.localizedDescription, details: nil) + result(error) + } + } + private func setupAppInbox(_ args: Any?, with result: FlutterResult) { guard let configMap = args as? NSDictionary, let appInboxStyle = try? AppInboxStyleParser(configMap).parse() else { @@ -550,6 +577,7 @@ public class SwiftExponeaPlugin: NSObject, FlutterPlugin { advancedAuthEnabled: config.advancedAuthEnabled ) exponeaInstance.pushNotificationsDelegate = self + exponeaInstance.inAppMessagesDelegate = InAppMessageActionStreamHandler.currentInstance result(true) } catch { let error = FlutterError(code: errorCode, message: error.localizedDescription, details: nil)