Commit
This commit is contained in:
138
Sources/PediFoods/Views/Main/HomeView+Data.swift
Normal file
138
Sources/PediFoods/Views/Main/HomeView+Data.swift
Normal file
@@ -0,0 +1,138 @@
|
||||
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<String>()
|
||||
|
||||
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<String>()
|
||||
|
||||
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."
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user