Use native navigation list for iOS launcher settings

This commit is contained in:
seedds committed 2026-10-25 18:59:47 +08:00
1 parent 641799d51b
commit d62ddbbd25
1 file changed
+73 -23
+73 -23
View File
@@ -49,26 +49,65 @@ private final class LauncherModel: ObservableObject {
private struct SettingRow: View {
let s: Setting
@ObservedObject var m: LauncherModel
private var locked: Bool { m.fromFile.contains(s.env) }
private var note: String {
locked ? (s.note.isEmpty ? "" : s.note + " · ") + "Set by an env file" : s.note
}
private var rangeValue: Binding<Double> {
Binding(get: { Double(m.values[s.env] ?? s.def) ?? s.max ?? 1 },
set: { m.values[s.env] = String(format: "%.2f", $0) })
}
private var percentage: String { "\(Int((rangeValue.wrappedValue * 100).rounded())) %" }
private var settingLabel: some View {
VStack(alignment: .leading, spacing: 4) {
Text(s.label)
if !note.isEmpty { Text(note).font(.footnote).foregroundStyle(.secondary) }
}
}
var body: some View {
let locked = m.fromFile.contains(s.env)
Section {
Group {
switch s.kind {
case "choice":
Picker(s.label, selection: m.binding(s)) { ForEach(s.options ?? [], id: \.[0]) { Text($0[1]).tag($0[0]) } }
Picker(selection: m.binding(s)) {
ForEach(s.options ?? [], id: \.[0]) { Text($0[1]).tag($0[0]) }
} label: {
settingLabel
}
.pickerStyle(.navigationLink)
case "range":
let lo = s.min ?? 0, hi = s.max ?? 1
let v = Binding<Double>(get: { Double(m.values[s.env] ?? s.def) ?? hi }, set: { m.values[s.env] = String(format: "%.2f", $0) })
HStack {
Text(s.label)
Slider(value: v, in: lo...hi, step: s.step ?? 0.05)
Text("\(Int((v.wrappedValue * 100).rounded())) %").monospacedDigit().foregroundStyle(.secondary).frame(width: 56, alignment: .trailing)
NavigationLink {
Form {
Section {
HStack {
Text(s.label)
Spacer()
Text(percentage).monospacedDigit().foregroundStyle(.secondary)
}
Slider(value: rangeValue, in: (s.min ?? 0)...(s.max ?? 1), step: s.step ?? 0.05)
.accessibilityLabel(s.label)
.accessibilityValue(percentage)
.disabled(locked)
} footer: {
if !note.isEmpty { Text(note) }
}
}
.navigationTitle(s.label)
.navigationBarTitleDisplayMode(.inline)
} label: {
HStack {
settingLabel
Spacer()
Text(percentage).monospacedDigit().foregroundStyle(.secondary)
}
}
default:
Toggle(s.label, isOn: Binding(get: { (m.values[s.env] ?? s.def) == "1" }, set: { m.values[s.env] = $0 ? "1" : "0" }))
Toggle(isOn: Binding(get: { (m.values[s.env] ?? s.def) == "1" }, set: { m.values[s.env] = $0 ? "1" : "0" })) {
settingLabel
}
}
} footer: {
let note = locked ? (s.note.isEmpty ? "" : s.note + " · ") + "Set by an env file" : s.note
if !note.isEmpty { Text(note) }
}
.disabled(locked)
}
@@ -83,23 +122,34 @@ private struct LauncherView: View {
HStack(spacing: 0) {
VStack(spacing: 16) {
if let icon = appIcon() {
Image(uiImage: icon).resizable().frame(width: 96, height: 96).clipShape(RoundedRectangle(cornerRadius: 22))
Image(uiImage: icon).resizable().frame(width: 96, height: 96)
.clipShape(RoundedRectangle(cornerRadius: 22))
.accessibilityHidden(true)
}
Text((info("CFBundleDisplayName") as? String) ?? "Game").font(.title.weight(.bold)).multilineTextAlignment(.center)
Text("Version \((info("CFBundleShortVersionString") as? String) ?? "")").font(.callout).foregroundStyle(.secondary)
Text((info("CFBundleDisplayName") as? String) ?? "Game")
.font(.title.weight(.bold)).multilineTextAlignment(.center)
Text("Version \((info("CFBundleShortVersionString") as? String) ?? "")")
.font(.callout).foregroundStyle(.secondary)
playButton
}
.padding(24)
.frame(maxWidth: .infinity)
Form {
ForEach(m.settings) { SettingRow(s: $0, m: m) }
Section {
HStack {
Text("Memory available")
Spacer()
Text(ByteCountFormatter.string(fromByteCount: Int64(memory), countStyle: .memory)).monospacedDigit().foregroundStyle(.secondary)
NavigationStack {
List {
Section {
ForEach(m.settings) { SettingRow(s: $0, m: m) }
}
Section {
HStack {
Text("Memory available")
Spacer()
Text(ByteCountFormatter.string(fromByteCount: Int64(memory), countStyle: .memory)).monospacedDigit().foregroundStyle(.secondary)
}
}
}
.listStyle(.insetGrouped)
.navigationTitle("Settings")
.navigationBarTitleDisplayMode(.inline)
}
.frame(maxWidth: .infinity)
}