import SwiftUI import IndieBackup import os /// The iPhone app's entry point. Two tabs, per the mobile MVP charter: Boards (the whole /// board → lane → card navigation stack) and Settings (backup/restore). @main struct MobileApp: App { /// **The app's root model, owned here and nowhere else** — the container home, the one /// `NSMetadataQuery` the process runs, and the per-board session cache. `@State` because the /// scene owns its lifetime; every screen reaches it through `.environment`, never by /// constructing one. @State private var boardIndex = BoardIndexStore() /// Backup/restore (indie-backup skill), built once `boardIndex.home` resolves to a real /// `CloudHome` — see `MobileBackupConfiguration`'s doc comment for why this can't happen at /// `@State` init time. Stays `nil` for the lifetime of a run with no iCloud account; /// `SettingsTabView` renders the quiet disabled state for exactly that case. @State private var backupController: BackupController? private static let logger = Logger(subsystem: "dev.rzen.indie.KanbanMobile", category: "backup") var body: some Scene { WindowGroup { RootTabView() .environment(boardIndex) .environment(\.backupController, backupController) .task(id: boardIndex.home) { guard backupController == nil, let home = boardIndex.home else { return } backupController = BackupController( configuration: MobileBackupConfiguration(root: home.documentsURL, index: boardIndex) ) } .onOpenURL { url in // A backup file opened from Files, AirDrop, or a share sheet. Silently // ignored before `backupController` exists — there is no resolved cloud home // to restore into either, at that point. guard url.pathExtension == MobileBackupConfiguration.fileExtension, let backupController else { return } Task { do { try await backupController.restoreBackup(from: url) } catch { Self.logger.error("restore from opened file failed: \(error.localizedDescription, privacy: .public)") } } } } } } struct RootTabView: View { var body: some View { TabView { Tab("Boards", systemImage: "rectangle.stack") { BoardsTabView() } Tab("Settings", systemImage: "gearshape") { SettingsTabView() } } } }