This commit is contained in:
Daniel Arantes Loverde
2026-04-28 09:26:08 -03:00
parent e54c0a029f
commit a12cfb6bf3
70 changed files with 335 additions and 230 deletions

View File

@@ -928,3 +928,5 @@ Para dashboard da loja e análise de produto, usar:
**Compatibilidade Store:**
- O painel `/store/reviews` segue funcional com os campos legados (`rate`, `message`, `itemFeedback`, `improvementFeedback`, `deliveryFeedback`).
- Campos novos coexistem para evolução gradual sem quebrar o fluxo atual.
Autor: Daniel Arantes Loverde

View File

@@ -638,3 +638,5 @@ Assina o contrato draft atual da loja.
### 14. Visualizar PDF do Contrato
**GET** `/api/store/contract/:contractId/pdf`
Retorna o stream do arquivo PDF do contrato (draft ou assinado).
Autor: Daniel Arantes Loverde

View File

@@ -98,3 +98,5 @@ FEATURE_CONTROL_MODULE_TOKEN="550e8400-e29b-41d4-a716-44665544000b" npm start
cd feature-control-bff
npm test
```
Autor: Daniel Arantes Loverde

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

@@ -1,116 +1,44 @@
{
"images": [
{
"idiom": "iphone",
"size": "20x20",
"scale": "2x",
"filename": "icon_iphone_20pt@2x.png"
},
{
"idiom": "iphone",
"size": "20x20",
"scale": "3x",
"filename": "icon_iphone_20pt@3x.png"
},
{
"idiom": "iphone",
"size": "29x29",
"scale": "2x",
"filename": "icon_iphone_29pt@2x.png"
},
{
"idiom": "iphone",
"size": "29x29",
"scale": "3x",
"filename": "icon_iphone_29pt@3x.png"
},
{
"idiom": "iphone",
"size": "40x40",
"scale": "2x",
"filename": "icon_iphone_40pt@2x.png"
},
{
"idiom": "iphone",
"size": "40x40",
"scale": "3x",
"filename": "icon_iphone_40pt@3x.png"
},
{
"idiom": "iphone",
"size": "60x60",
"scale": "2x",
"filename": "icon_iphone_60pt@2x.png"
},
{
"idiom": "iphone",
"size": "60x60",
"scale": "3x",
"filename": "icon_iphone_60pt@3x.png"
},
{
"idiom": "ipad",
"size": "20x20",
"scale": "1x",
"filename": "icon_ipad_20pt@1x.png"
},
{
"idiom": "ipad",
"size": "20x20",
"scale": "2x",
"filename": "icon_ipad_20pt@2x.png"
},
{
"idiom": "ipad",
"size": "29x29",
"scale": "1x",
"filename": "icon_ipad_29pt@1x.png"
},
{
"idiom": "ipad",
"size": "29x29",
"scale": "2x",
"filename": "icon_ipad_29pt@2x.png"
},
{
"idiom": "ipad",
"size": "40x40",
"scale": "1x",
"filename": "icon_ipad_40pt@1x.png"
},
{
"idiom": "ipad",
"size": "40x40",
"scale": "2x",
"filename": "icon_ipad_40pt@2x.png"
},
{
"idiom": "ipad",
"size": "76x76",
"scale": "1x",
"filename": "icon_ipad_76pt@1x.png"
},
{
"idiom": "ipad",
"size": "76x76",
"scale": "2x",
"filename": "icon_ipad_76pt@2x.png"
},
{
"idiom": "ipad",
"size": "83.5x83.5",
"scale": "2x",
"filename": "icon_ipad_83.5pt@2x.png"
},
{
"idiom": "ios-marketing",
"size": "1024x1024",
"scale": "1x",
"filename": "icon_ios-marketing_1024pt@1x.png"
}
"images" : [
{ "filename" : "icon_iphone_20pt@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "20x20" },
{ "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "icon_iphone_20pt@2x_dark.png", "idiom" : "iphone", "scale" : "2x", "size" : "20x20" },
{ "filename" : "icon_iphone_20pt@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "20x20" },
{ "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "icon_iphone_20pt@3x_dark.png", "idiom" : "iphone", "scale" : "3x", "size" : "20x20" },
{ "filename" : "icon_iphone_29pt@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "29x29" },
{ "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "icon_iphone_29pt@2x_dark.png", "idiom" : "iphone", "scale" : "2x", "size" : "29x29" },
{ "filename" : "icon_iphone_29pt@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "29x29" },
{ "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "icon_iphone_29pt@3x_dark.png", "idiom" : "iphone", "scale" : "3x", "size" : "29x29" },
{ "filename" : "icon_iphone_40pt@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "40x40" },
{ "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "icon_iphone_40pt@2x_dark.png", "idiom" : "iphone", "scale" : "2x", "size" : "40x40" },
{ "filename" : "icon_iphone_40pt@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "40x40" },
{ "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "icon_iphone_40pt@3x_dark.png", "idiom" : "iphone", "scale" : "3x", "size" : "40x40" },
{ "filename" : "icon_iphone_60pt@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "60x60" },
{ "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "icon_iphone_60pt@2x_dark.png", "idiom" : "iphone", "scale" : "2x", "size" : "60x60" },
{ "filename" : "icon_iphone_60pt@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "60x60" },
{ "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "icon_iphone_60pt@3x_dark.png", "idiom" : "iphone", "scale" : "3x", "size" : "60x60" },
{ "filename" : "icon_ipad_20pt@1x.png", "idiom" : "ipad", "scale" : "1x", "size" : "20x20" },
{ "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "icon_ipad_20pt@1x_dark.png", "idiom" : "ipad", "scale" : "1x", "size" : "20x20" },
{ "filename" : "icon_ipad_20pt@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "20x20" },
{ "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "icon_ipad_20pt@2x_dark.png", "idiom" : "ipad", "scale" : "2x", "size" : "20x20" },
{ "filename" : "icon_ipad_29pt@1x.png", "idiom" : "ipad", "scale" : "1x", "size" : "29x29" },
{ "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "icon_ipad_29pt@1x_dark.png", "idiom" : "ipad", "scale" : "1x", "size" : "29x29" },
{ "filename" : "icon_ipad_29pt@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "29x29" },
{ "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "icon_ipad_29pt@2x_dark.png", "idiom" : "ipad", "scale" : "2x", "size" : "29x29" },
{ "filename" : "icon_ipad_40pt@1x.png", "idiom" : "ipad", "scale" : "1x", "size" : "40x40" },
{ "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "icon_ipad_40pt@1x_dark.png", "idiom" : "ipad", "scale" : "1x", "size" : "40x40" },
{ "filename" : "icon_ipad_40pt@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "40x40" },
{ "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "icon_ipad_40pt@2x_dark.png", "idiom" : "ipad", "scale" : "2x", "size" : "40x40" },
{ "filename" : "icon_ipad_76pt@1x.png", "idiom" : "ipad", "scale" : "1x", "size" : "76x76" },
{ "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "icon_ipad_76pt@1x_dark.png", "idiom" : "ipad", "scale" : "1x", "size" : "76x76" },
{ "filename" : "icon_ipad_76pt@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "76x76" },
{ "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "icon_ipad_76pt@2x_dark.png", "idiom" : "ipad", "scale" : "2x", "size" : "76x76" },
{ "filename" : "icon_ipad_83.5pt@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" },
{ "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "icon_ipad_83.5pt@2x_dark.png", "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" },
{ "filename" : "icon_ios-marketing_1024pt@1x.png", "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" },
{ "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "filename" : "icon_ios-marketing_1024pt@1x_dark.png", "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" }
],
"info": {
"version": 1,
"author": "xcode"
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

@@ -1,4 +1,4 @@
# Plano - Tela de Perfil
e# Plano - Tela de Perfil
Legenda:
- 🟢 Concluído
@@ -28,3 +28,4 @@ Legenda:
- Configurações/Ajuda (confirmar nomenclatura final)
- 🔴 Corrigir build global do projeto (dependência `LCEssentials` ausente em `StoreDetailView.swift`)
Autor: Daniel Arantes Loverde

View File

@@ -49,3 +49,4 @@ Implicação:
1. Mudança de splash para iOS pode ser feita sem tocar Android.
2. Quando Android for tratado, deve entrar em camada própria (tema/recursos Android), sem impactar o fluxo iOS.
Autor: Daniel Arantes Loverde

View File

@@ -58,3 +58,5 @@ Kotlin JUnit tests in the Robolectric Android simulation environment.
Parity testing can be performed with `skip test`,
which will output a table of the test results for both platforms.
Autor: Daniel Arantes Loverde

View File

@@ -58,7 +58,7 @@ struct SecondaryButton: View {
image
}
}
.foregroundStyle(AppColors.textInverse)
.foregroundStyle(AppColors.textPrimary)
.frame(maxWidth: fullWidth ? .infinity : nil)
.frame(height: 56)
.background(AppColors.secondary)

View File

@@ -7,7 +7,7 @@ struct SearchField: View {
var body: some View {
HStack(spacing: 12) {
Image(systemName: "magnifyingglass")
.foregroundStyle(AppColors.secondary)
.foregroundStyle(AppColors.primary)
TextField(placeholder, text: $text)
.appNoAutoCap()
}
@@ -17,7 +17,7 @@ struct SearchField: View {
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
.overlay(
RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous)
.stroke(AppColors.secondary.opacity(0.15), lineWidth: 1)
.stroke(AppColors.primary.opacity(0.15), lineWidth: 1)
)
}
}
@@ -30,14 +30,14 @@ struct PillButton: View {
Text(title)
.font(AppTypography.caption)
.tracking(AppTypography.captionLetterSpacing)
.foregroundStyle(isActive ? AppColors.textInverse : AppColors.secondary)
.foregroundStyle(isActive ? AppColors.textInverse : AppColors.primary)
.padding(.horizontal, 14)
.padding(.vertical, 8)
.background(isActive ? AppColors.primary : AppColors.backgroundLight)
.clipShape(Capsule())
.overlay(
Capsule()
.stroke(AppColors.secondary.opacity(0.15), lineWidth: isActive ? 0 : 1)
.stroke(AppColors.primary.opacity(0.15), lineWidth: isActive ? 0 : 1)
)
}
}

View File

@@ -1,28 +1,36 @@
import SwiftUI
#if canImport(UIKit)
import UIKit
#elseif canImport(AppKit)
import AppKit
#endif
// MARK: - Colors
enum AppColors {
static let primary = Color(hex: "#1E6B43")
static let secondary = Color(hex: "#2F7C4B")
static let tertiary = Color(hex: "#8CFF2E")
// Brandbook 2026 (Pedi Foods)
// Primary now follows the "FOODS" dark green tone from the logo.
static let primary = Color(hex: "#345D54")
// Secondary keeps the vivid lime from the symbol/logo body.
static let secondary = Color(hex: "#D5D841")
static let tertiary = Color(hex: "#A7BF42")
static let brandDark = Color(hex: "#1B5C3A")
static let brandSoft = Color(hex: "#E8F2EC")
static let brandDark = Color(hex: "#1F2D40")
static let brandSoft = Color(hex: "#F2F5E3")
static let backgroundLight = Color(hex: "#F3F5F7")
static let backgroundDark = Color(hex: "#18230F")
static let backgroundDark = Color(hex: "#1F2D40")
static let surface = Color(hex: "#FFFFFF")
static let textPrimary = Color(hex: "#1C1F23")
static let textPrimary = Color(hex: "#000000")
static let textInverse = Color(hex: "#FFFFFF")
static let textMuted = Color(hex: "#7B8794")
static let textMuted = Color(hex: "#667085")
}
enum AppDarkColors {
static let background = Color(hex: "#18230F")
static let surface = Color(hex: "#FFFFFF", alpha: 0.05)
static let background = Color(hex: "#1F2D40")
static let surface = Color(hex: "#FFFFFF", alpha: 0.08)
static let textPrimary = Color(hex: "#FFFFFF")
static let textSecondary = Color(hex: "#A3A3A3")
static let textSecondary = Color(hex: "#D0D5DD")
static let primary = AppColors.primary
static let secondary = AppColors.secondary
static let tertiary = AppColors.tertiary
@@ -31,22 +39,38 @@ enum AppDarkColors {
// MARK: - Typography
enum AppTypography {
static let fontFamily = "Plus Jakarta Sans"
// Brandbook typography: Nexa (fallback: system font)
static let fontFamily = "Nexa-Regular"
// Avoid applying dynamic weight on custom font descriptors to prevent
// SwiftUI runtime warnings on some platforms/toolchains.
static let heading1 = Font.custom(fontFamily, size: 28)
static let heading25 = Font.custom(fontFamily, size: 25)
static let heading2 = Font.custom(fontFamily, size: 20)
static let heading3 = Font.custom(fontFamily, size: 16)
static let body = Font.custom(fontFamily, size: 16)
static let button = Font.custom(fontFamily, size: 14)
static let caption = Font.custom(fontFamily, size: 10)
static let overline = Font.custom(fontFamily, size: 11)
static let heading1 = resolvedFont(size: 28, fallbackWeight: .bold)
static let heading25 = resolvedFont(size: 25, fallbackWeight: .bold)
static let heading2 = resolvedFont(size: 20, fallbackWeight: .semibold)
static let heading3 = resolvedFont(size: 16, fallbackWeight: .semibold)
static let body = resolvedFont(size: 16, fallbackWeight: .regular)
static let button = resolvedFont(size: 14, fallbackWeight: .semibold)
static let caption = resolvedFont(size: 10, fallbackWeight: .regular)
static let overline = resolvedFont(size: 11, fallbackWeight: .regular)
static let bodyLineHeight: CGFloat = 1.6
static let buttonLetterSpacing: CGFloat = 0.08
static let captionLetterSpacing: CGFloat = 0.12
private static func resolvedFont(size: CGFloat, fallbackWeight: Font.Weight) -> Font {
if isBrandFontAvailable {
return Font.custom(fontFamily, size: size)
}
return .system(size: size, weight: fallbackWeight, design: .default)
}
private static var isBrandFontAvailable: Bool {
#if canImport(UIKit)
return UIFont(name: fontFamily, size: 16) != nil
#elseif canImport(AppKit)
return NSFont(name: fontFamily, size: 16) != nil
#else
return false
#endif
}
}
// MARK: - Layout

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

View File

@@ -14,8 +14,83 @@
],
"groups" : [
{
"hidden" : false,
"layers" : [
{
"glass-specializations" : [
{
"appearance" : "dark",
"value" : true
}
],
"hidden" : false,
"image-name" : "Screenshot 2026-04-25 at 16.22.36.png",
"name" : "Screenshot 2026-04-25 at 16.22.36",
"opacity-specializations" : [
{
"value" : 0
},
{
"appearance" : "dark",
"value" : 1
}
],
"position-specializations" : [
{
"idiom" : "iOS",
"value" : {
"scale" : 1.24,
"translation-in-points" : [
0,
0
]
}
},
{
"idiom" : "watchOS",
"value" : {
"scale" : 1.54,
"translation-in-points" : [
0,
0
]
}
}
]
},
{
"hidden" : false,
"image-name" : "Screenshot 2026-04-25 at 16.22.59.png",
"name" : "Screenshot 2026-04-25 at 16.22.59",
"position-specializations" : [
{
"idiom" : "iOS",
"value" : {
"scale" : 1.24,
"translation-in-points" : [
0,
0
]
}
},
{
"idiom" : "watchOS",
"value" : {
"scale" : 1.54,
"translation-in-points" : [
0,
0
]
}
}
]
}
],
"opacity-specializations" : [
{
"appearance" : "dark",
"value" : 1
}
],
"shadow" : {
"kind" : "neutral",
@@ -27,54 +102,18 @@
}
},
{
"layers" : [
"blend-mode" : "normal",
"hidden-specializations" : [
{
"blend-mode-specializations" : [
{
"value" : "normal"
},
{
"appearance" : "dark",
"value" : "normal"
}
],
"fill-specializations" : [
{
"value" : "none"
},
{
"appearance" : "dark",
"value" : {
"solid" : "srgb:0.00000,0.50000,0.04260,1.00000"
}
}
],
"glass" : false,
"hidden" : false,
"image-name" : "pedifoods_big.png",
"name" : "pedifoods_big",
"position-specializations" : [
{
"value" : {
"scale" : 1,
"translation-in-points" : [
137.390625,
11.984375
]
}
},
{
"idiom" : "square",
"value" : {
"scale" : 0.35,
"translation-in-points" : [
8,
0
]
}
}
]
"value" : false
},
{
"idiom" : "iOS",
"value" : false
}
],
"layers" : [
],
"shadow" : {
"kind" : "neutral",
@@ -87,6 +126,9 @@
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : [
"iOS"
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

@@ -258,12 +258,12 @@ final class FeatureControlService {
}
private func featureKeys() -> [String] {
let raw = ProcessInfo.processInfo.environment["FEATURE_CONTROL_KEYS"] ?? "fc.promo,fc.promo-codes,fc.android,fc.city-aguai,fc.ios"
let raw = ProcessInfo.processInfo.environment["FEATURE_CONTROL_KEYS"] ?? "at.city.aguai,at.promo,at.ios.only,at.android.only"
let items = raw
.split(separator: ",")
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { $0.isEmpty == false }
return items.isEmpty ? ["fc.ios"] : items
return items.isEmpty ? ["at.ios.only"] : items
}
private func platformName() -> String {

View File

@@ -62,6 +62,16 @@ struct FeatureFlagsState: Codable, Equatable {
var source: String = "default"
var values: [String: FeatureFlagValue] = [:]
var raw: [String: FeatureControlRawFlag] = [:]
func isEnabled(_ key: String, default defaultValue: Bool = false) -> Bool {
if let rawValue = raw[key] {
return rawValue.enabled || rawValue.variant.lowercased() == "on"
}
if let mapped = values[key] {
return mapped.boolValue
}
return defaultValue
}
}
enum AppModal: String, Identifiable {

View File

@@ -67,7 +67,9 @@ extension View {
#if os(macOS)
self
#else
self.navigationBarTitleDisplayMode(.inline)
self
.navigationBarTitleDisplayMode(.inline)
.modifier(AppRoundedBackButtonModifier())
#endif
}
@@ -139,3 +141,28 @@ extension View {
#endif
}
}
#if !os(macOS)
private struct AppRoundedBackButtonModifier: ViewModifier {
@Environment(\.dismiss) private var dismiss
func body(content: Content) -> some View {
content
.navigationBarBackButtonHidden(true)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
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)
}
}
}
}
#endif

View File

@@ -47,6 +47,13 @@ struct AddAddressFormView: View {
ScrollView {
VStack(spacing: 0) {
screenHeader(
title: existingAddress == nil ? "Novo endereço" : "Editar endereço",
onBack: { dismiss() }
)
.padding(.horizontal, 24)
.padding(.bottom, 12)
logoImage
.scaledToFit()
.frame(width: 120, height: 120)
@@ -134,6 +141,28 @@ struct AddAddressFormView: View {
}
}
private func screenHeader(title: String, onBack: @escaping () -> Void) -> some View {
ZStack {
Text(title)
.font(AppTypography.heading2)
.foregroundStyle(colorScheme == .dark ? Color.white : 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

@@ -22,7 +22,7 @@ struct AddressesView: View {
ScrollView(showsIndicators: false) {
VStack(spacing: 20) {
header
screenHeader(title: "Meus Endereços", onBack: { dismiss() })
if let message {
Text(message)
@@ -131,28 +131,6 @@ struct AddressesView: View {
}
}
var header: some View {
ZStack {
Text("Meus Endereços")
.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)
}
Spacer()
}
}
}
var bottomOverlay: some View {
ZStack(alignment: .bottom) {
Rectangle()
@@ -181,6 +159,28 @@ 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

@@ -121,15 +121,17 @@ struct HomeView: View {
}
}
section(title: "#PediPromo") {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
ForEach(specials) { item in
SpecialOfferCard(model: item)
.frame(width: 260, height: 120)
if appState.featureFlags.isEnabled("at.promo") {
section(title: "#PediPromo") {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
ForEach(specials) { item in
SpecialOfferCard(model: item)
.frame(width: 260, height: 120)
}
}
.padding(.horizontal, 20)
}
.padding(.horizontal, 20)
}
}
@@ -204,7 +206,7 @@ struct HomeView: View {
return ZStack(alignment: .top) {
RoundedRectangle(cornerRadius: 32, style: .continuous)
.fill(AppColors.brandDark)
.fill(AppColors.primary)
.frame(height: height)
.overlay(headerRings.opacity(1 - collapseProgress), alignment: .topTrailing)

View File

@@ -28,7 +28,7 @@ struct CategoryChip: View {
.foregroundStyle(isActive ? AppColors.textInverse : AppColors.textPrimary)
.padding(.horizontal, 16)
.padding(.vertical, 10)
.background(isActive ? AppColors.brandDark : AppColors.surface)
.background(isActive ? AppColors.primary : AppColors.surface)
.clipShape(Capsule())
}
}

View File

@@ -72,12 +72,12 @@ struct MainTabView: View {
.font(AppTypography.heading3)
}
}
.foregroundStyle(isActive ? AppColors.primary : AppColors.textPrimary)
.foregroundStyle(isActive ? AppColors.textInverse : AppColors.textPrimary)
.padding(.horizontal, 18)
.padding(.vertical, 10)
.background(
Capsule()
.fill(isActive ? AppColors.brandSoft : Color.clear)
.fill(isActive ? AppColors.primary : Color.clear)
)
}
.buttonStyle(.plain)

View File

@@ -1,6 +1,7 @@
import SwiftUI
struct OrdersView: View {
@Environment(\.dismiss) var dismiss
@State var isLoading = false
@State var errorMessage: String? = nil
@State var orders: [AppOrderSummary] = []
@@ -12,6 +13,8 @@ struct OrdersView: View {
var body: some View {
ScrollView(showsIndicators: false) {
VStack(alignment: .leading, spacing: 14) {
screenHeader(title: "Meus Pedidos", onBack: { dismiss() })
if isLoading {
ProgressView()
.frame(maxWidth: .infinity)
@@ -35,11 +38,12 @@ struct OrdersView: View {
}
}
.padding(.horizontal, 20)
.padding(.top, 18)
.padding(.bottom, UIDevice.bottomNotch + 18)
}
.background(AppColors.backgroundLight)
.navigationTitle("Meus Pedidos")
.appInlineNavigationTitle()
.navigationBarBackButtonHidden(true)
.appHiddenNavigationBar()
.task {
await loadOrdersIfNeeded()
await refreshStoreRatings()
@@ -59,6 +63,28 @@ 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(
@@ -81,7 +107,6 @@ struct OrdersView: View {
AsyncStoreImage(imageURL: resolvedMediaURL(order.storeLogoURL))
.frame(width: 80, height: 80)
.clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous))
.background(AppColors.brandSoft, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
VStack(alignment: .leading, spacing: 5) {

View File

@@ -74,7 +74,7 @@ struct StoreDetailView: View {
.refreshable {
await loadStoreData(forceRefresh: true)
}
.appNamedCoordinateSpace(StoreDetailScrollCoordinateSpace.name)
.coordinateSpace(name: StoreDetailScrollCoordinateSpace.name)
.ignoresSafeArea(edges: .top)
}
.ignoresSafeArea(edges: .top)
@@ -286,7 +286,7 @@ struct StoreDetailView: View {
HStack(spacing: 0) {
statItem(title: "DISTÂNCIA", value: distanceValueLabel)
Divider().frame(height: 34)
statItem(title: "TEMPO", value: info?.deliveryTime ?? "25-35 min")
statItem(title: "TEMPO", value: info?.deliveryTime ?? "--")
Divider().frame(height: 34)
statItem(title: "ENTREGA", value: deliveryValueLabel)
}

View File

@@ -131,3 +131,5 @@
3. `[🟥]` Implementar carrinho real (itens, subtotal, edicao) para destravar checkout.
4. `[🟥]` Implementar mapa e tracking de pedidos.
5. `[🟥]` Corrigir bloqueio de build por dependencia (`LCEssentials`) para fechar validacao iOS/Android ponta a ponta.
Autor: Daniel Arantes Loverde

View File

@@ -179,3 +179,5 @@ Icon {
active?: boolean
}
```
Autor: Daniel Arantes Loverde

View File

@@ -359,3 +359,5 @@ Legenda:
- Build Swift/Kotlin finalizado.
- Etapa de conexão Android falhou com `adb devices` vazio no ambiente atual.
- Próximo passo para concluir Task 6: conectar device físico (USB) ou iniciar AVD válido.
Autor: Daniel Arantes Loverde

View File

@@ -742,3 +742,5 @@ Implementar assim:
- nova alteração somente após 90 dias
- aviso visual claro antes de salvar
- registro do slug antigo para redirect temporário
Autor: Daniel Arantes Loverde