Add playoff round view, game numbers, goal scorer notifications, standings
- Fetch NHL standings and surface league/season game counts in the menu bar
- Prefix regular-season rows with the league-wide game number (from gameId)
- New ROUND section shows each active playoff series (matchup, series score,
next game number + time) derived from /v1/playoff-bracket; rows always open
the NHL series page so completed series remain clickable
- Goal notifications include scorer sweater, abbreviated name, and strength
(PPG/SHG/EN), resolved via /v1/gamecenter/{id}/play-by-play
- Drop the per-team filter submenu and NHLTeam enum
- Regenerate AppIcon with the full 10-size macOS set (alpha preserved) so
notifications render the app icon correctly; rename the iOS marketing PNG
to icon-ios-1024.png
- gitignore .claude/ local tooling settings
This commit is contained in:
@@ -73,30 +73,58 @@ struct Scoreboard: Codable {
|
||||
"https://videocast.nhl.com/game/\(id)/usnded?autoplay=true"
|
||||
}
|
||||
|
||||
/// Time string in ET for display (e.g., "7:00 PM")
|
||||
/// Time string in ET, right-aligned to 8 chars (e.g. " 9:30 PM", "10:00 PM")
|
||||
var startTimeET: String {
|
||||
date.formatDateET(format: "h:mm a")
|
||||
let raw = date.formatDateET(format: "h:mm a")
|
||||
return raw.count < 8
|
||||
? String(repeating: " ", count: 8 - raw.count) + raw
|
||||
: raw
|
||||
}
|
||||
|
||||
/// Formatted menu title: "NYR @ WAS 0:2 (FINAL)" or "DAL @ TOR Today @ 7:30 PM"
|
||||
/// Formatted menu title:
|
||||
/// "NYR @ WAS 0: 2 9:30 PM" (finished/live — padded score + time)
|
||||
/// "DAL @ TOR 7:30 PM" (future — no score gap)
|
||||
var menuTitle: String {
|
||||
let state = parsedGameState
|
||||
let matchup = "\(awayTeam.abbrev) @ \(homeTeam.abbrev)"
|
||||
|
||||
if state.isFuture {
|
||||
let isToday = gameDate == Date.todayET
|
||||
let prefix = isToday ? "Today @ " : ""
|
||||
return "\(matchup) \(prefix)\(startTimeET)"
|
||||
return "\(matchup) \(startTimeET)"
|
||||
}
|
||||
|
||||
// Has scores
|
||||
let score = "\(awayTeam.score ?? 0):\(homeTeam.score ?? 0)"
|
||||
return "\(matchup) \(score) (\(gameState))"
|
||||
let aScore = String(format: "%2d", awayTeam.score ?? 0)
|
||||
let hScore = String(format: "%-2d", homeTeam.score ?? 0)
|
||||
return "\(matchup) \(aScore):\(hScore) \(startTimeET)"
|
||||
}
|
||||
|
||||
/// Whether this game involves a specific team
|
||||
func involves(team abbrev: String) -> Bool {
|
||||
awayTeam.abbrev == abbrev || homeTeam.abbrev == abbrev
|
||||
/// Sequential game number encoded in the last 4 digits of `id`.
|
||||
/// Regular season: 1…~1312. Playoffs: 111–417 (`RSG` round/series/game).
|
||||
var seasonGameNumber: Int { id % 10_000 }
|
||||
|
||||
/// Parsed playoff context; nil for non-playoff games.
|
||||
var playoffContext: PlayoffContext? {
|
||||
guard gameType == 3 else { return nil }
|
||||
let n = id % 1000
|
||||
return PlayoffContext(
|
||||
round: n / 100,
|
||||
seriesInRound: (n / 10) % 10,
|
||||
gameInSeries: n % 10
|
||||
)
|
||||
}
|
||||
|
||||
struct PlayoffContext: Equatable {
|
||||
let round: Int
|
||||
let seriesInRound: Int
|
||||
let gameInSeries: Int
|
||||
|
||||
/// A…O by cumulative position (R1 S1 = A, R1 S8 = H, R2 S1 = I, …, R4 S1 = O).
|
||||
var seriesLetter: String {
|
||||
let roundStartIndex = [0, 0, 8, 12, 14]
|
||||
guard round >= 1, round <= 4 else { return "" }
|
||||
let index = roundStartIndex[round] + (seriesInRound - 1)
|
||||
guard index >= 0, index < 15 else { return "" }
|
||||
return String(UnicodeScalar(65 + index)!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user