Skip to content

Commit 2fcb281

Browse files
author
ComputelessComputer
committed
Surface restart state after update install
Show a restart action once the installer is ready instead of leaving the UI stuck on Updating, and add coverage for the new status text.
1 parent a52c442 commit 2fcb281

4 files changed

Lines changed: 100 additions & 22 deletions

File tree

Sources/OpenbirdApp/AppModel.swift

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ final class AppModel: ObservableObject {
8181
@Published var isGeneratingTodayJournal = false
8282
@Published var isCheckingForUpdates = false
8383
@Published var isInstallingUpdate = false
84+
@Published var isUpdateRestartPending = false
8485
@Published var isLoadingInstalledApplications = false
8586
@Published var isShowingRawLogInspector = false
8687
@Published private(set) var isSendingChat = false
@@ -320,22 +321,14 @@ final class AppModel: ObservableObject {
320321
}
321322

322323
var menuUpdateStatusText: String? {
323-
guard appVersion != nil else {
324-
return nil
325-
}
326-
if isInstallingUpdate {
327-
return "Installing update..."
328-
}
329-
if let update = availableUpdate {
330-
return "Update available - \(update.version)"
331-
}
332-
if isCheckingForUpdates {
333-
return "Checking for updates..."
334-
}
335-
if updateStatusMessage.isEmpty == false {
336-
return updateStatusMessage
337-
}
338-
return "No new update"
324+
Self.updateStatusText(
325+
appVersionAvailable: appVersion != nil,
326+
isInstallingUpdate: isInstallingUpdate,
327+
isUpdateRestartPending: isUpdateRestartPending,
328+
availableUpdateVersion: availableUpdate?.version,
329+
isCheckingForUpdates: isCheckingForUpdates,
330+
updateStatusMessage: updateStatusMessage
331+
)
339332
}
340333

341334
var availableChatModels: [ProviderModelInfo] {
@@ -453,6 +446,35 @@ final class AppModel: ObservableObject {
453446
}
454447
}
455448

449+
nonisolated static func updateStatusText(
450+
appVersionAvailable: Bool,
451+
isInstallingUpdate: Bool,
452+
isUpdateRestartPending: Bool,
453+
availableUpdateVersion: String?,
454+
isCheckingForUpdates: Bool,
455+
updateStatusMessage: String
456+
) -> String? {
457+
guard appVersionAvailable || isUpdateRestartPending else {
458+
return nil
459+
}
460+
if isUpdateRestartPending {
461+
return "Restart Openbird to finish update"
462+
}
463+
if isInstallingUpdate {
464+
return "Installing update..."
465+
}
466+
if let availableUpdateVersion {
467+
return "Update available - \(availableUpdateVersion)"
468+
}
469+
if isCheckingForUpdates {
470+
return "Checking for updates..."
471+
}
472+
if updateStatusMessage.isEmpty == false {
473+
return updateStatusMessage
474+
}
475+
return "No new update"
476+
}
477+
456478
func prepareForTermination() async {
457479
guard isShuttingDown == false else {
458480
return
@@ -656,6 +678,7 @@ final class AppModel: ObservableObject {
656678
}
657679

658680
isInstallingUpdate = true
681+
isUpdateRestartPending = false
659682
updateStatusMessage = "Installing Openbird \(availableUpdate.version)"
660683
logger.notice("Installing Openbird update \(availableUpdate.version, privacy: .public)")
661684

@@ -669,16 +692,28 @@ final class AppModel: ObservableObject {
669692
update: availableUpdate,
670693
appBundleURL: Bundle.main.bundleURL
671694
)
695+
isInstallingUpdate = false
696+
isUpdateRestartPending = true
697+
updateStatusMessage = "Restart Openbird to finish updating to \(availableUpdate.version)."
672698
quitApplication()
673699
} catch {
674700
logger.error("Failed to install Openbird update \(availableUpdate.version, privacy: .public): \(OpenbirdLog.errorDescription(error), privacy: .public)")
675701
isInstallingUpdate = false
702+
isUpdateRestartPending = false
676703
updateStatusMessage = "Openbird \(availableUpdate.version) is available."
677704
errorMessage = "Failed to update Openbird: \(error.localizedDescription)"
678705
}
679706
}
680707
}
681708

709+
func restartToFinishUpdate() {
710+
guard isUpdateRestartPending else {
711+
return
712+
}
713+
714+
quitApplication()
715+
}
716+
682717
func refreshCollectorState() async {
683718
handleCurrentDayChangeIfNeeded()
684719
do {
@@ -1003,7 +1038,7 @@ final class AppModel: ObservableObject {
10031038
}
10041039
return
10051040
}
1006-
guard isInstallingUpdate == false else {
1041+
guard isInstallingUpdate == false, isUpdateRestartPending == false else {
10071042
return
10081043
}
10091044

Sources/OpenbirdApp/OpenbirdAppMain.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ private struct OpenbirdStatusMenu: View {
171171
.disabled(true)
172172
}
173173

174+
if model.isUpdateRestartPending {
175+
Button("Restart to Finish Update") {
176+
model.restartToFinishUpdate()
177+
}
178+
}
179+
174180
Button("Check for Updates") {
175181
model.checkForUpdates()
176182
}

Sources/OpenbirdApp/RootView.swift

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ struct RootView: View {
1515
ToolbarItem(placement: .automatic) {
1616
CaptureToolbarButton(model: model)
1717
}
18-
if model.availableUpdate != nil {
18+
if model.availableUpdate != nil || model.isUpdateRestartPending {
1919
ToolbarItem(placement: .automatic) {
2020
UpdateToolbarButton(model: model)
2121
}
@@ -196,6 +196,9 @@ private struct UpdateToolbarButton: View {
196196
@State private var isHovering = false
197197

198198
private var title: String {
199+
if model.isUpdateRestartPending {
200+
return "Restart to Update"
201+
}
199202
if model.isInstallingUpdate {
200203
return "Updating…"
201204
}
@@ -206,6 +209,9 @@ private struct UpdateToolbarButton: View {
206209
}
207210

208211
private var helpText: String {
212+
if model.isUpdateRestartPending, let update = model.availableUpdate {
213+
return "Restart Openbird to finish installing \(update.version)."
214+
}
209215
if let update = model.availableUpdate {
210216
return "Install Openbird \(update.version)"
211217
}
@@ -216,7 +222,7 @@ private struct UpdateToolbarButton: View {
216222
}
217223

218224
private var tintColor: Color {
219-
model.availableUpdate == nil ? .secondary : .accentColor
225+
(model.availableUpdate == nil && model.isUpdateRestartPending == false) ? .secondary : .accentColor
220226
}
221227

222228
private var borderColor: Color {
@@ -234,6 +240,9 @@ private struct UpdateToolbarButton: View {
234240
}
235241

236242
private var symbolName: String {
243+
if model.isUpdateRestartPending {
244+
return "arrow.clockwise"
245+
}
237246
if model.availableUpdate != nil {
238247
return "arrow.down"
239248
}
@@ -242,7 +251,9 @@ private struct UpdateToolbarButton: View {
242251

243252
var body: some View {
244253
Button {
245-
if model.availableUpdate != nil {
254+
if model.isUpdateRestartPending {
255+
model.restartToFinishUpdate()
256+
} else if model.availableUpdate != nil {
246257
model.installAvailableUpdate()
247258
} else {
248259
model.checkForUpdates()
@@ -251,7 +262,7 @@ private struct UpdateToolbarButton: View {
251262
HStack(spacing: 10) {
252263
ZStack {
253264
RoundedRectangle(cornerRadius: 8, style: .continuous)
254-
.fill(tintColor.opacity(model.availableUpdate != nil ? 0.18 : 0.12))
265+
.fill(tintColor.opacity((model.availableUpdate != nil || model.isUpdateRestartPending) ? 0.18 : 0.12))
255266
if model.isInstallingUpdate || model.isCheckingForUpdates {
256267
ProgressView()
257268
.controlSize(.small)
@@ -278,7 +289,7 @@ private struct UpdateToolbarButton: View {
278289
}
279290
}
280291
.buttonStyle(.plain)
281-
.disabled(model.isInstallingUpdate || model.isCheckingForUpdates || (model.availableUpdate == nil && model.appVersion == nil))
292+
.disabled(model.isInstallingUpdate || model.isCheckingForUpdates || (model.availableUpdate == nil && model.isUpdateRestartPending == false && model.appVersion == nil))
282293
.help(helpText)
283294
.onHover { hovering in
284295
isHovering = hovering

Tests/OpenbirdAppTests/AppModelUpdateCheckTests.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,32 @@ import Testing
44
@testable import OpenbirdApp
55

66
struct AppModelUpdateCheckTests {
7+
@Test func showsRestartStatusAfterInstallerIsReady() {
8+
#expect(
9+
AppModel.updateStatusText(
10+
appVersionAvailable: true,
11+
isInstallingUpdate: false,
12+
isUpdateRestartPending: true,
13+
availableUpdateVersion: "0.2.0",
14+
isCheckingForUpdates: false,
15+
updateStatusMessage: "Restart Openbird to finish updating to 0.2.0."
16+
) == "Restart Openbird to finish update"
17+
)
18+
}
19+
20+
@Test func prefersInstallingStatusUntilRestartIsPending() {
21+
#expect(
22+
AppModel.updateStatusText(
23+
appVersionAvailable: true,
24+
isInstallingUpdate: true,
25+
isUpdateRestartPending: false,
26+
availableUpdateVersion: "0.2.0",
27+
isCheckingForUpdates: false,
28+
updateStatusMessage: "Installing Openbird 0.2.0…"
29+
) == "Installing update..."
30+
)
31+
}
32+
733
@Test func allowsAutomaticChecksWhenAppVersionExistsAndNoRecentCheckRan() {
834
#expect(
935
AppModel.shouldAutomaticallyCheckForUpdates(

0 commit comments

Comments
 (0)