[push-deeplink-routing-contract] Generalize push tap routing to DeepLinkDestination
Replace order_status-only NotificationCenter path with a single DeepLinkDestination enum + PushDeepLinkParser, decoded once in PushNotificationCoordinator and dispatched via ContentView.route(to:). Also fixes NotificationService reading userInfo["image"] instead of the guide's stale "imageUrl" key.
This commit is contained in:
@@ -15,7 +15,7 @@ struct ContentView: View {
|
||||
@State private var sessionExpiredObserver: NSObjectProtocol?
|
||||
@State var cartResetObserver: Any?
|
||||
@State var appResumeObserver: Any?
|
||||
@State var pushOrderTapObserver: Any?
|
||||
@State var pushDeepLinkObserver: Any?
|
||||
@StateObject var snackbarCenter = SnackbarCenter.shared
|
||||
|
||||
var body: some View {
|
||||
@@ -91,13 +91,13 @@ struct ContentView: View {
|
||||
attachCartResetObserverIfNeeded()
|
||||
attachAppResumeObserverIfNeeded()
|
||||
attachSessionExpiredObserverIfNeeded()
|
||||
attachPushOrderTapObserverIfNeeded()
|
||||
attachPushDeepLinkObserverIfNeeded()
|
||||
}
|
||||
.onDisappear {
|
||||
detachCartResetObserver()
|
||||
detachAppResumeObserver()
|
||||
detachSessionExpiredObserver()
|
||||
detachPushOrderTapObserver()
|
||||
detachPushDeepLinkObserver()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,36 +244,50 @@ struct ContentView: View {
|
||||
self.appResumeObserver = nil
|
||||
}
|
||||
|
||||
/// §6 of the push notifications guide — an `order_status` push tap
|
||||
/// (reported by `PushNotificationCoordinator`) routes here to the
|
||||
/// Profile tab's Orders list, pre-targeted at that order.
|
||||
private func attachPushOrderTapObserverIfNeeded() {
|
||||
guard pushOrderTapObserver == nil else { return }
|
||||
pushOrderTapObserver = NotificationCenter.default.addObserver(
|
||||
forName: .pushTappedOrderStatus,
|
||||
/// §6 of the push notifications guide, generalized: any tapped push that
|
||||
/// resolves to a `DeepLinkDestination` (reported by
|
||||
/// `PushNotificationCoordinator`) lands here. Adding a future promo/coupon
|
||||
/// screen means adding one case to `route(to:)` below — this observer,
|
||||
/// the notification name, and the AppState plumbing for it stay put.
|
||||
private func attachPushDeepLinkObserverIfNeeded() {
|
||||
guard pushDeepLinkObserver == nil else { return }
|
||||
pushDeepLinkObserver = NotificationCenter.default.addObserver(
|
||||
forName: .pushDeepLinkReceived,
|
||||
object: nil,
|
||||
queue: nil
|
||||
) { notification in
|
||||
let orderId = notification.userInfo?["orderId"] as? String
|
||||
let shortId = notification.userInfo?["shortId"] as? String
|
||||
guard let destination = notification.userInfo?["destination"] as? DeepLinkDestination else { return }
|
||||
Task { @MainActor in
|
||||
guard root == .main, let orderId else { return }
|
||||
appState.pendingOrderDeepLink = OrderRouteContext(
|
||||
orderId: orderId,
|
||||
shortId: shortId,
|
||||
paymentMethod: nil,
|
||||
total: nil,
|
||||
intent: .auto
|
||||
)
|
||||
selectedTab = .profile
|
||||
route(to: destination)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func detachPushOrderTapObserver() {
|
||||
guard let pushOrderTapObserver else { return }
|
||||
NotificationCenter.default.removeObserver(pushOrderTapObserver)
|
||||
self.pushOrderTapObserver = nil
|
||||
private func detachPushDeepLinkObserver() {
|
||||
guard let pushDeepLinkObserver else { return }
|
||||
NotificationCenter.default.removeObserver(pushDeepLinkObserver)
|
||||
self.pushDeepLinkObserver = nil
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func route(to destination: DeepLinkDestination) {
|
||||
guard root == .main else { return }
|
||||
switch destination {
|
||||
case .orderTracking(let orderId, let shortId):
|
||||
appState.pendingOrderDeepLink = OrderRouteContext(
|
||||
orderId: orderId,
|
||||
shortId: shortId,
|
||||
paymentMethod: nil,
|
||||
total: nil,
|
||||
intent: .auto
|
||||
)
|
||||
selectedTab = .profile
|
||||
case .screen(let name, let params):
|
||||
// Promo/coupon screens (participating stores/products, §6's
|
||||
// `targetScreen` convention) land here once they exist — see
|
||||
// decisions/2026-08-04-push-deeplink-routing-contract.md.
|
||||
logger.debug("Unhandled deep-link screen: \(name, privacy: .public) params=\(params, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
||||
Reference in New Issue
Block a user