diff --git a/.github/workflows/release-mobile.yml b/.github/workflows/release-mobile.yml index b57ef8694eeaf..644f960346ca0 100644 --- a/.github/workflows/release-mobile.yml +++ b/.github/workflows/release-mobile.yml @@ -123,6 +123,10 @@ jobs: enableScripts: false - name: Cap sync run: yarn workspace @affine/ios cap sync + - name: Write afdevkey + working-directory: packages/frontend/apps/ios/App + run: | + echo "${{ env.APPSFLYER_DEV_KEY }}" | base64 --decode -o App/afdevkey.plist - name: Signing By Apple Developer ID uses: apple-actions/import-codesign-certs@v3 id: import-codesign-certs diff --git a/packages/frontend/apps/ios/.gitignore b/packages/frontend/apps/ios/.gitignore index b5ddda903f108..9144ce2b8e8cf 100644 --- a/packages/frontend/apps/ios/.gitignore +++ b/packages/frontend/apps/ios/.gitignore @@ -17,3 +17,5 @@ App/**/*.p8 *.zip *.cer App/fastlane/report.xml + +afdevkey.plist \ No newline at end of file diff --git a/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj b/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj index a7d6f5f240407..5950a95b48e4e 100644 --- a/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj +++ b/packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 9D90BE2E2CCB9876006677DB /* public in Resources */ = {isa = PBXBuildFile; fileRef = 9D90BE232CCB9876006677DB /* public */; }; C4C413792CBE705D00337889 /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; }; E93B276C2CED92B1001409B8 /* NavigationGesturePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = E93B276B2CED92B1001409B8 /* NavigationGesturePlugin.swift */; }; + E97774C12CF846B9009FF983 /* afdevkey.plist in Resources */ = {isa = PBXBuildFile; fileRef = E97774C02CF846AE009FF983 /* afdevkey.plist */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -37,6 +38,7 @@ AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = ""; }; E93B276B2CED92B1001409B8 /* NavigationGesturePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationGesturePlugin.swift; sourceTree = ""; }; + E97774C02CF846AE009FF983 /* afdevkey.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = afdevkey.plist; sourceTree = ""; }; FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -111,6 +113,7 @@ 9D90BE242CCB9876006677DB /* App */ = { isa = PBXGroup; children = ( + E97774C02CF846AE009FF983 /* afdevkey.plist */, 9D90BE1A2CCB9876006677DB /* plugins */, 9D90BE1B2CCB9876006677DB /* AFFiNEViewController.swift */, 9D90BE1C2CCB9876006677DB /* AppDelegate.swift */, @@ -144,6 +147,7 @@ 504EC3011FED79650016851F /* Frameworks */, 504EC3021FED79650016851F /* Resources */, 9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */, + 06BC71AE56DF9F7D9910CD4F /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -196,6 +200,7 @@ 9D90BE292CCB9876006677DB /* Assets.xcassets in Resources */, 9D90BE2A2CCB9876006677DB /* capacitor.config.json in Resources */, 9D90BE2B2CCB9876006677DB /* config.xml in Resources */, + E97774C12CF846B9009FF983 /* afdevkey.plist in Resources */, 9D90BE2D2CCB9876006677DB /* Main.storyboard in Resources */, 9D90BE2E2CCB9876006677DB /* public in Resources */, ); @@ -204,6 +209,21 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 06BC71AE56DF9F7D9910CD4F /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App/Pods-App-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/packages/frontend/apps/ios/App/App/AppDelegate.swift b/packages/frontend/apps/ios/App/App/AppDelegate.swift index c3cd83b5c0a62..948cbfebeb668 100644 --- a/packages/frontend/apps/ios/App/App/AppDelegate.swift +++ b/packages/frontend/apps/ios/App/App/AppDelegate.swift @@ -1,49 +1,112 @@ import UIKit import Capacitor +import AppsFlyerLib +import AppTrackingTransparency @UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate { +class AppDelegate: UIResponder, UIApplicationDelegate, AppsFlyerLibDelegate, DeepLinkDelegate { + func onConversionDataSuccess(_ conversionInfo: [AnyHashable : Any]) { + print("onConversionDataSuccess: \(conversionInfo)") + } + + func onConversionDataFail(_ error: any Error) { + print("onConversionDataFail: \(error)") + } + - var window: UIWindow? + var window: UIWindow? + var appFlyerReady = false - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. - return true - } + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - func applicationWillResignActive(_ application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. + // Get AppsFlyer preferences from .plist file + guard let propertiesPath = Bundle.main.path(forResource: "afdevkey", ofType: "plist"), + let properties = NSDictionary(contentsOfFile: propertiesPath) as? [String:String] else { + print("WARNING: Cannot find `afdevkey`") + return true + } + guard let appsFlyerDevKey = properties["appsFlyerDevKey"], + let appleAppID = properties["appleAppID"] else { + print("WARNING: Cannot find `appsFlyerDevKey` or `appleAppID` key") + return true + } + + if appsFlyerDevKey.isEmpty || appleAppID.isEmpty { + return true } + + self.appFlyerReady = true - func applicationDidEnterBackground(_ application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } + AppsFlyerLib.shared().isDebug = true - func applicationWillEnterForeground(_ application: UIApplication) { - // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. - } + AppsFlyerLib.shared().appsFlyerDevKey = appsFlyerDevKey + AppsFlyerLib.shared().appleAppID = appleAppID - func applicationDidBecomeActive(_ application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } + AppsFlyerLib.shared().waitForATTUserAuthorization(timeoutInterval: 60) + + AppsFlyerLib.shared().delegate = self + AppsFlyerLib.shared().deepLinkDelegate = self + + print("Appflyer is ready") + + return true + } - func applicationWillTerminate(_ application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } + func applicationWillResignActive(_ application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. + } - func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { - // Called when the app was launched with a url. Feel free to add additional processing here, - // but if you want the App API to support tracking app url opens, make sure to keep this call - return ApplicationDelegateProxy.shared.application(app, open: url, options: options) - } + func applicationDidEnterBackground(_ application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(_ application: UIApplication) { + // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(_ application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + +// if self.appFlyerReady { +// AppsFlyerLib.shared().start() +// if #available(iOS 14, *) { +// ATTrackingManager.requestTrackingAuthorization { (status) in +// switch status { +// case .denied: +// print("AuthorizationStatus is denied") +// case .notDetermined: +// print("AuthorizationStatus is notDetermined") +// case .restricted: +// print("AuthorizationStatus is restricted") +// case .authorized: +// print("AuthorizationStatus is authorized") +// @unknown default: +// fatalError("Invalid authorization status") +// } +// } +// } +// } + } + + func applicationWillTerminate(_ application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } - func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { - // Called when the app was launched with an activity, including Universal Links. - // Feel free to add additional processing here, but if you want the App API to support - // tracking app url opens, make sure to keep this call - return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler) + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + // Called when the app was launched with a url. Feel free to add additional processing here, + // but if you want the App API to support tracking app url opens, make sure to keep this call + if self.appFlyerReady { + AppsFlyerLib.shared().handleOpen(url, options: options) } + return ApplicationDelegateProxy.shared.application(app, open: url, options: options) + } + func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + // Called when the app was launched with an activity, including Universal Links. + // Feel free to add additional processing here, but if you want the App API to support + // tracking app url opens, make sure to keep this call + return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler) + } } diff --git a/packages/frontend/apps/ios/App/App/Info.plist b/packages/frontend/apps/ios/App/App/Info.plist index 10b7d08c8b45e..01fc3889556d7 100644 --- a/packages/frontend/apps/ios/App/App/Info.plist +++ b/packages/frontend/apps/ios/App/App/Info.plist @@ -63,5 +63,7 @@ UIViewControllerBasedStatusBarAppearance + NSUserTrackingUsageDescription + AFFiNE needs access to track your activity on other apps and websites. diff --git a/packages/frontend/apps/ios/App/App/afdevkey.plist b/packages/frontend/apps/ios/App/App/afdevkey.plist new file mode 100644 index 0000000000000..c86487eb7ed98 --- /dev/null +++ b/packages/frontend/apps/ios/App/App/afdevkey.plist @@ -0,0 +1,10 @@ + + + + + appsFlyerDevKey + + appleAppID + + + diff --git a/packages/frontend/apps/ios/App/Podfile b/packages/frontend/apps/ios/App/Podfile index 804c58d3b069b..d56861dad5f45 100644 --- a/packages/frontend/apps/ios/App/Podfile +++ b/packages/frontend/apps/ios/App/Podfile @@ -20,6 +20,7 @@ target 'App' do capacitor_pods # Add your Pods here pod 'CryptoSwift', '~> 1.8.3' + pod 'AppsFlyerFramework' end post_install do |installer| diff --git a/packages/frontend/apps/ios/App/Podfile.lock b/packages/frontend/apps/ios/App/Podfile.lock index 21f407e7444d4..d9e07f69b2cbb 100644 --- a/packages/frontend/apps/ios/App/Podfile.lock +++ b/packages/frontend/apps/ios/App/Podfile.lock @@ -1,16 +1,20 @@ PODS: - - Capacitor (6.1.2): + - AppsFlyerFramework (6.15.3): + - AppsFlyerFramework/Main (= 6.15.3) + - AppsFlyerFramework/Main (6.15.3) + - Capacitor (6.2.0): - CapacitorCordova - - CapacitorApp (6.0.1): + - CapacitorApp (6.0.2): - Capacitor - - CapacitorBrowser (6.0.3): + - CapacitorBrowser (6.0.4): - Capacitor - - CapacitorCordova (6.1.2) - - CapacitorKeyboard (6.0.2): + - CapacitorCordova (6.2.0) + - CapacitorKeyboard (6.0.3): - Capacitor - CryptoSwift (1.8.3) DEPENDENCIES: + - AppsFlyerFramework - "Capacitor (from `../../../../../node_modules/@capacitor/ios`)" - "CapacitorApp (from `../../../../../node_modules/@capacitor/app`)" - "CapacitorBrowser (from `../../../../../node_modules/@capacitor/browser`)" @@ -20,6 +24,7 @@ DEPENDENCIES: SPEC REPOS: trunk: + - AppsFlyerFramework - CryptoSwift EXTERNAL SOURCES: @@ -35,13 +40,14 @@ EXTERNAL SOURCES: :path: "../../../../../node_modules/@capacitor/keyboard" SPEC CHECKSUMS: - Capacitor: 679f9673fdf30597493a6362a5d5bf233d46abc2 - CapacitorApp: 0bc633b4eae40a1f32cd2834788fad3bc42da6a1 - CapacitorBrowser: aab1ed943b01c0365c4810538a8b3477e2d9f72e - CapacitorCordova: f48c89f96c319101cd2f0ce8a2b7449b5fb8b3dd - CapacitorKeyboard: 2700f9b18687be021e28b5a09b59eb151a46d5e0 + AppsFlyerFramework: ad7ff0d22aa36c7f8cc4f71a5424e19b89ccb8ae + Capacitor: 1f3c7b9802d958cd8c4eb63895fff85dff2e1eea + CapacitorApp: 2a8c3a0b0814322e5e6e15fe595f02c3808f0f8b + CapacitorBrowser: ef0529d16cd8839281050c350e7bbee4f5c6d65f + CapacitorCordova: b33e7f4aa4ed105dd43283acdd940964374a87d9 + CapacitorKeyboard: 460c6f9ec5e52c84f2742d5ce2e67bbc7ab0ebb0 CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483 -PODFILE CHECKSUM: 1b0d3fe81862c0e9ce712ddd0c5a0accd0097698 +PODFILE CHECKSUM: 1bf138ed644b943d4f8ccc008bbfdbf1cea13503 COCOAPODS: 1.16.2