Fixs
This commit is contained in:
@@ -1,3 +1,6 @@
|
|||||||
org.gradle.jvmargs=-Xmx4g
|
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8
|
||||||
|
org.gradle.workers.max=2
|
||||||
|
org.gradle.parallel=false
|
||||||
|
kotlin.daemon.jvmargs=-Xmx1g
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"red" : "0.9529411764705882",
|
||||||
|
"green" : "0.9607843137254902",
|
||||||
|
"blue" : "0.9686274509803922",
|
||||||
|
"alpha" : "1.0000000000000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,8 @@ GENERATE_INFOPLIST_FILE = YES
|
|||||||
// iOS-specific Info.plist property keys
|
// iOS-specific Info.plist property keys
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphone*] = YES
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphone*] = YES
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphone*] = YES
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphone*] = YES
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphone*] = YES
|
INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphone*] = NO
|
||||||
|
INFOPLIST_KEY_UILaunchStoryboardName[sdk=iphone*] = LaunchScreen
|
||||||
INFOPLIST_KEY_UIStatusBarStyle[sdk=iphone*] = UIStatusBarStyleDefault
|
INFOPLIST_KEY_UIStatusBarStyle[sdk=iphone*] = UIStatusBarStyleDefault
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations[sdk=iphone*] = UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown
|
INFOPLIST_KEY_UISupportedInterfaceOrientations[sdk=iphone*] = UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
496BDBEE2B8A7E9C00C09264 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */; };
|
496BDBEE2B8A7E9C00C09264 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */; };
|
||||||
499CD43B2AC5B799001AE8D8 /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49F90C2B2A52156200F06D93 /* Main.swift */; };
|
499CD43B2AC5B799001AE8D8 /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49F90C2B2A52156200F06D93 /* Main.swift */; };
|
||||||
499CD4402AC5B799001AE8D8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49F90C2F2A52156300F06D93 /* Assets.xcassets */; };
|
499CD4402AC5B799001AE8D8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49F90C2F2A52156300F06D93 /* Assets.xcassets */; };
|
||||||
|
B185A1FD2FB67A12001C7771 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B185A1FC2FB67A12001C7771 /* LaunchScreen.storyboard */; };
|
||||||
B13CABF22F36437300469FD6 /* LCEssentials in Frameworks */ = {isa = PBXBuildFile; productRef = B13CABF12F36437300469FD6 /* LCEssentials */; };
|
B13CABF22F36437300469FD6 /* LCEssentials in Frameworks */ = {isa = PBXBuildFile; productRef = B13CABF12F36437300469FD6 /* LCEssentials */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@
|
|||||||
49F90C2B2A52156200F06D93 /* Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Main.swift; path = Sources/Main.swift; sourceTree = SOURCE_ROOT; };
|
49F90C2B2A52156200F06D93 /* Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Main.swift; path = Sources/Main.swift; sourceTree = SOURCE_ROOT; };
|
||||||
49F90C2F2A52156300F06D93 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
49F90C2F2A52156300F06D93 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
49F90C312A52156300F06D93 /* Entitlements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Entitlements.plist; sourceTree = "<group>"; };
|
49F90C312A52156300F06D93 /* Entitlements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Entitlements.plist; sourceTree = "<group>"; };
|
||||||
|
B185A1FC2FB67A12001C7771 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Sources/LaunchScreen.storyboard; sourceTree = SOURCE_ROOT; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -90,6 +92,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
49F90C2B2A52156200F06D93 /* Main.swift */,
|
49F90C2B2A52156200F06D93 /* Main.swift */,
|
||||||
|
B185A1FC2FB67A12001C7771 /* LaunchScreen.storyboard */,
|
||||||
49F90C2F2A52156300F06D93 /* Assets.xcassets */,
|
49F90C2F2A52156300F06D93 /* Assets.xcassets */,
|
||||||
49F90C312A52156300F06D93 /* Entitlements.plist */,
|
49F90C312A52156300F06D93 /* Entitlements.plist */,
|
||||||
4900101C2BACEA710000DE33 /* Info.plist */,
|
4900101C2BACEA710000DE33 /* Info.plist */,
|
||||||
@@ -162,6 +165,7 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
B185A1FD2FB67A12001C7771 /* LaunchScreen.storyboard in Resources */,
|
||||||
499CD4402AC5B799001AE8D8 /* Assets.xcassets in Resources */,
|
499CD4402AC5B799001AE8D8 /* Assets.xcassets in Resources */,
|
||||||
496BDBEE2B8A7E9C00C09264 /* Localizable.xcstrings in Resources */,
|
496BDBEE2B8A7E9C00C09264 /* Localizable.xcstrings in Resources */,
|
||||||
);
|
);
|
||||||
|
|||||||
43
pedi-foods/Darwin/Sources/LaunchScreen.storyboard
Normal file
43
pedi-foods/Darwin/Sources/LaunchScreen.storyboard
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
|
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--View Controller-->
|
||||||
|
<scene sceneID="EHf-IW-A2E">
|
||||||
|
<objects>
|
||||||
|
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||||
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" image="pedifoods" translatesAutoresizingMaskIntoConstraints="NO" id="W6R-s6-hHG">
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="height" constant="160" id="S0g-eL-cqR"/>
|
||||||
|
</constraints>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
|
||||||
|
<color key="backgroundColor" red="0.9529411765" green="0.9607843137" blue="0.9686274510" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="W6R-s6-hHG" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="F0M-uu-fA3"/>
|
||||||
|
<constraint firstItem="W6R-s6-hHG" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="M3D-C3-C0c"/>
|
||||||
|
<constraint firstItem="W6R-s6-hHG" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Bcu-3y-fUS" secondAttribute="leading" constant="24" id="kT5-M8-Yrq"/>
|
||||||
|
<constraint firstItem="Bcu-3y-fUS" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="W6R-s6-hHG" secondAttribute="trailing" constant="24" id="rbz-Yf-pdY"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="50" y="32"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
<resources>
|
||||||
|
<image name="pedifoods" width="1024" height="1024"/>
|
||||||
|
</resources>
|
||||||
|
</document>
|
||||||
51
pedi-foods/PROJECT_ANALYSIS_SKIP.md
Normal file
51
pedi-foods/PROJECT_ANALYSIS_SKIP.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Análise Técnica — Arquitetura Skip (PediFoods)
|
||||||
|
|
||||||
|
## Entendimento confirmado
|
||||||
|
|
||||||
|
O projeto usa **Skip com fonte principal em Swift/SwiftUI** e geração/transpilação para Android.
|
||||||
|
|
||||||
|
Diretriz operacional validada:
|
||||||
|
|
||||||
|
1. **iOS é a plataforma prioritária** de desenvolvimento e validação.
|
||||||
|
2. **Android entra depois**, com adaptações isoladas por compilação condicional.
|
||||||
|
3. Toda alteração deve preservar comportamento de iOS e evitar regressão cross-platform.
|
||||||
|
|
||||||
|
## Como o projeto está estruturado
|
||||||
|
|
||||||
|
1. Código compartilhado principal: `Sources/PediFoods/*` (Swift/SwiftUI).
|
||||||
|
2. Entry point iOS: `Darwin/Sources/Main.swift`.
|
||||||
|
3. Entry point Android: `Android/app/src/main/kotlin/Main.kt`.
|
||||||
|
4. Configuração Skip: `Sources/PediFoods/Skip/skip.yml` (`mode: native`).
|
||||||
|
5. Build/transpilação Android via plugin Skip (`skipstone`) no target Swift Package.
|
||||||
|
|
||||||
|
## Evidências no código (padrão já em uso)
|
||||||
|
|
||||||
|
1. Uso extensivo de condicionais:
|
||||||
|
- `#if os(iOS)`
|
||||||
|
- `#if os(Android)`
|
||||||
|
- `#if canImport(UIKit)`
|
||||||
|
2. Dependência iOS-only no `Package.swift`:
|
||||||
|
- `LCEssentials` com `condition: .when(platforms: [.iOS])`
|
||||||
|
3. Exemplo claro de adaptação segura:
|
||||||
|
- `ContentView.swift`: `SnackbarCenter` com implementação distinta para Android e iOS.
|
||||||
|
|
||||||
|
## Regra prática para próximas tarefas
|
||||||
|
|
||||||
|
1. Implementar primeiro para iOS.
|
||||||
|
2. Validar iOS (build/execução/comportamento).
|
||||||
|
3. Só depois adaptar Android, sempre em bloco condicional separado.
|
||||||
|
4. Nunca alterar caminho iOS por causa de ajuste Android.
|
||||||
|
5. Em mudanças de UI de inicialização (ex.: splash), tratar iOS e Android em fluxos independentes.
|
||||||
|
|
||||||
|
## Nota específica para Splash
|
||||||
|
|
||||||
|
Estado atual identificado:
|
||||||
|
|
||||||
|
1. iOS com Launch Screen gerado automaticamente (`INFOPLIST_KEY_UILaunchScreen_Generation = YES`).
|
||||||
|
2. Android sem implementação custom de splash dedicada no `res/values` + tema de launch.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
@@ -6,6 +6,11 @@ struct ContentView: View {
|
|||||||
private let tokenStore: TokenStore = DefaultTokenStore()
|
private let tokenStore: TokenStore = DefaultTokenStore()
|
||||||
@State var appState = AppState()
|
@State var appState = AppState()
|
||||||
@State var isBootstrappingSession = false
|
@State var isBootstrappingSession = false
|
||||||
|
@State var hasPerformedInitialLaunchBootstrap = false
|
||||||
|
@State var showLaunchSplash = true
|
||||||
|
@State var shouldPulseLaunchSplash = true
|
||||||
|
@State var shouldPrepareAuthEntryAnimation = DefaultTokenStore().jwt == nil
|
||||||
|
@State var authEntryAnimationToken = 0
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@State private var sessionExpiredObserver: NSObjectProtocol?
|
@State private var sessionExpiredObserver: NSObjectProtocol?
|
||||||
#endif
|
#endif
|
||||||
@@ -20,7 +25,14 @@ struct ContentView: View {
|
|||||||
Group {
|
Group {
|
||||||
switch root {
|
switch root {
|
||||||
case .auth:
|
case .auth:
|
||||||
AuthFlowView(root: $root, selectedTab: $selectedTab, tokenStore: tokenStore, appState: $appState)
|
AuthFlowView(
|
||||||
|
root: $root,
|
||||||
|
selectedTab: $selectedTab,
|
||||||
|
tokenStore: tokenStore,
|
||||||
|
appState: $appState,
|
||||||
|
shouldPrepareLoginEntry: shouldPrepareAuthEntryAnimation,
|
||||||
|
authEntryAnimationToken: authEntryAnimationToken
|
||||||
|
)
|
||||||
case .main:
|
case .main:
|
||||||
if isBootstrappingSession {
|
if isBootstrappingSession {
|
||||||
sessionBootstrapLoadingView
|
sessionBootstrapLoadingView
|
||||||
@@ -31,6 +43,11 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SnackbarOverlay(center: snackbarCenter)
|
SnackbarOverlay(center: snackbarCenter)
|
||||||
|
|
||||||
|
if showLaunchSplash {
|
||||||
|
LaunchSplashView(shouldPulse: shouldPulseLaunchSplash)
|
||||||
|
.transition(.opacity)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.sheet(item: $appState.activeModal) { modal in
|
.sheet(item: $appState.activeModal) { modal in
|
||||||
switch modal {
|
switch modal {
|
||||||
@@ -55,8 +72,15 @@ struct ContentView: View {
|
|||||||
.onReceive(NotificationCenter.default.publisher(for: .cartDidReset)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: .cartDidReset)) { _ in
|
||||||
appState.cart = CartState()
|
appState.cart = CartState()
|
||||||
}
|
}
|
||||||
.task(id: root) {
|
.task {
|
||||||
await bootstrapSessionStateIfNeeded()
|
await performInitialLaunchBootstrap()
|
||||||
|
}
|
||||||
|
.onChange(of: root) { _, newValue in
|
||||||
|
if newValue == .main {
|
||||||
|
Task {
|
||||||
|
await bootstrapSessionStateIfNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@@ -81,6 +105,43 @@ struct ContentView: View {
|
|||||||
.background(AppColors.backgroundLight)
|
.background(AppColors.backgroundLight)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private func performInitialLaunchBootstrap() async {
|
||||||
|
guard hasPerformedInitialLaunchBootstrap == false else { return }
|
||||||
|
hasPerformedInitialLaunchBootstrap = true
|
||||||
|
|
||||||
|
let start = Date()
|
||||||
|
await bootstrapSessionStateIfNeeded()
|
||||||
|
|
||||||
|
// Keep the in-app splash visible long enough to avoid abrupt transition
|
||||||
|
// between native launch screen and app content.
|
||||||
|
let elapsed = Date().timeIntervalSince(start)
|
||||||
|
let minimumSplashDuration: TimeInterval = 1.0
|
||||||
|
if elapsed < minimumSplashDuration {
|
||||||
|
let remaining = minimumSplashDuration - elapsed
|
||||||
|
let nanoseconds = UInt64(remaining * 1_000_000_000)
|
||||||
|
try? await Task.sleep(nanoseconds: nanoseconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
if root == .auth {
|
||||||
|
shouldPrepareAuthEntryAnimation = true
|
||||||
|
shouldPulseLaunchSplash = false
|
||||||
|
try? await Task.sleep(nanoseconds: 180_000_000)
|
||||||
|
withAnimation(.easeInOut(duration: 0.34)) {
|
||||||
|
showLaunchSplash = false
|
||||||
|
}
|
||||||
|
try? await Task.sleep(nanoseconds: 360_000_000)
|
||||||
|
authEntryAnimationToken += 1
|
||||||
|
scheduleDisableAuthEntryPreparation()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldPulseLaunchSplash = false
|
||||||
|
withAnimation(.easeOut(duration: 0.28)) {
|
||||||
|
showLaunchSplash = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
private func bootstrapSessionStateIfNeeded() async {
|
private func bootstrapSessionStateIfNeeded() async {
|
||||||
guard root == .main else { return }
|
guard root == .main else { return }
|
||||||
@@ -215,7 +276,17 @@ struct ContentView: View {
|
|||||||
isBootstrappingSession = false
|
isBootstrappingSession = false
|
||||||
appState = AppState()
|
appState = AppState()
|
||||||
selectedTab = .home
|
selectedTab = .home
|
||||||
|
shouldPrepareAuthEntryAnimation = true
|
||||||
root = .auth
|
root = .auth
|
||||||
|
authEntryAnimationToken += 1
|
||||||
|
scheduleDisableAuthEntryPreparation()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func scheduleDisableAuthEntryPreparation() {
|
||||||
|
Task { @MainActor in
|
||||||
|
try? await Task.sleep(nanoseconds: 900_000_000)
|
||||||
|
shouldPrepareAuthEntryAnimation = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func hasConfiguredAddress() -> Bool {
|
private func hasConfiguredAddress() -> Bool {
|
||||||
|
|||||||
@@ -1165,7 +1165,7 @@
|
|||||||
"comment" : "A title for the OTP verification screen.",
|
"comment" : "A title for the OTP verification screen.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Versão 4.20.1" : {
|
"Versão 1.0b" : {
|
||||||
"comment" : "The version number of the app.",
|
"comment" : "The version number of the app.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -82,15 +82,7 @@ final class ApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func send<T: Decodable>(_ request: ApiRequest) async throws -> T {
|
func send<T: Decodable>(_ request: ApiRequest) async throws -> T {
|
||||||
// NOTE:
|
|
||||||
// /api/customer/login is strict about body fields (email/phoneNumber/otp).
|
|
||||||
// On iOS, route this endpoint through URLSession to preserve raw JSON body behavior.
|
|
||||||
if request.path == "/api/customer/login" {
|
|
||||||
if let body = request.body, let bodyText = String(data: body, encoding: .utf8) {
|
|
||||||
print("[ApiClient] /api/customer/login body: \(bodyText)")
|
|
||||||
}
|
|
||||||
return try await sendWithURLSession(request)
|
|
||||||
}
|
|
||||||
#if canImport(LCEssentials) && os(iOS)
|
#if canImport(LCEssentials) && os(iOS)
|
||||||
return try await sendWithLCEssentials(request)
|
return try await sendWithLCEssentials(request)
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -11,12 +11,21 @@ struct AuthFlowView: View {
|
|||||||
@Binding var selectedTab: MainTab
|
@Binding var selectedTab: MainTab
|
||||||
let tokenStore: TokenStore
|
let tokenStore: TokenStore
|
||||||
@Binding var appState: AppState
|
@Binding var appState: AppState
|
||||||
|
var shouldPrepareLoginEntry: Bool = false
|
||||||
|
var authEntryAnimationToken: Int = 0
|
||||||
|
|
||||||
@State var path: [Route] = []
|
@State var path: [Route] = []
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack(path: $path) {
|
NavigationStack(path: $path) {
|
||||||
LoginView(root: $root, selectedTab: $selectedTab, tokenStore: tokenStore, appState: $appState) { route in
|
LoginView(
|
||||||
|
root: $root,
|
||||||
|
selectedTab: $selectedTab,
|
||||||
|
tokenStore: tokenStore,
|
||||||
|
appState: $appState,
|
||||||
|
shouldPrepareEntryAnimation: shouldPrepareLoginEntry,
|
||||||
|
authEntryAnimationToken: authEntryAnimationToken
|
||||||
|
) { route in
|
||||||
path.append(route)
|
path.append(route)
|
||||||
}
|
}
|
||||||
.navigationDestination(for: Route.self) { route in
|
.navigationDestination(for: Route.self) { route in
|
||||||
|
|||||||
@@ -6,7 +6,13 @@ struct LoginView: View {
|
|||||||
let tokenStore: TokenStore
|
let tokenStore: TokenStore
|
||||||
@Binding var appState: AppState
|
@Binding var appState: AppState
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
var shouldPrepareEntryAnimation: Bool = false
|
||||||
|
var authEntryAnimationToken: Int = 0
|
||||||
let navigate: (Route) -> Void
|
let navigate: (Route) -> Void
|
||||||
|
@State var heroVisible = true
|
||||||
|
@State var textVisible = true
|
||||||
|
@State var buttonVisible = true
|
||||||
|
@State var lastAnimatedToken = 0
|
||||||
|
|
||||||
@ViewBuilder private var logoImage: some View {
|
@ViewBuilder private var logoImage: some View {
|
||||||
#if os(Android)
|
#if os(Android)
|
||||||
@@ -31,6 +37,7 @@ struct LoginView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { geo in
|
GeometryReader { geo in
|
||||||
let heroHeight = max(360, geo.size.height * 0.44)
|
let heroHeight = max(360, geo.size.height * 0.44)
|
||||||
|
let logoTopInset = max(0, (geo.size.height - 180) / 2)
|
||||||
|
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
(colorScheme == .dark ? Color.black : AppColors.backgroundLight)
|
(colorScheme == .dark ? Color.black : AppColors.backgroundLight)
|
||||||
@@ -39,7 +46,7 @@ struct LoginView: View {
|
|||||||
pinHeroImage
|
pinHeroImage
|
||||||
.scaledToFill()
|
.scaledToFill()
|
||||||
.frame(height: heroHeight + 80)
|
.frame(height: heroHeight + 80)
|
||||||
.offset(y: -60)
|
.offset(y: heroVisible ? -60 : -(heroHeight + 220))
|
||||||
.mask(
|
.mask(
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
colors: [.black, .black, .black.opacity(0.0)],
|
colors: [.black, .black, .black.opacity(0.0)],
|
||||||
@@ -49,7 +56,7 @@ struct LoginView: View {
|
|||||||
)
|
)
|
||||||
|
|
||||||
VStack(spacing: 18) {
|
VStack(spacing: 18) {
|
||||||
Spacer().frame(height: heroHeight - 100)
|
Spacer().frame(height: logoTopInset)
|
||||||
|
|
||||||
logoImage
|
logoImage
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
@@ -61,6 +68,8 @@ struct LoginView: View {
|
|||||||
.multilineTextAlignment(.leading)
|
.multilineTextAlignment(.leading)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.padding(.horizontal, 28)
|
.padding(.horizontal, 28)
|
||||||
|
.opacity(textVisible ? 1.0 : 0.0)
|
||||||
|
.offset(y: textVisible ? 0 : 24)
|
||||||
|
|
||||||
Spacer().frame(height: 40)
|
Spacer().frame(height: 40)
|
||||||
|
|
||||||
@@ -71,6 +80,8 @@ struct LoginView: View {
|
|||||||
}
|
}
|
||||||
.padding(.horizontal, 28)
|
.padding(.horizontal, 28)
|
||||||
.tint(AppColors.tertiary)
|
.tint(AppColors.tertiary)
|
||||||
|
.offset(y: buttonVisible ? 0 : 140)
|
||||||
|
.opacity(buttonVisible ? 1.0 : 0.0)
|
||||||
|
|
||||||
HStack(spacing: 6) {
|
HStack(spacing: 6) {
|
||||||
Text("Não tem conta ainda?")
|
Text("Não tem conta ainda?")
|
||||||
@@ -81,11 +92,67 @@ struct LoginView: View {
|
|||||||
.foregroundStyle(AppColors.primary)
|
.foregroundStyle(AppColors.primary)
|
||||||
}
|
}
|
||||||
.font(AppTypography.body)
|
.font(AppTypography.body)
|
||||||
|
.opacity(textVisible ? 1.0 : 0.0)
|
||||||
|
.offset(y: textVisible ? 0 : 24)
|
||||||
|
|
||||||
Spacer().frame(height: 12)
|
Spacer().frame(height: 12)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
|
.onAppear {
|
||||||
|
if shouldPrepareEntryAnimation {
|
||||||
|
applyHiddenStateWithoutAnimation()
|
||||||
|
} else {
|
||||||
|
showFinalStateWithoutAnimation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.task(id: authEntryAnimationToken) {
|
||||||
|
await runEntryAnimationIfNeeded(for: authEntryAnimationToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private func applyHiddenStateWithoutAnimation() {
|
||||||
|
var transaction = Transaction()
|
||||||
|
transaction.disablesAnimations = true
|
||||||
|
withTransaction(transaction) {
|
||||||
|
heroVisible = false
|
||||||
|
textVisible = false
|
||||||
|
buttonVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private func showFinalStateWithoutAnimation() {
|
||||||
|
var transaction = Transaction()
|
||||||
|
transaction.disablesAnimations = true
|
||||||
|
withTransaction(transaction) {
|
||||||
|
heroVisible = true
|
||||||
|
textVisible = true
|
||||||
|
buttonVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private func runEntryAnimationIfNeeded(for token: Int) async {
|
||||||
|
guard token > 0 else { return }
|
||||||
|
guard token != lastAnimatedToken else { return }
|
||||||
|
lastAnimatedToken = token
|
||||||
|
|
||||||
|
applyHiddenStateWithoutAnimation()
|
||||||
|
try? await Task.sleep(nanoseconds: 40_000_000)
|
||||||
|
|
||||||
|
withAnimation(.spring(response: 0.64, dampingFraction: 0.9)) {
|
||||||
|
heroVisible = true
|
||||||
|
}
|
||||||
|
try? await Task.sleep(nanoseconds: 160_000_000)
|
||||||
|
withAnimation(.easeOut(duration: 0.42)) {
|
||||||
|
textVisible = true
|
||||||
|
}
|
||||||
|
try? await Task.sleep(nanoseconds: 150_000_000)
|
||||||
|
withAnimation(.spring(response: 0.52, dampingFraction: 0.86)) {
|
||||||
|
buttonVisible = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct LaunchSplashView: View {
|
||||||
|
var shouldPulse: Bool = true
|
||||||
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
@State var isAnimating = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
GeometryReader { geo in
|
||||||
|
let logoTopInset = max(0, (geo.size.height - 180) / 2 - 8)
|
||||||
|
|
||||||
|
ZStack(alignment: .top) {
|
||||||
|
(colorScheme == .dark ? Color.black : AppColors.backgroundLight)
|
||||||
|
.ignoresSafeArea()
|
||||||
|
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
Spacer().frame(height: logoTopInset)
|
||||||
|
|
||||||
|
splashLogo
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 180, height: 180)
|
||||||
|
.scaleEffect(isAnimating ? 1.03 : 0.97)
|
||||||
|
.opacity(isAnimating ? 1.0 : 0.9)
|
||||||
|
.animation(.easeInOut(duration: 0.9).repeatForever(autoreverses: true), value: isAnimating)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
isAnimating = shouldPulse
|
||||||
|
}
|
||||||
|
.onChange(of: shouldPulse) { _, newValue in
|
||||||
|
isAnimating = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder var splashLogo: some View {
|
||||||
|
#if os(Android)
|
||||||
|
SwiftUI.Image("pedifoods")
|
||||||
|
.resizable()
|
||||||
|
#else
|
||||||
|
SwiftUI.Image("pedifoods")
|
||||||
|
.resizable()
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,8 +22,8 @@ struct HomeView: View {
|
|||||||
@State var stores: [StoreSummary] = []
|
@State var stores: [StoreSummary] = []
|
||||||
|
|
||||||
private let specials: [SpecialOfferCardModel] = [
|
private let specials: [SpecialOfferCardModel] = [
|
||||||
.init(id: "ddddd", title: "Get 50% OFF", subtitle: "For your first order", colors: [Color(hex: "#1E6B43"), Color(hex: "#58A56C")]),
|
// .init(id: "ddddd", title: "Get 50% OFF", subtitle: "For your first order", colors: [Color(hex: "#1E6B43"), Color(hex: "#58A56C")]),
|
||||||
.init(id: "eeeee", title: "Enjoy spicy day", subtitle: "Delivery in 20 min", colors: [Color(hex: "#F04B3E"), Color(hex: "#FF8C42")])
|
// .init(id: "eeeee", title: "Enjoy spicy day", subtitle: "Delivery in 20 min", colors: [Color(hex: "#F04B3E"), Color(hex: "#FF8C42")])
|
||||||
]
|
]
|
||||||
|
|
||||||
private let headerExpandedHeight: CGFloat = 240
|
private let headerExpandedHeight: CGFloat = 240
|
||||||
@@ -123,7 +123,7 @@ struct HomeView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
section(title: "#SpecialForYou") {
|
section(title: "#PediPromo") {
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
HStack(spacing: 16) {
|
HStack(spacing: 16) {
|
||||||
ForEach(specials) { item in
|
ForEach(specials) { item in
|
||||||
@@ -135,7 +135,7 @@ struct HomeView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
section(title: "Near you") {
|
section(title: "Pertinho de você") {
|
||||||
if isLoadingStores {
|
if isLoadingStores {
|
||||||
HStack {
|
HStack {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
@@ -226,7 +226,7 @@ struct HomeView: View {
|
|||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
|
|
||||||
VStack(alignment: .center, spacing: 4) {
|
VStack(alignment: .center, spacing: 4) {
|
||||||
Text("DELIVERY LOCATION")
|
Text("ENTREGAR EM:")
|
||||||
.font(AppTypography.overline)
|
.font(AppTypography.overline)
|
||||||
.tracking(AppTypography.captionLetterSpacing)
|
.tracking(AppTypography.captionLetterSpacing)
|
||||||
.foregroundStyle(AppColors.brandSoft)
|
.foregroundStyle(AppColors.brandSoft)
|
||||||
|
|||||||
@@ -60,12 +60,12 @@ struct ProfileView: View {
|
|||||||
// }
|
// }
|
||||||
// .buttonStyle(.plain)
|
// .buttonStyle(.plain)
|
||||||
|
|
||||||
NavigationLink {
|
// NavigationLink {
|
||||||
Text("Ajuda")
|
// Text("Ajuda")
|
||||||
} label: {
|
// } label: {
|
||||||
ProfileMenuRow(icon: "gearshape.fill", title: "Configurações")
|
// ProfileMenuRow(icon: "gearshape.fill", title: "Configurações")
|
||||||
}
|
// }
|
||||||
.buttonStyle(.plain)
|
// .buttonStyle(.plain)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ struct ProfileView: View {
|
|||||||
.padding(.top, 10)
|
.padding(.top, 10)
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
|
|
||||||
Text("Versão 4.20.1")
|
Text("Versão 1.0b")
|
||||||
.font(.system(size: 16, weight: .medium))
|
.font(.system(size: 16, weight: .medium))
|
||||||
.foregroundStyle(AppColors.textMuted)
|
.foregroundStyle(AppColors.textMuted)
|
||||||
.padding(.bottom, 12)
|
.padding(.bottom, 12)
|
||||||
|
|||||||
@@ -6,10 +6,15 @@ ANDROID_SDK_ROOT_DEFAULT="$HOME/Library/Android/sdk"
|
|||||||
EMULATOR_BIN_DEFAULT="$ANDROID_SDK_ROOT_DEFAULT/emulator/emulator"
|
EMULATOR_BIN_DEFAULT="$ANDROID_SDK_ROOT_DEFAULT/emulator/emulator"
|
||||||
ADB_BIN_DEFAULT="$ANDROID_SDK_ROOT_DEFAULT/platform-tools/adb"
|
ADB_BIN_DEFAULT="$ANDROID_SDK_ROOT_DEFAULT/platform-tools/adb"
|
||||||
|
|
||||||
AVD_NAME="${1:-lc_arm64}"
|
AVD_NAME="${1:-auto}"
|
||||||
BOOT_TIMEOUT_SECONDS="${BOOT_TIMEOUT_SECONDS:-120}"
|
BOOT_TIMEOUT_SECONDS="${BOOT_TIMEOUT_SECONDS:-120}"
|
||||||
ADB_CONNECT_TIMEOUT_SECONDS="${ADB_CONNECT_TIMEOUT_SECONDS:-90}"
|
ADB_CONNECT_TIMEOUT_SECONDS="${ADB_CONNECT_TIMEOUT_SECONDS:-90}"
|
||||||
APP_ID_DEFAULT="com.br.pedifoods.app"
|
APP_ID_DEFAULT="com.br.pedifoods.app"
|
||||||
|
REQUIRED_BRIDGE_SO="${REQUIRED_BRIDGE_SO:-libPediFoods.so}"
|
||||||
|
SKIP_EXPORT_ARCH="${SKIP_EXPORT_ARCH:-aarch64}"
|
||||||
|
START_EMULATOR_WHEN_NO_DEVICE="${START_EMULATOR_WHEN_NO_DEVICE:-0}"
|
||||||
|
EMULATOR_HEADLESS="${EMULATOR_HEADLESS:-1}"
|
||||||
|
EMULATOR_MEMORY_MB="${EMULATOR_MEMORY_MB:-1536}"
|
||||||
|
|
||||||
if [[ -n "${ANDROID_SDK_ROOT:-}" ]]; then
|
if [[ -n "${ANDROID_SDK_ROOT:-}" ]]; then
|
||||||
EMULATOR_BIN="$ANDROID_SDK_ROOT/emulator/emulator"
|
EMULATOR_BIN="$ANDROID_SDK_ROOT/emulator/emulator"
|
||||||
@@ -39,73 +44,14 @@ if ! command -v skip >/dev/null 2>&1; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! "$EMULATOR_BIN" -list-avds | grep -qx "$AVD_NAME"; then
|
TARGET_SERIAL=""
|
||||||
echo "Erro: AVD '$AVD_NAME' não existe. Disponíveis:"
|
|
||||||
"$EMULATOR_BIN" -list-avds
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
TARGET_SERIAL="$("$ADB_BIN" devices | awk 'NR>1 && $2=="device" {print $1; exit}')"
|
|
||||||
EMULATOR_PID=""
|
EMULATOR_PID=""
|
||||||
|
|
||||||
"$ADB_BIN" start-server >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
if [[ -z "$TARGET_SERIAL" ]]; then
|
|
||||||
echo "Iniciando emulador: $AVD_NAME"
|
|
||||||
nohup "$EMULATOR_BIN" -avd "$AVD_NAME" >/tmp/skip-android-emulator.log 2>&1 &
|
|
||||||
EMULATOR_PID="$!"
|
|
||||||
sleep 2
|
|
||||||
else
|
|
||||||
BOOTED="$("$ADB_BIN" -s "$TARGET_SERIAL" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')"
|
|
||||||
if [[ "$BOOTED" == "1" ]]; then
|
|
||||||
echo "Dispositivo já pronto."
|
|
||||||
else
|
|
||||||
echo "Dispositivo conectado, aguardando finalização do boot."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "$TARGET_SERIAL" ]]; then
|
|
||||||
echo "Aguardando ADB conectar..."
|
|
||||||
CONNECT_START_TS="$(date +%s)"
|
|
||||||
while true; do
|
|
||||||
TARGET_SERIAL="$("$ADB_BIN" devices | awk 'NR>1 && $2=="device" {print $1; exit}')"
|
|
||||||
if [[ -n "$TARGET_SERIAL" ]]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
NOW_TS="$(date +%s)"
|
|
||||||
if (( NOW_TS - CONNECT_START_TS >= ADB_CONNECT_TIMEOUT_SECONDS )); then
|
|
||||||
echo "Timeout aguardando ADB conectar (${ADB_CONNECT_TIMEOUT_SECONDS}s)."
|
|
||||||
echo "Estado atual do adb:"
|
|
||||||
"$ADB_BIN" devices || true
|
|
||||||
if [[ -n "$EMULATOR_PID" ]] && ! kill -0 "$EMULATOR_PID" 2>/dev/null; then
|
|
||||||
echo "Processo do emulador encerrou antes da conexão."
|
|
||||||
fi
|
|
||||||
echo "Últimas linhas do log do emulador (/tmp/skip-android-emulator.log):"
|
|
||||||
tail -n 80 /tmp/skip-android-emulator.log || true
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Aguardando boot do Android..."
|
|
||||||
START_TS="$(date +%s)"
|
|
||||||
until [[ "$("$ADB_BIN" -s "$TARGET_SERIAL" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" == "1" ]]; do
|
|
||||||
NOW_TS="$(date +%s)"
|
|
||||||
if (( NOW_TS - START_TS >= BOOT_TIMEOUT_SECONDS )); then
|
|
||||||
echo "Timeout aguardando boot do Android (${BOOT_TIMEOUT_SECONDS}s) no dispositivo $TARGET_SERIAL."
|
|
||||||
"$ADB_BIN" devices || true
|
|
||||||
tail -n 80 /tmp/skip-android-emulator.log || true
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
APP_ID="$(sed -nE 's/^[[:space:]]*PRODUCT_BUNDLE_IDENTIFIER[[:space:]]*=[[:space:]]*([^[:space:]]+).*/\1/p' "$PROJECT_DIR/Skip.env" | head -n1)"
|
APP_ID="$(sed -nE 's/^[[:space:]]*PRODUCT_BUNDLE_IDENTIFIER[[:space:]]*=[[:space:]]*([^[:space:]]+).*/\1/p' "$PROJECT_DIR/Skip.env" | head -n1)"
|
||||||
APP_ID="${APP_ID:-$APP_ID_DEFAULT}"
|
APP_ID="${APP_ID:-$APP_ID_DEFAULT}"
|
||||||
REQUIRE_FRESH_APK="${REQUIRE_FRESH_APK:-1}"
|
REQUIRE_FRESH_APK="${REQUIRE_FRESH_APK:-1}"
|
||||||
ALLOW_INSTALLED_FALLBACK="${ALLOW_INSTALLED_FALLBACK:-1}"
|
ALLOW_INSTALLED_FALLBACK="${ALLOW_INSTALLED_FALLBACK:-1}"
|
||||||
|
RUN_STABILITY_SECONDS="${RUN_STABILITY_SECONDS:-8}"
|
||||||
|
|
||||||
repair_skip_packages_links() {
|
repair_skip_packages_links() {
|
||||||
local swift_dir="$PROJECT_DIR/.build/plugins/outputs/pedi-foods/PediFoods/destination/skipstone/PediFoods/src/main/swift"
|
local swift_dir="$PROJECT_DIR/.build/plugins/outputs/pedi-foods/PediFoods/destination/skipstone/PediFoods/src/main/swift"
|
||||||
@@ -147,7 +93,8 @@ clean_skip_generated_outputs() {
|
|||||||
|
|
||||||
run_skip_export() {
|
run_skip_export() {
|
||||||
cd "$PROJECT_DIR"
|
cd "$PROJECT_DIR"
|
||||||
if skip export --debug --no-ios --android; then
|
echo "Executando skip export com arquitetura: $SKIP_EXPORT_ARCH"
|
||||||
|
if skip export --debug --no-ios --android --arch "$SKIP_EXPORT_ARCH"; then
|
||||||
repair_skip_packages_links
|
repair_skip_packages_links
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -157,7 +104,7 @@ run_skip_export() {
|
|||||||
repair_skip_packages_links
|
repair_skip_packages_links
|
||||||
|
|
||||||
echo "Tentando skip export novamente após limpeza de cache..."
|
echo "Tentando skip export novamente após limpeza de cache..."
|
||||||
if skip export --debug --no-ios --android; then
|
if skip export --debug --no-ios --android --arch "$SKIP_EXPORT_ARCH"; then
|
||||||
repair_skip_packages_links
|
repair_skip_packages_links
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -166,9 +113,210 @@ run_skip_export() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_avd_abi() {
|
||||||
|
local avd_name="$1"
|
||||||
|
local cfg="$HOME/.android/avd/${avd_name}.avd/config.ini"
|
||||||
|
if [[ ! -f "$cfg" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
sed -nE 's/^[[:space:]]*abi\.type[[:space:]]*=[[:space:]]*([^[:space:]]+).*/\1/p' "$cfg" | head -n1
|
||||||
|
}
|
||||||
|
|
||||||
|
apk_supports_abi() {
|
||||||
|
local apk="$1"
|
||||||
|
local abi="$2"
|
||||||
|
unzip -l "$apk" 2>/dev/null | grep "lib/${abi}/${REQUIRED_BRIDGE_SO}$" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
apk_contains_required_so() {
|
||||||
|
local apk="$1"
|
||||||
|
unzip -l "$apk" 2>/dev/null | grep "/${REQUIRED_BRIDGE_SO}$" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
first_connected_device() {
|
||||||
|
"$ADB_BIN" devices | awk 'NR>1 && $2=="device" {print $1; exit}'
|
||||||
|
}
|
||||||
|
|
||||||
|
pick_connected_device_for_apk() {
|
||||||
|
local apk="$1"
|
||||||
|
local serial abi
|
||||||
|
while IFS= read -r serial; do
|
||||||
|
[[ -n "$serial" ]] || continue
|
||||||
|
abi="$("$ADB_BIN" -s "$serial" shell getprop ro.product.cpu.abi 2>/dev/null | tr -d '\r')"
|
||||||
|
if [[ -n "$abi" ]] && apk_supports_abi "$apk" "$abi"; then
|
||||||
|
echo "$serial"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done < <("$ADB_BIN" devices | awk 'NR>1 && $2=="device" {print $1}')
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pick_compatible_avd() {
|
||||||
|
local apk="$1"
|
||||||
|
local preferred="$2"
|
||||||
|
local avd_list
|
||||||
|
avd_list="$("$EMULATOR_BIN" -list-avds)"
|
||||||
|
local host_arch
|
||||||
|
host_arch="$(uname -m 2>/dev/null || echo "")"
|
||||||
|
|
||||||
|
if [[ "$preferred" != "auto" ]] && echo "$avd_list" | grep -qx "$preferred"; then
|
||||||
|
local preferred_abi
|
||||||
|
preferred_abi="$(get_avd_abi "$preferred" || true)"
|
||||||
|
if [[ -n "$preferred_abi" ]] && apk_supports_abi "$apk" "$preferred_abi"; then
|
||||||
|
echo "$preferred"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
local avd abi
|
||||||
|
while IFS= read -r avd; do
|
||||||
|
[[ -n "$avd" ]] || continue
|
||||||
|
abi="$(get_avd_abi "$avd" || true)"
|
||||||
|
if [[ "$host_arch" == "arm64" || "$host_arch" == "aarch64" ]]; then
|
||||||
|
if [[ "$abi" == "x86_64" || "$abi" == "x86" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ -n "$abi" ]] && apk_supports_abi "$apk" "$abi"; then
|
||||||
|
echo "$avd"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done <<< "$avd_list"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_android_ready() {
|
||||||
|
local apk_path="${1:-}"
|
||||||
|
local avd_to_boot="$AVD_NAME"
|
||||||
|
|
||||||
|
if [[ -n "$apk_path" && -f "$apk_path" ]]; then
|
||||||
|
if ! apk_contains_required_so "$apk_path"; then
|
||||||
|
echo "Erro: APK não contém ${REQUIRED_BRIDGE_SO}. Build inválido para app Skip."
|
||||||
|
echo "APK: $apk_path"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
local picked
|
||||||
|
picked="$(pick_compatible_avd "$apk_path" "$AVD_NAME" || true)"
|
||||||
|
if [[ -n "$picked" ]]; then
|
||||||
|
avd_to_boot="$picked"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$ADB_BIN" start-server >/dev/null 2>&1 || true
|
||||||
|
if [[ -n "$apk_path" && -f "$apk_path" ]]; then
|
||||||
|
TARGET_SERIAL="$(pick_connected_device_for_apk "$apk_path" || true)"
|
||||||
|
else
|
||||||
|
TARGET_SERIAL="$(first_connected_device)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$TARGET_SERIAL" ]]; then
|
||||||
|
if [[ "$START_EMULATOR_WHEN_NO_DEVICE" != "1" ]]; then
|
||||||
|
echo "Erro: nenhum dispositivo Android conectado."
|
||||||
|
echo "Para evitar consumo de memória, este script não inicia emulador por padrão."
|
||||||
|
echo "Conecte um celular com USB debugging e rode novamente."
|
||||||
|
echo "Se quiser forçar emulador: START_EMULATOR_WHEN_NO_DEVICE=1 ./scripts/android-run.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! "$EMULATOR_BIN" -list-avds | grep -qx "$avd_to_boot"; then
|
||||||
|
echo "Erro: AVD '$avd_to_boot' não existe. Disponíveis:"
|
||||||
|
"$EMULATOR_BIN" -list-avds
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Iniciando emulador: $avd_to_boot"
|
||||||
|
local emulator_args=("$EMULATOR_BIN" "-avd" "$avd_to_boot" "-no-snapshot-load")
|
||||||
|
if [[ "$EMULATOR_HEADLESS" == "1" ]]; then
|
||||||
|
emulator_args+=("-no-window" "-gpu" "swiftshader_indirect" "-no-audio")
|
||||||
|
fi
|
||||||
|
if [[ -n "$EMULATOR_MEMORY_MB" ]]; then
|
||||||
|
emulator_args+=("-memory" "$EMULATOR_MEMORY_MB")
|
||||||
|
fi
|
||||||
|
|
||||||
|
nohup "${emulator_args[@]}" >/tmp/skip-android-emulator.log 2>&1 &
|
||||||
|
EMULATOR_PID="$!"
|
||||||
|
sleep 2
|
||||||
|
else
|
||||||
|
BOOTED="$("$ADB_BIN" -s "$TARGET_SERIAL" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')"
|
||||||
|
if [[ "$BOOTED" == "1" ]]; then
|
||||||
|
echo "Dispositivo já pronto."
|
||||||
|
else
|
||||||
|
echo "Dispositivo conectado, aguardando finalização do boot."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$TARGET_SERIAL" ]]; then
|
||||||
|
echo "Aguardando ADB conectar..."
|
||||||
|
CONNECT_START_TS="$(date +%s)"
|
||||||
|
while true; do
|
||||||
|
if [[ -n "$apk_path" && -f "$apk_path" ]]; then
|
||||||
|
TARGET_SERIAL="$(pick_connected_device_for_apk "$apk_path" || true)"
|
||||||
|
else
|
||||||
|
TARGET_SERIAL="$(first_connected_device)"
|
||||||
|
fi
|
||||||
|
if [[ -n "$TARGET_SERIAL" ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
NOW_TS="$(date +%s)"
|
||||||
|
if (( NOW_TS - CONNECT_START_TS >= ADB_CONNECT_TIMEOUT_SECONDS )); then
|
||||||
|
echo "Timeout aguardando ADB conectar (${ADB_CONNECT_TIMEOUT_SECONDS}s)."
|
||||||
|
echo "Estado atual do adb:"
|
||||||
|
"$ADB_BIN" devices || true
|
||||||
|
if [[ -n "$EMULATOR_PID" ]] && ! kill -0 "$EMULATOR_PID" 2>/dev/null; then
|
||||||
|
echo "Processo do emulador encerrou antes da conexão."
|
||||||
|
fi
|
||||||
|
echo "Últimas linhas do log do emulador (/tmp/skip-android-emulator.log):"
|
||||||
|
tail -n 80 /tmp/skip-android-emulator.log || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ -n "$EMULATOR_PID" ]] && ! kill -0 "$EMULATOR_PID" 2>/dev/null; then
|
||||||
|
echo "Processo do emulador encerrou antes da conexão."
|
||||||
|
echo "Últimas linhas do log do emulador (/tmp/skip-android-emulator.log):"
|
||||||
|
tail -n 80 /tmp/skip-android-emulator.log || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Aguardando boot do Android..."
|
||||||
|
START_TS="$(date +%s)"
|
||||||
|
until [[ "$("$ADB_BIN" -s "$TARGET_SERIAL" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" == "1" ]]; do
|
||||||
|
NOW_TS="$(date +%s)"
|
||||||
|
if (( NOW_TS - START_TS >= BOOT_TIMEOUT_SECONDS )); then
|
||||||
|
echo "Timeout aguardando boot do Android (${BOOT_TIMEOUT_SECONDS}s) no dispositivo $TARGET_SERIAL."
|
||||||
|
"$ADB_BIN" devices || true
|
||||||
|
tail -n 80 /tmp/skip-android-emulator.log || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -n "$apk_path" && -f "$apk_path" ]]; then
|
||||||
|
local device_abi
|
||||||
|
device_abi="$("$ADB_BIN" -s "$TARGET_SERIAL" shell getprop ro.product.cpu.abi 2>/dev/null | tr -d '\r')"
|
||||||
|
if [[ -n "$device_abi" ]] && ! apk_supports_abi "$apk_path" "$device_abi"; then
|
||||||
|
echo "Erro: APK não possui ${REQUIRED_BRIDGE_SO} para ABI do dispositivo: $device_abi"
|
||||||
|
echo "Tente outro AVD: x86_64 para APK x86_64, arm64-v8a para APK arm64."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
repair_skip_packages_links
|
repair_skip_packages_links
|
||||||
|
|
||||||
find_apk_path() {
|
find_apk_path() {
|
||||||
|
if [[ -f "$PROJECT_DIR/.build/skip-export/PediFoods-debug.apk" ]]; then
|
||||||
|
echo "$PROJECT_DIR/.build/skip-export/PediFoods-debug.apk"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local exported_apk
|
||||||
|
exported_apk="$(find "$PROJECT_DIR/.build/skip-export" -type f -name '*-debug.apk' 2>/dev/null | head -n1 || true)"
|
||||||
|
if [[ -n "$exported_apk" ]]; then
|
||||||
|
echo "$exported_apk"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
if [[ -f "$PROJECT_DIR/.build/Android/app/outputs/apk/debug/app-debug.apk" ]]; then
|
if [[ -f "$PROJECT_DIR/.build/Android/app/outputs/apk/debug/app-debug.apk" ]]; then
|
||||||
echo "$PROJECT_DIR/.build/Android/app/outputs/apk/debug/app-debug.apk"
|
echo "$PROJECT_DIR/.build/Android/app/outputs/apk/debug/app-debug.apk"
|
||||||
return 0
|
return 0
|
||||||
@@ -214,6 +362,7 @@ APK_PATH="$(find_apk_path)"
|
|||||||
|
|
||||||
if [[ -z "$APK_PATH" ]]; then
|
if [[ -z "$APK_PATH" ]]; then
|
||||||
if [[ "$ALLOW_INSTALLED_FALLBACK" == "1" ]]; then
|
if [[ "$ALLOW_INSTALLED_FALLBACK" == "1" ]]; then
|
||||||
|
ensure_android_ready
|
||||||
echo "Aviso: APK não encontrado. Tentando abrir app já instalado: $APP_ID"
|
echo "Aviso: APK não encontrado. Tentando abrir app já instalado: $APP_ID"
|
||||||
"$ADB_BIN" -s "$TARGET_SERIAL" shell monkey -p "$APP_ID" -c android.intent.category.LAUNCHER 1
|
"$ADB_BIN" -s "$TARGET_SERIAL" shell monkey -p "$APP_ID" -c android.intent.category.LAUNCHER 1
|
||||||
echo "App aberto sem reinstalar (fallback)."
|
echo "App aberto sem reinstalar (fallback)."
|
||||||
@@ -223,6 +372,8 @@ if [[ -z "$APK_PATH" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
ensure_android_ready "$APK_PATH"
|
||||||
|
|
||||||
if [[ "$REQUIRE_FRESH_APK" == "1" ]]; then
|
if [[ "$REQUIRE_FRESH_APK" == "1" ]]; then
|
||||||
APK_MTIME="$(stat -f '%m' "$APK_PATH" 2>/dev/null || echo 0)"
|
APK_MTIME="$(stat -f '%m' "$APK_PATH" 2>/dev/null || echo 0)"
|
||||||
SOURCE_MTIME="$(find "$PROJECT_DIR/Sources" -type f -name '*.swift' -exec stat -f '%m' {} \; 2>/dev/null | sort -nr | head -n1 || echo 0)"
|
SOURCE_MTIME="$(find "$PROJECT_DIR/Sources" -type f -name '*.swift' -exec stat -f '%m' {} \; 2>/dev/null | sort -nr | head -n1 || echo 0)"
|
||||||
@@ -253,4 +404,21 @@ else
|
|||||||
"$ADB_BIN" -s "$TARGET_SERIAL" shell monkey -p "$APP_ID" -c android.intent.category.LAUNCHER 1
|
"$ADB_BIN" -s "$TARGET_SERIAL" shell monkey -p "$APP_ID" -c android.intent.category.LAUNCHER 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "Validando execução do app..."
|
||||||
|
sleep 3
|
||||||
|
APP_PID="$("$ADB_BIN" -s "$TARGET_SERIAL" shell pidof "$APP_ID" 2>/dev/null | tr -d '\r')"
|
||||||
|
if [[ -z "$APP_PID" ]]; then
|
||||||
|
echo "Erro: app não permaneceu em execução após abrir."
|
||||||
|
"$ADB_BIN" -s "$TARGET_SERIAL" logcat -d | tail -n 120 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep "$RUN_STABILITY_SECONDS"
|
||||||
|
APP_PID_AFTER="$("$ADB_BIN" -s "$TARGET_SERIAL" shell pidof "$APP_ID" 2>/dev/null | tr -d '\r')"
|
||||||
|
if [[ -z "$APP_PID_AFTER" ]]; then
|
||||||
|
echo "Erro: app encerrou durante validação de estabilidade (${RUN_STABILITY_SECONDS}s)."
|
||||||
|
"$ADB_BIN" -s "$TARGET_SERIAL" logcat -d | tail -n 160 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "App instalado e aberto no Android."
|
echo "App instalado e aberto no Android."
|
||||||
|
|||||||
Reference in New Issue
Block a user