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,113 @@
|
||||
//
|
||||
// AppSettings.swift
|
||||
// IceGlass
|
||||
//
|
||||
// Copyright 2026 Rouslan Zenetl. All Rights Reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
#if os(macOS)
|
||||
import ServiceManagement
|
||||
#endif
|
||||
|
||||
class AppSettings: @unchecked Sendable {
|
||||
private let logger = IceGlassLogger(
|
||||
subsystem: Bundle.main.bundleIdentifier ?? "dev.rzen.indie.IceGlass",
|
||||
category: "AppSettings"
|
||||
)
|
||||
|
||||
static let shared = AppSettings()
|
||||
|
||||
private enum UserDefaultsKey {
|
||||
static let launchAtLogin = "launchAtLogin"
|
||||
static let displayOption = "displayOption"
|
||||
static let statusBarOption = "statusBarOption"
|
||||
}
|
||||
|
||||
/// Controls which days are shown in the menu
|
||||
enum DisplayOption: String, CaseIterable {
|
||||
case yesterdayTodayTomorrow = "yesterdayTodayTomorrow"
|
||||
case todayTomorrow = "todayTomorrow"
|
||||
case todayOnly = "todayOnly"
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .yesterdayTodayTomorrow: return "Yesterday / Today / Tomorrow"
|
||||
case .todayTomorrow: return "Today / Tomorrow"
|
||||
case .todayOnly: return "Today"
|
||||
}
|
||||
}
|
||||
|
||||
func includedDates() -> Set<String> {
|
||||
switch self {
|
||||
case .yesterdayTodayTomorrow:
|
||||
return [Date.yesterdayET, Date.todayET, Date.tomorrowET]
|
||||
case .todayTomorrow:
|
||||
return [Date.todayET, Date.tomorrowET]
|
||||
case .todayOnly:
|
||||
return [Date.todayET]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Controls what number shows next to the menu bar icon
|
||||
enum StatusBarOption: String, CaseIterable {
|
||||
case gameCount = "gameCount"
|
||||
case gamesPlayed = "gamesPlayed"
|
||||
case gamesPlayedTotal = "gamesPlayedTotal"
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .gameCount: return "Game Count"
|
||||
case .gamesPlayed: return "Games Played"
|
||||
case .gamesPlayedTotal: return "Games Played / Total"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Launch at login
|
||||
var launchAtLogin: Bool {
|
||||
get { UserDefaults.standard.bool(forKey: UserDefaultsKey.launchAtLogin) }
|
||||
set { UserDefaults.standard.set(newValue, forKey: UserDefaultsKey.launchAtLogin) }
|
||||
}
|
||||
|
||||
// Display option
|
||||
var displayOption: DisplayOption {
|
||||
get {
|
||||
if let rawValue = UserDefaults.standard.string(forKey: UserDefaultsKey.displayOption),
|
||||
let option = DisplayOption(rawValue: rawValue) {
|
||||
return option
|
||||
}
|
||||
return .yesterdayTodayTomorrow
|
||||
}
|
||||
set { UserDefaults.standard.set(newValue.rawValue, forKey: UserDefaultsKey.displayOption) }
|
||||
}
|
||||
|
||||
// Status bar option
|
||||
var statusBarOption: StatusBarOption {
|
||||
get {
|
||||
if let rawValue = UserDefaults.standard.string(forKey: UserDefaultsKey.statusBarOption),
|
||||
let option = StatusBarOption(rawValue: rawValue) {
|
||||
return option
|
||||
}
|
||||
return .gameCount
|
||||
}
|
||||
set { UserDefaults.standard.set(newValue.rawValue, forKey: UserDefaultsKey.statusBarOption) }
|
||||
}
|
||||
|
||||
func updateLoginItem(enabled: Bool) {
|
||||
#if os(macOS)
|
||||
do {
|
||||
if enabled {
|
||||
try SMAppService.mainApp.register()
|
||||
} else {
|
||||
try SMAppService.mainApp.unregister()
|
||||
}
|
||||
} catch {
|
||||
logger.error("Failed to \(enabled ? "enable" : "disable") launch at login: \(error.localizedDescription)")
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private init() {}
|
||||
}
|
||||
Reference in New Issue
Block a user