Split UI-free logic into a RunnerCore swift_library and cover it with a swift_test suite using the Swift Testing library (import Testing / @Test / #expect), matching Tests/m323Tests. Runs via `bazel test`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
87 lines
2.7 KiB
Swift
87 lines
2.7 KiB
Swift
import Foundation
|
|
import RunnerCore
|
|
import SwiftTUI
|
|
|
|
// MARK: - Configuration
|
|
|
|
let resultsURL = "/Users/cediackermann/Projects/m323/Sources/MiniProject/data/results.txt"
|
|
let startListURL = "/Users/cediackermann/Projects/m323/Sources/MiniProject/data/startList.txt"
|
|
|
|
// MARK: - Entry Point
|
|
|
|
let results = parseResults(readFile(resultsURL), separator: "\t")
|
|
let starters = parseStarters(readFile(startListURL), separator: "\t")
|
|
let finished = assembleRunners(starters: starters, results: results)
|
|
let absent = nonParticipants(starters: starters, results: results)
|
|
|
|
Application(rootView: RunnerBrowser(finished: finished, absent: absent)).start()
|
|
|
|
// MARK: - TUI
|
|
|
|
struct RunnerBrowser: View {
|
|
let finished: [Runner]
|
|
let absent: [Runner]
|
|
|
|
@State private var query: String = ""
|
|
@State private var sortKey: SortKey = .number
|
|
@State private var includedGroups: Set<AgeGroup> = Set(AgeGroup.allCases)
|
|
@State private var showAbsent: Bool = false
|
|
|
|
private var source: [Runner] {
|
|
showAbsent ? absent : finished
|
|
}
|
|
|
|
private var visible: [Runner] {
|
|
visibleRunners(source, query: query, includedGroups: includedGroups, sortKey: sortKey)
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading) {
|
|
Text("Stadt-Rundlauf - \(visible.count)/\(source.count) runners")
|
|
.bold()
|
|
|
|
HStack {
|
|
Text("Show:")
|
|
Button(menuLabel("Finished", on: !showAbsent)) {
|
|
showAbsent = false
|
|
}
|
|
Button(menuLabel("Did not start", on: showAbsent)) {
|
|
showAbsent = true
|
|
}
|
|
}
|
|
|
|
HStack {
|
|
Text("Sort:")
|
|
ForEach(SortKey.allCases, id: \.rawValue) { key in
|
|
Button(menuLabel(key.rawValue, on: key == sortKey)) {
|
|
sortKey = key
|
|
}
|
|
}
|
|
}
|
|
|
|
HStack {
|
|
Text("Groups:")
|
|
ForEach(AgeGroup.allCases, id: \.rawValue) { group in
|
|
Button(menuLabel("\(group)", on: includedGroups.contains(group))) {
|
|
includedGroups = toggling(includedGroups, group)
|
|
}
|
|
}
|
|
}
|
|
|
|
TextField(placeholder: "Search by name…") { value in
|
|
query = value
|
|
}
|
|
|
|
Divider()
|
|
|
|
Text(formatRow("Nr", "Name", "Group", "Time")).bold()
|
|
|
|
ScrollView {
|
|
ForEach(visible, id: \.number) { runner in
|
|
Text(formatRow("#\(runner.number)", runner.name, "\(runner.ageGroup)", runner.time))
|
|
}
|
|
}
|
|
}
|
|
.padding()
|
|
}
|
|
}
|