From 1e541979e96165bb43a70564788c59bcbc2b637e Mon Sep 17 00:00:00 2001 From: psmattas Date: Tue, 18 Aug 2026 16:51:13 +0100 Subject: [PATCH] =?UTF-8?q?fix(settings):=20sidebar=20version=20footer=20?= =?UTF-8?q?=E2=80=94=20alpha=20tag,=20offline=20handling,=20auto-refresh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Outpost's version was missing the -ALPHA suffix About already shows — extracted OutpostVersion (Support/) as the one shared source for both, so a third divergent copy can't happen the way AboutInfoView's own doc comment already warns against for its two call sites. Outline's version now accounts for the manual Offline Mode toggle too, not just real connectivity, shows "Outline — offline" instead of just disappearing when nothing's been fetched yet, and re-fetches automatically via .task(id: isEffectivelyOnline) whenever connectivity changes — previously a one-shot fetch on first sidebar mount only. --- Outpost/Features/About/AboutView.swift | 12 +------ .../Account/SettingsSidebarList.swift | 19 +++++++++--- Outpost/Support/OutpostVersion.swift | 31 +++++++++++++++++++ 3 files changed, 46 insertions(+), 16 deletions(-) create mode 100644 Outpost/Support/OutpostVersion.swift diff --git a/Outpost/Features/About/AboutView.swift b/Outpost/Features/About/AboutView.swift index 6f4c53b..93b5afd 100644 --- a/Outpost/Features/About/AboutView.swift +++ b/Outpost/Features/About/AboutView.swift @@ -14,17 +14,7 @@ struct AboutInfoView: View { Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String ?? "Outpost" } - /// Bumped alongside `MARKETING_VERSION` in the Xcode project — kept out - /// of the bundle version itself since `CFBundleShortVersionString` is - /// expected to stay a plain dotted-numeric string, not `0.0.1-ALPHA`. - private let releaseStage = "ALPHA" - - var versionString: String { - let shortVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0.0.1" - let buildNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "1" - let stageSuffix = releaseStage.isEmpty ? "" : "-\(releaseStage)" - return "Version \(shortVersion)\(stageSuffix) (\(buildNumber))" - } + var versionString: String { OutpostVersion.fullVersionString } private var copyrightYear: String { String(Calendar.current.component(.year, from: Date())) diff --git a/Outpost/Features/Account/SettingsSidebarList.swift b/Outpost/Features/Account/SettingsSidebarList.swift index d855429..48b7fbf 100644 --- a/Outpost/Features/Account/SettingsSidebarList.swift +++ b/Outpost/Features/Account/SettingsSidebarList.swift @@ -20,10 +20,13 @@ struct SettingsSidebarList: View { let onDone: () -> Void @Environment(SessionStore.self) private var session + @AppStorage(CachingOutlineAPIClient.offlineModeDefaultsKey) private var isOfflineModeEnabled = false @State private var outlineVersion: String? - private var outpostVersion: String { - Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0.0.1" + /// Mirrors `SettingsView`'s own check — a real dropped connection or + /// the manual Offline Mode toggle both mean there's no server to ask. + private var isEffectivelyOnline: Bool { + session.networkMonitor.isOnline && !isOfflineModeEnabled } var body: some View { @@ -67,14 +70,20 @@ struct SettingsSidebarList: View { .frame(maxWidth: .infinity) .padding(12) } - .task { await refreshOutlineVersion() } + // Keyed to connectivity, not a one-shot `.task {}` — reconnecting + // (or turning the manual Offline Mode toggle back off) re-fires + // this automatically instead of leaving the footer stuck on + // whatever it last knew, or blank, until Settings is reopened. + .task(id: isEffectivelyOnline) { await refreshOutlineVersion() } } private var versionFooter: some View { VStack(alignment: .leading, spacing: 2) { - Text("Outpost \(outpostVersion)") + Text("Outpost \(OutpostVersion.displayString)") if let outlineVersion { Text("Outline \(outlineVersion)") + } else if !isEffectivelyOnline { + Text("Outline — offline") } } .font(.caption2) @@ -85,7 +94,7 @@ struct SettingsSidebarList: View { } private func refreshOutlineVersion() async { - guard let apiClient = session.apiClient else { return } + guard isEffectivelyOnline, let apiClient = session.apiClient else { return } outlineVersion = try? await apiClient.installationInfo().version } } diff --git a/Outpost/Support/OutpostVersion.swift b/Outpost/Support/OutpostVersion.swift new file mode 100644 index 0000000..85aee99 --- /dev/null +++ b/Outpost/Support/OutpostVersion.swift @@ -0,0 +1,31 @@ +import Foundation + +/// Single source of truth for how Outpost's own version is formatted — +/// used by both the About page and the Settings sidebar footer, so they +/// can't drift out of sync the way `AboutInfoView` was already written to +/// avoid for its own two call sites. +enum OutpostVersion { + /// Bumped alongside `MARKETING_VERSION` in the Xcode project — kept out + /// of the bundle version itself since `CFBundleShortVersionString` is + /// expected to stay a plain dotted-numeric string, not `0.0.1-ALPHA`. + static let releaseStage = "ALPHA" + + static var shortVersion: String { + Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0.0.1" + } + + static var buildNumber: String { + Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "1" + } + + /// e.g. `"0.0.3-ALPHA"` — for compact display (sidebar footer). + static var displayString: String { + let stageSuffix = releaseStage.isEmpty ? "" : "-\(releaseStage)" + return "\(shortVersion)\(stageSuffix)" + } + + /// e.g. `"Version 0.0.3-ALPHA (1)"` — for the About page. + static var fullVersionString: String { + "Version \(displayString) (\(buildNumber))" + } +}