From bb4317486db920540ca303f3ec45d740df762b5d Mon Sep 17 00:00:00 2001 From: matiasbzurovski <164921079+matiasbzurovski@users.noreply.github.com> Date: Fri, 28 Jun 2024 15:57:31 +0200 Subject: [PATCH] [ABW-3437] Implement deferred deep link solution (#1176) --- .../AppsFlyerClient+Interface.swift | 10 +++++++ .../AppsFlyerClient+Live.swift | 30 +++++++++++++++++++ .../Features/AppFeature/AppDelegate.swift | 8 ++++- .../Features/AppFeature/SceneDelegate.swift | 5 ++++ fastlane/Fastfile | 2 +- fastlane/README.md | 2 +- 6 files changed, 54 insertions(+), 3 deletions(-) diff --git a/RadixWallet/Clients/AppsFlyerClient/AppsFlyerClient+Interface.swift b/RadixWallet/Clients/AppsFlyerClient/AppsFlyerClient+Interface.swift index 966062c118..4f36078f82 100644 --- a/RadixWallet/Clients/AppsFlyerClient/AppsFlyerClient+Interface.swift +++ b/RadixWallet/Clients/AppsFlyerClient/AppsFlyerClient+Interface.swift @@ -1,11 +1,21 @@ // MARK: - AppsFlyerClient struct AppsFlyerClient: Sendable { + /// Method to be called once on app start. var start: Start + + /// Method to be called every time the `AppDelegate`/`SceneDelegate` is called to continue + /// with a user activity. + /// + /// Note: such methods aren't actually called right now on neither of those classes. However, given AppsFlyer documentation + /// indicates that we should delegate the call to their lib (so that it can resolves deferred deep links), I am adding support for it + /// in case the situation changes in the future. + var `continue`: Continue } // MARK: AppsFlyerClient.Start extension AppsFlyerClient { typealias Start = @Sendable () -> Void + typealias Continue = @Sendable (NSUserActivity) -> Void } extension DependencyValues { diff --git a/RadixWallet/Clients/AppsFlyerClient/AppsFlyerClient+Live.swift b/RadixWallet/Clients/AppsFlyerClient/AppsFlyerClient+Live.swift index a1eacf94da..1628b8999f 100644 --- a/RadixWallet/Clients/AppsFlyerClient/AppsFlyerClient+Live.swift +++ b/RadixWallet/Clients/AppsFlyerClient/AppsFlyerClient+Live.swift @@ -1,8 +1,14 @@ import AppsFlyerLib +// MARK: - AppsFlyerClient + DependencyKey extension AppsFlyerClient: DependencyKey { static var liveValue: AppsFlyerClient { @Dependency(\.sensitiveInfoClient) var sensitiveInfoClient + let state = State() + + actor State { + let delegate = Delegate() + } return .init( start: { @@ -10,17 +16,41 @@ extension AppsFlyerClient: DependencyKey { let devKey = sensitiveInfoClient.read(.appsFlyerDevKey), let appId = sensitiveInfoClient.read(.appsFlyerAppId) else { + loggerGlobal.info("Skipping AppsFlyer start as keys are missing") return } AppsFlyerLib.shared().appsFlyerDevKey = devKey AppsFlyerLib.shared().appleAppID = appId + AppsFlyerLib.shared().deepLinkDelegate = state.delegate + #if DEBUG AppsFlyerLib.shared().isDebug = true #endif AppsFlyerLib.shared().start() + }, + continue: { userActivity in + AppsFlyerLib.shared().continue(userActivity) } ) } + + private class Delegate: NSObject, DeepLinkDelegate, @unchecked Sendable { + func didResolveDeepLink(_ result: DeepLinkResult) { + if let deepLink = result.deepLink { + loggerGlobal.info("did resolve deep link. Is deferred: \(deepLink.isDeferred). Click events: \(deepLink.clickEvent)") + if deepLink.isDeferred { + let message = if let deepLinkValue = deepLink.clickEvent["deep_link_value"] as? String { + "Resolved deferred DL with value \(deepLinkValue)" + } else { + "Resolved deferred DL without value" + } + AppsFlyerLib.shared().logEvent(message, withValues: deepLink.clickEvent) + } + } else if let error = result.error { + loggerGlobal.info("failed to resolve deep link. Status: \(result.status), Error: \(error.localizedDescription)") + } + } + } } diff --git a/RadixWallet/Features/AppFeature/AppDelegate.swift b/RadixWallet/Features/AppFeature/AppDelegate.swift index b6df8d74cf..4d49989af0 100644 --- a/RadixWallet/Features/AppFeature/AppDelegate.swift +++ b/RadixWallet/Features/AppFeature/AppDelegate.swift @@ -1,7 +1,10 @@ import ComposableArchitecture import SwiftUI +// MARK: - AppDelegate public final class AppDelegate: NSObject, UIApplicationDelegate { + @Dependency(\.appsFlyerClient) var appsFlyerClient + public func application( _ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, @@ -13,9 +16,12 @@ public final class AppDelegate: NSObject, UIApplicationDelegate { } public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { - @Dependency(\.appsFlyerClient) var appsFlyerClient appsFlyerClient.start() + return true + } + public func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([any UIUserActivityRestoring]?) -> Void) -> Bool { + appsFlyerClient.continue(userActivity) return true } } diff --git a/RadixWallet/Features/AppFeature/SceneDelegate.swift b/RadixWallet/Features/AppFeature/SceneDelegate.swift index 78fd242de6..e71e29aea0 100644 --- a/RadixWallet/Features/AppFeature/SceneDelegate.swift +++ b/RadixWallet/Features/AppFeature/SceneDelegate.swift @@ -20,6 +20,11 @@ public final class SceneDelegate: NSObject, UIWindowSceneDelegate, ObservableObj } } + public func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + @Dependency(\.appsFlyerClient) var appsFlyerClient + appsFlyerClient.continue(userActivity) + } + func overlayWindow(in scene: UIWindowScene) { let overlayView = OverlayReducer.View( store: .init( diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 636b960866..a6c525af26 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -97,7 +97,7 @@ platform :ios do end desc "Installs distribution certificates" - desc "Usage `bundle exec fastlane ios install_distribution_certificate --env ios.`" + desc "Usage `bundle exec fastlane ios install_distribution_certificates --env ios.`" lane :install_distribution_certificates do code_signing(type: "appstore") end diff --git a/fastlane/README.md b/fastlane/README.md index bb5963b630..7003ec4773 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -73,7 +73,7 @@ Archive and export the iOS app Installs distribution certificates -Usage `bundle exec fastlane ios install_distribution_certificate --env ios.` +Usage `bundle exec fastlane ios install_distribution_certificates --env ios.` ### ios install_development_certificates