From 17dc080a20041074560d40f8d9238e4f7f538f28 Mon Sep 17 00:00:00 2001 From: NimBold Date: Wed, 10 Jun 2026 01:21:49 +0330 Subject: [PATCH] fix: suppress keychain UI prompt, improve media format selector, overhaul update checker UI --- Sources/Firelink/AddDownloadsView.swift | 63 +++--- .../Firelink/KeychainCredentialStore.swift | 6 +- .../Firelink/Settings/AboutSettingsPane.swift | 187 ++++++------------ 3 files changed, 99 insertions(+), 157 deletions(-) diff --git a/Sources/Firelink/AddDownloadsView.swift b/Sources/Firelink/AddDownloadsView.swift index 7696272..f87b15a 100644 --- a/Sources/Firelink/AddDownloadsView.swift +++ b/Sources/Firelink/AddDownloadsView.swift @@ -216,6 +216,34 @@ struct AddDownloadsView: View { } } } + + if let firstMedia = pendingDownloads.first(where: { $0.isMedia }), !firstMedia.mediaOptions.isEmpty { + GridRow(alignment: .firstTextBaseline) { + Label("Media Format", systemImage: "film") + .font(.subheadline.weight(.semibold)) + Picker("Format", selection: Binding( + get: { firstMedia.selectedMediaOption?.id ?? "" }, + set: { newId in + for index in pendingDownloads.indices where pendingDownloads[index].isMedia { + if let option = pendingDownloads[index].mediaOptions.first(where: { $0.id == newId }) { + pendingDownloads[index].selectedMediaOption = option + if let metadata = pendingDownloads[index].mediaMetadata { + let cleanTitle = FileClassifier.sanitizedFileName(metadata.title ?? "Media") + pendingDownloads[index].fileName = "\(cleanTitle).\(option.outputExtension)" + pendingDownloads[index].category = FileClassifier.category(forFileName: pendingDownloads[index].fileName) + } + } + } + } + )) { + ForEach(firstMedia.mediaOptions) { option in + Text(option.name).tag(option.id) + } + } + .labelsHidden() + .frame(maxWidth: 220, alignment: .leading) + } + } } } @@ -261,36 +289,11 @@ struct AddDownloadsView: View { } TableColumn("Status") { $item in - if item.isMedia { - if !item.mediaOptions.isEmpty { - Menu { - ForEach(item.mediaOptions) { option in - Button { - item.selectedMediaOption = option - if let metadata = item.mediaMetadata { - let cleanTitle = FileClassifier.sanitizedFileName(metadata.title ?? "Media") - item.fileName = "\(cleanTitle).\(option.outputExtension)" - item.category = FileClassifier.category(forFileName: item.fileName) - } - } label: { - Text(option.name) - } - } - } label: { - Text(item.selectedMediaOption?.name ?? "Select Format") - .lineLimit(1) - } - .menuStyle(.borderlessButton) - .buttonStyle(.plain) - .fixedSize() - } else if case .loading = item.state { - HStack { - ProgressView().controlSize(.small) - Text("Checking") - }.foregroundStyle(.secondary) - } else { - MetadataStatusView(state: item.state) - } + if item.isMedia, case .loading = item.state { + HStack { + ProgressView().controlSize(.small) + Text("Checking") + }.foregroundStyle(.secondary) } else { MetadataStatusView(state: item.state) } diff --git a/Sources/Firelink/KeychainCredentialStore.swift b/Sources/Firelink/KeychainCredentialStore.swift index 32d48af..a3e84ec 100644 --- a/Sources/Firelink/KeychainCredentialStore.swift +++ b/Sources/Firelink/KeychainCredentialStore.swift @@ -10,7 +10,8 @@ enum KeychainCredentialStore { kSecAttrService as String: service, kSecAttrAccount as String: id.uuidString, kSecReturnData as String: true, - kSecMatchLimit as String: kSecMatchLimitOne + kSecMatchLimit as String: kSecMatchLimitOne, + kSecUseAuthenticationUI as String: kSecUseAuthenticationUISkip ] var result: CFTypeRef? @@ -57,7 +58,8 @@ enum KeychainCredentialStore { kSecAttrService as String: extensionTokenService, kSecAttrAccount as String: extensionTokenAccount, kSecReturnData as String: true, - kSecMatchLimit as String: kSecMatchLimitOne + kSecMatchLimit as String: kSecMatchLimitOne, + kSecUseAuthenticationUI as String: kSecUseAuthenticationUISkip ] var result: CFTypeRef? diff --git a/Sources/Firelink/Settings/AboutSettingsPane.swift b/Sources/Firelink/Settings/AboutSettingsPane.swift index 8b1ebb3..5a8a2cd 100644 --- a/Sources/Firelink/Settings/AboutSettingsPane.swift +++ b/Sources/Firelink/Settings/AboutSettingsPane.swift @@ -101,144 +101,81 @@ struct AboutSettingsPane: View { @ViewBuilder private var updateStatusView: some View { - switch updateChecker.state { - case .idle: - VStack(alignment: .leading, spacing: 12) { - updateHeader( - systemImage: "arrow.down.circle", - tint: .blue, - title: "Check for Updates", - subtitle: "Firelink checks GitHub Releases and opens the download page when a new version is available." - ) - - HStack(spacing: 12) { - Button { - updateChecker.checkForUpdates() - } label: { - Label("Check for Updates", systemImage: "arrow.clockwise") - } - .buttonStyle(.bordered) - - Button { - NSWorkspace.shared.open(releasesURL) - } label: { - Label("Release Notes", systemImage: "doc.text") - } - } - } - - case .checking: - HStack(spacing: 12) { - ProgressView() - .controlSize(.small) - VStack(alignment: .leading, spacing: 3) { - Text("Checking GitHub Releases") + HStack(alignment: .center) { + switch updateChecker.state { + case .idle: + VStack(alignment: .leading, spacing: 2) { + Text("Check for Updates") .font(.headline) - Text("Looking for the latest stable Firelink release.") + Text("Firelink checks GitHub Releases for new versions.") .font(.subheadline) .foregroundStyle(.secondary) } - } - - case .updateAvailable(let update): - VStack(alignment: .leading, spacing: 12) { - updateHeader( - systemImage: "arrow.down.circle.fill", - tint: .green, - title: "Firelink \(update.version) Is Available", - subtitle: "You have Firelink \(appVersion). Download the new release from GitHub when you're ready." - ) - - HStack(spacing: 12) { - Button { - NSWorkspace.shared.open(update.releaseURL) - } label: { - Label("Open GitHub Release", systemImage: "arrow.up.forward.app") - .frame(maxWidth: .infinity) - } - .buttonStyle(.borderedProminent) - - Button { - updateChecker.checkForUpdates() - } label: { - Label("Check Again", systemImage: "arrow.clockwise") - } - .buttonStyle(.bordered) + Spacer() + Button("Check Now") { + updateChecker.checkForUpdates() } - } - case .upToDate(let latestVersion, let localVersion): - VStack(alignment: .leading, spacing: 12) { - let subtitle = latestVersion == localVersion - ? "Firelink \(localVersion) is the newest stable release." - : "Firelink \(localVersion) is newer than the latest stable GitHub release, \(latestVersion)." - - updateHeader( - systemImage: "checkmark.seal.fill", - tint: .green, - title: "You're Up to Date", - subtitle: subtitle - ) - - HStack(spacing: 12) { - Button { - updateChecker.checkForUpdates() - } label: { - Label("Check Again", systemImage: "arrow.clockwise") - } - .buttonStyle(.bordered) - - Button { - NSWorkspace.shared.open(releasesURL) - } label: { - Label("Release Notes", systemImage: "doc.text") - } + case .checking: + VStack(alignment: .leading, spacing: 2) { + Text("Checking for updates...") + .font(.headline) + Text("Connecting to GitHub...") + .font(.subheadline) + .foregroundStyle(.secondary) } - } + Spacer() + ProgressView() + .controlSize(.small) + .padding(.trailing, 16) - case .failed(let message, let recovery): - VStack(alignment: .leading, spacing: 12) { - updateHeader( - systemImage: "exclamationmark.triangle.fill", - tint: .orange, - title: message, - subtitle: recovery - ) + case .updateAvailable(let update): + VStack(alignment: .leading, spacing: 2) { + Text("Firelink \(update.version) is available!") + .font(.headline) + Text("You currently have version \(appVersion).") + .font(.subheadline) + .foregroundStyle(.secondary) + + Link("View Release Notes", destination: update.releaseURL) + .font(.caption) + .padding(.top, 2) + } + Spacer() + Button("Download Update") { + NSWorkspace.shared.open(update.releaseURL) + } + .buttonStyle(.borderedProminent) - HStack(spacing: 12) { - Button { - updateChecker.checkForUpdates() - } label: { - Label("Check Again", systemImage: "arrow.clockwise") - } - .buttonStyle(.borderedProminent) + case .upToDate(let latestVersion, _): + VStack(alignment: .leading, spacing: 2) { + Text("Firelink is up to date") + .font(.headline) + Text("Version \(latestVersion) is the newest stable release.") + .font(.subheadline) + .foregroundStyle(.secondary) + } + Spacer() + Button("Check Again") { + updateChecker.checkForUpdates() + } - Button { - NSWorkspace.shared.open(releasesURL) - } label: { - Label("Open Releases", systemImage: "safari") - } + case .failed(let message, let recovery): + VStack(alignment: .leading, spacing: 2) { + Text(message) + .font(.headline) + Text(recovery) + .font(.subheadline) + .foregroundStyle(.secondary) + .lineLimit(2) + } + Spacer() + Button("Try Again") { + updateChecker.checkForUpdates() } } } - } - - private func updateHeader(systemImage: String, tint: Color, title: String, subtitle: String) -> some View { - HStack(alignment: .top, spacing: 12) { - Image(systemName: systemImage) - .font(.title2) - .foregroundStyle(tint) - .frame(width: 28) - - VStack(alignment: .leading, spacing: 4) { - Text(title) - .font(.headline) - Text(subtitle) - .font(.subheadline) - .foregroundStyle(.secondary) - .fixedSize(horizontal: false, vertical: true) - } - } + .padding(.vertical, 4) } }