Skip to content

Commit

Permalink
✨ Add SoftwareUpdater to check for updates
Browse files Browse the repository at this point in the history
  • Loading branch information
MrKai77 committed Aug 10, 2023
1 parent 90ba654 commit ce88f35
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 54 deletions.
12 changes: 8 additions & 4 deletions Loop.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -61,6 +62,7 @@
A8330AD02A3AC25100673C8D /* CaseIterable+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CaseIterable+Extensions.swift"; sourceTree = "<group>"; };
A8330AD32A3AC27600673C8D /* WindowDirection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowDirection.swift; sourceTree = "<group>"; };
A83667C72A3D7D910001D630 /* AXUIElement+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AXUIElement+Extensions.swift"; sourceTree = "<group>"; };
A8504D2C2A85832F00C2EFDA /* SoftwareUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftwareUpdater.swift; sourceTree = "<group>"; };
A86AFD7529888B29008F4892 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
A86CB7322A3D22E7006A78F2 /* WindowEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowEngine.swift; sourceTree = "<group>"; };
A8789F6629805B190040512E /* RadialMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadialMenuView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -122,6 +124,7 @@
A8330AD32A3AC27600673C8D /* WindowDirection.swift */,
A8E6D2002A416494005751D4 /* LoopTriggerKeys.swift */,
A8A2ABEA2A3FBFBA0067B5A9 /* VisualEffectView.swift */,
A8504D2C2A85832F00C2EFDA /* SoftwareUpdater.swift */,
);
path = Helpers;
sourceTree = "<group>";
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
80 changes: 80 additions & 0 deletions Loop/Helpers/SoftwareUpdater.swift
Original file line number Diff line number Diff line change
@@ -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<String> {
if includeDevelopmentVersions {
return ["development"]
}
return []
}

func checkForUpdates() {
updater?.checkForUpdates()
}
}

extension URL {
static var appcast = URL(
string: "https://mrkai77.github.io/Loop/appcast.xml"
)!
}
14 changes: 3 additions & 11 deletions Loop/Settings/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
}
Expand Down
48 changes: 9 additions & 39 deletions Loop/Settings/Views/MoreSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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)
}
}

0 comments on commit ce88f35

Please sign in to comment.