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,291 @@
import Foundation
struct AppState {
var session = SessionState()
var profile = ProfileState()
var cart = CartState()
var address = AddressState()
var favorites = FavoritesState()
var featureFlags = FeatureFlagsState()
var homeFilters = HomeFiltersState()
var activeModal: AppModal? = nil
var shouldNavigateToOrders: Bool = false
/// Set when a push tap (§6 of the push notifications guide) targets a
/// specific order consumed once by `OrdersView`, which routes to it via
/// `OrderEntryDestinationView` and clears it.
var pendingOrderDeepLink: OrderRouteContext? = nil
}
enum FeatureFlagValue: Codable, Equatable {
case boolean(Bool)
case text(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let boolValue = try? container.decode(Bool.self) {
self = .boolean(boolValue)
return
}
if let stringValue = try? container.decode(String.self) {
self = .text(stringValue)
return
}
if let intValue = try? container.decode(Int.self) {
self = .text(String(intValue))
return
}
if let doubleValue = try? container.decode(Double.self) {
self = .text(String(doubleValue))
return
}
self = .text("off")
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .boolean(let value):
try container.encode(value)
case .text(let value):
try container.encode(value)
}
}
var boolValue: Bool {
switch self {
case .boolean(let value):
return value
case .text(let value):
return value.lowercased() == "on" || value.lowercased() == "true"
}
}
}
struct FeatureFlagsState: Codable, Equatable {
var configVersion: Int = 0
var evaluatedAt: String? = nil
var source: String = "default"
var values: [String: FeatureFlagValue] = [:]
var raw: [String: FeatureControlRawFlag] = [:]
func isEnabled(_ key: String, default defaultValue: Bool = false) -> Bool {
if let rawValue = raw[key] {
return rawValue.enabled || rawValue.variant.lowercased() == "on"
}
if let mapped = values[key] {
return mapped.boolValue
}
return defaultValue
}
}
enum AppModal: String, Identifiable {
case addressPicker
case filters
var id: String { rawValue }
}
struct SessionState {
var isAuthenticated: Bool = false
var jwt: String? = nil
}
struct ProfileState {
var id: String? = nil
var name: String = ""
var email: String = ""
var phone: String = ""
var profilePicture: String = ""
var cpf: String = ""
var notificationsEnabled: Bool = false
var faceIdEnabled: Bool = false
}
struct AddressState {
var selectedId: String? = nil
var display: String = "Defina seu endereco"
var latitude: Double? = nil
var longitude: Double? = nil
var onboardingMessage: String? = nil
}
struct FavoritesState {
var storeIds: Set<String> = []
}
enum HomeSortOption: String, CaseIterable, Identifiable {
case relevance
case rating
case deliveryTime
case price
var id: String { rawValue }
var title: String {
switch self {
case .relevance: return "Relevância"
case .rating: return "Avaliação"
case .deliveryTime: return "Tempo de entrega"
case .price: return "Preço"
}
}
var icon: String {
switch self {
case .relevance: return "checkmark.seal.fill"
case .rating: return "star.fill"
case .deliveryTime: return "clock.fill"
case .price: return "dollarsign"
}
}
}
enum HomePriceTier: String, CaseIterable, Identifiable {
case low = "$"
case medium = "$$"
case high = "$$$"
case veryHigh = "$$$$"
var id: String { rawValue }
}
struct HomeFiltersState {
var sortOption: HomeSortOption = .relevance
var selectedCategories: Set<String> = []
var selectedPriceTier: HomePriceTier? = nil
var maxDistanceKm: Double = 10
var availableCategories: [String] = []
mutating func reset() {
sortOption = .relevance
selectedCategories = []
selectedPriceTier = nil
maxDistanceKm = 10
}
}
struct CartState {
var storeId: String? = nil
var storeName: String? = nil
var items: [CartItemState] = []
var total: Double = 0
}
struct CartItemState: Identifiable {
let id: String
var productId: String
var storeId: String
var name: String
var imageURL: String? = nil
var details: String? = nil
var choices: [String]? = nil
var addons: [CartItemAddonState] = []
var quantity: Int
var unitPrice: Double
}
struct CartItemAddonState: Identifiable, Hashable {
let id: String
var name: String
var quantity: Int
var unitPrice: Double
}
extension CartState {
var totalItems: Int {
items.reduce(0) { $0 + $1.quantity }
}
mutating func recalculateTotal() {
total = items.reduce(0) { $0 + (Double($1.quantity) * $1.unitPrice) }
}
mutating func clear() {
storeId = nil
storeName = nil
items = []
total = 0
SessionStateStore.clearCart()
}
mutating func add(item: CartItemState) {
if let index = items.firstIndex(where: { $0.id == item.id }) {
items[index].quantity += item.quantity
} else {
items.append(item)
}
recalculateTotal()
SessionStateStore.saveCart(self)
}
mutating func set(item: CartItemState) {
if let index = items.firstIndex(where: { $0.id == item.id }) {
if item.quantity <= 0 {
items.remove(at: index)
} else {
items[index] = item
}
} else if item.quantity > 0 {
items.append(item)
}
if items.isEmpty {
storeId = nil
storeName = nil
}
recalculateTotal()
if items.isEmpty {
SessionStateStore.clearCart()
} else {
SessionStateStore.saveCart(self)
}
}
mutating func increment(itemId: String) {
guard let index = items.firstIndex(where: { $0.id == itemId }) else { return }
items[index].quantity += 1
recalculateTotal()
SessionStateStore.saveCart(self)
}
mutating func decrement(itemId: String) {
guard let index = items.firstIndex(where: { $0.id == itemId }) else { return }
items[index].quantity -= 1
if items[index].quantity <= 0 {
items.remove(at: index)
}
if items.isEmpty {
storeId = nil
storeName = nil
}
recalculateTotal()
if items.isEmpty {
SessionStateStore.clearCart()
} else {
SessionStateStore.saveCart(self)
}
}
func toOrderItemsPayload() -> [CreateOrderItemPayload] {
items.map { item in
CreateOrderItemPayload(
productId: item.productId,
name: item.name,
qty: item.quantity,
price: item.unitPrice,
addons: item.addons
.filter { $0.quantity > 0 }
.map {
CreateOrderAddonPayload(
addonId: $0.id,
name: $0.name,
qty: $0.quantity,
price: $0.unitPrice
)
},
choices: item.choices?.isEmpty == false ? item.choices : nil
)
}
}
}

View File

@@ -0,0 +1,94 @@
import Foundation
import SwiftUI
extension Notification.Name {
static let snackbarDidChange = Notification.Name("snackbarDidChange")
}
@MainActor
final class SnackbarCenter: ObservableObject {
static let shared = SnackbarCenter()
@Published var current: SnackbarMessage?
private var dismissTask: Task<Void, Never>?
func show(
title: String,
style: SnackbarStyle = .info,
icon: String? = nil,
duration: TimeInterval = 3.5,
isPersistent: Bool = false,
action: (() -> Void)? = nil
) {
dismissTask?.cancel()
dismissTask = nil
current = SnackbarMessage(
title: title,
style: style,
iconSystemName: icon,
duration: duration,
isPersistent: isPersistent,
action: action
)
guard isPersistent == false else { return }
dismissTask = Task { [weak self] in
let nanos = UInt64(max(0.2, duration) * 1_000_000_000)
try? await Task.sleep(nanoseconds: nanos)
guard !Task.isCancelled else { return }
self?.dismiss(animated: true)
}
}
func handleTap() {
guard current?.isPersistent != true else { return }
let action = current?.action
dismiss(animated: true)
action?()
}
func dismiss(animated: Bool) {
dismissTask?.cancel()
dismissTask = nil
if animated {
withAnimation(.spring(response: 0.3, dampingFraction: 0.9)) {
current = nil
}
} else {
current = nil
}
}
func dismissPersistent() {
guard current?.isPersistent == true else { return }
dismiss(animated: true)
}
}
enum SnackbarStyle: Sendable, Equatable {
case info
case success
case warning
case error
var backgroundColor: Color {
switch self {
case .info: return Color(hex: "#3B93F7")
case .success: return Color(hex: "#2E7D32")
case .warning: return Color(hex: "#C77700")
case .error: return Color(hex: "#C62828")
}
}
}
struct SnackbarMessage: Identifiable {
let id = UUID()
let title: String
let style: SnackbarStyle
let iconSystemName: String?
let duration: TimeInterval
let isPersistent: Bool
let action: (() -> Void)?
}