diff --git a/shared/ios/Keybase.xcodeproj/project.pbxproj b/shared/ios/Keybase.xcodeproj/project.pbxproj index 7750bbcd19d1..04efe02c9f9d 100644 --- a/shared/ios/Keybase.xcodeproj/project.pbxproj +++ b/shared/ios/Keybase.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ DBDF89F62DF7779900EA18C2 /* Pusher.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDF89F52DF7779900EA18C2 /* Pusher.swift */; }; DBF123462DF1234500A12345 /* ShareIntentDonatorImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBF123452DF1234500A12345 /* ShareIntentDonatorImpl.swift */; }; DBPERF022600000002 /* PerfFPSMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBPERF022600000001 /* PerfFPSMonitor.swift */; }; + DBSCENE00270000000000002 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBSCENE00270000000000001 /* SceneDelegate.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -120,6 +121,7 @@ DBDF89F52DF7779900EA18C2 /* Pusher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pusher.swift; sourceTree = ""; }; DBF123452DF1234500A12345 /* ShareIntentDonatorImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareIntentDonatorImpl.swift; sourceTree = ""; }; DBPERF022600000001 /* PerfFPSMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerfFPSMonitor.swift; sourceTree = ""; }; + DBSCENE00270000000000001 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; F68DC40B579A1F9AC0F34950 /* Pods_KeybaseShare.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_KeybaseShare.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -270,6 +272,7 @@ children = ( 005BF9961BB9C6B000BD8953 /* Keybase.entitlements */, DBB8CC202DF336C200D43215 /* AppDelegate.swift */, + DBSCENE00270000000000001 /* SceneDelegate.swift */, DBDCF3081B8D03DD00BA95D8 /* Images.xcassets */, DBDCF3091B8D03DD00BA95D8 /* Info.plist */, DB07050422E21B8B002F273D /* KeepThisFile.swift */, @@ -592,6 +595,7 @@ buildActionMask = 2147483647; files = ( DBB8CC212DF336C200D43215 /* AppDelegate.swift in Sources */, + DBSCENE00270000000000002 /* SceneDelegate.swift in Sources */, DB07050522E21B8B002F273D /* KeepThisFile.swift in Sources */, DBDF89F62DF7779900EA18C2 /* Pusher.swift in Sources */, DBF123462DF1234500A12345 /* ShareIntentDonatorImpl.swift in Sources */, diff --git a/shared/ios/Keybase/AppDelegate.swift b/shared/ios/Keybase/AppDelegate.swift index 53ae0007c74b..04648fc15bdd 100644 --- a/shared/ios/Keybase/AppDelegate.swift +++ b/shared/ios/Keybase/AppDelegate.swift @@ -11,36 +11,13 @@ import os private let log = Logger(subsystem: "com.keybase.app", category: "delegate") -class KeyboardWindow: UIWindow { - override func pressesBegan(_ presses: Set, with event: UIPressesEvent?) { - guard let key = presses.first?.key else { - super.pressesBegan(presses, with: event) - return - } - - if key.keyCode == .keyboardReturnOrEnter { - if key.modifierFlags.contains(.shift) { - NotificationCenter.default.post(name: NSNotification.Name("hardwareKeyPressed"), - object: nil, - userInfo: ["pressedKey": "shift-enter"]) - } else { - NotificationCenter.default.post(name: NSNotification.Name("hardwareKeyPressed"), - object: nil, - userInfo: ["pressedKey": "enter"]) - } - return - } - - super.pressesBegan(presses, with: event) - } -} - @main -class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UIDropInteractionDelegate { +class AppDelegate: ExpoAppDelegate, ExpoReactNativeFactoryProvider, UNUserNotificationCenterDelegate, UIDropInteractionDelegate { var window: UIWindow? var reactNativeDelegate: ExpoReactNativeFactoryDelegate? var reactNativeFactory: RCTReactNativeFactory? + var reactNativeFactoryModuleName: String { "Keybase" } var resignImageView: UIImageView? var fsPaths: [String: String] = [:] @@ -89,15 +66,6 @@ class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UIDropInte reactNativeDelegate = delegate reactNativeFactory = factory -#if os(iOS) || os(tvOS) - let screenBounds = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.screen.bounds ?? UIScreen.main.bounds - window = KeyboardWindow(frame: screenBounds) - factory.startReactNative( - withModuleName: "Keybase", - in: window, - launchOptions: launchOptions) -#endif - self.writeStartupTimingLog("After RN init") self.closeStartupLogFile() @@ -106,31 +74,33 @@ class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UIDropInte // Start FPS monitoring if launched with -PERF_FPS_MONITOR PerfFPSMonitor.startIfEnabled() - if let rootView = self.window?.rootViewController?.view { - self.addDrop(rootView) - self.didLaunchSetupAfter(application: application, rootView: rootView) - } + self.didLaunchSetupAfter(application: application) return true } - // Linking API - override func application( - _ app: UIApplication, - open url: URL, - options: [UIApplication.OpenURLOptionsKey: Any] = [:] - ) -> Bool { - return super.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options) - } + // Hardware keyboard enter/shift-enter reaches the app delegate at the end of the + // responder chain (window -> scene -> application -> delegate). + override func pressesBegan(_ presses: Set, with event: UIPressesEvent?) { + guard let key = presses.first?.key else { + super.pressesBegan(presses, with: event) + return + } - // Universal Links - override func application( - _ application: UIApplication, - continue userActivity: NSUserActivity, - restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void - ) -> Bool { - let result = RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler) - return super.application(application, continue: userActivity, restorationHandler: restorationHandler) || result + if key.keyCode == .keyboardReturnOrEnter { + if key.modifierFlags.contains(.shift) { + NotificationCenter.default.post(name: NSNotification.Name("hardwareKeyPressed"), + object: nil, + userInfo: ["pressedKey": "shift-enter"]) + } else { + NotificationCenter.default.post(name: NSNotification.Name("hardwareKeyPressed"), + object: nil, + userInfo: ["pressedKey": "enter"]) + } + return + } + + super.pressesBegan(presses, with: event) } /////// KB specific @@ -249,13 +219,26 @@ class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UIDropInte UNUserNotificationCenter.current().delegate = self } - func didLaunchSetupAfter(application: UIApplication, rootView: UIView) { + // BGTaskScheduler.register must run before didFinishLaunching returns, so this + // can't wait for the scene to connect. + func didLaunchSetupAfter(application: UIApplication) { notifyAppState(application) + BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.keybase.app.refresh", using: nil) { task in + self.handleAppRefresh(task: task as! BGAppRefreshTask) + } + scheduleAppRefresh() + } + + // Called by SceneDelegate once the window exists and React Native has started in it. + func didStartReactNative(in window: UIWindow) { + guard let rootView = window.rootViewController?.view else { return } + addDrop(rootView) + rootView.backgroundColor = .systemBackground // Snapshot resizing workaround for iPad - let screenBounds = self.window?.windowScene?.screen.bounds ?? UIScreen.main.bounds + let screenBounds = window.windowScene?.screen.bounds ?? window.bounds var dim = screenBounds.width if screenBounds.height > dim { dim = screenBounds.height @@ -266,12 +249,7 @@ class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UIDropInte self.resignImageView?.alpha = 0 self.resignImageView?.backgroundColor = rootView.backgroundColor self.resignImageView?.image = UIImage(named: "LaunchImage") - if let view = self.resignImageView { self.window?.addSubview(view) } - - BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.keybase.app.refresh", using: nil) { task in - self.handleAppRefresh(task: task as! BGAppRefreshTask) - } - scheduleAppRefresh() + if let view = self.resignImageView { window.addSubview(view) } } func addDrop(_ rootView: UIView) { @@ -295,7 +273,8 @@ class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UIDropInte self.iph = ItemProviderHelper(forShare: false, withItems: [items]) { [weak self] in guard let self else { return } let url = URL(string: "keybase://incoming-share")! - _ = self.application(UIApplication.shared, open: url, options: [:]) + let app = UIApplication.shared + _ = self.application(app, open: url, options: [:]) || RCTLinkingManager.application(app, open: url, options: [:]) self.iph = nil } self.iph?.startProcessing() @@ -469,7 +448,9 @@ class AppDelegate: ExpoAppDelegate, UNUserNotificationCenterDelegate, UIDropInte log.info("applicationDidBecomeActive: hiding keyz screen.") hideCover() log.info("applicationDidBecomeActive: notifying service.") - notifyAppState(application) + // Forwarded from sceneDidBecomeActive, where applicationState still reads + // .inactive; notifyAppState would stop the http server. + Keybasego.KeybaseSetAppStateForeground() // Re-emit a notification the user tapped while React Native wasn't ready yet. KbEmitStoredNotificationOnBecomeActive() diff --git a/shared/ios/Keybase/Info.plist b/shared/ios/Keybase/Info.plist index 4cf9da2c060b..2f56065bc0e3 100644 --- a/shared/ios/Keybase/Info.plist +++ b/shared/ios/Keybase/Info.plist @@ -87,6 +87,23 @@ SourceCodePro-Semibold.ttf kb.ttf + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + BGTaskSchedulerPermittedIdentifiers com.keybase.app.refresh diff --git a/shared/ios/Keybase/SceneDelegate.swift b/shared/ios/Keybase/SceneDelegate.swift new file mode 100644 index 000000000000..17be345c2006 --- /dev/null +++ b/shared/ios/Keybase/SceneDelegate.swift @@ -0,0 +1,18 @@ +internal import Expo +import UIKit + +@objc(SceneDelegate) +class SceneDelegate: ExpoAppSceneDelegate { + override func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + super.scene(scene, willConnectTo: session, options: connectionOptions) + + guard let window = self.window, + let appDelegate = UIApplication.shared.delegate as? AppDelegate + else { return } + appDelegate.didStartReactNative(in: window) + } +}