Files
lanework/KanbanMobile/Screens/SettingsTabView.swift
T
rzen 209c991030 The phone learns to say who it is — IndieAbout joins the Settings tab with a changelog of its own
The About section lands last in the mobile Settings form, mirroring the Mac AboutBox voice: license link, copyright, and a version label that doubles as the changelog link. The changelog is the phone app's own — a separate product with its own 1.0, so the Mac CHANGELOG.md would narrate someone else's releases. The Update Build Info stamping phase rides along so the version line can carry a build number and date.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
2026-08-08 10:22:04 -04:00

62 lines
2.6 KiB
Swift

import SwiftUI
import IndieAbout
import IndieBackup
/// Backup/restore (indie-backup skill) and the About section (indie-about skill).
struct SettingsTabView: View {
@Environment(BoardIndexStore.self) private var index
@Environment(\.backupController) private var backupController
var body: some View {
NavigationStack {
Form {
backupSection
// Last section by convention. The version label doubles as the changelog link,
// which is why the changelog sits in its dedicated slot and not in `documents`.
// Same voice as the Mac app's `AboutBox.configuration`; the changelog itself is
// the phone app's own (see project.yml — separate product, separate 1.0).
Section {
IndieAbout(configuration: .init(
copyrightText: "© 2026 rzen",
documents: [.license(extension: "md")],
changelogDocument: .changelog()
))
}
}
.navigationTitle("Settings")
}
// Idempotent (BoardIndexStore.start()), and harmless if BoardsTabView already called it —
// this tab can be the first one the user opens, and `backupController` only ever appears
// once `index.home` resolves.
.task { index.start() }
}
/// `backupController` is `nil` until `MobileApp` has a resolved `CloudHome` to build one from
/// — which is also exactly the condition under which there is a `Documents/` folder to back
/// up. Everything short of that gets the same quiet explanation rather than a section that
/// looks broken or, worse, a crash on a nil root.
@ViewBuilder
private var backupSection: some View {
if let backupController {
BackupsSectionView(controller: backupController)
} else {
Section {
switch index.phase {
case .idle, .loading:
LabeledContent("Backup") {
ProgressView()
}
case .unavailable, .ready:
// `.ready` can appear briefly here too: `backupController` lags one runloop
// turn behind `index.home` resolving (MobileApp's `.task(id:)`).
Label("Backup and restore need iCloud Drive.", systemImage: "icloud.slash")
.foregroundStyle(.secondary)
}
} footer: {
Text("Sign into iCloud in Settings, then come back here to back up your boards.")
}
}
}
}