Commit 4f441dbc04
Verified · cmc
Layout: unified · split
gitbay/Push/AppDelegate.swift +36 −2
| @@ -14,6 +14,19 @@ final class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCent | |||
| 14 | weak var router: PushRouter? | 14 | weak var router: PushRouter? |
| 15 | weak var session: SessionStore? | 15 | weak var session: SessionStore? |
| 16 | 16 | ||
| 17 | /// A tap that arrived before `gitbayApp` wired this delegate up. | ||
| 18 | /// | ||
| 19 | /// This is the common case, not an edge one: tapping a notification | ||
| 20 | /// while the app is not running launches it, and iOS can deliver | ||
| 21 | /// the tap before SwiftUI's root `.task` has run. Held as the raw | ||
| 22 | /// `userInfo` rather than a resolved `PushTarget`, because resolving | ||
| 23 | /// one needs `session.accounts` — precisely what is missing at that | ||
| 24 | /// moment. | ||
| 25 | /// | ||
| 26 | /// One slot, newest wins. A queue would open several screens in | ||
| 27 | /// sequence, which is not what a tap asks for. | ||
| 28 | private var pendingUserInfo: [AnyHashable: Any]? | ||
| 29 | |||
| 17 | func application( | 30 | func application( |
| 18 | _ application: UIApplication, | 31 | _ application: UIApplication, |
| 19 | didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]? = nil | 32 | didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]? = nil |
| @@ -50,8 +63,29 @@ final class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCent | |||
| 50 | _ center: UNUserNotificationCenter, | 63 | _ center: UNUserNotificationCenter, |
| 51 | didReceive response: UNNotificationResponse | 64 | didReceive response: UNNotificationResponse |
| 52 | ) async { | 65 | ) async { |
| 53 | guard let session, let router, | 66 | deliver(response.notification.request.content.userInfo) |
| 54 | let payload = PushPayload(userInfo: response.notification.request.content.userInfo), | 67 | } |
| 68 | |||
| 69 | /// Drains a tap that arrived before the wiring did. Called from | ||
| 70 | /// `gitbayApp`'s root task, once `session` and `router` are set. | ||
| 71 | func deliverPendingNotification() { | ||
| 72 | guard let userInfo = pendingUserInfo else { return } | ||
| 73 | // Cleared whatever the resolve decides: a payload for an | ||
| 74 | // account the user has since signed out of resolves to no | ||
| 75 | // target, and must not sit here waiting to fire later. | ||
| 76 | pendingUserInfo = nil | ||
| 77 | deliver(userInfo) | ||
| 78 | } | ||
| 79 | |||
| 80 | /// The one resolve-and-deliver path, so a buffered tap and a live | ||
| 81 | /// one cannot diverge. Parks the payload if the wiring is not up | ||
| 82 | /// yet; `deliverPendingNotification` comes back for it. | ||
| 83 | private func deliver(_ userInfo: [AnyHashable: Any]) { | ||
| 84 | guard let session, let router else { | ||
| 85 | pendingUserInfo = userInfo | ||
| 86 | return | ||
| 87 | } | ||
| 88 | guard let payload = PushPayload(userInfo: userInfo), | ||
| 55 | let target = PushTarget(payload: payload, accounts: session.accounts) | 89 | let target = PushTarget(payload: payload, accounts: session.accounts) |
| 56 | else { return } | 90 | else { return } |
| 57 | router.pending = target | 91 | router.pending = target |
gitbay/gitbayApp.swift +3
| @@ -40,6 +40,9 @@ struct gitbayApp: App { | |||
| 40 | session.willRemoveAccount = { [weak registrar] account in | 40 | session.willRemoveAccount = { [weak registrar] account in |
| 41 | await registrar?.deregister(account) | 41 | await registrar?.deregister(account) |
| 42 | } | 42 | } |
| 43 | // A tap that launched the app reaches the delegate | ||
| 44 | // before this runs; it parked the payload. | ||
| 45 | appDelegate.deliverPendingNotification() | ||
| 43 | await registrar.registerIfAlreadyAuthorized() | 46 | await registrar.registerIfAlreadyAuthorized() |
| 44 | } | 47 | } |
| 45 | } | 48 | } |