eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1import SwiftUI
2import Sparkle
3
4// Sparkle's "gentle scheduled update reminders": a background check that finds
5// an update must never pop the update dialog over someone's conversation (the
6// stock driver's behaviour — the exact interruption Rogue Amoeba's apps moved
7// away from). Instead the find is published here and the rail paints a quiet
8// inline indicator; clicking it re-runs the check as USER-initiated, which
9// shows Sparkle's standard flow at a moment the member chose. The app-menu
10// "Check for Updates…" is user-initiated already and unchanged.
11final class GentleUpdateReminder: NSObject, ObservableObject, SPUStandardUserDriverDelegate {
12 // The version string the rail shows; nil = no indicator. Delegate
13 // callbacks arrive on the main queue, so plain @Published is safe.
14 @Published var availableVersion: String?
15
16 // Set right after the controller is built (AppDelegate) — the click's
17 // route back into Sparkle. Weak: the controller owns the updater.
18 weak var updater: SPUUpdater?
19
20 var supportsGentleScheduledUpdateReminders: Bool { true }
21
22 // Never let a scheduled check take the window — even the "immediate
23 // focus" case (an update found moments after launch) waits for a click.
24 func standardUserDriverShouldHandleShowingScheduledUpdate(
25 _ update: SUAppcastItem, andInImmediateFocus immediateFocus: Bool) -> Bool {
26 false
27 }
28
29 func standardUserDriverWillHandleShowingUpdate(
30 _ handleShowingUpdate: Bool, forUpdate update: SUAppcastItem,
31 state: SPUUserUpdateState) {
32 guard !state.userInitiated else { return }
33 availableVersion = update.displayVersionString
34 }
35
36 // The member engaged with Sparkle's own UI (via our indicator or the
37 // menu) — the reminder has done its job either way.
38 func standardUserDriverDidReceiveUserAttention(forUpdate update: SUAppcastItem) {
39 availableVersion = nil
40 }
41
42 func standardUserDriverWillFinishUpdateSession() {
43 availableVersion = nil
44 }
45
46 // The indicator's click: a user-initiated check re-finds the update and
47 // presents the standard flow.
48 func install() {
49 updater?.checkForUpdates()
50 }
51}
52
53// Sparkle's own documented SwiftUI idiom for a menu item that's disabled while
54// a check is already in flight (canCheckForUpdates), rather than a plain
55// always-enabled button.
56final class CheckForUpdatesViewModel: ObservableObject {
57 @Published var canCheckForUpdates = false
58
59 init(updater: SPUUpdater) {
60 updater.publisher(for: \.canCheckForUpdates).assign(to: &$canCheckForUpdates)
61 }
62}
63
64struct CheckForUpdatesView: View {
65 @ObservedObject private var model: CheckForUpdatesViewModel
66 private let updater: SPUUpdater
67
68 init(updater: SPUUpdater) {
69 self.updater = updater
70 self.model = CheckForUpdatesViewModel(updater: updater)
71 }
72
73 var body: some View {
74 Button("Check for Updates…", action: updater.checkForUpdates)
75 .disabled(!model.canCheckForUpdates)
76 }
77}
78
79// The Settings window's General pane: a single on/off switch for background
80// updates (rather than separate "check" and "download" toggles) — "Check for
81// Updates…" in the app menu always works for a manual kick-off regardless of
82// this setting.
83// The Updates pane of Settings. (Formerly misnamed GeneralSettingsView — the
84// General pane is MacSettingsView.)
85struct UpdatesSettingsView: View {
86 let updater: SPUUpdater
87 @State private var automaticUpdates: Bool
88
89 init(updater: SPUUpdater) {
90 self.updater = updater
91 _automaticUpdates = State(initialValue: updater.automaticallyDownloadsUpdates)
92 }
93
94 var body: some View {
95 // A bare checkbox + clickable label, per Mac preference conventions —
96 // a grouped-form box around a single control is chrome with nothing
97 // to group. (The General pane keeps .grouped for now.) The TabView in
98 // LChatMacApp owns the Settings window's one fixed frame, so no size
99 // is pinned here.
100 Toggle("Automatically download and install updates", isOn: $automaticUpdates)
101 .toggleStyle(.checkbox) // Settings-window convention — see MacSettingsView
102 .onChange(of: automaticUpdates) { _, enabled in
103 // Downloading automatically requires checking automatically;
104 // turning the checkbox off falls back to fully manual
105 // (menu-triggered) checks only.
106 updater.automaticallyChecksForUpdates = enabled
107 updater.automaticallyDownloadsUpdates = enabled
108 }
109 .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
110 .padding(20)
111 }
112}
113