diff --git a/KanbanMobile/App/MobileApp.swift b/KanbanMobile/App/MobileApp.swift index 90974b8..9c6a6c2 100644 --- a/KanbanMobile/App/MobileApp.swift +++ b/KanbanMobile/App/MobileApp.swift @@ -1,9 +1,7 @@ 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). +/// board → lane → card navigation stack) and Settings. @main struct MobileApp: App { @@ -13,40 +11,10 @@ struct MobileApp: App { /// 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)") - } - } - } } } } diff --git a/KanbanMobile/Backup/MobileBackupConfiguration.swift b/KanbanMobile/Backup/MobileBackupConfiguration.swift deleted file mode 100644 index 6241d12..0000000 --- a/KanbanMobile/Backup/MobileBackupConfiguration.swift +++ /dev/null @@ -1,68 +0,0 @@ -import Foundation -import IndieBackup -import SwiftUI - -/// IndieBackup's app-specific wiring (indie-backup skill): what gets backed up, and how the -/// app's in-memory board list is rebuilt after a restore. -/// -/// **Why this type is constructed rather than defaulted.** `BackupConfiguration.backupRoot` is a -/// non-optional `URL`, but the ubiquity container resolves asynchronously and can fail outright -/// (`CloudHomeResolver`, CloudHome.swift) — there is no root to hand IndieBackup until -/// `BoardIndexStore.home` exists. Rather than answer with a placeholder path, this type is only -/// ever constructed once a real `CloudHome` is in hand (`MobileApp`'s `.task(id:)`); before that -/// there is no `BackupController` at all, and `SettingsTabView` renders a quiet disabled -/// explanation instead of a section built on a fabricated root. -final class MobileBackupConfiguration: BackupConfiguration { - /// No dot. Shared by the Info.plist document-type/UTI registration - /// (`dev.rzen.indie.kanban-backup`) and the archive filenames IndieBackup writes. - static let fileExtension = "kanbanbackup" - - private let root: URL - private let index: BoardIndexStore - - /// - Parameters: - /// - root: The resolved `CloudHome.documentsURL` — the same `Documents/` folder every - /// `.kanban` board lives directly inside. IndieBackup walks it file-by-file (it has no - /// notion of "package"), so a board's directory structure round-trips through a backup - /// as an ordinary tree of files — no directory registration or special-casing needed. - /// - index: Rescanned after a restore replaces the tree, in `rebuildCacheAfterRestore`. - init(root: URL, index: BoardIndexStore) { - self.root = root - self.index = index - } - - var backupFileExtension: String { Self.fileExtension } - var backupDisplayName: String { "Lanework Backup" } - var backupRoot: URL { root } - - /// `BoardIndexStore` caches nothing durable — no Core Data, no SwiftData, no file on disk — - /// only an in-memory `[BoardSummary]` scanned straight from the files under `backupRoot`. A - /// restore already replaced that tree by the time this runs, so the only rebuild step is - /// asking the store to rescan it; a `BoardSession` for a board that's open elsewhere reloads - /// its own content from disk independently the next time it's asked (BoardIndexStore's doc - /// comment on the observer flow). - /// - /// `refresh()` only *starts* the rescan (it hands off to a detached `Task` and returns), so - /// this reports done as soon as the request lands, not once the boards tab has repainted — - /// same as any other call to `refresh()` in this app (e.g. pull-to-refresh). - func rebuildCacheAfterRestore(progress: @escaping (Double, String) -> Void) async throws { - progress(0, "Rescanning boards…") - await index.refresh() - progress(1, "Rescanning boards…") - } -} - -/// Threads the lazily-constructed `BackupController` from `MobileApp` down to `SettingsTabView` -/// (the backup section) and back up to `MobileApp`'s own `onOpenURL` handler (imported backup -/// files), without forcing `RootTabView` — or anything else between them — to know backup exists. -/// `nil` until the cloud home resolves; see `MobileBackupConfiguration`. -private struct BackupControllerKey: EnvironmentKey { - static let defaultValue: BackupController? = nil -} - -extension EnvironmentValues { - var backupController: BackupController? { - get { self[BackupControllerKey.self] } - set { self[BackupControllerKey.self] = newValue } - } -} diff --git a/KanbanMobile/CHANGELOG.md b/KanbanMobile/CHANGELOG.md index f4cd0cc..7055ba8 100644 --- a/KanbanMobile/CHANGELOG.md +++ b/KanbanMobile/CHANGELOG.md @@ -3,5 +3,3 @@ Version 1.0: Lanework comes to iPhone — browse your boards from iCloud Drive, move cards between lanes, and edit them on the go. Swipe a board in the list to open its settings and move it between iCloud and this iPhone, which now works without an iCloud account. - -Back up your boards from Settings and restore any backup later, even on a new phone. diff --git a/KanbanMobile/Info.plist b/KanbanMobile/Info.plist index 23774f3..f0d7048 100644 --- a/KanbanMobile/Info.plist +++ b/KanbanMobile/Info.plist @@ -28,9 +28,8 @@ UIInterfaceOrientationPortrait - + LSSupportsOpeningDocumentsInPlace - - CFBundleTypeName - Lanework Backup - CFBundleTypeRole - Editor - LSHandlerRank - Owner - LSItemContentTypes - - dev.rzen.indie.kanban-backup - - UTExportedTypeDeclarations @@ -93,23 +77,6 @@ kanban - - UTTypeConformsTo - - public.data - - UTTypeDescription - Lanework Backup - UTTypeIdentifier - dev.rzen.indie.kanban-backup - UTTypeTagSpecification - - public.filename-extension - - kanbanbackup - - -