~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
76 lines · 3.2 KB Raw History
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)
    }
}