diff --git a/Xcodes/Backend/AppState.swift b/Xcodes/Backend/AppState.swift index 68dec29..0f62038 100644 --- a/Xcodes/Backend/AppState.swift +++ b/Xcodes/Backend/AppState.swift @@ -167,12 +167,22 @@ class AppState: ObservableObject { updatePublisher = update() .sink( receiveCompletion: { [unowned self] _ in + Current.defaults.setDate(Current.date(), forKey: "lastUpdated") self.updatePublisher = nil }, receiveValue: { _ in } ) } + func updateIfNeeded() { + guard + let lastUpdated = Current.defaults.date(forKey: "lastUpdated"), + // This is bad date math but for this use case it doesn't need to be exact + lastUpdated < Current.date().addingTimeInterval(-60 * 60 * 24) + else { return } + update() as Void + } + private func update() -> AnyPublisher<[Xcode], Never> { signInIfNeeded() .flatMap { diff --git a/Xcodes/Backend/Environment.swift b/Xcodes/Backend/Environment.swift index c0c6c73..fa3be9d 100644 --- a/Xcodes/Backend/Environment.swift +++ b/Xcodes/Backend/Environment.swift @@ -19,6 +19,7 @@ public struct Environment { public var logging = Logging() public var keychain = Keychain() public var defaults = Defaults() + public var date: () -> Date = Date.init } public var Current = Environment() @@ -153,6 +154,16 @@ public struct Defaults { string(key) } + public var date: (String) -> Date? = { Date(timeIntervalSince1970: UserDefaults.standard.double(forKey: $0)) } + public func date(forKey key: String) -> Date? { + date(key) + } + + public var setDate: (Date?, String) -> Void = { UserDefaults.standard.set($0?.timeIntervalSince1970, forKey: $1) } + public func setDate(_ value: Date?, forKey key: String) { + setDate(value, key) + } + public var set: (Any?, String) -> Void = { UserDefaults.standard.set($0, forKey: $1) } public func set(_ value: Any?, forKey key: String) { set(value, key) diff --git a/Xcodes/Frontend/XcodeList/XcodeListView.swift b/Xcodes/Frontend/XcodeList/XcodeListView.swift index 7b70958..cb5523a 100644 --- a/Xcodes/Frontend/XcodeList/XcodeListView.swift +++ b/Xcodes/Frontend/XcodeList/XcodeListView.swift @@ -106,7 +106,6 @@ struct XcodeListView: View { } .navigationSubtitle(Text("Updated \(Date().addingTimeInterval(-600), style: .relative) ago")) .frame(minWidth: 200, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity) - .onAppear(perform: appState.update) .alert(item: $appState.error) { error in Alert(title: Text(error.title), message: Text(verbatim: error.message), diff --git a/Xcodes/XcodesApp.swift b/Xcodes/XcodesApp.swift index 0a46137..18cfdea 100644 --- a/Xcodes/XcodesApp.swift +++ b/Xcodes/XcodesApp.swift @@ -4,12 +4,21 @@ import AppKit @main struct XcodesApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate: AppDelegate + @SwiftUI.Environment(\.scenePhase) private var scenePhase: ScenePhase @StateObject private var appState = AppState() var body: some Scene { WindowGroup("Xcodes") { XcodeListView() .environmentObject(appState) + // This is intentionally used on a View, and not on a WindowGroup, + // so that it's triggered when an individual window's phase changes instead of all window phases. + // When used on a View it's also invoked on launch, which doesn't occur with a WindowGroup. + .onChange(of: scenePhase) { newScenePhase in + if case .active = newScenePhase { + appState.updateIfNeeded() + } + } } .commands { CommandGroup(replacing: .appInfo) {