506bea04cc
New TeamLogo view loads bundled TeamLogos/{abbrev}.png with a UIImage
cache so scrolling doesn't repeatedly re-decode. GameRow and SeriesRow
now render [logo] TRI @ [logo] TRI with tricodes in a monospaced font
so columns line up regardless of which letters are present. SF Symbol
fallback when an abbrev has no bundled logo (e.g. "TBD" for unfilled
playoff slots).
94 lines
2.7 KiB
Swift
94 lines
2.7 KiB
Swift
//
|
||
// GameRow.swift
|
||
// IceGlass-iOS
|
||
//
|
||
// Copyright 2026 Rouslan Zenetl. All Rights Reserved.
|
||
//
|
||
|
||
import SwiftUI
|
||
|
||
struct GameRow: View {
|
||
let game: Scoreboard.Game
|
||
|
||
var body: some View {
|
||
Button(action: open) {
|
||
HStack(spacing: 10) {
|
||
if game.gameType == 2 {
|
||
Text("#\(game.seasonGameNumber)")
|
||
.font(.caption2.monospacedDigit())
|
||
.foregroundStyle(.tertiary)
|
||
.frame(width: 44, alignment: .leading)
|
||
}
|
||
|
||
matchupBlock
|
||
|
||
Spacer(minLength: 8)
|
||
|
||
rightContent
|
||
}
|
||
.padding(.horizontal, 14)
|
||
.padding(.vertical, 10)
|
||
.contentShape(Rectangle())
|
||
}
|
||
.buttonStyle(.plain)
|
||
}
|
||
|
||
private var matchupBlock: some View {
|
||
HStack(spacing: 6) {
|
||
TeamLogo(abbrev: game.awayTeam.abbrev)
|
||
Text(game.awayTeam.abbrev)
|
||
.font(.body.monospaced())
|
||
.fontWeight(.medium)
|
||
.foregroundStyle(.primary)
|
||
Text("@")
|
||
.font(.subheadline)
|
||
.foregroundStyle(.tertiary)
|
||
TeamLogo(abbrev: game.homeTeam.abbrev)
|
||
Text(game.homeTeam.abbrev)
|
||
.font(.body.monospaced())
|
||
.fontWeight(.medium)
|
||
.foregroundStyle(.primary)
|
||
}
|
||
}
|
||
|
||
@ViewBuilder
|
||
private var rightContent: some View {
|
||
let state = game.parsedGameState
|
||
VStack(alignment: .trailing, spacing: 2) {
|
||
if state.isFuture {
|
||
Text(game.startTimeET.trimmingCharacters(in: .whitespaces))
|
||
.font(.subheadline.monospacedDigit())
|
||
.foregroundStyle(.secondary)
|
||
} else {
|
||
Text(scoreText)
|
||
.font(.body.monospacedDigit())
|
||
.fontWeight(.semibold)
|
||
.foregroundStyle(.primary)
|
||
Text(statusLine)
|
||
.font(.caption2)
|
||
.foregroundStyle(state.isLive ? .red : .secondary)
|
||
}
|
||
}
|
||
}
|
||
|
||
private var scoreText: String {
|
||
let a = game.awayTeam.score ?? 0
|
||
let h = game.homeTeam.score ?? 0
|
||
return "\(a) – \(h)"
|
||
}
|
||
|
||
private var statusLine: String {
|
||
let state = game.parsedGameState
|
||
let tag = state.shortTag
|
||
let time = game.startTimeET.trimmingCharacters(in: .whitespaces)
|
||
if tag.isEmpty { return time }
|
||
if state.isLive { return tag }
|
||
return tag
|
||
}
|
||
|
||
private func open() {
|
||
guard let url = URL(string: game.gameCenterUrl) else { return }
|
||
UIApplication.shared.open(url)
|
||
}
|
||
}
|