macos: add a "restart later" option to the installing state
parent
bed219c132
commit
791d8f8200
|
|
@ -22,7 +22,13 @@ extension UpdateDriver: SPUUpdaterDelegate {
|
||||||
/// When `auto-update = check`, Sparkle will call the corresponding
|
/// When `auto-update = check`, Sparkle will call the corresponding
|
||||||
/// delegate method on the responsible driver instead.
|
/// delegate method on the responsible driver instead.
|
||||||
func updater(_ updater: SPUUpdater, willInstallUpdateOnQuit item: SUAppcastItem, immediateInstallationBlock immediateInstallHandler: @escaping () -> Void) -> Bool {
|
func updater(_ updater: SPUUpdater, willInstallUpdateOnQuit item: SUAppcastItem, immediateInstallationBlock immediateInstallHandler: @escaping () -> Void) -> Bool {
|
||||||
viewModel.state = .installing(.init(isAutoUpdate: true, retryTerminatingApplication: immediateInstallHandler))
|
viewModel.state = .installing(.init(
|
||||||
|
isAutoUpdate: true,
|
||||||
|
retryTerminatingApplication: immediateInstallHandler,
|
||||||
|
dismiss: { [weak viewModel] in
|
||||||
|
viewModel?.state = .idle
|
||||||
|
}
|
||||||
|
))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,12 @@ class UpdateDriver: NSObject, SPUUserDriver {
|
||||||
}
|
}
|
||||||
|
|
||||||
func showInstallingUpdate(withApplicationTerminated applicationTerminated: Bool, retryTerminatingApplication: @escaping () -> Void) {
|
func showInstallingUpdate(withApplicationTerminated applicationTerminated: Bool, retryTerminatingApplication: @escaping () -> Void) {
|
||||||
viewModel.state = .installing(.init(retryTerminatingApplication: retryTerminatingApplication))
|
viewModel.state = .installing(.init(
|
||||||
|
retryTerminatingApplication: retryTerminatingApplication,
|
||||||
|
dismiss: { [weak viewModel] in
|
||||||
|
viewModel?.state = .idle
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
if !hasUnobtrusiveTarget {
|
if !hasUnobtrusiveTarget {
|
||||||
standard.showInstallingUpdate(withApplicationTerminated: applicationTerminated, retryTerminatingApplication: retryTerminatingApplication)
|
standard.showInstallingUpdate(withApplicationTerminated: applicationTerminated, retryTerminatingApplication: retryTerminatingApplication)
|
||||||
|
|
|
||||||
|
|
@ -291,7 +291,15 @@ fileprivate struct InstallingView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
|
Button("Restart Later") {
|
||||||
|
installing.dismiss()
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
.keyboardShortcut(.cancelAction)
|
||||||
|
.controlSize(.small)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Button("Restart Now") {
|
Button("Restart Now") {
|
||||||
installing.retryTerminatingApplication()
|
installing.retryTerminatingApplication()
|
||||||
dismiss()
|
dismiss()
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,9 @@ enum UpdateSimulator {
|
||||||
/// Shows the installing state with restart button: installing (stays until dismissed)
|
/// Shows the installing state with restart button: installing (stays until dismissed)
|
||||||
case installing
|
case installing
|
||||||
|
|
||||||
|
/// Simulates auto-update flow: goes directly to installing state without showing intermediate UI
|
||||||
|
case autoUpdate
|
||||||
|
|
||||||
func simulate(with viewModel: UpdateViewModel) {
|
func simulate(with viewModel: UpdateViewModel) {
|
||||||
switch self {
|
switch self {
|
||||||
case .happyPath:
|
case .happyPath:
|
||||||
|
|
@ -49,6 +52,8 @@ enum UpdateSimulator {
|
||||||
simulateCancelDuringChecking(viewModel)
|
simulateCancelDuringChecking(viewModel)
|
||||||
case .installing:
|
case .installing:
|
||||||
simulateInstalling(viewModel)
|
simulateInstalling(viewModel)
|
||||||
|
case .autoUpdate:
|
||||||
|
simulateAutoUpdate(viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -270,9 +275,27 @@ enum UpdateSimulator {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func simulateInstalling(_ viewModel: UpdateViewModel) {
|
private func simulateInstalling(_ viewModel: UpdateViewModel) {
|
||||||
viewModel.state = .installing(.init(retryTerminatingApplication: {
|
viewModel.state = .installing(.init(
|
||||||
print("Restart button clicked in simulator - resetting to idle")
|
retryTerminatingApplication: {
|
||||||
viewModel.state = .idle
|
print("Restart button clicked in simulator - resetting to idle")
|
||||||
}))
|
viewModel.state = .idle
|
||||||
|
},
|
||||||
|
dismiss: {
|
||||||
|
viewModel.state = .idle
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func simulateAutoUpdate(_ viewModel: UpdateViewModel) {
|
||||||
|
viewModel.state = .installing(.init(
|
||||||
|
isAutoUpdate: true,
|
||||||
|
retryTerminatingApplication: {
|
||||||
|
print("Restart button clicked in simulator - resetting to idle")
|
||||||
|
viewModel.state = .idle
|
||||||
|
},
|
||||||
|
dismiss: {
|
||||||
|
viewModel.state = .idle
|
||||||
|
}
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -367,5 +367,6 @@ enum UpdateState: Equatable {
|
||||||
/// True if this state is triggered by ``Ghostty/UpdateDriver/updater(_:willInstallUpdateOnQuit:immediateInstallationBlock:)``
|
/// True if this state is triggered by ``Ghostty/UpdateDriver/updater(_:willInstallUpdateOnQuit:immediateInstallationBlock:)``
|
||||||
var isAutoUpdate = false
|
var isAutoUpdate = false
|
||||||
let retryTerminatingApplication: () -> Void
|
let retryTerminatingApplication: () -> Void
|
||||||
|
let dismiss: () -> Void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,10 @@ struct UpdateStateTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func testInstallingEquality() {
|
@Test func testInstallingEquality() {
|
||||||
let state1: UpdateState = .installing(.init(isAutoUpdate: false, retryTerminatingApplication: {}))
|
let state1: UpdateState = .installing(.init(isAutoUpdate: false, retryTerminatingApplication: {}, dismiss: {}))
|
||||||
let state2: UpdateState = .installing(.init(isAutoUpdate: false, retryTerminatingApplication: {}))
|
let state2: UpdateState = .installing(.init(isAutoUpdate: false, retryTerminatingApplication: {}, dismiss: {}))
|
||||||
#expect(state1 == state2)
|
#expect(state1 == state2)
|
||||||
let state3: UpdateState = .installing(.init(isAutoUpdate: true, retryTerminatingApplication: {}))
|
let state3: UpdateState = .installing(.init(isAutoUpdate: true, retryTerminatingApplication: {}, dismiss: {}))
|
||||||
#expect(state3 != state2)
|
#expect(state3 != state2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,9 @@ struct UpdateViewModelTests {
|
||||||
|
|
||||||
@Test func testInstallingText() {
|
@Test func testInstallingText() {
|
||||||
let viewModel = UpdateViewModel()
|
let viewModel = UpdateViewModel()
|
||||||
viewModel.state = .installing(.init(isAutoUpdate: false, retryTerminatingApplication: {}))
|
viewModel.state = .installing(.init(isAutoUpdate: false, retryTerminatingApplication: {}, dismiss: {}))
|
||||||
#expect(viewModel.text == "Installing…")
|
#expect(viewModel.text == "Installing…")
|
||||||
viewModel.state = .installing(.init(isAutoUpdate: true, retryTerminatingApplication: {}))
|
viewModel.state = .installing(.init(isAutoUpdate: true, retryTerminatingApplication: {}, dismiss: {}))
|
||||||
#expect(viewModel.text == "Restart to Complete Update")
|
#expect(viewModel.text == "Restart to Complete Update")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue