From ce88f35ee1f0ff7fe3c9da28fa971b90360f2a46 Mon Sep 17 00:00:00 2001 From: Kai Azim <68963405+MrKai77@users.noreply.github.com> Date: Thu, 10 Aug 2023 15:10:06 -0600 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20SoftwareUpdater=20to=20check?= =?UTF-8?q?=20for=20updates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Loop.xcodeproj/project.pbxproj | 12 ++-- Loop/Helpers/SoftwareUpdater.swift | 80 ++++++++++++++++++++++ Loop/Settings/SettingsView.swift | 14 +--- Loop/Settings/Views/MoreSettingsView.swift | 48 +++---------- 4 files changed, 100 insertions(+), 54 deletions(-) create mode 100644 Loop/Helpers/SoftwareUpdater.swift diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index ac1043fc..d787b193 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ A8330AD12A3AC25100673C8D /* CaseIterable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8330AD02A3AC25100673C8D /* CaseIterable+Extensions.swift */; }; A8330AD42A3AC27600673C8D /* WindowDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8330AD32A3AC27600673C8D /* WindowDirection.swift */; }; A83667C82A3D7D910001D630 /* AXUIElement+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A83667C72A3D7D910001D630 /* AXUIElement+Extensions.swift */; }; + A8504D2D2A85832F00C2EFDA /* SoftwareUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8504D2C2A85832F00C2EFDA /* SoftwareUpdater.swift */; }; A86CB7332A3D22E7006A78F2 /* WindowEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = A86CB7322A3D22E7006A78F2 /* WindowEngine.swift */; }; A8789F6729805B190040512E /* RadialMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8789F6629805B190040512E /* RadialMenuView.swift */; }; A8789F6929805B340040512E /* PreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8789F6829805B340040512E /* PreviewView.swift */; }; @@ -61,6 +62,7 @@ A8330AD02A3AC25100673C8D /* CaseIterable+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CaseIterable+Extensions.swift"; sourceTree = ""; }; A8330AD32A3AC27600673C8D /* WindowDirection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowDirection.swift; sourceTree = ""; }; A83667C72A3D7D910001D630 /* AXUIElement+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AXUIElement+Extensions.swift"; sourceTree = ""; }; + A8504D2C2A85832F00C2EFDA /* SoftwareUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftwareUpdater.swift; sourceTree = ""; }; A86AFD7529888B29008F4892 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; A86CB7322A3D22E7006A78F2 /* WindowEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowEngine.swift; sourceTree = ""; }; A8789F6629805B190040512E /* RadialMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadialMenuView.swift; sourceTree = ""; }; @@ -122,6 +124,7 @@ A8330AD32A3AC27600673C8D /* WindowDirection.swift */, A8E6D2002A416494005751D4 /* LoopTriggerKeys.swift */, A8A2ABEA2A3FBFBA0067B5A9 /* VisualEffectView.swift */, + A8504D2C2A85832F00C2EFDA /* SoftwareUpdater.swift */, ); path = Helpers; sourceTree = ""; @@ -365,6 +368,7 @@ A86CB7332A3D22E7006A78F2 /* WindowEngine.swift in Sources */, A8E59C39297F5E9A0064D4BA /* LoopApp.swift in Sources */, A8330ACB2A3AC1C000673C8D /* Angle+Extensions.swift in Sources */, + A8504D2D2A85832F00C2EFDA /* SoftwareUpdater.swift in Sources */, A8330ACF2A3AC1E900673C8D /* View+Extensions.swift in Sources */, A82521EE29E235AC00139654 /* AccessibilityAccessManager.swift in Sources */, A8263BDE2A48FA4C00E86EAA /* CGKeyCode+Extensions.swift in Sources */, @@ -505,12 +509,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-Default"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = ""; CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Loop/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 6783T5MU9D; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -538,12 +542,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-Default"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = ""; CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Loop/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 6783T5MU9D; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; diff --git a/Loop/Helpers/SoftwareUpdater.swift b/Loop/Helpers/SoftwareUpdater.swift new file mode 100644 index 00000000..22c04ea2 --- /dev/null +++ b/Loop/Helpers/SoftwareUpdater.swift @@ -0,0 +1,80 @@ +// +// SoftwareUpdater.swift +// Loop +// +// Created by Kai Azim on 2023-08-10. +// + +import Foundation +import Sparkle + +class SoftwareUpdater: NSObject, ObservableObject, SPUUpdaterDelegate { + private var updater: SPUUpdater? + private var automaticallyChecksForUpdatesObservation: NSKeyValueObservation? + private var lastUpdateCheckDateObservation: NSKeyValueObservation? + + @Published var automaticallyChecksForUpdates = false { + didSet { + updater?.automaticallyChecksForUpdates = automaticallyChecksForUpdates + } + } + + @Published var lastUpdateCheckDate: Date? + + @Published var includeDevelopmentVersions = false { + didSet { + UserDefaults.standard.setValue(includeDevelopmentVersions, forKey: "includeDevelopmentVersions") + } + } + + private var feedURLTask: Task<(), Never>? + + override init() { + super.init() + updater = SPUStandardUpdaterController( + startingUpdater: true, + updaterDelegate: self, + userDriverDelegate: nil + ).updater + + automaticallyChecksForUpdatesObservation = updater?.observe( + \.automaticallyChecksForUpdates, + options: [.initial, .new, .old], + changeHandler: { [unowned self] updater, change in + guard change.newValue != change.oldValue else { return } + self.automaticallyChecksForUpdates = updater.automaticallyChecksForUpdates + } + ) + + lastUpdateCheckDateObservation = updater?.observe( + \.lastUpdateCheckDate, + options: [.initial, .new, .old], + changeHandler: { [unowned self] updater, _ in + self.lastUpdateCheckDate = updater.lastUpdateCheckDate + } + ) + + includeDevelopmentVersions = UserDefaults.standard.bool(forKey: "includePrereleaseVersions") + } + + deinit { + feedURLTask?.cancel() + } + + func allowedChannels(for updater: SPUUpdater) -> Set { + if includeDevelopmentVersions { + return ["development"] + } + return [] + } + + func checkForUpdates() { + updater?.checkForUpdates() + } +} + +extension URL { + static var appcast = URL( + string: "https://mrkai77.github.io/Loop/appcast.xml" + )! +} diff --git a/Loop/Settings/SettingsView.swift b/Loop/Settings/SettingsView.swift index cfe26ace..c8439610 100644 --- a/Loop/Settings/SettingsView.swift +++ b/Loop/Settings/SettingsView.swift @@ -9,17 +9,8 @@ import SwiftUI import Sparkle struct SettingsView: View { - private let updaterController: SPUStandardUpdaterController - - init() { - updaterController = SPUStandardUpdaterController( - startingUpdater: true, - updaterDelegate: nil, - userDriverDelegate: nil - ) - } - @State var currentSettingsTab = 1 + private let updater = SoftwareUpdater() var body: some View { TabView(selection: $currentSettingsTab) { @@ -51,12 +42,13 @@ struct SettingsView: View { Text("Keybindings") } - MoreSettingsView(updater: updaterController.updater) + MoreSettingsView() .tag(5) .tabItem { Image(systemName: "ellipsis.circle") Text("More") } + .environmentObject(updater) } .frame(width: 450) } diff --git a/Loop/Settings/Views/MoreSettingsView.swift b/Loop/Settings/Views/MoreSettingsView.swift index f6f2245d..6e3b6efd 100644 --- a/Loop/Settings/Views/MoreSettingsView.swift +++ b/Loop/Settings/Views/MoreSettingsView.swift @@ -10,36 +10,15 @@ import Sparkle struct MoreSettingsView: View { - private let updater: SPUUpdater - - // This is used when the user manually checks for updates - @ObservedObject private var checkForUpdatesViewModel: CheckForUpdatesViewModel - - @State private var automaticallyChecksForUpdates: Bool - @State private var automaticallyDownloadsUpdates: Bool - - init(updater: SPUUpdater) { - self.updater = updater - - // Create our view model for our CheckForUpdatesView - checkForUpdatesViewModel = CheckForUpdatesViewModel(updater: updater) - - self.automaticallyChecksForUpdates = updater.automaticallyChecksForUpdates - self.automaticallyDownloadsUpdates = updater.automaticallyDownloadsUpdates - } + @EnvironmentObject var updater: SoftwareUpdater var body: some View { Form { Section(content: { - Toggle("Automatically check for updates", isOn: $automaticallyChecksForUpdates) - .onChange(of: automaticallyChecksForUpdates) { newValue in - updater.automaticallyChecksForUpdates = newValue - } - Toggle("Automatically download updates", isOn: $automaticallyDownloadsUpdates) - .disabled(!automaticallyChecksForUpdates) - .onChange(of: automaticallyDownloadsUpdates) { newValue in - updater.automaticallyDownloadsUpdates = newValue - } + Toggle("Automatically check for updates", isOn: $updater.automaticallyChecksForUpdates) +// Toggle("Automatically download updates", isOn: $updater.a) +// .disabled(!automaticallyChecksForUpdates) + Toggle("Include development versions", isOn: $updater.includeDevelopmentVersions) }, header: { HStack { VStack(alignment: .leading, spacing: 0) { @@ -51,22 +30,13 @@ struct MoreSettingsView: View { Spacer() - Button("Check for Updates…", action: updater.checkForUpdates) - .disabled(!checkForUpdatesViewModel.canCheckForUpdates) - .buttonStyle(.link) + Button("Check for Updates…") { + updater.checkForUpdates() + } + .buttonStyle(.link) } }) } .formStyle(.grouped) } } - -// This view model class publishes when new updates can be checked by the user -final class CheckForUpdatesViewModel: ObservableObject { - @Published var canCheckForUpdates = false - - init(updater: SPUUpdater) { - updater.publisher(for: \.canCheckForUpdates) - .assign(to: &$canCheckForUpdates) - } -}