feat: add contextual actions and recoverable application uninstall
This commit is contained in:
@@ -12,6 +12,10 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
||||
private let loginToggle = NSButton(checkboxWithTitle: "Launch at login", target: nil, action: nil)
|
||||
private let loginStatus = NSTextField(wrappingLabelWithString: "")
|
||||
private let loginSettings = NSButton(title: "Open Login Items Settings…", target: nil, action: nil)
|
||||
private let recoveryLabel = NSTextField(wrappingLabelWithString: "")
|
||||
private let recoveryButton = NSButton(title: "Retry uninstall recovery…", target: nil, action: nil)
|
||||
private var recoveryReport: UninstallReport?
|
||||
var retryUninstallRecovery: (() async -> UninstallReport)?
|
||||
|
||||
init(shortcut: GlobalShortcut) {
|
||||
self.shortcut = shortcut
|
||||
@@ -113,6 +117,13 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
||||
loginSettings.action = #selector(openLoginSettings)
|
||||
loginSettings.bezelStyle = .rounded
|
||||
loginStatus.setAccessibilityLabel("Launch at login status")
|
||||
recoveryLabel.textColor = SunsetPalette.secondaryText
|
||||
recoveryLabel.isHidden = true
|
||||
recoveryLabel.setAccessibilityLabel("Uninstall recovery status")
|
||||
recoveryButton.isHidden = true
|
||||
recoveryButton.bezelStyle = .rounded
|
||||
recoveryButton.target = self
|
||||
recoveryButton.action = #selector(showUninstallRecovery)
|
||||
|
||||
heading.font = .systemFont(ofSize: 20, weight: .semibold)
|
||||
heading.textColor = SunsetPalette.text
|
||||
@@ -128,7 +139,7 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
||||
|
||||
let stack = NSStackView(views: [
|
||||
heading, controls, instructions, statusLabel, errorLabel, spotlight, keyboardSettings,
|
||||
loginHeading, loginToggle, loginStatus, loginSettings
|
||||
loginHeading, loginToggle, loginStatus, loginSettings, recoveryLabel, recoveryButton
|
||||
])
|
||||
stack.orientation = .vertical
|
||||
stack.alignment = .leading
|
||||
@@ -152,11 +163,38 @@ final class SettingsWindowController: NSWindowController, NSWindowDelegate {
|
||||
stack.bottomAnchor.constraint(lessThanOrEqualTo: content.bottomAnchor, constant: -24),
|
||||
recorder.widthAnchor.constraint(greaterThanOrEqualToConstant: 190)
|
||||
])
|
||||
for label in [instructions, statusLabel, errorLabel, spotlight, loginStatus] {
|
||||
for label in [instructions, statusLabel, errorLabel, spotlight, loginStatus, recoveryLabel] {
|
||||
label.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
func setUninstallRecoveryReport(_ report: UninstallReport) {
|
||||
recoveryReport = report
|
||||
recoveryLabel.isHidden = report.messages.isEmpty && !report.needsRecovery
|
||||
recoveryButton.isHidden = recoveryLabel.isHidden
|
||||
recoveryLabel.stringValue = report.needsRecovery
|
||||
? "An interrupted uninstall needs attention. Files have been retained for recovery."
|
||||
: "Interrupted uninstall recovery completed."
|
||||
recoveryButton.title = report.needsRecovery ? "Retry uninstall recovery…" : "View recovery details…"
|
||||
}
|
||||
|
||||
@objc private func showUninstallRecovery() {
|
||||
guard let report = recoveryReport, let window else { return }
|
||||
recoveryButton.isEnabled = false
|
||||
Task {
|
||||
let result: UninstallReport
|
||||
if report.needsRecovery, let retryUninstallRecovery {
|
||||
result = await retryUninstallRecovery()
|
||||
setUninstallRecoveryReport(result)
|
||||
} else { result = report }
|
||||
let alert = NSAlert()
|
||||
alert.messageText = result.needsRecovery ? "Recovery needs attention" : "Recovery complete"
|
||||
alert.informativeText = result.messages.isEmpty ? "No pending files need recovery." : result.messages.joined(separator: "\n")
|
||||
await alert.beginSheetModal(for: window)
|
||||
recoveryButton.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
private func refreshLogin() {
|
||||
let status = SMAppService.mainApp.status
|
||||
loginToggle.state = (status == .enabled || status == .requiresApproval) ? .on : .off
|
||||
|
||||
Reference in New Issue
Block a user