From 074052763638a36e274e18d00f5f5ab21846be28 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Sat, 25 Jul 2026 11:35:46 -0500 Subject: feat: enrich Settings → App Info with metadata & resource links (#56) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the three-row App Info screen with a full About/Resources/Support/ Legal layout, driven by a declarative AppInfoRow model with all URLs centralized in one AppLinks namespace. - About: Version now shows "5.0.0 (build N)" (CFBundleShortVersionString + CFBundleVersion), plus Storage, Backup Schema, and Minimum iOS (17.6). - Resources: What's New (bundled ReleaseNotes.json sheet, no network), Documentation & FAQ, Source Code, Privacy Policy, and Acknowledgements ("no third-party dependencies" + MIT license). - Support: Report an Issue (a sheet that shows the locally-assembled version/OS/device diagnostics before offering Email or GitHub — nothing is collected silently) and Contact (mailto). - Support the App: Rate (SwiftUI's @Environment(\.requestReview), which handles the scene internally and respects Apple's throttling — the modern, safer equivalent of the issue's SKStoreReviewController + connectedScenes path) and Share (ShareLink to the App Store listing). - Legal: copyright footer with the current year. External rows use Link/openURL with an "opens outside the app" accessibility hint; the screen is standard adaptive Form controls with semantic colors, so it tracks Dynamic Type and light/dark automatically. New files (AppLinks, AppInfo, ReleaseNotes[.swift/.json], AppInfoView) auto- compile/bundle via the synchronized DomainDig/ group. AppInfoTests covers the mailto builder, version format, diagnostics contents, and that the bundled notes ship and parse. Unit suite green. Placeholder pending the App Store listing: AppLinks.appStoreID (the review/share URLs derive from it), flagged with a TODO. --- DomainDig/AppInfo.swift | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 DomainDig/AppInfo.swift (limited to 'DomainDig/AppInfo.swift') diff --git a/DomainDig/AppInfo.swift b/DomainDig/AppInfo.swift new file mode 100644 index 0000000..28b0826 --- /dev/null +++ b/DomainDig/AppInfo.swift @@ -0,0 +1,52 @@ +import Foundation +import UIKit + +/// Runtime app metadata read from the bundle, plus the locally-assembled +/// diagnostics used to prefill an issue report. Nothing here touches the network +/// or collects any identifier beyond the app version, iOS version, and hardware +/// model string. +enum AppInfo { + /// CFBundleShortVersionString (marketing version), falling back to the + /// compiled-in `AppVersion.current` if the Info.plist key is missing. + static var marketingVersion: String { + Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? AppVersion.current + } + + /// CFBundleVersion (build number). + static var buildNumber: String { + Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "—" + } + + /// "5.0.0 (build 45)" — version alongside build, as the App Info row shows it. + static var versionDisplay: String { + "\(marketingVersion) (build \(buildNumber))" + } + + static let minimumOS = "17.6" + + static var systemVersion: String { + UIDevice.current.systemVersion + } + + /// Hardware model identifier (e.g. "iPhone15,2") — a model string, not a + /// per-device identifier. + static var deviceModel: String { + var systemInfo = utsname() + uname(&systemInfo) + let identifier = withUnsafeBytes(of: &systemInfo.machine) { raw -> String in + let bytes = raw.prefix { $0 != 0 } + return String(decoding: bytes, as: UTF8.self) + } + return identifier.isEmpty ? "Unknown" : identifier + } + + /// Diagnostics prefilled into an issue report. Shown to the user before any + /// send — never collected silently. + static var diagnosticsReport: String { + """ + DomainDig \(versionDisplay) + iOS \(systemVersion) + Device \(deviceModel) + """ + } +} -- cgit v1.2.3