[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:
Daniel Arantes Loverde
2026-08-24 14:35:48 -03:00
parent 59ec6ffd6f
commit 345618426f
17 changed files with 284 additions and 486 deletions

View File

@@ -114,6 +114,7 @@
CCE23FD7706B34BDE544E05A /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC381D02A37312BE360024F /* Buttons.swift */; }; CCE23FD7706B34BDE544E05A /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC381D02A37312BE360024F /* Buttons.swift */; };
CD96980F3D7B603126FDE6AC /* URLProtocolStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51798A414469C911D44F0F6 /* URLProtocolStub.swift */; }; CD96980F3D7B603126FDE6AC /* URLProtocolStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51798A414469C911D44F0F6 /* URLProtocolStub.swift */; };
CEA08090E08B5E39FD0DD5D6 /* CachedRemoteImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EECF1BBC608B65EC1F1B6114 /* CachedRemoteImage.swift */; }; CEA08090E08B5E39FD0DD5D6 /* CachedRemoteImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EECF1BBC608B65EC1F1B6114 /* CachedRemoteImage.swift */; };
38452FD60CBDE248531DA484 /* AppBackButtonIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EEF6262A813BB8274266C8 /* AppBackButtonIcon.swift */; };
D2FB3C624B3C27E59BCA997A /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EA34EE194C71755B3C82F30 /* MainTabView.swift */; }; D2FB3C624B3C27E59BCA997A /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EA34EE194C71755B3C82F30 /* MainTabView.swift */; };
D3EC6E0A68B809F7A3370B1B /* ProductDetailSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553A05B9EAD0B9D0D9F1B369 /* ProductDetailSheet.swift */; }; D3EC6E0A68B809F7A3370B1B /* ProductDetailSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 553A05B9EAD0B9D0D9F1B369 /* ProductDetailSheet.swift */; };
D48148B155BA2F76C41C0787 /* HomeFiltersStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FBD7D809ACE1840165A9DCC /* HomeFiltersStateTests.swift */; }; D48148B155BA2F76C41C0787 /* HomeFiltersStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FBD7D809ACE1840165A9DCC /* HomeFiltersStateTests.swift */; };
@@ -299,6 +300,7 @@
EB95D6804877D2ED244811D0 /* Inputs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inputs.swift; sourceTree = "<group>"; }; EB95D6804877D2ED244811D0 /* Inputs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inputs.swift; sourceTree = "<group>"; };
ECBFCE20C54A79DEA8F4512B /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = "<group>"; }; ECBFCE20C54A79DEA8F4512B /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = "<group>"; };
EECF1BBC608B65EC1F1B6114 /* CachedRemoteImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedRemoteImage.swift; sourceTree = "<group>"; }; EECF1BBC608B65EC1F1B6114 /* CachedRemoteImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedRemoteImage.swift; sourceTree = "<group>"; };
52EEF6262A813BB8274266C8 /* AppBackButtonIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppBackButtonIcon.swift; sourceTree = "<group>"; };
F0B079DFAB606DCA87A536C9 /* LaunchSplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchSplashView.swift; sourceTree = "<group>"; }; F0B079DFAB606DCA87A536C9 /* LaunchSplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchSplashView.swift; sourceTree = "<group>"; };
F18F768F7A2A47111A85FE88 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; }; F18F768F7A2A47111A85FE88 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
F2B2C5BBDFF4473C472F45B4 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; }; F2B2C5BBDFF4473C472F45B4 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
@@ -558,6 +560,7 @@
children = ( children = (
6EC381D02A37312BE360024F /* Buttons.swift */, 6EC381D02A37312BE360024F /* Buttons.swift */,
EECF1BBC608B65EC1F1B6114 /* CachedRemoteImage.swift */, EECF1BBC608B65EC1F1B6114 /* CachedRemoteImage.swift */,
52EEF6262A813BB8274266C8 /* AppBackButtonIcon.swift */,
EB95D6804877D2ED244811D0 /* Inputs.swift */, EB95D6804877D2ED244811D0 /* Inputs.swift */,
C4745C12F14B695C77DFE178 /* SnackbarOverlay.swift */, C4745C12F14B695C77DFE178 /* SnackbarOverlay.swift */,
31916C1A4005396ADA5FF3BC /* StoreCard.swift */, 31916C1A4005396ADA5FF3BC /* StoreCard.swift */,
@@ -779,6 +782,7 @@
3689239282AADF06783CBBEA /* AuthFormatters.swift in Sources */, 3689239282AADF06783CBBEA /* AuthFormatters.swift in Sources */,
CCE23FD7706B34BDE544E05A /* Buttons.swift in Sources */, CCE23FD7706B34BDE544E05A /* Buttons.swift in Sources */,
CEA08090E08B5E39FD0DD5D6 /* CachedRemoteImage.swift in Sources */, CEA08090E08B5E39FD0DD5D6 /* CachedRemoteImage.swift in Sources */,
38452FD60CBDE248531DA484 /* AppBackButtonIcon.swift in Sources */,
DE499D6150C15EB046AF7963 /* CartView.swift in Sources */, DE499D6150C15EB046AF7963 /* CartView.swift in Sources */,
70EA850C067AB6C6B87539E2 /* CheckoutTypes.swift in Sources */, 70EA850C067AB6C6B87539E2 /* CheckoutTypes.swift in Sources */,
41BE548D929D3814A736FA12 /* CheckoutView+Logic.swift in Sources */, 41BE548D929D3814A736FA12 /* CheckoutView+Logic.swift in Sources */,

View File

@@ -0,0 +1,13 @@
import SwiftUI
struct AppBackButtonIcon: View {
var body: some View {
Image(systemName: "chevron.left")
.font(.system(size: 14, weight: .semibold))
.foregroundStyle(AppColors.textPrimary)
.frame(width: 32, height: 32)
.background(AppColors.surface)
.clipShape(Circle())
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
}
}

View File

@@ -1,5 +1,8 @@
import SwiftUI import SwiftUI
import WebKit import WebKit
#if canImport(LCEssentials)
import LCEssentials
#endif
private struct RemotePDFView: View { private struct RemotePDFView: View {
let url: URL let url: URL
@@ -69,52 +72,32 @@ private struct LegalDocumentScreen: View {
@State private var hasError = false @State private var hasError = false
var body: some View { var body: some View {
VStack(spacing: 0) { LCENavigationView {
screenHeader 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) .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()) .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()
}
}
} }
} }

View File

@@ -1,4 +1,7 @@
import SwiftUI import SwiftUI
#if canImport(LCEssentials)
import LCEssentials
#endif
struct AddAddressFormView: View { struct AddAddressFormView: View {
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
@@ -31,18 +34,19 @@ struct AddAddressFormView: View {
} }
var body: some 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 { ZStack {
AppColors.backgroundLight.ignoresSafeArea() AppColors.backgroundLight.ignoresSafeArea()
ScrollView(showsIndicators: false) { ScrollView(showsIndicators: false) {
VStack(spacing: 0) { 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) { VStack(alignment: .leading, spacing: 16) {
LoginField(icon: "tag", placeholder: "Ex: Casa, Trabalho", keyboardType: .default, text: $label) LoginField(icon: "tag", placeholder: "Ex: Casa, Trabalho", keyboardType: .default, text: $label)
LoginField(icon: "mail", placeholder: "CEP", keyboardType: .numberPad, text: $zipCode) 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() { private func saveAddress() {
guard !isLoading else { return } guard !isLoading else { return }
let latLong: [Double]? = { let latLong: [Double]? = {

View File

@@ -1,4 +1,7 @@
import SwiftUI import SwiftUI
#if canImport(LCEssentials)
import LCEssentials
#endif
struct AddCardFormView: View { struct AddCardFormView: View {
let appState: AppState let appState: AppState
@@ -53,10 +56,16 @@ struct AddCardFormView: View {
} }
var body: some 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) { ScrollView(showsIndicators: false) {
VStack(spacing: 20) { VStack(spacing: 20) {
screenHeader
formSection("Dados do Cartão") { formSection("Dados do Cartão") {
cardNumberField cardNumberField
labeledField("Nome no cartão", placeholder: "Como impresso no cartão", text: $holderName, autocap: true) 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 // 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 { private var cardNumberField: some View {
VStack(alignment: .leading, spacing: 6) { VStack(alignment: .leading, spacing: 6) {

View File

@@ -1,4 +1,7 @@
import SwiftUI import SwiftUI
#if canImport(LCEssentials)
import LCEssentials
#endif
struct AddressesView: View { struct AddressesView: View {
let message: String? let message: String?
@@ -17,14 +20,20 @@ struct AddressesView: View {
let tabBarClearance: CGFloat = 96 let tabBarClearance: CGFloat = 96
var body: some View { 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 { ZStack {
AppColors.backgroundLight AppColors.backgroundLight
.ignoresSafeArea() .ignoresSafeArea()
ScrollView(showsIndicators: false) { ScrollView(showsIndicators: false) {
VStack(spacing: 20) { VStack(spacing: 20) {
screenHeader(title: "Meus Endereços", onBack: { dismiss() })
if let message { if let message {
Text(message) Text(message)
.font(AppTypography.body) .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) { private func selectAddress(_ address: CustomerAddress) {
appState.address.selectedId = address.id appState.address.selectedId = address.id
let label = (address.label ?? "").trimmingCharacters(in: .whitespacesAndNewlines) let label = (address.label ?? "").trimmingCharacters(in: .whitespacesAndNewlines)

View File

@@ -1,5 +1,8 @@
import SwiftUI import SwiftUI
import UIKit import UIKit
#if canImport(LCEssentials)
import LCEssentials
#endif
struct CheckoutView: View { struct CheckoutView: View {
@Binding var appState: AppState @Binding var appState: AppState
@@ -106,33 +109,11 @@ struct CheckoutView: View {
} }
var body: some View { var body: some View {
ScrollView(showsIndicators: false) { LCENavigationView {
VStack(alignment: .leading, spacing: 18) { content
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)
} }
.background(AppColors.backgroundLight) .setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() }
.appHiddenNavigationBar() .setTitle(text: Text("Finalizar Pedido").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary))
.navigationBarBackButtonHidden(true)
.appBottomSafeAreaInset { .appBottomSafeAreaInset {
bottomBar bottomBar
} }
@@ -232,25 +213,31 @@ struct CheckoutView: View {
} }
} }
private var screenHeader: some View { private var content: some View {
ZStack { ScrollView(showsIndicators: false) {
Text("Finalizar Pedido") VStack(alignment: .leading, spacing: 18) {
.font(AppTypography.heading2) deliveryTypeSection
.foregroundStyle(AppColors.textPrimary) addressSection
HStack { orderSummarySection
Button(action: { dismiss() }) { paymentSection
Image(systemName: "chevron.left")
.font(.system(size: 24, weight: .semibold)) if let errorMessage, errorMessage.isEmpty == false {
.foregroundStyle(AppColors.textPrimary) Text(errorMessage)
.frame(width: 52, height: 52) .font(.caption)
.background(AppColors.surface) .foregroundStyle(Color.red)
.clipShape(Circle()) }
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
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 { private var deliveryTypeSection: some View {
@@ -730,9 +717,59 @@ struct PaymentPixView: View {
} }
var body: some 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) { ScrollView(showsIndicators: false) {
VStack(spacing: 14) { VStack(spacing: 14) {
screenHeader
RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous) RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous)
.fill(AppColors.surface) .fill(AppColors.surface)
.overlay( .overlay(
@@ -824,72 +861,6 @@ struct PaymentPixView: View {
.padding(20) .padding(20)
} }
.background(AppColors.backgroundLight) .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() { private func openTrackingOnce() {

View File

@@ -1,4 +1,7 @@
import SwiftUI import SwiftUI
#if canImport(LCEssentials)
import LCEssentials
#endif
struct OrderDetailsView: View { struct OrderDetailsView: View {
let order: PublicOrderResult let order: PublicOrderResult
@@ -15,9 +18,16 @@ struct OrderDetailsView: View {
@State private var showClearCartAlert = false @State private var showClearCartAlert = false
var body: some View { 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) { ScrollView(showsIndicators: false) {
VStack(spacing: 14) { VStack(spacing: 14) {
screenHeader
statusCard statusCard
storeCard storeCard
itemsCard 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 { private var statusCard: some View {
HStack(spacing: 14) { HStack(spacing: 14) {
Circle() Circle()

View File

@@ -1,5 +1,8 @@
import SwiftUI import SwiftUI
import UIKit import UIKit
#if canImport(LCEssentials)
import LCEssentials
#endif
private struct TrackingStep: Identifiable { private struct TrackingStep: Identifiable {
let id: String let id: String
@@ -29,9 +32,25 @@ struct OrderTrackingView: View {
@State var showPushOptInAlert = false @State var showPushOptInAlert = false
var body: some View { 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) { ScrollView(showsIndicators: false) {
VStack(spacing: 16) { VStack(spacing: 16) {
screenHeader
topHeader topHeader
orderTitleSection orderTitleSection
statusBanner statusBanner
@@ -82,9 +101,6 @@ struct OrderTrackingView: View {
tracker.stop() tracker.stop()
detachReviewSavedObserver() detachReviewSavedObserver()
} }
.navigationDestination(item: $reviewDraft) { draft in
MyReviewsView(initialOrder: draft)
}
} }
private func attachReviewSavedObserverIfNeeded() { private func attachReviewSavedObserverIfNeeded() {
@@ -110,33 +126,6 @@ struct OrderTrackingView: View {
self.reviewSavedObserver = nil 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 { private var topHeader: some View {
HStack(spacing: 10) { HStack(spacing: 10) {
Circle() Circle()

View File

@@ -1,4 +1,7 @@
import SwiftUI import SwiftUI
#if canImport(LCEssentials)
import LCEssentials
#endif
struct OrdersView: View { struct OrdersView: View {
@Binding var appState: AppState @Binding var appState: AppState
@@ -14,10 +17,16 @@ struct OrdersView: View {
@State var selectedOrderRoute: OrderRouteContext? = nil @State var selectedOrderRoute: OrderRouteContext? = nil
var body: some View { 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) { ScrollView(showsIndicators: false) {
VStack(alignment: .leading, spacing: 14) { VStack(alignment: .leading, spacing: 14) {
screenHeader(title: "Meus Pedidos", onBack: { dismiss() })
if isLoading { if isLoading {
ProgressView() ProgressView()
.frame(maxWidth: .infinity) .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 { private func orderCard(_ order: AppOrderSummary) -> some View {
let status = orderVisualStatus(for: order) let status = orderVisualStatus(for: order)
let detailsRoute = OrderRouteContext( let detailsRoute = OrderRouteContext(

View File

@@ -1,5 +1,8 @@
import Foundation import Foundation
import SwiftUI import SwiftUI
#if canImport(LCEssentials)
import LCEssentials
#endif
struct PizzaFlavorAddonsSheet: View { struct PizzaFlavorAddonsSheet: View {
let flavor: StoreCatalogProduct let flavor: StoreCatalogProduct
@@ -8,9 +11,16 @@ struct PizzaFlavorAddonsSheet: View {
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
var body: some View { 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) { ScrollView(showsIndicators: false) {
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 12) {
screenHeader
Text(flavor.name) Text(flavor.name)
.font(AppTypography.heading2) .font(AppTypography.heading2)
.foregroundStyle(AppColors.textPrimary) .foregroundStyle(AppColors.textPrimary)
@@ -82,26 +92,6 @@ struct PizzaFlavorAddonsSheet: View {
.navigationBarBackButtonHidden(true) .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) { private func increment(_ addonId: String) {
quantities[addonId, default: 0] += 1 quantities[addonId, default: 0] += 1

View File

@@ -1,5 +1,8 @@
import Foundation import Foundation
import SwiftUI import SwiftUI
#if canImport(LCEssentials)
import LCEssentials
#endif
struct PizzaProductDetailSheet: View { struct PizzaProductDetailSheet: View {
let category: StoreCatalogCategory let category: StoreCatalogCategory
@@ -216,9 +219,16 @@ struct PizzaProductDetailSheet: View {
} }
var body: some 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) { ScrollView(showsIndicators: false) {
VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 16) {
screenHeader
Rectangle() Rectangle()
.fill(AppColors.brandSoft) .fill(AppColors.brandSoft)
.frame(maxWidth: .infinity) .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()
}
}
}
} }

View File

@@ -1,5 +1,8 @@
import Foundation import Foundation
import SwiftUI import SwiftUI
#if canImport(LCEssentials)
import LCEssentials
#endif
struct ProductDetailSheet: View { struct ProductDetailSheet: View {
let product: StoreCatalogProduct let product: StoreCatalogProduct
@@ -70,9 +73,16 @@ struct ProductDetailSheet: View {
} }
var body: some 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) { ScrollView(showsIndicators: false) {
VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 16) {
screenHeader
AsyncStoreImage(imageURL: imageURL) AsyncStoreImage(imageURL: imageURL)
.frame(height: 220) .frame(height: 220)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusXL, style: .continuous)) .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 { private func formatCurrency(_ value: Double) -> String {
String(format: "R$ %.2f", value).replacingOccurrences(of: ".", with: ",") String(format: "R$ %.2f", value).replacingOccurrences(of: ".", with: ",")

View File

@@ -1,4 +1,7 @@
import SwiftUI import SwiftUI
#if canImport(LCEssentials)
import LCEssentials
#endif
/// State -> city picker for anonymous browsing (public store locator). /// State -> city picker for anonymous browsing (public store locator).
/// Replaces AddressesView in the address-picker modal when the user is /// Replaces AddressesView in the address-picker modal when the user is
@@ -28,12 +31,18 @@ struct PublicLocationPickerView: View {
} }
var body: some 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 { ZStack {
AppColors.backgroundLight.ignoresSafeArea() AppColors.backgroundLight.ignoresSafeArea()
VStack(spacing: 20) { VStack(spacing: 20) {
header
if isLoading { if isLoading {
ProgressView() ProgressView()
.padding(.top, 40) .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 { private var list: some View {
ScrollView(showsIndicators: false) { ScrollView(showsIndicators: false) {
LazyVStack(spacing: 12) { LazyVStack(spacing: 12) {

View File

@@ -31,9 +31,21 @@ struct MyReviewsView: View {
} }
var body: some 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) { ScrollView(showsIndicators: false) {
VStack(spacing: 12) { VStack(spacing: 12) {
screenHeader
if isLoading && reviews.isEmpty && pendingReviews.isEmpty { if isLoading && reviews.isEmpty && pendingReviews.isEmpty {
ProgressView() ProgressView()
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
@@ -69,13 +81,6 @@ struct MyReviewsView: View {
.padding(.bottom, UIDevice.bottomNotch + 30) .padding(.bottom, UIDevice.bottomNotch + 30)
} }
.background(AppColors.backgroundLight) .background(AppColors.backgroundLight)
.appHiddenNavigationBar()
.navigationBarBackButtonHidden(true)
.navigationDestination(item: $selectedDraft) { draft in
OrderReviewView(draft: draft) {
Task { await loadReviewsFromBackend(forceRefresh: true) }
}
}
.onAppear { .onAppear {
Task { await loadReviewsFromBackend(forceRefresh: true) } 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 { private var emptyState: some View {
VStack(spacing: 8) { VStack(spacing: 8) {
Text("Você não tem avaliações nem pendências no momento.") Text("Você não tem avaliações nem pendências no momento.")
@@ -458,9 +442,16 @@ struct OrderReviewView: View {
@State var orderItems: [PublicOrderItem] = [] @State var orderItems: [PublicOrderItem] = []
var body: some View { 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) { ScrollView(showsIndicators: false) {
VStack(alignment: .leading, spacing: 24) { VStack(alignment: .leading, spacing: 24) {
screenHeader
topSection topSection
orderItemsSection orderItemsSection
if existingReview != nil { 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 { private var newReviewContent: some View {
VStack(alignment: .leading, spacing: 24) { VStack(alignment: .leading, spacing: 24) {
starsSection starsSection

View File

@@ -1,4 +1,7 @@
import SwiftUI import SwiftUI
#if canImport(LCEssentials)
import LCEssentials
#endif
struct SavedCardsView: View { struct SavedCardsView: View {
@Binding var appState: AppState @Binding var appState: AppState
@@ -15,13 +18,19 @@ struct SavedCardsView: View {
private let tabBarClearance: CGFloat = 96 private let tabBarClearance: CGFloat = 96
var body: some View { 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 { ZStack {
AppColors.backgroundLight.ignoresSafeArea() AppColors.backgroundLight.ignoresSafeArea()
ScrollView(showsIndicators: false) { ScrollView(showsIndicators: false) {
VStack(spacing: 20) { VStack(spacing: 20) {
screenHeader
VStack(spacing: 12) { VStack(spacing: 12) {
if isLoading { if isLoading {
ProgressView() ProgressView()
@@ -86,27 +95,6 @@ struct SavedCardsView: View {
.task { await loadCards() } .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 { private var addCardButton: some View {
ZStack(alignment: .bottom) { ZStack(alignment: .bottom) {
Rectangle() Rectangle()

View File

@@ -3,6 +3,9 @@ import SwiftUI
import PhotosUI import PhotosUI
import UIKit import UIKit
#endif #endif
#if canImport(LCEssentials)
import LCEssentials
#endif
struct UserProfileView: View { struct UserProfileView: View {
@Binding var appState: AppState @Binding var appState: AppState
@@ -26,9 +29,16 @@ struct UserProfileView: View {
#endif #endif
var body: some View { 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) { ScrollView(showsIndicators: false) {
VStack(spacing: 22) { VStack(spacing: 22) {
screenHeader
avatarSection avatarSection
formSection formSection
preferencesSection preferencesSection
@@ -57,27 +67,6 @@ struct UserProfileView: View {
#endif #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 { private var avatarSection: some View {
VStack(spacing: 12) { VStack(spacing: 12) {
Circle() Circle()