fix(android): isolate platform-specific shared ui code
This commit is contained in:
72
pedi-foods/Sources/PediFoods/Support/PlatformCompat.swift
Normal file
72
pedi-foods/Sources/PediFoods/Support/PlatformCompat.swift
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import Foundation
|
||||||
|
#if canImport(SwiftUI)
|
||||||
|
import SwiftUI
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if canImport(CoreGraphics)
|
||||||
|
import CoreGraphics
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if canImport(UIKit)
|
||||||
|
import UIKit
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !canImport(UIKit)
|
||||||
|
struct UIDevice {
|
||||||
|
static let topNotch: CGFloat = 0
|
||||||
|
static let bottomNotch: CGFloat = 0
|
||||||
|
|
||||||
|
var modelName: String {
|
||||||
|
"android"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !(canImport(LCEssentials) && os(iOS))
|
||||||
|
func printLog(title: String, msg: String) {
|
||||||
|
print("[\(title)] \(msg)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func printError(title: String, msg: String) {
|
||||||
|
print("[\(title)] \(msg)")
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
@ViewBuilder
|
||||||
|
func appInlineNavigationTitle() -> some View {
|
||||||
|
#if os(macOS)
|
||||||
|
self
|
||||||
|
#else
|
||||||
|
self.navigationBarTitleDisplayMode(.inline)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
func appHiddenNavigationBar() -> some View {
|
||||||
|
#if os(macOS)
|
||||||
|
self
|
||||||
|
#else
|
||||||
|
self
|
||||||
|
.toolbar(.hidden, for: .navigationBar)
|
||||||
|
.toolbarBackground(.hidden, for: .navigationBar)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
func appTopBarTrailingToolbar<Content: View>(@ViewBuilder content: () -> Content) -> some View {
|
||||||
|
#if os(macOS)
|
||||||
|
self.toolbar {
|
||||||
|
ToolbarItem {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
self.toolbar {
|
||||||
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ struct TermsOfUseView: View {
|
|||||||
.background((colorScheme == .dark ? Color.black : AppColors.backgroundLight).ignoresSafeArea())
|
.background((colorScheme == .dark ? Color.black : AppColors.backgroundLight).ignoresSafeArea())
|
||||||
.navigationTitle("Termos de Uso")
|
.navigationTitle("Termos de Uso")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.appInlineNavigationTitle()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,7 @@ struct PrivacyPolicyView: View {
|
|||||||
.background((colorScheme == .dark ? Color.black : AppColors.backgroundLight).ignoresSafeArea())
|
.background((colorScheme == .dark ? Color.black : AppColors.backgroundLight).ignoresSafeArea())
|
||||||
.navigationTitle("Privacidade")
|
.navigationTitle("Privacidade")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.appInlineNavigationTitle()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ struct AddAddressFormView: View {
|
|||||||
.padding(.top, 0)
|
.padding(.top, 0)
|
||||||
}
|
}
|
||||||
.navigationBarBackButtonHidden(true)
|
.navigationBarBackButtonHidden(true)
|
||||||
.toolbar(.hidden, for: .navigationBar)
|
.appHiddenNavigationBar()
|
||||||
.onAppear {
|
.onAppear {
|
||||||
populateFromExistingAddressIfNeeded()
|
populateFromExistingAddressIfNeeded()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ struct AddressesView: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
.navigationBarBackButtonHidden(true)
|
.navigationBarBackButtonHidden(true)
|
||||||
.toolbar(.hidden, for: .navigationBar)
|
.appHiddenNavigationBar()
|
||||||
.sheet(isPresented: $openAddAddressForm) {
|
.sheet(isPresented: $openAddAddressForm) {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
AddAddressFormView(existingAddress: editingAddress) { updatedAddresses, wasEditing in
|
AddAddressFormView(existingAddress: editingAddress) { updatedAddresses, wasEditing in
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ struct CheckoutView: View {
|
|||||||
}
|
}
|
||||||
.background(AppColors.backgroundLight)
|
.background(AppColors.backgroundLight)
|
||||||
.navigationTitle("Finalizar Pedido")
|
.navigationTitle("Finalizar Pedido")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.appInlineNavigationTitle()
|
||||||
.safeAreaInset(edge: .bottom) {
|
.safeAreaInset(edge: .bottom) {
|
||||||
bottomBar
|
bottomBar
|
||||||
}
|
}
|
||||||
@@ -659,7 +659,7 @@ struct PaymentPixView: View {
|
|||||||
}
|
}
|
||||||
.background(AppColors.backgroundLight)
|
.background(AppColors.backgroundLight)
|
||||||
.navigationTitle("Pagamento via PIX")
|
.navigationTitle("Pagamento via PIX")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.appInlineNavigationTitle()
|
||||||
.task {
|
.task {
|
||||||
tracker.onOrderUpdated = { updated in
|
tracker.onOrderUpdated = { updated in
|
||||||
latestOrder = updated
|
latestOrder = updated
|
||||||
@@ -851,7 +851,7 @@ struct PaymentCardView: View {
|
|||||||
}
|
}
|
||||||
.background(AppColors.backgroundLight)
|
.background(AppColors.backgroundLight)
|
||||||
.navigationTitle("Pagamento")
|
.navigationTitle("Pagamento")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.appInlineNavigationTitle()
|
||||||
.task {
|
.task {
|
||||||
tracker.onOrderUpdated = { updated in
|
tracker.onOrderUpdated = { updated in
|
||||||
latestOrder = updated
|
latestOrder = updated
|
||||||
@@ -879,8 +879,7 @@ struct PaymentCardView: View {
|
|||||||
.font(AppTypography.caption)
|
.font(AppTypography.caption)
|
||||||
.foregroundStyle(AppColors.textMuted)
|
.foregroundStyle(AppColors.textMuted)
|
||||||
TextField(placeholder, text: text)
|
TextField(placeholder, text: text)
|
||||||
.textInputAutocapitalization(.never)
|
.appNoAutoCap()
|
||||||
.disableAutocorrection(true)
|
|
||||||
.padding(.horizontal, 12)
|
.padding(.horizontal, 12)
|
||||||
.frame(height: 46)
|
.frame(height: 46)
|
||||||
.background(AppColors.backgroundLight)
|
.background(AppColors.backgroundLight)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ struct OrderDetailsView: View {
|
|||||||
}
|
}
|
||||||
.background(AppColors.backgroundLight)
|
.background(AppColors.backgroundLight)
|
||||||
.navigationTitle("Detalhes do Pedido")
|
.navigationTitle("Detalhes do Pedido")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.appInlineNavigationTitle()
|
||||||
.safeAreaInset(edge: .bottom) {
|
.safeAreaInset(edge: .bottom) {
|
||||||
VStack {
|
VStack {
|
||||||
reorderButton
|
reorderButton
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ struct OrderTrackingView: View {
|
|||||||
}
|
}
|
||||||
.background(AppColors.backgroundLight)
|
.background(AppColors.backgroundLight)
|
||||||
.navigationTitle("Pedido \(displayOrderTitle)")
|
.navigationTitle("Pedido \(displayOrderTitle)")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.appInlineNavigationTitle()
|
||||||
.alert("Motivo do cancelamento", isPresented: $showCancellationReason) {
|
.alert("Motivo do cancelamento", isPresented: $showCancellationReason) {
|
||||||
Button("Fechar", role: .cancel) {}
|
Button("Fechar", role: .cancel) {}
|
||||||
} message: {
|
} message: {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ struct OrdersView: View {
|
|||||||
}
|
}
|
||||||
.background(AppColors.backgroundLight)
|
.background(AppColors.backgroundLight)
|
||||||
.navigationTitle("Meus Pedidos")
|
.navigationTitle("Meus Pedidos")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.appInlineNavigationTitle()
|
||||||
.task {
|
.task {
|
||||||
await loadOrdersIfNeeded()
|
await loadOrdersIfNeeded()
|
||||||
await refreshStoreRatings()
|
await refreshStoreRatings()
|
||||||
|
|||||||
@@ -78,14 +78,12 @@ struct PizzaFlavorAddonsSheet: View {
|
|||||||
}
|
}
|
||||||
.background(AppColors.backgroundLight.ignoresSafeArea())
|
.background(AppColors.backgroundLight.ignoresSafeArea())
|
||||||
.navigationTitle("Adicionais")
|
.navigationTitle("Adicionais")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.appInlineNavigationTitle()
|
||||||
.toolbar {
|
.appTopBarTrailingToolbar {
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
|
||||||
Button("Concluir") { dismiss() }
|
Button("Concluir") { dismiss() }
|
||||||
.foregroundStyle(AppColors.primary)
|
.foregroundStyle(AppColors.primary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private func increment(_ addonId: String) {
|
private func increment(_ addonId: String) {
|
||||||
quantities[addonId, default: 0] += 1
|
quantities[addonId, default: 0] += 1
|
||||||
|
|||||||
@@ -292,13 +292,11 @@ struct PizzaProductDetailSheet: View {
|
|||||||
}
|
}
|
||||||
.background(AppColors.backgroundLight.ignoresSafeArea())
|
.background(AppColors.backgroundLight.ignoresSafeArea())
|
||||||
.navigationTitle("Monte sua pizza")
|
.navigationTitle("Monte sua pizza")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.appInlineNavigationTitle()
|
||||||
.toolbar {
|
.appTopBarTrailingToolbar {
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
|
||||||
Button("Fechar") { dismiss() }
|
Button("Fechar") { dismiss() }
|
||||||
.foregroundStyle(AppColors.primary)
|
.foregroundStyle(AppColors.primary)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.onAppear {
|
.onAppear {
|
||||||
applyAutoSelections()
|
applyAutoSelections()
|
||||||
let existing = currentQuantityForItemId(cartItemId)
|
let existing = currentQuantityForItemId(cartItemId)
|
||||||
|
|||||||
@@ -222,13 +222,11 @@ struct ProductDetailSheet: View {
|
|||||||
}
|
}
|
||||||
.background(AppColors.backgroundLight.ignoresSafeArea())
|
.background(AppColors.backgroundLight.ignoresSafeArea())
|
||||||
.navigationTitle("Detalhes")
|
.navigationTitle("Detalhes")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.appInlineNavigationTitle()
|
||||||
.toolbar {
|
.appTopBarTrailingToolbar {
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
|
||||||
Button("Fechar") { dismiss() }
|
Button("Fechar") { dismiss() }
|
||||||
.foregroundStyle(AppColors.primary)
|
.foregroundStyle(AppColors.primary)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.onAppear {
|
.onAppear {
|
||||||
let existing = currentQuantityForItemId(cartItemId)
|
let existing = currentQuantityForItemId(cartItemId)
|
||||||
quantity = existing > 0 ? existing : 1
|
quantity = existing > 0 ? existing : 1
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
#if canImport(LCEssentials) && os(iOS)
|
||||||
import LCEssentials
|
import LCEssentials
|
||||||
|
#endif
|
||||||
#if canImport(UIKit)
|
#if canImport(UIKit)
|
||||||
import UIKit
|
import UIKit
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ struct MyReviewsView: View {
|
|||||||
}
|
}
|
||||||
.background(AppColors.backgroundLight)
|
.background(AppColors.backgroundLight)
|
||||||
.navigationTitle("Minhas Avaliações")
|
.navigationTitle("Minhas Avaliações")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.appInlineNavigationTitle()
|
||||||
.navigationDestination(item: $selectedDraft) { draft in
|
.navigationDestination(item: $selectedDraft) { draft in
|
||||||
OrderReviewView(draft: draft) {
|
OrderReviewView(draft: draft) {
|
||||||
Task { await loadReviewsFromBackend(forceRefresh: true) }
|
Task { await loadReviewsFromBackend(forceRefresh: true) }
|
||||||
@@ -451,7 +451,7 @@ struct OrderReviewView: View {
|
|||||||
}
|
}
|
||||||
.background(AppColors.backgroundLight)
|
.background(AppColors.backgroundLight)
|
||||||
.navigationTitle("Avaliar Pedido")
|
.navigationTitle("Avaliar Pedido")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.appInlineNavigationTitle()
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if let cachedReview = SessionStateStore.loadOrderReview(orderId: draft.orderId) {
|
if let cachedReview = SessionStateStore.loadOrderReview(orderId: draft.orderId) {
|
||||||
existingReview = cachedReview
|
existingReview = cachedReview
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
#if canImport(LCEssentials) && os(iOS)
|
||||||
import LCEssentials
|
import LCEssentials
|
||||||
|
#endif
|
||||||
#if canImport(UIKit)
|
#if canImport(UIKit)
|
||||||
import UIKit
|
import UIKit
|
||||||
#endif
|
#endif
|
||||||
@@ -74,8 +76,7 @@ struct StoreDetailView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationBarBackButtonHidden(true)
|
.navigationBarBackButtonHidden(true)
|
||||||
.toolbar(.hidden, for: .navigationBar)
|
.appHiddenNavigationBar()
|
||||||
.toolbarBackground(.hidden, for: .navigationBar)
|
|
||||||
.task {
|
.task {
|
||||||
guard didLoad == false else { return }
|
guard didLoad == false else { return }
|
||||||
didLoad = true
|
didLoad = true
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ struct UserProfileView: View {
|
|||||||
}
|
}
|
||||||
.background(AppColors.backgroundLight)
|
.background(AppColors.backgroundLight)
|
||||||
.navigationTitle("Meu Perfil")
|
.navigationTitle("Meu Perfil")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.appInlineNavigationTitle()
|
||||||
.onAppear {
|
.onAppear {
|
||||||
hydrateFromAppState()
|
hydrateFromAppState()
|
||||||
}
|
}
|
||||||
|
|||||||
310
skip_for_android_plan.md
Normal file
310
skip_for_android_plan.md
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
# Plano de Ação - Skip para Android
|
||||||
|
|
||||||
|
## Objetivo
|
||||||
|
|
||||||
|
Preparar o projeto `PediFoods` para o primeiro ciclo real de testes no Android, preservando integralmente o funcionamento atual do iOS.
|
||||||
|
|
||||||
|
Regra principal deste plano:
|
||||||
|
|
||||||
|
1. Todo ajuste necessário para Android deve ser isolado e não pode causar regressão no iOS.
|
||||||
|
2. Sempre que possível, a separação deve ser feita com condicionais por plataforma, abstrações compatíveis ou implementações específicas para Android.
|
||||||
|
3. Cada task concluída e validada deve gerar commit próprio.
|
||||||
|
|
||||||
|
## Premissas confirmadas
|
||||||
|
|
||||||
|
1. O projeto já possui base Skip dual-platform.
|
||||||
|
2. O iOS está funcional e deve ser preservado como referência de comportamento.
|
||||||
|
3. O Android já possui estrutura inicial pronta:
|
||||||
|
- `pedi-foods/Android/*`
|
||||||
|
- `pedi-foods/Android/app/src/main/kotlin/Main.kt`
|
||||||
|
- `pedi-foods/Android/app/src/main/AndroidManifest.xml`
|
||||||
|
- `pedi-foods/Sources/PediFoods/Skip/skip.yml`
|
||||||
|
4. O objetivo imediato não é publicar Android, e sim conseguir compilar, instalar, abrir e validar os fluxos principais.
|
||||||
|
|
||||||
|
## Diagnóstico atual
|
||||||
|
|
||||||
|
### 1. Estrutura Android existente
|
||||||
|
|
||||||
|
O projeto já possui:
|
||||||
|
|
||||||
|
1. Manifest com permissões de internet e localização.
|
||||||
|
2. `Main.kt` com tratamento inicial de permissão de localização no Android.
|
||||||
|
3. Script dedicado para exportar e rodar no Android:
|
||||||
|
- `pedi-foods/scripts/android-run.sh`
|
||||||
|
4. Recursos Android básicos como ícones launcher.
|
||||||
|
|
||||||
|
### 2. Riscos identificados
|
||||||
|
|
||||||
|
Os principais riscos para o primeiro teste Android são:
|
||||||
|
|
||||||
|
1. Imports e dependências iOS-only ainda expostos em arquivos compartilhados.
|
||||||
|
2. Build local atualmente bloqueado por problema de artefato/toolchain Skip.
|
||||||
|
3. Recursos e comportamentos com paridade incompleta no Android.
|
||||||
|
4. Cobertura de testes ainda insuficiente para confiar só em automação.
|
||||||
|
|
||||||
|
### 3. Bloqueios técnicos já identificados
|
||||||
|
|
||||||
|
#### 3.1. Código compartilhado com dependência iOS-only
|
||||||
|
|
||||||
|
Foram encontrados pontos com alto risco para Android:
|
||||||
|
|
||||||
|
1. `pedi-foods/Sources/PediFoods/Views/Main/StoreDetailView.swift`
|
||||||
|
- possui `import LCEssentials` sem blindagem de plataforma.
|
||||||
|
2. `pedi-foods/Sources/PediFoods/Views/Main/ProfileView.swift`
|
||||||
|
- possui `import LCEssentials` sem blindagem de plataforma.
|
||||||
|
3. Outros arquivos com dependências iOS-only exigem revisão cuidadosa:
|
||||||
|
- `pedi-foods/Sources/PediFoods/Views/Main/HomeView.swift`
|
||||||
|
- `pedi-foods/Sources/PediFoods/Views/Main/ReviewsView.swift`
|
||||||
|
- `pedi-foods/Sources/PediFoods/Views/Main/UserProfileView.swift`
|
||||||
|
- `pedi-foods/Sources/PediFoods/Services/ApiClient.swift`
|
||||||
|
|
||||||
|
#### 3.2. Ambiente/build Skip inconsistente
|
||||||
|
|
||||||
|
Na validação local, o comando de build falhou antes da compilação funcional:
|
||||||
|
|
||||||
|
1. `swift build` retornou erro de artefato ausente do Skip.
|
||||||
|
2. O erro aponta para caminho antigo de projeto, indicando problema de cache, artefato ou configuração local do ambiente.
|
||||||
|
|
||||||
|
Sem resolver isso, não é possível validar corretamente o Android.
|
||||||
|
|
||||||
|
#### 3.3. Paridade Android ainda parcial
|
||||||
|
|
||||||
|
Alguns comportamentos parecem estar deliberadamente simplificados para Android:
|
||||||
|
|
||||||
|
1. Snackbar no Android está atualmente neutro/stubado.
|
||||||
|
2. Splash nativo Android ainda não aparenta estar finalizado.
|
||||||
|
3. Recursos visuais e alguns fluxos podem abrir, mas não com equivalência total ao iOS.
|
||||||
|
|
||||||
|
Isso não impede o primeiro boot, mas entra como ajuste após a compilação inicial.
|
||||||
|
|
||||||
|
## Estratégia de execução
|
||||||
|
|
||||||
|
A execução deve seguir uma ordem rígida para evitar retrabalho:
|
||||||
|
|
||||||
|
1. Primeiro remover bloqueios de compilação cross-platform.
|
||||||
|
2. Depois estabilizar o ambiente Skip/Android local.
|
||||||
|
3. Em seguida gerar build Android real.
|
||||||
|
4. Depois validar execução no device/emulador.
|
||||||
|
5. Só então ajustar bugs e diferenças de comportamento específicas do Android.
|
||||||
|
6. Ao fim de cada etapa funcional concluída, gerar commit.
|
||||||
|
|
||||||
|
## Plano de ação detalhado
|
||||||
|
|
||||||
|
### Fase 1 - Blindagem do código compartilhado para Android
|
||||||
|
|
||||||
|
Objetivo:
|
||||||
|
Garantir que o código compartilhado consiga ser compilado para Android sem quebrar o iOS.
|
||||||
|
|
||||||
|
Ações:
|
||||||
|
|
||||||
|
1. Revisar todos os arquivos compartilhados com:
|
||||||
|
- `import LCEssentials`
|
||||||
|
- `import UIKit`
|
||||||
|
- `import PhotosUI`
|
||||||
|
- qualquer API não portável pelo Skip
|
||||||
|
2. Encapsular uso iOS-only com:
|
||||||
|
- `#if os(iOS)`
|
||||||
|
- `#if canImport(...)`
|
||||||
|
- abstrações seguras por plataforma
|
||||||
|
3. Confirmar que nenhum ajuste Android altere o comportamento já funcional do iOS.
|
||||||
|
4. Validar se os fallbacks Android são suficientes para compilar.
|
||||||
|
|
||||||
|
Critério de conclusão:
|
||||||
|
|
||||||
|
1. O código compartilhado deixa de possuir bloqueios óbvios de compilação Android.
|
||||||
|
2. As dependências iOS-only ficam totalmente isoladas.
|
||||||
|
|
||||||
|
Commit ao concluir:
|
||||||
|
|
||||||
|
1. Criar commit específico da blindagem cross-platform.
|
||||||
|
|
||||||
|
### Fase 2 - Saneamento do ambiente Skip/Android
|
||||||
|
|
||||||
|
Objetivo:
|
||||||
|
Restabelecer o pipeline de build para que a validação Android seja real e repetível.
|
||||||
|
|
||||||
|
Ações:
|
||||||
|
|
||||||
|
1. Identificar a origem do erro de artefato do Skip.
|
||||||
|
2. Verificar caches, artefatos e referências para caminho antigo do projeto.
|
||||||
|
3. Corrigir ambiente local sem destruir configuração válida do iOS.
|
||||||
|
4. Validar novamente:
|
||||||
|
- `swift build`
|
||||||
|
- fluxo de export Android via Skip
|
||||||
|
|
||||||
|
Critério de conclusão:
|
||||||
|
|
||||||
|
1. O build compartilhado executa sem erro de artefato.
|
||||||
|
2. O projeto consegue iniciar a etapa de export Android.
|
||||||
|
|
||||||
|
Commit ao concluir:
|
||||||
|
|
||||||
|
1. Criar commit específico do ajuste de ambiente/configuração, se houver mudança versionada no repositório.
|
||||||
|
2. Se a correção for apenas local e não versionável, registrar isso no andamento e seguir sem commit desta subparte.
|
||||||
|
|
||||||
|
### Fase 3 - Primeiro build Android real
|
||||||
|
|
||||||
|
Objetivo:
|
||||||
|
Gerar APK/build Android funcional para teste.
|
||||||
|
|
||||||
|
Ações:
|
||||||
|
|
||||||
|
1. Rodar export/build Android pelo fluxo oficial do projeto.
|
||||||
|
2. Validar geração dos artefatos Android esperados.
|
||||||
|
3. Confirmar compatibilidade com ABI do emulador/device.
|
||||||
|
4. Corrigir falhas de build especificamente Android sem alterar o caminho iOS.
|
||||||
|
|
||||||
|
Critério de conclusão:
|
||||||
|
|
||||||
|
1. APK ou build instalável gerado com sucesso.
|
||||||
|
2. Nenhum erro de compilação Android pendente.
|
||||||
|
|
||||||
|
Commit ao concluir:
|
||||||
|
|
||||||
|
1. Criar commit específico do primeiro build Android estabilizado.
|
||||||
|
|
||||||
|
### Fase 4 - Primeiro teste funcional no Android
|
||||||
|
|
||||||
|
Objetivo:
|
||||||
|
Instalar, abrir e validar o app no Android pela primeira vez.
|
||||||
|
|
||||||
|
Ações:
|
||||||
|
|
||||||
|
1. Instalar no emulador ou device.
|
||||||
|
2. Validar abertura do app.
|
||||||
|
3. Confirmar:
|
||||||
|
- splash inicial
|
||||||
|
- fluxo de login
|
||||||
|
- navegação principal por tabs
|
||||||
|
- home
|
||||||
|
- detalhe da loja
|
||||||
|
- perfil
|
||||||
|
- endereços
|
||||||
|
4. Observar crashes, telas em branco, problemas de navegação ou layout quebrado.
|
||||||
|
|
||||||
|
Critério de conclusão:
|
||||||
|
|
||||||
|
1. O app abre no Android.
|
||||||
|
2. Os fluxos principais navegam sem crash.
|
||||||
|
|
||||||
|
Commit ao concluir:
|
||||||
|
|
||||||
|
1. Criar commit específico do primeiro ciclo funcional de execução Android.
|
||||||
|
|
||||||
|
### Fase 5 - Correções de runtime específicas do Android
|
||||||
|
|
||||||
|
Objetivo:
|
||||||
|
Ajustar o que só aparece após o app abrir no Android.
|
||||||
|
|
||||||
|
Ações:
|
||||||
|
|
||||||
|
1. Validar permissão de localização.
|
||||||
|
2. Validar persistência/local cache usados pelo app no Android.
|
||||||
|
3. Validar imagens e carregamento remoto.
|
||||||
|
4. Validar teclado, foco e `adjustResize`.
|
||||||
|
5. Validar sheets, modais, overlays e navegação.
|
||||||
|
6. Corrigir bugs Android-only com isolamento por plataforma.
|
||||||
|
|
||||||
|
Critério de conclusão:
|
||||||
|
|
||||||
|
1. Os fluxos principais deixam de ter falhas críticas no Android.
|
||||||
|
2. Nenhuma correção Android impacta o iOS.
|
||||||
|
|
||||||
|
Commit ao concluir:
|
||||||
|
|
||||||
|
1. Criar commits separados por grupo funcional concluído e validado.
|
||||||
|
|
||||||
|
### Fase 6 - Paridade mínima de UX Android
|
||||||
|
|
||||||
|
Objetivo:
|
||||||
|
Atingir um nível mínimo aceitável de experiência para teste Android sem buscar perfeição visual prematura.
|
||||||
|
|
||||||
|
Ações:
|
||||||
|
|
||||||
|
1. Ajustar snackbar Android para comportamento visível e funcional.
|
||||||
|
2. Revisar splash Android nativo, se necessário.
|
||||||
|
3. Corrigir diferenças de layout mais críticas.
|
||||||
|
4. Revisar componentes com comportamento visual diferente entre plataformas.
|
||||||
|
|
||||||
|
Critério de conclusão:
|
||||||
|
|
||||||
|
1. O Android fica funcional e utilizável para teste contínuo.
|
||||||
|
2. As diferenças visuais restantes não impedem validação do produto.
|
||||||
|
|
||||||
|
Commit ao concluir:
|
||||||
|
|
||||||
|
1. Criar commit específico das melhorias de UX Android aprovadas.
|
||||||
|
|
||||||
|
### Fase 7 - Regressão rápida no iOS
|
||||||
|
|
||||||
|
Objetivo:
|
||||||
|
Garantir que os ajustes Android não introduziram regressões no iOS.
|
||||||
|
|
||||||
|
Ações:
|
||||||
|
|
||||||
|
1. Rodar build iOS novamente.
|
||||||
|
2. Validar navegação principal no iOS.
|
||||||
|
3. Confirmar que os fluxos já funcionais continuam intactos.
|
||||||
|
4. Revisar especialmente os pontos alterados com condicionais de plataforma.
|
||||||
|
|
||||||
|
Critério de conclusão:
|
||||||
|
|
||||||
|
1. iOS continua funcional após os ajustes Android.
|
||||||
|
2. Nenhuma regressão relevante é detectada.
|
||||||
|
|
||||||
|
Commit ao concluir:
|
||||||
|
|
||||||
|
1. Criar commit final de consolidação apenas se houver mudanças adicionais nesta etapa.
|
||||||
|
|
||||||
|
## Regras operacionais durante a execução
|
||||||
|
|
||||||
|
1. Nenhum arquivo pode ultrapassar 500 linhas.
|
||||||
|
2. Ajustes Android devem ser isolados e não devem reescrever a lógica funcional do iOS.
|
||||||
|
3. Toda correção deve passar por validação antes de seguir para a próxima task.
|
||||||
|
4. Cada task funcional concluída deve resultar em commit.
|
||||||
|
5. Se uma task encontrar erro, ela retorna ao ciclo:
|
||||||
|
- identificar causa raiz
|
||||||
|
- corrigir
|
||||||
|
- testar novamente
|
||||||
|
- só então marcar como concluída
|
||||||
|
|
||||||
|
## Convenção de commits
|
||||||
|
|
||||||
|
Padrão recomendado:
|
||||||
|
|
||||||
|
1. `fix(android): isolate ios-only dependencies for skip build`
|
||||||
|
2. `fix(build): stabilize skip android export pipeline`
|
||||||
|
3. `feat(android): first successful android build`
|
||||||
|
4. `fix(android): resolve runtime issues on first boot`
|
||||||
|
5. `feat(android): improve minimum ux parity`
|
||||||
|
|
||||||
|
## Sequência de tasks para acompanhamento
|
||||||
|
|
||||||
|
Legenda:
|
||||||
|
|
||||||
|
- 🔴 Pendente
|
||||||
|
- 🟢 Concluído
|
||||||
|
|
||||||
|
1. 🟢 Revisar e blindar imports/dependências iOS-only no código compartilhado.
|
||||||
|
2. 🟢 Corrigir o problema de artefato/caminho do ambiente Skip local.
|
||||||
|
3. 🟢 Validar `swift build` sem erro de ambiente.
|
||||||
|
4. 🔴 Validar export/build Android via Skip.
|
||||||
|
5. 🔴 Gerar APK/build Android instalável.
|
||||||
|
6. 🔴 Instalar e abrir o app no Android.
|
||||||
|
7. 🔴 Validar login no Android.
|
||||||
|
8. 🔴 Validar home e navegação principal no Android.
|
||||||
|
9. 🔴 Validar detalhe de loja e carrinho no Android.
|
||||||
|
10. 🔴 Validar perfil e endereços no Android.
|
||||||
|
11. 🔴 Validar permissão e uso de localização no Android.
|
||||||
|
12. 🔴 Corrigir bugs críticos de runtime Android.
|
||||||
|
13. 🔴 Ajustar snackbar Android.
|
||||||
|
14. 🔴 Revisar splash Android, se necessário.
|
||||||
|
15. 🔴 Rodar regressão rápida no iOS após os ajustes Android.
|
||||||
|
16. 🟢 Criar commit após cada task concluída e funcional.
|
||||||
|
|
||||||
|
## Andamento
|
||||||
|
|
||||||
|
1. Task 1 concluída e validada via build compartilhado.
|
||||||
|
2. Task 2 concluída com ajuste local no cache/estado de build do Skip.
|
||||||
|
- Correção local não versionada.
|
||||||
|
3. Task 3 concluída com `swift build` finalizado com sucesso.
|
||||||
Reference in New Issue
Block a user