import Foundation import SwiftUI extension HomeView { func buildCategories(from stores: [StoreSummary]) -> [CategoryModel] { var unique: [CategoryModel] = [ .init(id: "all", title: "Todas", systemIcon: "line.3.horizontal.decrease.circle", emojiIcon: nil) ] var seen = Set() for store in stores { let raw = (store.category ?? "").trimmingCharacters(in: .whitespacesAndNewlines) if raw.isEmpty { continue } let dedupe = raw.lowercased() if seen.contains(dedupe) { continue } seen.insert(dedupe) unique.append(.init(id: raw, title: raw, systemIcon: categoryIcon(for: raw), emojiIcon: nil)) } return unique } @MainActor func loadHomeCategories(withFallbackStores stores: [StoreSummary], forceRefresh: Bool = false) async { let cacheKey = "public-categories" if forceRefresh == false, let cached: [CategoryModel] = AppContentCache.shared.value(for: cacheKey, as: [CategoryModel].self) { categories = cached return } do { let response = try await ApiService().listPublicCategories(forceRefresh: forceRefresh) if response.error == false, let remote = response.result, remote.isEmpty == false { let mapped = mapPublicCategories(remote) categories = mapped AppContentCache.shared.set(mapped, for: cacheKey, ttl: AppCacheTTL.twoHours) return } } catch { // Fallback handled below. } let fallback = buildCategories(from: stores) categories = fallback AppContentCache.shared.set(fallback, for: cacheKey, ttl: AppCacheTTL.twoHours) } func mapPublicCategories(_ remote: [PublicCategory]) -> [CategoryModel] { var mapped: [CategoryModel] = [] var seen = Set() for item in remote { let id = item.id.trimmingCharacters(in: .whitespacesAndNewlines) let title = item.name.trimmingCharacters(in: .whitespacesAndNewlines) if id.isEmpty || title.isEmpty { continue } if seen.contains(id.lowercased()) { continue } seen.insert(id.lowercased()) mapped.append( .init( id: id, title: title, systemIcon: id.lowercased() == "all" ? "line.3.horizontal.decrease.circle" : nil, emojiIcon: item.icon ) ) } if mapped.contains(where: { $0.id.lowercased() == "all" }) == false { mapped.insert(.init(id: "all", title: "Todas", systemIcon: "line.3.horizontal.decrease.circle", emojiIcon: nil), at: 0) } else { mapped.sort { lhs, rhs in if lhs.id.lowercased() == "all" { return true } if rhs.id.lowercased() == "all" { return false } return lhs.title < rhs.title } } return mapped } func categoryIcon(for category: String) -> String { let value = category.folding(options: [.diacriticInsensitive, .caseInsensitive], locale: .current).lowercased() if value.contains("pizza") { return "takeoutbag.and.cup.and.straw" } if value.contains("lanche") || value.contains("hamburg") || value.contains("burger") { return "fork.knife" } if value.contains("cafe") || value.contains("breakfast") { return "sun.max" } if value.contains("doce") || value.contains("sobremesa") || value.contains("dessert") { return "cup.and.saucer" } return "storefront" } @MainActor func resolveCoordinates(forceRefresh: Bool) async -> (Double, Double)? { guard hasConfiguredAddress() else { return nil } // If user selected/saved an address, always trust its coordinates. // This avoids overriding the chosen city with current device GPS. if let lat = appState.address.latitude, let lng = appState.address.longitude { return (lat, lng) } if !forceRefresh, let cached = LocationService.shared.cachedLocation() { appState.address.latitude = cached.0 appState.address.longitude = cached.1 return cached } // Fallback to device location only when no address coordinates are available. let deviceCoordinate = await LocationService.shared.requestLocationAsync(timeoutSeconds: 3) if let deviceCoordinate { appState.address.latitude = deviceCoordinate.0 appState.address.longitude = deviceCoordinate.1 } return deviceCoordinate } func hasConfiguredAddress() -> Bool { if appState.address.selectedId != nil { return true } let normalized = appState.address.display .trimmingCharacters(in: .whitespacesAndNewlines) .lowercased() return normalized.isEmpty == false && normalized != "defina seu endereco" } func storesUserMessage(_ error: Error) -> String { if let service = error as? ApiServiceError { return service.errorDescription ?? "Não foi possível carregar os estabelecimentos." } if let network = error as? NetworkError { return network.errorDescription ?? "Não foi possível carregar os estabelecimentos." } return "Não foi possível carregar os estabelecimentos." } }