Initial commit: IceGlass NHL game tracker
macOS menu bar app providing NHL game situational awareness with league-wide scoreboard, dynamic polling, notifications with team logos, and configurable display options.
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// ScoreboardModel.swift
|
||||
// IceGlass
|
||||
//
|
||||
// Copyright 2026 Rouslan Zenetl. All Rights Reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct Scoreboard: Codable {
|
||||
let focusedDate: String
|
||||
let focusedDateCount: Int
|
||||
let gamesByDate: [GameDay]
|
||||
|
||||
struct GameDay: Codable {
|
||||
let date: String // "YYYY-MM-DD"
|
||||
let games: [Game]
|
||||
}
|
||||
|
||||
struct Game: Codable, Equatable {
|
||||
static func == (lhs: Game, rhs: Game) -> Bool {
|
||||
lhs.id == rhs.id
|
||||
}
|
||||
|
||||
let id: Int
|
||||
let season: Int
|
||||
let gameType: Int
|
||||
let gameDate: String
|
||||
let gameCenterLink: String
|
||||
let startTimeUTC: String
|
||||
let gameState: String
|
||||
let gameScheduleState: String
|
||||
let awayTeam: Team
|
||||
let homeTeam: Team
|
||||
let period: Int?
|
||||
let periodDescriptor: PeriodDescriptor?
|
||||
|
||||
struct LocalizedString: Codable {
|
||||
let `default`: String
|
||||
}
|
||||
|
||||
struct Team: Codable {
|
||||
let id: Int
|
||||
let name: LocalizedString
|
||||
let commonName: LocalizedString
|
||||
let abbrev: String
|
||||
let score: Int?
|
||||
let record: String?
|
||||
let logo: String
|
||||
}
|
||||
|
||||
struct PeriodDescriptor: Codable {
|
||||
let number: Int
|
||||
let periodType: String
|
||||
let maxRegulationPeriods: Int
|
||||
}
|
||||
|
||||
// MARK: - Computed Properties
|
||||
|
||||
var parsedGameState: GameState {
|
||||
GameState(rawValue: gameState) ?? .future
|
||||
}
|
||||
|
||||
var date: Date {
|
||||
ISO8601DateFormatter().date(from: startTimeUTC) ?? .now
|
||||
}
|
||||
|
||||
var gameCenterUrl: String {
|
||||
"https://www.nhl.com\(gameCenterLink)"
|
||||
}
|
||||
|
||||
var videocastUrl: String {
|
||||
"https://videocast.nhl.com/game/\(id)/usnded?autoplay=true"
|
||||
}
|
||||
|
||||
/// Time string in ET for display (e.g., "7:00 PM")
|
||||
var startTimeET: String {
|
||||
date.formatDateET(format: "h:mm a")
|
||||
}
|
||||
|
||||
/// Formatted menu title: "NYR @ WAS 0:2 (FINAL)" or "DAL @ TOR Today @ 7:30 PM"
|
||||
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)"
|
||||
}
|
||||
|
||||
// Has scores
|
||||
let score = "\(awayTeam.score ?? 0):\(homeTeam.score ?? 0)"
|
||||
return "\(matchup) \(score) (\(gameState))"
|
||||
}
|
||||
|
||||
/// Whether this game involves a specific team
|
||||
func involves(team abbrev: String) -> Bool {
|
||||
awayTeam.abbrev == abbrev || homeTeam.abbrev == abbrev
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user