8f8f8b2755
macOS menu bar app providing NHL game situational awareness with league-wide scoreboard, dynamic polling, notifications with team logos, and configurable display options.
103 lines
2.7 KiB
Swift
103 lines
2.7 KiB
Swift
//
|
|
// 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
|
|
}
|
|
}
|
|
}
|