Files

207 lines
7.9 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import SwiftUI
// MARK: - Launch gate
//
// The tool is only meaningful below 27.2: every primitive here demonstrates a check Apple
// added in 27.2. On 27.2+ nothing fires, so the honest behaviour is to say so and stop —
// not to show a workbench full of buttons that can only fail.
enum VersionGate {
enum Result {
case supported(major: Int, minor: Int, build: String)
case unsupported(major: Int, minor: Int, build: String)
}
static func evaluate() -> Result {
let v = ProcessInfo.processInfo.operatingSystemVersion
let build = sysctlString("kern.osversion") ?? "—"
// Supported window: 17.0 through 27.1.
//
// The lower bound is the app's own deployment target — below 17.0 the binary will
// not launch at all, so it cannot be reached from here. Between 17.0 and 26.x the
// fix window has not happened yet, so every primitive in this build is live. 27.0
// and 27.1 sit immediately before the 27.2 gate wave and are the primary targets.
// From 27.2 onward every check these primitives demonstrate already shipped.
let supported: Bool
if v.majorVersion < 17 {
supported = false // below the deployment target
} else if v.majorVersion < 27 {
supported = true // 17.0 … 26.x — fixes not yet shipped
} else if v.majorVersion == 27 {
supported = v.minorVersion < 2 // 27.0, 27.1
} else {
supported = false // 27.2+ / 28+ — patched
}
// An explicit override exists for research builds where the version string lies.
if ProcessInfo.processInfo.environment["CINDER27_FORCE_GATE"] == "1" {
return .supported(major: v.majorVersion, minor: v.minorVersion, build: build)
}
return supported
? .supported(major: v.majorVersion, minor: v.minorVersion, build: build)
: .unsupported(major: v.majorVersion, minor: v.minorVersion, build: build)
}
static var isSupported: Bool {
if case .supported = evaluate() { return true }
return false
}
}
struct GateView: View {
@State private var phase: Phase = .checking
@State private var entered = false
enum Phase {
case checking
case supported(major: Int, minor: Int, build: String)
case unsupported(major: Int, minor: Int, build: String)
}
var body: some View {
ZStack {
T.bg.ignoresSafeArea()
if entered, case let .supported(major, minor, build) = phase {
RootView()
.environment(\.gateInfo, GateInfo(major: major, minor: minor, build: build))
.transition(.opacity)
} else {
content
.transition(.opacity)
}
}
.animation(.easeInOut(duration: 0.25), value: entered)
.onAppear(perform: check)
.onChange(of: phaseText) { _, _ in
// Headless harness: CINDER27_AUTOENTER=1 walks through the gate so tab
// screenshots and autoruns do not need a tap.
if ProcessInfo.processInfo.environment["CINDER27_AUTOENTER"] == "1",
case .supported = phase, !entered {
entered = true
}
}
}
@ViewBuilder
private var content: some View {
VStack(spacing: 22) {
Spacer()
VStack(spacing: 10) {
Text("Cinder27")
.font(.system(size: 34, weight: .bold, design: .rounded))
.foregroundColor(.white)
Text(t("ov.headline"))
.font(.system(size: 13, weight: .medium, design: .rounded))
.foregroundColor(.white.opacity(0.4))
.multilineTextAlignment(.center)
}
switch phase {
case .checking:
VStack(spacing: 12) {
ProgressView().tint(T.ember)
Text(t("gate.checking"))
.font(T.body).foregroundColor(.white.opacity(0.5))
}
.padding(.top, 20)
case let .supported(major, minor, build):
Card {
VStack(alignment: .leading, spacing: 10) {
HStack(spacing: 8) {
Image(systemName: "checkmark.seal.fill")
.font(.system(size: 14))
.foregroundColor(T.good)
Text(t("gate.supported"))
.font(.system(size: 15, weight: .semibold, design: .rounded))
.foregroundColor(T.good)
}
KeyValueRow(key: t("gate.detected"), value: "iOS \(major).\(minor)")
KeyValueRow(key: t("gate.build"), value: build)
KeyValueRow(key: t("gate.required"), value: "17.0 – 27.1")
}
}
.padding(.horizontal, 24)
RunButton(title: t("gate.enter"), systemImage: "arrow.right.circle.fill", tint: T.ember) {
entered = true
}
.padding(.horizontal, 24)
case let .unsupported(major, minor, build):
Card {
VStack(alignment: .leading, spacing: 10) {
HStack(spacing: 8) {
Image(systemName: "xmark.octagon.fill")
.font(.system(size: 14))
.foregroundColor(T.bad)
Text(t("gate.unsupported.title"))
.font(.system(size: 15, weight: .semibold, design: .rounded))
.foregroundColor(T.bad)
}
KeyValueRow(key: t("gate.detected"), value: "iOS \(major).\(minor)", tint: T.bad)
KeyValueRow(key: t("gate.build"), value: build)
KeyValueRow(key: t("gate.required"), value: "17.0 – 27.1")
Divider().overlay(T.stroke)
Text(t("gate.unsupported.body"))
.font(T.body)
.foregroundColor(.white.opacity(0.62))
.fixedSize(horizontal: false, vertical: true)
}
}
.padding(.horizontal, 24)
RunButton(title: t("gate.recheck"), systemImage: "arrow.clockwise", tint: T.info) {
check()
}
.padding(.horizontal, 24)
}
Spacer()
Text("Cinder27 · \(Lang.resolved.native)")
.font(T.tiny)
.foregroundColor(.white.opacity(0.22))
.padding(.bottom, 18)
}
}
private var phaseText: String {
switch phase {
case .checking: return "checking"
case let .supported(m, n, _): return "ok-\(m).\(n)"
case let .unsupported(m, n, _): return "no-\(m).\(n)"
}
}
private func check() {
phase = .checking
DispatchQueue.main.asyncAfter(deadline: .now() + 0.45) {
switch VersionGate.evaluate() {
case let .supported(major, minor, build):
phase = .supported(major: major, minor: minor, build: build)
case let .unsupported(major, minor, build):
phase = .unsupported(major: major, minor: minor, build: build)
}
}
}
}
struct GateInfo {
let major: Int
let minor: Int
let build: String
}
private struct GateInfoKey: EnvironmentKey {
static let defaultValue = GateInfo(major: 0, minor: 0, build: "—")
}
extension EnvironmentValues {
var gateInfo: GateInfo {
get { self[GateInfoKey.self] }
set { self[GateInfoKey.self] = newValue }
}
}