fix(settings): sidebar version footer — alpha tag, offline handling, auto-refresh

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.
This commit is contained in:
2026-08-18 16:51:13 +01:00
parent 26b0d7b118
commit 1e541979e9
3 changed files with 46 additions and 16 deletions
+1 -11
View File
@@ -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()))
@@ -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
}
}
+31
View File
@@ -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))"
}
}