[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:
Daniel Arantes Loverde
2026-08-05 15:19:18 -03:00
parent 7dd4cda1a4
commit c4d6998595
4 changed files with 109 additions and 47 deletions

View File

@@ -11,10 +11,12 @@ enum PushAuthorizationState {
case notDetermined
}
/// Posted when the user taps an `order_status` push (§6) so `ContentView`
/// can route to that order without this service depending on `AppState`.
/// Posted when a tapped push resolves to a navigable `DeepLinkDestination`
/// (§6) so `ContentView` can route without this service depending on
/// `AppState`. One name for every destination, present and future see
/// `DeepLinkDestination`.
extension Notification.Name {
static let pushTappedOrderStatus = Notification.Name("pushTappedOrderStatus")
static let pushDeepLinkReceived = Notification.Name("pushDeepLinkReceived")
}
#if os(iOS)
@@ -153,26 +155,21 @@ final class PushNotificationCoordinator: NSObject {
}
}
/// §6 routes on the tapped push's `data` payload. `order_status` gets
/// forwarded to `ContentView` via `NotificationCenter` (this service has
/// no `AppState` binding of its own); `campaign` self-reports its open.
/// §6 routes on the tapped push's `data` payload. Campaign-open
/// reporting (§6a, a side effect, not a navigation target) is decided
/// directly on `type` here; navigation is delegated to
/// `PushDeepLinkParser` and forwarded to `ContentView` via
/// `NotificationCenter` (this service has no `AppState` binding of its
/// own). A push can do both e.g. a future campaign that also sets
/// `targetScreen` reports its open *and* navigates.
fileprivate func handleTap(userInfo: [AnyHashable: Any]) {
guard let type = userInfo["type"] as? String else { return }
switch type {
case "campaign":
guard let campaignId = userInfo["campaignId"] as? String else { return }
if let type = userInfo["type"] as? String, type == "campaign",
let campaignId = userInfo["campaignId"] as? String {
Task { await reportCampaignOpened(campaignId: campaignId) }
case "order_status":
guard let orderId = userInfo["orderId"] as? String else { return }
let shortId = userInfo["shortId"] as? String
NotificationCenter.default.post(
name: .pushTappedOrderStatus,
object: nil,
userInfo: ["orderId": orderId, "shortId": shortId as Any]
)
default:
break
}
guard let destination = PushDeepLinkParser.parse(userInfo) else { return }
NotificationCenter.default.post(name: .pushDeepLinkReceived, object: nil, userInfo: ["destination": destination])
}
}