johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
import SwiftUI
/// Whether the server still lets this build run. The answer is kept, so a build
/// that was stopped opens stopped, offline too. A server that doesn't answer
/// changes nothing, so an outage never locks anyone out.
@MainActor @Observable
final class AppUpdate {
private static let minimumKey = "minimumBuild"
private static let reasonKey = "minimumBuildReason"
/// This build, the highest there is if it can't be read, so that it runs.
private static let build = (Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String).flatMap(Int.init) ?? .max
private(set) var required: Bool
private(set) var reason: String?
init() {
let kept = UserDefaults.standard
required = kept.integer(forKey: Self.minimumKey) > Self.build
reason = kept.string(forKey: Self.reasonKey)
}
func check() async {
guard let answer: ServerDescription = try? await Client.shared.query("describeServer") else { return }
let kept = UserDefaults.standard
kept.set(answer.ios?.minimumBuild ?? 0, forKey: Self.minimumKey)
kept.set(answer.ios?.reason, forKey: Self.reasonKey)
required = (answer.ios?.minimumBuild ?? 0) > Self.build
reason = answer.ios?.reason
}
}
/// All a build too old shows: why, and the way to a newer one.
struct UpdateScreen: View {
private static let appID = "6831466657"
let reason: String?
@Environment(\.openURL) private var openURL
/// A TestFlight build updates in TestFlight, where the App Store may offer
/// nothing newer. A sandbox receipt is what tells them apart without asking
/// a server; the API Apple offers instead is async and can ask to sign in.
private var store: URL {
let beta = Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
return URL(string: beta ? "itms-beta://beta.itunes.apple.com/v1/app/\(Self.appID)" : "https://apps.apple.com/app/id\(Self.appID)")!
}
var body: some View {
VStack(spacing: Space.s3) {
Spacer(minLength: Space.s4)
Image("mark").renderingMode(.template).resizable().scaledToFit()
.frame(width: 112)
.foregroundStyle(.primary)
.accessibilityHidden(true)
Spacer(minLength: Space.s4)
Text("Update Knotless").font(.title3.weight(.semibold))
Text(reason ?? "This version no longer works. Update to keep using it.")
.font(.body)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
.fixedSize(horizontal: false, vertical: true)
.padding(.bottom, Space.s3)
Button { openURL(store) } label: {
Text("Update")
.font(.body.weight(.semibold))
.foregroundStyle(Color.page)
.frame(maxWidth: .infinity, minHeight: Size.signIn)
.background(Color.fg, in: Capsule())
.contentShape(Capsule())
}
.buttonStyle(.plain)
.padding(.bottom, Space.s6)
}
.padding(.horizontal, Space.s6)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}