Initial commit
This commit is contained in:
108
mlbTVOS/Views/Components/LinescoreView.swift
Normal file
108
mlbTVOS/Views/Components/LinescoreView.swift
Normal file
@@ -0,0 +1,108 @@
|
||||
import SwiftUI
|
||||
|
||||
struct LinescoreView: View {
|
||||
let linescore: StatsLinescore
|
||||
let awayCode: String
|
||||
let homeCode: String
|
||||
|
||||
private var totalInnings: Int {
|
||||
linescore.scheduledInnings ?? 9
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
// Header
|
||||
HStack(spacing: 0) {
|
||||
Text("")
|
||||
.frame(width: 70, alignment: .leading)
|
||||
|
||||
ForEach(1...totalInnings, id: \.self) { inning in
|
||||
let isCurrent = inning == linescore.currentInning
|
||||
HStack(spacing: 0) {
|
||||
if inning > 1 && inning % 3 == 1 {
|
||||
Divider()
|
||||
.frame(width: 1, height: 18)
|
||||
.background(.secondary.opacity(0.3))
|
||||
.padding(.trailing, 4)
|
||||
}
|
||||
Text("\(inning)")
|
||||
.font(.callout.weight(.semibold).monospacedDigit())
|
||||
.foregroundStyle(isCurrent ? .primary : .secondary)
|
||||
.frame(width: 44)
|
||||
}
|
||||
}
|
||||
|
||||
Divider().frame(width: 1, height: 20).padding(.horizontal, 6)
|
||||
|
||||
ForEach(["R", "H", "E"], id: \.self) { label in
|
||||
Text(label)
|
||||
.font(.callout.weight(.bold).monospacedDigit())
|
||||
.foregroundStyle(.secondary)
|
||||
.frame(width: 48)
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.background(.ultraThinMaterial)
|
||||
|
||||
Divider()
|
||||
|
||||
teamRow(code: awayCode, innings: linescore.innings ?? [], side: .away, totals: linescore.teams?.away)
|
||||
Divider()
|
||||
teamRow(code: homeCode, innings: linescore.innings ?? [], side: .home, totals: linescore.teams?.home)
|
||||
}
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
.background(.regularMaterial)
|
||||
}
|
||||
|
||||
private enum Side { case away, home }
|
||||
|
||||
@ViewBuilder
|
||||
private func teamRow(code: String, innings: [StatsInningScore], side: Side, totals: StatsLinescoreTotals?) -> some View {
|
||||
HStack(spacing: 0) {
|
||||
Text(code)
|
||||
.font(.callout.weight(.bold))
|
||||
.foregroundStyle(TeamAssets.color(for: code))
|
||||
.frame(width: 70, alignment: .leading)
|
||||
|
||||
ForEach(1...totalInnings, id: \.self) { inning in
|
||||
let runs = inningRuns(innings: innings, inning: inning, side: side)
|
||||
let isCurrent = inning == linescore.currentInning
|
||||
HStack(spacing: 0) {
|
||||
if inning > 1 && inning % 3 == 1 {
|
||||
Divider()
|
||||
.frame(width: 1, height: 22)
|
||||
.background(.secondary.opacity(0.2))
|
||||
.padding(.trailing, 4)
|
||||
}
|
||||
Text(runs.map { "\($0)" } ?? "-")
|
||||
.font(.callout.weight(runs != nil ? .semibold : .regular).monospacedDigit())
|
||||
.foregroundStyle(runs == nil ? .tertiary : isCurrent ? .primary : .secondary)
|
||||
.frame(width: 44)
|
||||
}
|
||||
}
|
||||
|
||||
Divider().frame(width: 1, height: 24).padding(.horizontal, 6)
|
||||
|
||||
Text(totals?.runs.map { "\($0)" } ?? "-")
|
||||
.font(.callout.weight(.bold).monospacedDigit())
|
||||
.frame(width: 48)
|
||||
Text(totals?.hits.map { "\($0)" } ?? "-")
|
||||
.font(.callout.weight(.medium).monospacedDigit())
|
||||
.foregroundStyle(.secondary)
|
||||
.frame(width: 48)
|
||||
Text(totals?.errors.map { "\($0)" } ?? "-")
|
||||
.font(.callout.weight(.medium).monospacedDigit())
|
||||
.foregroundStyle(.secondary)
|
||||
.frame(width: 48)
|
||||
}
|
||||
.padding(.vertical, 12)
|
||||
}
|
||||
|
||||
private func inningRuns(innings: [StatsInningScore], inning: Int, side: Side) -> Int? {
|
||||
guard let data = innings.first(where: { $0.num == inning }) else { return nil }
|
||||
switch side {
|
||||
case .away: return data.away?.runs
|
||||
case .home: return data.home?.runs
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user