Add iPhone target with shared data layer and persistent cache
Two-target restructure: shared sources (models, services, settings, extensions, team logos) move into Shared/, consumed by both the existing macOS menu bar app and a new iOS app. MainService no longer imports AppKit — platform code attaches via a MainServiceObserver protocol (MacObserverAdapter wires back to MenuManager / StatusItemManager / NotificationManager). iPhone app is a single SwiftUI page mirroring the macOS menu (playoff round + yesterday/today/tomorrow), with a gear-icon settings sheet (display option + IndieAbout for license/changelog). Persistent JSON snapshot in Application Support paints last-known data on cold launch; "Updated …" header escalates secondary → orange (>5min) → red (>30min) so staleness is visually unmistakable. Foreground polling, scenePhase refresh, and pull-to-refresh; no notifications on iOS in v1.
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// MainView.swift
|
||||
// IceGlass-iOS
|
||||
//
|
||||
// Copyright 2026 Rouslan Zenetl. All Rights Reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MainView: View {
|
||||
@Environment(ScoreboardViewModel.self) private var viewModel
|
||||
@State private var showingSettings = false
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading, spacing: 16, pinnedViews: []) {
|
||||
UpdatedHeader(
|
||||
lastUpdated: viewModel.lastUpdated,
|
||||
isRefreshing: viewModel.isRefreshing
|
||||
)
|
||||
.padding(.horizontal)
|
||||
|
||||
if !viewModel.currentRoundSeriesItems.isEmpty,
|
||||
let round = viewModel.currentRoundNumber {
|
||||
PlayoffRoundSection(
|
||||
round: round,
|
||||
items: viewModel.currentRoundSeriesItems
|
||||
)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
let gameDays = viewModel.gamesByDate
|
||||
if gameDays.isEmpty && viewModel.currentRoundSeriesItems.isEmpty {
|
||||
emptyState
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 40)
|
||||
} else {
|
||||
ForEach(gameDays, id: \.date) { gameDay in
|
||||
GameDaySection(gameDay: gameDay)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical)
|
||||
}
|
||||
.refreshable {
|
||||
await viewModel.refreshNow()
|
||||
}
|
||||
.navigationTitle("IceGlass")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button {
|
||||
showingSettings = true
|
||||
} label: {
|
||||
Image(systemName: "gearshape")
|
||||
}
|
||||
.accessibilityLabel("Settings")
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showingSettings) {
|
||||
SettingsSheet()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var emptyState: some View {
|
||||
VStack(spacing: 8) {
|
||||
Image(systemName: "hockey.puck")
|
||||
.font(.system(size: 40))
|
||||
.foregroundStyle(.tertiary)
|
||||
Text("No games scheduled")
|
||||
.font(.headline)
|
||||
.foregroundStyle(.secondary)
|
||||
Text("Pull down to refresh")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.tertiary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user