[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

@@ -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 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()
}
}
}
}

View File

@@ -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]? = {

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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() {

View File

@@ -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()

View File

@@ -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()

View File

@@ -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(

View File

@@ -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

View File

@@ -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()
}
}
}
}

View File

@@ -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: ",")

View File

@@ -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) {

View File

@@ -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

View File

@@ -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()

View File

@@ -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()