Files
iceglass/IceGlass-iOS/Views/SeriesRow.swift
T
rzen 541aa3d52c Show full playoff bracket, mark series results, harden API decoding
Playoffs:
- List every round played so far (Round 1 → current) instead of only the
  current round, on both macOS menu and iPhone
- Strike through the eliminated team's tricode in a finished series and drop
  the now-redundant "(Final … wins)" tag on completed earlier rounds
- Refetch the bracket when a finished game implies more completed games than
  the cached bracket records, so the series score and round no longer get
  stuck on stale data after cold launch or the NHL bracket endpoint's lag

API robustness:
- Tolerate optional gameCenterLink/startTimeUTC on TBD playoff matchups so the
  scoreboard decode no longer aborts
- Reject API state regressions via a monotonic FUT→…→OFF progression rank so a
  brief glitch can't downgrade a finished game back to "-:-"
2026-05-30 08:21:28 -04:00

103 lines
3.0 KiB
Swift
Raw 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.
//
// SeriesRow.swift
// IceGlass-iOS
//
// Copyright 2026 Rouslan Zenetl. All Rights Reserved.
//
import SwiftUI
struct SeriesRow: View {
let item: MainService.RoundSeriesItem
/// Completed earlier rounds hide the status/next-game column; the struck-out
/// loser already conveys the result.
var showStatus: Bool = true
private static let logoSize: CGFloat = 40
var body: some View {
Button(action: open) {
HStack(alignment: .center, spacing: 10) {
matchupBlock
Spacer(minLength: 8)
if showStatus {
rightContent
}
}
.padding(.horizontal, 14)
.padding(.vertical, 8)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
private var matchupBlock: some View {
let bottom = item.series.bottomSeedTeam?.abbrev ?? "TBD"
let top = item.series.topSeedTeam?.abbrev ?? "TBD"
return HStack(spacing: 6) {
TeamLogo(abbrev: bottom, size: Self.logoSize)
Text(bottom)
.font(.title3.monospaced())
.fontWeight(.semibold)
.strikethrough(item.series.loser == bottom)
.foregroundStyle(.primary)
.lineLimit(1)
.fixedSize()
Text(seriesScore)
.font(.title3.monospaced())
.fontWeight(.bold)
.foregroundStyle(.secondary)
.lineLimit(1)
.fixedSize()
.padding(.horizontal, 2)
Text(top)
.font(.title3.monospaced())
.fontWeight(.semibold)
.strikethrough(item.series.loser == top)
.foregroundStyle(.primary)
.lineLimit(1)
.fixedSize()
TeamLogo(abbrev: top, size: Self.logoSize)
}
}
private var rightContent: some View {
VStack(alignment: .trailing, spacing: 2) {
Text(statusText)
.font(.subheadline)
.fontWeight(.medium)
.foregroundStyle(.secondary)
if let trailing = trailingText {
Text(trailing)
.font(.caption2)
.foregroundStyle(.tertiary)
}
}
}
private var seriesScore: String {
"\(item.series.bottomSeedWins)\(item.series.topSeedWins)"
}
private var statusText: String {
if let winner = item.series.winner {
return "\(winner) wins"
}
if let n = item.series.nextGameNumber {
return "Game \(n)"
}
return ""
}
private var trailingText: String? {
guard item.series.winner == nil else { return "Final" }
return item.nextGame?.nextGameLabel
}
private func open() {
guard let urlString = item.series.fullSeriesUrl,
let url = URL(string: urlString) else { return }
UIApplication.shared.open(url)
}
}