[lcenavigationview-adoption] Adopt LCENavigationView across 15 screens
Standardizes the back button to a single shared component (AppBackButtonIcon, 32x32/14pt) instead of ~16 hand-duplicated 52x52/24pt copies, and wires each screen through LCENavigationView per this project's mandatory-usage rule. Home and Store Detail intentionally excluded - both have bespoke header designs (collapsing header, hero-image overlay) this change would visually disrupt.
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import SwiftUI
|
||||
import WebKit
|
||||
#if canImport(LCEssentials)
|
||||
import LCEssentials
|
||||
#endif
|
||||
|
||||
private struct RemotePDFView: View {
|
||||
let url: URL
|
||||
@@ -69,52 +72,32 @@ private struct LegalDocumentScreen: View {
|
||||
@State private var hasError = false
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
screenHeader
|
||||
LCENavigationView {
|
||||
content
|
||||
}
|
||||
.setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() }
|
||||
.setTitle(text: Text(headerTitle).font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary))
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
ZStack {
|
||||
RemotePDFView(url: document.url, isLoading: $isLoading, hasError: $hasError)
|
||||
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
}
|
||||
|
||||
if hasError {
|
||||
VStack(spacing: 8) {
|
||||
Text("Não foi possível carregar \(title.lowercased()).")
|
||||
.font(AppTypography.body)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.padding(24)
|
||||
|
||||
ZStack {
|
||||
RemotePDFView(url: document.url, isLoading: $isLoading, hasError: $hasError)
|
||||
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
}
|
||||
|
||||
if hasError {
|
||||
VStack(spacing: 8) {
|
||||
Text("Não foi possível carregar \(title.lowercased()).")
|
||||
.font(AppTypography.body)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.padding(24)
|
||||
}
|
||||
}
|
||||
}
|
||||
.background((colorScheme == .dark ? Color.black : AppColors.backgroundLight).ignoresSafeArea())
|
||||
.appHiddenNavigationBar()
|
||||
.navigationBarBackButtonHidden(true)
|
||||
}
|
||||
|
||||
private var screenHeader: some View {
|
||||
ZStack {
|
||||
Text(headerTitle)
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
HStack {
|
||||
Button(action: { dismiss() }) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import SwiftUI
|
||||
#if canImport(LCEssentials)
|
||||
import LCEssentials
|
||||
#endif
|
||||
|
||||
struct AddAddressFormView: View {
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@@ -31,18 +34,19 @@ struct AddAddressFormView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
LCENavigationView {
|
||||
content
|
||||
}
|
||||
.setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() }
|
||||
.setTitle(text: Text(existingAddress == nil ? "Novo endereço" : "Editar endereço").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary))
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
ZStack {
|
||||
AppColors.backgroundLight.ignoresSafeArea()
|
||||
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(spacing: 0) {
|
||||
screenHeader(
|
||||
title: existingAddress == nil ? "Novo endereço" : "Editar endereço",
|
||||
onBack: { dismiss() }
|
||||
)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
LoginField(icon: "tag", placeholder: "Ex: Casa, Trabalho", keyboardType: .default, text: $label)
|
||||
LoginField(icon: "mail", placeholder: "CEP", keyboardType: .numberPad, text: $zipCode)
|
||||
@@ -115,27 +119,6 @@ struct AddAddressFormView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func screenHeader(title: String, onBack: @escaping () -> Void) -> some View {
|
||||
ZStack {
|
||||
Text(title)
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
HStack {
|
||||
Button(action: onBack) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func saveAddress() {
|
||||
guard !isLoading else { return }
|
||||
let latLong: [Double]? = {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import SwiftUI
|
||||
#if canImport(LCEssentials)
|
||||
import LCEssentials
|
||||
#endif
|
||||
|
||||
struct AddCardFormView: View {
|
||||
let appState: AppState
|
||||
@@ -53,10 +56,16 @@ struct AddCardFormView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
LCENavigationView {
|
||||
content
|
||||
}
|
||||
.setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() }
|
||||
.setTitle(text: Text("Novo Cartão").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary))
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(spacing: 20) {
|
||||
screenHeader
|
||||
|
||||
formSection("Dados do Cartão") {
|
||||
cardNumberField
|
||||
labeledField("Nome no cartão", placeholder: "Como impresso no cartão", text: $holderName, autocap: true)
|
||||
@@ -240,26 +249,6 @@ struct AddCardFormView: View {
|
||||
|
||||
// MARK: - Sub-views
|
||||
|
||||
private var screenHeader: some View {
|
||||
ZStack {
|
||||
Text("Novo Cartão")
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
HStack {
|
||||
Button(action: { dismiss() }) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var cardNumberField: some View {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import SwiftUI
|
||||
#if canImport(LCEssentials)
|
||||
import LCEssentials
|
||||
#endif
|
||||
|
||||
struct AddressesView: View {
|
||||
let message: String?
|
||||
@@ -17,14 +20,20 @@ struct AddressesView: View {
|
||||
let tabBarClearance: CGFloat = 96
|
||||
|
||||
var body: some View {
|
||||
LCENavigationView {
|
||||
content
|
||||
}
|
||||
.setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() }
|
||||
.setTitle(text: Text("Meus Endereços").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary))
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
ZStack {
|
||||
AppColors.backgroundLight
|
||||
.ignoresSafeArea()
|
||||
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(spacing: 20) {
|
||||
screenHeader(title: "Meus Endereços", onBack: { dismiss() })
|
||||
|
||||
if let message {
|
||||
Text(message)
|
||||
.font(AppTypography.body)
|
||||
@@ -173,28 +182,6 @@ struct AddressesView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func screenHeader(title: String, onBack: @escaping () -> Void) -> some View {
|
||||
ZStack {
|
||||
Text(title)
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
|
||||
HStack {
|
||||
Button(action: onBack) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func selectAddress(_ address: CustomerAddress) {
|
||||
appState.address.selectedId = address.id
|
||||
let label = (address.label ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
#if canImport(LCEssentials)
|
||||
import LCEssentials
|
||||
#endif
|
||||
|
||||
struct CheckoutView: View {
|
||||
@Binding var appState: AppState
|
||||
@@ -106,33 +109,11 @@ struct CheckoutView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
screenHeader
|
||||
deliveryTypeSection
|
||||
addressSection
|
||||
orderSummarySection
|
||||
paymentSection
|
||||
|
||||
if let errorMessage, errorMessage.isEmpty == false {
|
||||
Text(errorMessage)
|
||||
.font(.caption)
|
||||
.foregroundStyle(Color.red)
|
||||
}
|
||||
|
||||
if let addressValidationMessage, addressValidationMessage.isEmpty == false {
|
||||
Text(addressValidationMessage)
|
||||
.font(.caption)
|
||||
.foregroundStyle(addressValidationBlocked ? Color.red : AppColors.primary)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.top, 16)
|
||||
.padding(.bottom, 10)
|
||||
LCENavigationView {
|
||||
content
|
||||
}
|
||||
.background(AppColors.backgroundLight)
|
||||
.appHiddenNavigationBar()
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() }
|
||||
.setTitle(text: Text("Finalizar Pedido").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary))
|
||||
.appBottomSafeAreaInset {
|
||||
bottomBar
|
||||
}
|
||||
@@ -232,25 +213,31 @@ struct CheckoutView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var screenHeader: some View {
|
||||
ZStack {
|
||||
Text("Finalizar Pedido")
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
HStack {
|
||||
Button(action: { dismiss() }) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
|
||||
private var content: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
deliveryTypeSection
|
||||
addressSection
|
||||
orderSummarySection
|
||||
paymentSection
|
||||
|
||||
if let errorMessage, errorMessage.isEmpty == false {
|
||||
Text(errorMessage)
|
||||
.font(.caption)
|
||||
.foregroundStyle(Color.red)
|
||||
}
|
||||
|
||||
if let addressValidationMessage, addressValidationMessage.isEmpty == false {
|
||||
Text(addressValidationMessage)
|
||||
.font(.caption)
|
||||
.foregroundStyle(addressValidationBlocked ? Color.red : AppColors.primary)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.top, 16)
|
||||
.padding(.bottom, 10)
|
||||
}
|
||||
.background(AppColors.backgroundLight)
|
||||
}
|
||||
|
||||
private var deliveryTypeSection: some View {
|
||||
@@ -730,9 +717,59 @@ struct PaymentPixView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
LCENavigationView {
|
||||
content
|
||||
}
|
||||
.setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() }
|
||||
.setTitle(text: Text("Pagamento via PIX").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary))
|
||||
.sheet(isPresented: $showChangePaymentSheet) {
|
||||
ChangePaymentSheet(
|
||||
pixContext: currentContext,
|
||||
savedCards: savedCards,
|
||||
appState: $appState,
|
||||
onChanged: { newContext in
|
||||
showChangePaymentSheet = false
|
||||
if let newContext {
|
||||
currentContext = newContext
|
||||
tracker.stop()
|
||||
tracker.start(orderId: newContext.orderId, jwt: DefaultTokenStore().jwt)
|
||||
}
|
||||
},
|
||||
onConfirmed: {
|
||||
showChangePaymentSheet = false
|
||||
onPaymentConfirmed?()
|
||||
openTrackingOnce()
|
||||
}
|
||||
)
|
||||
}
|
||||
.task {
|
||||
tracker.onOrderUpdated = { updated in
|
||||
latestOrder = updated
|
||||
if updated.isPaymentConfirmed {
|
||||
onPaymentConfirmed?()
|
||||
openTrackingOnce()
|
||||
}
|
||||
}
|
||||
tracker.start(orderId: currentContext.orderId, jwt: DefaultTokenStore().jwt)
|
||||
}
|
||||
.task {
|
||||
while Task.isCancelled == false {
|
||||
currentTime = Date()
|
||||
if isPixExpired {
|
||||
showPixExpiredSnackbar()
|
||||
return
|
||||
}
|
||||
try? await Task.sleep(nanoseconds: 1_000_000_000)
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
tracker.stop()
|
||||
}
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(spacing: 14) {
|
||||
screenHeader
|
||||
RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous)
|
||||
.fill(AppColors.surface)
|
||||
.overlay(
|
||||
@@ -824,72 +861,6 @@ struct PaymentPixView: View {
|
||||
.padding(20)
|
||||
}
|
||||
.background(AppColors.backgroundLight)
|
||||
.appHiddenNavigationBar()
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.sheet(isPresented: $showChangePaymentSheet) {
|
||||
ChangePaymentSheet(
|
||||
pixContext: currentContext,
|
||||
savedCards: savedCards,
|
||||
appState: $appState,
|
||||
onChanged: { newContext in
|
||||
showChangePaymentSheet = false
|
||||
if let newContext {
|
||||
currentContext = newContext
|
||||
tracker.stop()
|
||||
tracker.start(orderId: newContext.orderId, jwt: DefaultTokenStore().jwt)
|
||||
}
|
||||
},
|
||||
onConfirmed: {
|
||||
showChangePaymentSheet = false
|
||||
onPaymentConfirmed?()
|
||||
openTrackingOnce()
|
||||
}
|
||||
)
|
||||
}
|
||||
.task {
|
||||
tracker.onOrderUpdated = { updated in
|
||||
latestOrder = updated
|
||||
if updated.isPaymentConfirmed {
|
||||
onPaymentConfirmed?()
|
||||
openTrackingOnce()
|
||||
}
|
||||
}
|
||||
tracker.start(orderId: currentContext.orderId, jwt: DefaultTokenStore().jwt)
|
||||
}
|
||||
.task {
|
||||
while Task.isCancelled == false {
|
||||
currentTime = Date()
|
||||
if isPixExpired {
|
||||
showPixExpiredSnackbar()
|
||||
return
|
||||
}
|
||||
try? await Task.sleep(nanoseconds: 1_000_000_000)
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
tracker.stop()
|
||||
}
|
||||
}
|
||||
|
||||
private var screenHeader: some View {
|
||||
ZStack {
|
||||
Text("Pagamento via PIX")
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
HStack {
|
||||
Button(action: { dismiss() }) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func openTrackingOnce() {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import SwiftUI
|
||||
#if canImport(LCEssentials)
|
||||
import LCEssentials
|
||||
#endif
|
||||
|
||||
struct OrderDetailsView: View {
|
||||
let order: PublicOrderResult
|
||||
@@ -15,9 +18,16 @@ struct OrderDetailsView: View {
|
||||
@State private var showClearCartAlert = false
|
||||
|
||||
var body: some View {
|
||||
LCENavigationView {
|
||||
content
|
||||
}
|
||||
.setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() }
|
||||
.setTitle(text: Text("Detalhes do Pedido").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary))
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(spacing: 14) {
|
||||
screenHeader
|
||||
statusCard
|
||||
storeCard
|
||||
itemsCard
|
||||
@@ -81,27 +91,6 @@ struct OrderDetailsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var screenHeader: some View {
|
||||
ZStack {
|
||||
Text("Detalhes do Pedido")
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
HStack {
|
||||
Button(action: { dismiss() }) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var statusCard: some View {
|
||||
HStack(spacing: 14) {
|
||||
Circle()
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
#if canImport(LCEssentials)
|
||||
import LCEssentials
|
||||
#endif
|
||||
|
||||
private struct TrackingStep: Identifiable {
|
||||
let id: String
|
||||
@@ -29,9 +32,25 @@ struct OrderTrackingView: View {
|
||||
@State var showPushOptInAlert = false
|
||||
|
||||
var body: some View {
|
||||
LCENavigationView {
|
||||
content
|
||||
}
|
||||
.setLeftButton(image: AnyView(AppBackButtonIcon())) {
|
||||
if let postOrderBack {
|
||||
postOrderBack()
|
||||
} else {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
.setTitle(text: Text("Pedido \(displayOrderTitle)").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary))
|
||||
.navigationDestination(item: $reviewDraft) { draft in
|
||||
MyReviewsView(initialOrder: draft)
|
||||
}
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(spacing: 16) {
|
||||
screenHeader
|
||||
topHeader
|
||||
orderTitleSection
|
||||
statusBanner
|
||||
@@ -82,9 +101,6 @@ struct OrderTrackingView: View {
|
||||
tracker.stop()
|
||||
detachReviewSavedObserver()
|
||||
}
|
||||
.navigationDestination(item: $reviewDraft) { draft in
|
||||
MyReviewsView(initialOrder: draft)
|
||||
}
|
||||
}
|
||||
|
||||
private func attachReviewSavedObserverIfNeeded() {
|
||||
@@ -110,33 +126,6 @@ struct OrderTrackingView: View {
|
||||
self.reviewSavedObserver = nil
|
||||
}
|
||||
|
||||
private var screenHeader: some View {
|
||||
ZStack {
|
||||
Text("Pedido \(displayOrderTitle)")
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
HStack {
|
||||
Button(action: {
|
||||
if let postOrderBack {
|
||||
postOrderBack()
|
||||
} else {
|
||||
dismiss()
|
||||
}
|
||||
}) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var topHeader: some View {
|
||||
HStack(spacing: 10) {
|
||||
Circle()
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import SwiftUI
|
||||
#if canImport(LCEssentials)
|
||||
import LCEssentials
|
||||
#endif
|
||||
|
||||
struct OrdersView: View {
|
||||
@Binding var appState: AppState
|
||||
@@ -14,10 +17,16 @@ struct OrdersView: View {
|
||||
@State var selectedOrderRoute: OrderRouteContext? = nil
|
||||
|
||||
var body: some View {
|
||||
LCENavigationView {
|
||||
content
|
||||
}
|
||||
.setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() }
|
||||
.setTitle(text: Text("Meus Pedidos").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary))
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(alignment: .leading, spacing: 14) {
|
||||
screenHeader(title: "Meus Pedidos", onBack: { dismiss() })
|
||||
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -77,28 +86,6 @@ struct OrdersView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func screenHeader(title: String, onBack: @escaping () -> Void) -> some View {
|
||||
ZStack {
|
||||
Text(title)
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
|
||||
HStack {
|
||||
Button(action: onBack) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func orderCard(_ order: AppOrderSummary) -> some View {
|
||||
let status = orderVisualStatus(for: order)
|
||||
let detailsRoute = OrderRouteContext(
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
#if canImport(LCEssentials)
|
||||
import LCEssentials
|
||||
#endif
|
||||
|
||||
struct PizzaFlavorAddonsSheet: View {
|
||||
let flavor: StoreCatalogProduct
|
||||
@@ -8,9 +11,16 @@ struct PizzaFlavorAddonsSheet: View {
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
var body: some View {
|
||||
LCENavigationView {
|
||||
content
|
||||
}
|
||||
.setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() }
|
||||
.setTitle(text: Text("Adicionais").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary))
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
screenHeader
|
||||
Text(flavor.name)
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
@@ -82,26 +92,6 @@ struct PizzaFlavorAddonsSheet: View {
|
||||
.navigationBarBackButtonHidden(true)
|
||||
}
|
||||
|
||||
private var screenHeader: some View {
|
||||
ZStack {
|
||||
Text("Adicionais")
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
HStack {
|
||||
Button(action: { dismiss() }) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func increment(_ addonId: String) {
|
||||
quantities[addonId, default: 0] += 1
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
#if canImport(LCEssentials)
|
||||
import LCEssentials
|
||||
#endif
|
||||
|
||||
struct PizzaProductDetailSheet: View {
|
||||
let category: StoreCatalogCategory
|
||||
@@ -216,9 +219,16 @@ struct PizzaProductDetailSheet: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
LCENavigationView {
|
||||
content
|
||||
}
|
||||
.setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() }
|
||||
.setTitle(text: Text("Monte sua pizza").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary))
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
screenHeader
|
||||
Rectangle()
|
||||
.fill(AppColors.brandSoft)
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -340,25 +350,5 @@ struct PizzaProductDetailSheet: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var screenHeader: some View {
|
||||
ZStack {
|
||||
Text("Monte sua pizza")
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
HStack {
|
||||
Button(action: { dismiss() }) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
#if canImport(LCEssentials)
|
||||
import LCEssentials
|
||||
#endif
|
||||
|
||||
struct ProductDetailSheet: View {
|
||||
let product: StoreCatalogProduct
|
||||
@@ -70,9 +73,16 @@ struct ProductDetailSheet: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
LCENavigationView {
|
||||
content
|
||||
}
|
||||
.setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() }
|
||||
.setTitle(text: Text("Detalhes").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary))
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
screenHeader
|
||||
AsyncStoreImage(imageURL: imageURL)
|
||||
.frame(height: 220)
|
||||
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusXL, style: .continuous))
|
||||
@@ -243,26 +253,6 @@ struct ProductDetailSheet: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var screenHeader: some View {
|
||||
ZStack {
|
||||
Text("Detalhes")
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
HStack {
|
||||
Button(action: { dismiss() }) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func formatCurrency(_ value: Double) -> String {
|
||||
String(format: "R$ %.2f", value).replacingOccurrences(of: ".", with: ",")
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import SwiftUI
|
||||
#if canImport(LCEssentials)
|
||||
import LCEssentials
|
||||
#endif
|
||||
|
||||
/// State -> city picker for anonymous browsing (public store locator).
|
||||
/// Replaces AddressesView in the address-picker modal when the user is
|
||||
@@ -28,12 +31,18 @@ struct PublicLocationPickerView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
LCENavigationView {
|
||||
content
|
||||
}
|
||||
.setLeftButton(image: AnyView(AppBackButtonIcon())) { back() }
|
||||
.setTitle(text: Text(step == .state ? "Escolha seu estado" : "Escolha sua cidade").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary))
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
ZStack {
|
||||
AppColors.backgroundLight.ignoresSafeArea()
|
||||
|
||||
VStack(spacing: 20) {
|
||||
header
|
||||
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
.padding(.top, 40)
|
||||
@@ -66,29 +75,6 @@ struct PublicLocationPickerView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var header: some View {
|
||||
ZStack {
|
||||
Text(step == .state ? "Escolha seu estado" : "Escolha sua cidade")
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
|
||||
HStack {
|
||||
Button(action: back) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
|
||||
private var list: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
LazyVStack(spacing: 12) {
|
||||
|
||||
@@ -31,9 +31,21 @@ struct MyReviewsView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
LCENavigationView {
|
||||
content
|
||||
}
|
||||
.setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() }
|
||||
.setTitle(text: Text("Minhas Avaliações").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary))
|
||||
.navigationDestination(item: $selectedDraft) { draft in
|
||||
OrderReviewView(draft: draft) {
|
||||
Task { await loadReviewsFromBackend(forceRefresh: true) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(spacing: 12) {
|
||||
screenHeader
|
||||
if isLoading && reviews.isEmpty && pendingReviews.isEmpty {
|
||||
ProgressView()
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -69,13 +81,6 @@ struct MyReviewsView: View {
|
||||
.padding(.bottom, UIDevice.bottomNotch + 30)
|
||||
}
|
||||
.background(AppColors.backgroundLight)
|
||||
.appHiddenNavigationBar()
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.navigationDestination(item: $selectedDraft) { draft in
|
||||
OrderReviewView(draft: draft) {
|
||||
Task { await loadReviewsFromBackend(forceRefresh: true) }
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
Task { await loadReviewsFromBackend(forceRefresh: true) }
|
||||
}
|
||||
@@ -91,27 +96,6 @@ struct MyReviewsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var screenHeader: some View {
|
||||
ZStack {
|
||||
Text("Minhas Avaliações")
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
HStack {
|
||||
Button(action: { dismiss() }) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var emptyState: some View {
|
||||
VStack(spacing: 8) {
|
||||
Text("Você não tem avaliações nem pendências no momento.")
|
||||
@@ -458,9 +442,16 @@ struct OrderReviewView: View {
|
||||
@State var orderItems: [PublicOrderItem] = []
|
||||
|
||||
var body: some View {
|
||||
LCENavigationView {
|
||||
content
|
||||
}
|
||||
.setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() }
|
||||
.setTitle(text: Text("Avaliar Pedido").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary))
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(alignment: .leading, spacing: 24) {
|
||||
screenHeader
|
||||
topSection
|
||||
orderItemsSection
|
||||
if existingReview != nil {
|
||||
@@ -515,27 +506,6 @@ struct OrderReviewView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var screenHeader: some View {
|
||||
ZStack {
|
||||
Text("Avaliar Pedido")
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
HStack {
|
||||
Button(action: { dismiss() }) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var newReviewContent: some View {
|
||||
VStack(alignment: .leading, spacing: 24) {
|
||||
starsSection
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import SwiftUI
|
||||
#if canImport(LCEssentials)
|
||||
import LCEssentials
|
||||
#endif
|
||||
|
||||
struct SavedCardsView: View {
|
||||
@Binding var appState: AppState
|
||||
@@ -15,13 +18,19 @@ struct SavedCardsView: View {
|
||||
private let tabBarClearance: CGFloat = 96
|
||||
|
||||
var body: some View {
|
||||
LCENavigationView {
|
||||
content
|
||||
}
|
||||
.setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() }
|
||||
.setTitle(text: Text("Meus Cartões").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary))
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
ZStack {
|
||||
AppColors.backgroundLight.ignoresSafeArea()
|
||||
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(spacing: 20) {
|
||||
screenHeader
|
||||
|
||||
VStack(spacing: 12) {
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
@@ -86,27 +95,6 @@ struct SavedCardsView: View {
|
||||
.task { await loadCards() }
|
||||
}
|
||||
|
||||
private var screenHeader: some View {
|
||||
ZStack {
|
||||
Text("Meus Cartões")
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
HStack {
|
||||
Button(action: { dismiss() }) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var addCardButton: some View {
|
||||
ZStack(alignment: .bottom) {
|
||||
Rectangle()
|
||||
|
||||
@@ -3,6 +3,9 @@ import SwiftUI
|
||||
import PhotosUI
|
||||
import UIKit
|
||||
#endif
|
||||
#if canImport(LCEssentials)
|
||||
import LCEssentials
|
||||
#endif
|
||||
|
||||
struct UserProfileView: View {
|
||||
@Binding var appState: AppState
|
||||
@@ -26,9 +29,16 @@ struct UserProfileView: View {
|
||||
#endif
|
||||
|
||||
var body: some View {
|
||||
LCENavigationView {
|
||||
content
|
||||
}
|
||||
.setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() }
|
||||
.setTitle(text: Text("Meu Perfil").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary))
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(spacing: 22) {
|
||||
screenHeader
|
||||
avatarSection
|
||||
formSection
|
||||
preferencesSection
|
||||
@@ -57,27 +67,6 @@ struct UserProfileView: View {
|
||||
#endif
|
||||
}
|
||||
|
||||
private var screenHeader: some View {
|
||||
ZStack {
|
||||
Text("Meu Perfil")
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
HStack {
|
||||
Button(action: { dismiss() }) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var avatarSection: some View {
|
||||
VStack(spacing: 12) {
|
||||
Circle()
|
||||
|
||||
Reference in New Issue
Block a user