import SwiftUI #if canImport(LCEssentials) && os(iOS) import LCEssentials #endif #if canImport(UIKit) import UIKit #endif struct StoreDetailView: View { let storeId: String let storeName: String let storeCoverURL: String? let storeLogoURL: String? let storeCategory: String? let storeRating: Double? let storeDistance: String? let storeDeliveryFee: Double? @Binding var appState: AppState @Environment(\.dismiss) var dismiss @State var isLoading = true @State var errorMessage: String? = nil @State var info: StoreInfoResult? = nil @State var categories: [StoreCatalogCategory] = [] @State var selectedCategoryId: String? = nil @State var selectedProduct: StoreCatalogProduct? = nil @State var selectedPizzaCategoryId: String? = nil @State var showSwitchStoreAlert = false @State var pendingCartItem: CartItemState? = nil @State var pendingProductSheet: StoreCatalogProduct? = nil @State var pendingPizzaCategoryId: String? = nil @State var pendingCartAction: CartAction = .add @State var didLoad = false @State var categoryHeaderOffsets: [String: CGFloat] = [:] @State var isProgrammaticCategoryScroll = false @State var scrollOffset: CGFloat = 0 var safeAreaTop: CGFloat { #if canImport(UIKit) return UIDevice.appSafeAreaTop #else return 0.0 #endif } @State var isFavoriteRequestInFlight = false let cardTopInset: CGFloat = 180 let summaryCardBaseHeight: CGFloat = 212 let closedBannerHeight: CGFloat = 44 let coverVisibleUntilY: CGFloat = 253 let storeLogoSize: CGFloat = 84 var body: some View { ScrollViewReader { proxy in let stickyProgress = categoryTabsStickyProgress(safeTop: safeAreaTop) ZStack(alignment: .top) { AppColors.backgroundLight.ignoresSafeArea() ScrollView(showsIndicators: false) { LazyVStack(spacing: 0) { topSection Section { sectionedProducts } header: { categoryTabs(proxy: proxy, topExtension: 0, stickyProgress: 1) .opacity(Double(1 - stickyProgress)) } } .background( GeometryReader { geometry in Color.clear.preference( key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named(StoreDetailScrollCoordinateSpace.name)).minY ) } ) } .coordinateSpace(name: StoreDetailScrollCoordinateSpace.name) .onPreferenceChange(ScrollOffsetPreferenceKey.self) { minY in let normalizedOffset = max(0, -minY) if abs(scrollOffset - normalizedOffset) > 0.5 { scrollOffset = normalizedOffset } } .refreshable { await loadStoreData(forceRefresh: true) } .ignoresSafeArea(edges: .top) categoryTabs(proxy: proxy, topExtension: stickyProgress * safeAreaTop, stickyProgress: 1) .opacity(Double(stickyProgress)) .allowsHitTesting(stickyProgress > 0.1) .offset(y: safeAreaTop) } .ignoresSafeArea(edges: .top) .saturation(isStoreOpen ? 1 : 0) } .navigationBarBackButtonHidden(true) .appHiddenNavigationBar() .task { guard didLoad == false else { return } didLoad = true await loadStoreData(forceRefresh: false) } .sheet(item: $selectedProduct) { product in NavigationStack { ProductDetailSheet( product: product, imageURL: resolvedURL(product.image), storeId: storeId, currentQuantityForItemId: { itemId in currentQuantity(forCartItemId: itemId) }, onAdd: { item in guard isStoreOpen else { SnackbarCenter.shared.show(title: "Loja fechada no momento.", style: .warning, icon: "moon.zzz.fill", duration: 2.0) return } requestSetCartItem(item) } ) } } .sheet( isPresented: Binding( get: { selectedPizzaCategoryId != nil }, set: { isPresented in if isPresented == false { selectedPizzaCategoryId = nil } } ) ) { if let category = categories.first(where: { $0.id == selectedPizzaCategoryId }) { NavigationStack { PizzaProductDetailSheet( category: category, storeId: storeId, resolveImageURL: { raw in resolvedURL(raw) }, currentQuantityForItemId: { itemId in currentQuantity(forCartItemId: itemId) }, onAdd: { item in guard isStoreOpen else { SnackbarCenter.shared.show(title: "Loja fechada no momento.", style: .warning, icon: "moon.zzz.fill", duration: 2.0) return } requestSetCartItem(item) } ) } } else { ProgressView() } } .alert("Trocar de loja?", isPresented: $showSwitchStoreAlert) { Button("Cancelar", role: .cancel) { pendingCartItem = nil pendingProductSheet = nil pendingPizzaCategoryId = nil } Button("Limpar carrinho e adicionar", role: .destructive) { appState.cart.clear() switch pendingCartAction { case .add: guard let pendingCartItem else { return } applyAddToCart(pendingCartItem) case .set: guard let pendingCartItem else { return } applySetCartItem(pendingCartItem) case .openProductSheet: guard let pendingProductSheet else { return } selectedProduct = pendingProductSheet case .openPizzaSheet: guard let pendingPizzaCategoryId else { return } selectedPizzaCategoryId = pendingPizzaCategoryId } self.pendingCartItem = nil self.pendingProductSheet = nil self.pendingPizzaCategoryId = nil } } message: { Text("Seu carrinho tem itens de outra loja. Deseja limpar o carrinho atual para adicionar este produto?") } .onPreferenceChange(CategoryHeaderOffsetPreferenceKey.self) { offsets in categoryHeaderOffsets = offsets syncCategoryWithScroll() } } }