Make updater progress visible during install

This commit is contained in:
h3p 2026-03-30 20:38:51 +02:00
parent 5c10470c6b
commit f588bf717b
No known key found for this signature in database
5 changed files with 143 additions and 35 deletions

View file

@ -361,7 +361,7 @@
CODE_SIGNING_ALLOWED = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 594;
CURRENT_PROJECT_VERSION = 595;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = CS727NF72U;
ENABLE_APP_SANDBOX = YES;
@ -444,7 +444,7 @@
CODE_SIGNING_ALLOWED = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 594;
CURRENT_PROJECT_VERSION = 595;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = CS727NF72U;
ENABLE_APP_SANDBOX = YES;

View file

@ -272,8 +272,7 @@ final class AppUpdateManager: ObservableObject {
automaticPromptToken &+= 1
}
if source == .automatic,
autoDownloadEnabled,
if autoDownloadEnabled,
installNowSupported {
Task { [weak self] in
await self?.attemptAutoInstall(interactive: false)
@ -447,6 +446,33 @@ final class AppUpdateManager: ObservableObject {
installNowDisabledReason == nil
}
var isUserVisibleUpdateInProgress: Bool {
status == .checking || isInstalling || awaitingInstallCompletionAction
}
var userVisibleUpdateStatusTitle: String {
if status == .checking {
return "Checking for updates…"
}
if isInstalling {
return installPhase.isEmpty ? "Installing update…" : installPhase
}
if awaitingInstallCompletionAction {
return "Update ready to install"
}
return lastCheckResultSummary
}
var userVisibleUpdateStatusDetail: String? {
if status == .checking {
return "Current version: \(currentVersion)"
}
if awaitingInstallCompletionAction {
return installMessage ?? "The update is staged and will install after the app closes."
}
return installMessage
}
var installNowDisabledReason: String? {
guard ReleaseRuntimePolicy.isUpdaterEnabledForCurrentDistribution else {
return "Updater is disabled for this distribution channel."

View file

@ -77,12 +77,12 @@ struct AppUpdaterDialog: View {
switch appUpdateManager.status {
case .idle, .checking:
VStack(alignment: .leading, spacing: 12) {
ProgressView()
Text("Checking for updates…")
.font(.headline)
Text("Current version: \(appUpdateManager.currentVersion)")
.font(.subheadline)
.foregroundStyle(.secondary)
liveUpdateStatusSection
if appUpdateManager.status == .checking {
Text("Current version: \(appUpdateManager.currentVersion)")
.font(.subheadline)
.foregroundStyle(.secondary)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(12)
@ -146,19 +146,8 @@ struct AppUpdaterDialog: View {
.font(.caption)
.foregroundStyle(.secondary)
if appUpdateManager.isInstalling {
VStack(alignment: .leading, spacing: 6) {
ProgressView(value: appUpdateManager.installProgress, total: 1.0) {
Text(appUpdateManager.installPhase.isEmpty ? "Installing update…" : appUpdateManager.installPhase)
.font(.caption)
}
Text("\(Int((appUpdateManager.installProgress * 100).rounded()))%")
.font(.caption2)
.foregroundStyle(.secondary)
}
.accessibilityElement(children: .combine)
.accessibilityLabel("Update install progress")
.accessibilityValue("\(Int((appUpdateManager.installProgress * 100).rounded())) percent")
if appUpdateManager.isUserVisibleUpdateInProgress {
liveUpdateStatusSection
}
if let installMessage = appUpdateManager.installMessage {
@ -179,6 +168,44 @@ struct AppUpdaterDialog: View {
}
}
@ViewBuilder
private var liveUpdateStatusSection: some View {
VStack(alignment: .leading, spacing: 6) {
if appUpdateManager.isInstalling {
ProgressView(value: appUpdateManager.installProgress, total: 1.0) {
Text(appUpdateManager.userVisibleUpdateStatusTitle)
.font(.caption)
}
Text("\(Int((appUpdateManager.installProgress * 100).rounded()))%")
.font(.caption2)
.foregroundStyle(.secondary)
} else {
ProgressView {
Text(appUpdateManager.userVisibleUpdateStatusTitle)
.font(.headline)
}
}
if let detail = appUpdateManager.userVisibleUpdateStatusDetail,
!detail.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
Text(detail)
.font(.caption)
.foregroundStyle(.secondary)
.textSelection(.enabled)
}
}
.accessibilityElement(children: .combine)
.accessibilityLabel("Update status")
.accessibilityValue(accessibilityProgressValue)
}
private var accessibilityProgressValue: String {
if appUpdateManager.isInstalling {
return "\(Int((appUpdateManager.installProgress * 100).rounded())) percent"
}
return appUpdateManager.userVisibleUpdateStatusTitle
}
@ViewBuilder
private var actionRow: some View {
HStack {

View file

@ -2153,7 +2153,7 @@ struct ContentView: View {
}
private var rootViewWithStateObservers: some View {
basePlatformRootView
applyUpdateVisibilityObservers(to: basePlatformRootView)
.onAppear {
handleSettingsAndEditorDefaultsOnAppear()
}
@ -2163,23 +2163,12 @@ struct ContentView: View {
viewModel.isLineWrapEnabled = target
}
}
.onReceive(NotificationCenter.default.publisher(for: .whitespaceScalarInspectionResult)) { notif in
guard matchesCurrentWindow(notif) else { return }
if let msg = notif.userInfo?[EditorCommandUserInfo.inspectionMessage] as? String {
whitespaceInspectorMessage = msg
}
}
.onChange(of: viewModel.isLineWrapEnabled) { _, enabled in
guard projectOverrideLineWrapEnabled == nil else { return }
if settingsLineWrapEnabled != enabled {
settingsLineWrapEnabled = enabled
}
}
.onChange(of: appUpdateManager.automaticPromptToken) { _, _ in
if appUpdateManager.consumeAutomaticPromptIfNeeded() {
showUpdaterDialog(checkNow: false)
}
}
.onChange(of: settingsThemeName) { _, _ in
scheduleHighlightRefresh()
}
@ -2214,6 +2203,31 @@ struct ContentView: View {
.onChange(of: showMarkdownPreviewPane) { _, _ in
persistSessionIfReady()
}
}
private func applyUpdateVisibilityObservers<Content: View>(to view: Content) -> some View {
view
.onReceive(NotificationCenter.default.publisher(for: .whitespaceScalarInspectionResult)) { notif in
guard matchesCurrentWindow(notif) else { return }
if let msg = notif.userInfo?[EditorCommandUserInfo.inspectionMessage] as? String {
whitespaceInspectorMessage = msg
}
}
.onChange(of: appUpdateManager.automaticPromptToken) { _, _ in
if appUpdateManager.consumeAutomaticPromptIfNeeded() {
showUpdaterDialog(checkNow: false)
}
}
.onChange(of: appUpdateManager.isInstalling) { _, isInstalling in
if isInstalling && !showUpdateDialog {
showUpdaterDialog(checkNow: false)
}
}
.onChange(of: appUpdateManager.awaitingInstallCompletionAction) { _, awaitingAction in
if awaitingAction && !showUpdateDialog {
showUpdaterDialog(checkNow: false)
}
}
}
private var rootViewWithPlatformLifecycleObservers: some View {

View file

@ -3249,6 +3249,47 @@ struct NeonSettingsView: View {
}
}
if appUpdateManager.isUserVisibleUpdateInProgress {
VStack(alignment: .leading, spacing: UI.space8) {
Text("Update Activity")
.font(.subheadline.weight(.semibold))
if appUpdateManager.isInstalling {
ProgressView(value: appUpdateManager.installProgress, total: 1.0) {
Text(appUpdateManager.userVisibleUpdateStatusTitle)
.font(Typography.footnote)
}
Text("\(Int((appUpdateManager.installProgress * 100).rounded()))%")
.font(Typography.footnote)
.foregroundStyle(.secondary)
} else {
ProgressView {
Text(appUpdateManager.userVisibleUpdateStatusTitle)
.font(Typography.footnote)
}
}
if let detail = appUpdateManager.userVisibleUpdateStatusDetail,
!detail.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
Text(detail)
.font(Typography.footnote)
.foregroundStyle(.secondary)
.textSelection(.enabled)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(UI.space12)
.background(
RoundedRectangle(cornerRadius: UI.cardCorner, style: .continuous)
.fill(Color.secondary.opacity(0.08))
)
.accessibilityElement(children: .combine)
.accessibilityLabel("Update activity")
.accessibilityValue(
appUpdateManager.isInstalling
? "\(Int((appUpdateManager.installProgress * 100).rounded())) percent"
: appUpdateManager.userVisibleUpdateStatusTitle
)
}
Text("Uses GitHub release assets only. App Store Connect releases are not used by this updater.")
.font(Typography.footnote)
.foregroundStyle(.secondary)