migration

This commit is contained in:
Daniel Arantes Loverde
2026-08-11 13:22:02 -03:00
parent c4d6998595
commit 7702836fe7
231 changed files with 4329 additions and 1958 deletions

View File

@@ -0,0 +1,10 @@
enum RootFlow: Hashable {
case auth
case main
}
enum MainTab: Hashable {
case home
case cart
case profile
}

View File

@@ -0,0 +1,49 @@
import Foundation
/// Single decode target for anything that can deep-link into the app push
/// notification taps today (docs/api/push-notifications-integration-guide.md
/// §6), in-app promo/discount banner taps tomorrow (same shape: a string-keyed
/// dictionary naming a screen + its params). Adding a new destination means
/// adding one case here + one branch in `ContentView.route(to:)` never a new
/// `NotificationCenter` name, `AppState` field, or observer pair.
enum DeepLinkDestination: Equatable {
/// `type: "order_status"` the only push type Atomenta fixes today
/// (§6), so it gets its own case instead of routing through `targetScreen`.
case orderTracking(orderId: String, shortId: String?)
/// Everything else, keyed by `targetScreen` our own convention (§6
/// leaves this to us). Once a real screen exists for a given name (e.g.
/// future promo/coupon "list of participating stores/products"), give it
/// its own case; until then this carries the raw params so nothing is
/// silently dropped.
case screen(name: String, params: [String: String])
}
/// Resolves the *navigation* half of a tapped push's payload. Pure and
/// side-effect-free on purpose: easy to unit test, and reusable as-is for
/// in-app banner taps once those exist. Campaign-open reporting (§6a) is a
/// separate side effect, not a navigation target, and is not decided here
/// see `PushNotificationCoordinator.handleTap`.
struct PushDeepLinkParser {
static func parse(_ userInfo: [AnyHashable: Any]) -> DeepLinkDestination? {
if let type = userInfo["type"] as? String, type == "order_status" {
// Atomenta's real order-status push (PushNotificationService.
// notifyOrderStatusUpdate) sends only `shortId`, never `orderId`
// `GET /api/public/orders/:orderId` accepts either as the lookup
// key server-side, so `shortId` doubles as the id here too.
let shortId = userInfo["shortId"] as? String
guard let orderId = (userInfo["orderId"] as? String) ?? shortId else { return nil }
return .orderTracking(orderId: orderId, shortId: shortId)
}
guard let targetScreen = userInfo["targetScreen"] as? String else { return nil }
var params: [String: String] = [:]
for (key, value) in userInfo {
guard let key = key as? String, key != "type", key != "targetScreen" else { continue }
if let stringValue = value as? String {
params[key] = stringValue
}
}
return .screen(name: targetScreen, params: params)
}
}