121 lines
3.6 KiB
Swift
121 lines
3.6 KiB
Swift
import SwiftUI
|
|
|
|
// MARK: - App State
|
|
|
|
private enum AppState {
|
|
case onboarding
|
|
case auth
|
|
case unlock
|
|
case main
|
|
}
|
|
|
|
// MARK: - RosettaApp
|
|
|
|
@main
|
|
struct RosettaApp: App {
|
|
|
|
init() {
|
|
UIWindow.appearance().backgroundColor = .black
|
|
|
|
// Detect fresh install: UserDefaults are wiped on uninstall, Keychain is not.
|
|
// If this is the first launch after install, clear any stale Keychain data.
|
|
if !UserDefaults.standard.bool(forKey: "hasLaunchedBefore") {
|
|
try? AccountManager.shared.deleteAccount()
|
|
UserDefaults.standard.set(true, forKey: "hasLaunchedBefore")
|
|
}
|
|
|
|
// Avoid heavy startup work on MainActor; Lottie assets load lazily on first use.
|
|
}
|
|
|
|
@AppStorage("hasCompletedOnboarding") private var hasCompletedOnboarding = false
|
|
@AppStorage("isLoggedIn") private var isLoggedIn = false
|
|
@AppStorage("hasLaunchedBefore") private var hasLaunchedBefore = false
|
|
@State private var appState: AppState?
|
|
|
|
var body: some Scene {
|
|
WindowGroup {
|
|
ZStack {
|
|
RosettaColors.Dark.background
|
|
.ignoresSafeArea()
|
|
|
|
if let appState {
|
|
rootView(for: appState)
|
|
.transition(.opacity.animation(.easeInOut(duration: 0.5)))
|
|
}
|
|
}
|
|
.preferredColorScheme(.dark)
|
|
.onAppear {
|
|
if appState == nil {
|
|
appState = initialState()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@MainActor static var _bodyCount = 0
|
|
@ViewBuilder
|
|
private func rootView(for state: AppState) -> some View {
|
|
let _ = Self._bodyCount += 1
|
|
let _ = print("⭐ RosettaApp.rootView #\(Self._bodyCount) state=\(state)")
|
|
switch state {
|
|
case .onboarding:
|
|
OnboardingView {
|
|
withAnimation(.easeInOut(duration: 0.55)) {
|
|
hasCompletedOnboarding = true
|
|
appState = .auth
|
|
}
|
|
}
|
|
|
|
case .auth:
|
|
AuthCoordinator(
|
|
onAuthComplete: {
|
|
withAnimation(.easeInOut(duration: 0.55)) {
|
|
isLoggedIn = true
|
|
appState = .main
|
|
}
|
|
},
|
|
onBackToUnlock: AccountManager.shared.hasAccount ? {
|
|
// Go back to unlock screen if an account exists
|
|
withAnimation(.easeInOut(duration: 0.55)) {
|
|
appState = .unlock
|
|
}
|
|
} : nil
|
|
)
|
|
|
|
case .unlock:
|
|
UnlockView(
|
|
onUnlocked: {
|
|
withAnimation(.easeInOut(duration: 0.55)) {
|
|
isLoggedIn = true
|
|
appState = .main
|
|
}
|
|
},
|
|
onCreateNewAccount: {
|
|
// Go to auth flow (Welcome screen with back button)
|
|
// Does NOT delete the old account — Android keeps multiple accounts
|
|
withAnimation(.easeInOut(duration: 0.55)) {
|
|
appState = .auth
|
|
}
|
|
}
|
|
)
|
|
|
|
case .main:
|
|
MainTabView(onLogout: {
|
|
withAnimation(.easeInOut(duration: 0.55)) {
|
|
isLoggedIn = false
|
|
appState = .unlock
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
private func initialState() -> AppState {
|
|
if AccountManager.shared.hasAccount {
|
|
return .unlock
|
|
} else {
|
|
hasCompletedOnboarding = false
|
|
return .onboarding
|
|
}
|
|
}
|
|
}
|