fix: suppress keychain UI prompt, improve media format selector, overhaul update checker UI

This commit is contained in:
NimBold
2026-06-10 01:21:49 +03:30
parent 709f189f27
commit 17dc080a20
3 changed files with 99 additions and 157 deletions
+33 -30
View File
@@ -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 TableColumn("Status") { $item in
if item.isMedia { if item.isMedia, case .loading = item.state {
if !item.mediaOptions.isEmpty { HStack {
Menu { ProgressView().controlSize(.small)
ForEach(item.mediaOptions) { option in Text("Checking")
Button { }.foregroundStyle(.secondary)
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)
}
} else { } else {
MetadataStatusView(state: item.state) MetadataStatusView(state: item.state)
} }
@@ -10,7 +10,8 @@ enum KeychainCredentialStore {
kSecAttrService as String: service, kSecAttrService as String: service,
kSecAttrAccount as String: id.uuidString, kSecAttrAccount as String: id.uuidString,
kSecReturnData as String: true, kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne kSecMatchLimit as String: kSecMatchLimitOne,
kSecUseAuthenticationUI as String: kSecUseAuthenticationUISkip
] ]
var result: CFTypeRef? var result: CFTypeRef?
@@ -57,7 +58,8 @@ enum KeychainCredentialStore {
kSecAttrService as String: extensionTokenService, kSecAttrService as String: extensionTokenService,
kSecAttrAccount as String: extensionTokenAccount, kSecAttrAccount as String: extensionTokenAccount,
kSecReturnData as String: true, kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne kSecMatchLimit as String: kSecMatchLimitOne,
kSecUseAuthenticationUI as String: kSecUseAuthenticationUISkip
] ]
var result: CFTypeRef? var result: CFTypeRef?
+62 -125
View File
@@ -101,144 +101,81 @@ struct AboutSettingsPane: View {
@ViewBuilder @ViewBuilder
private var updateStatusView: some View { private var updateStatusView: some View {
switch updateChecker.state { HStack(alignment: .center) {
case .idle: switch updateChecker.state {
VStack(alignment: .leading, spacing: 12) { case .idle:
updateHeader( VStack(alignment: .leading, spacing: 2) {
systemImage: "arrow.down.circle", Text("Check for Updates")
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")
.font(.headline) .font(.headline)
Text("Looking for the latest stable Firelink release.") Text("Firelink checks GitHub Releases for new versions.")
.font(.subheadline) .font(.subheadline)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
} Spacer()
Button("Check Now") {
case .updateAvailable(let update): updateChecker.checkForUpdates()
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)
} }
}
case .upToDate(let latestVersion, let localVersion): case .checking:
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 2) {
let subtitle = latestVersion == localVersion Text("Checking for updates...")
? "Firelink \(localVersion) is the newest stable release." .font(.headline)
: "Firelink \(localVersion) is newer than the latest stable GitHub release, \(latestVersion)." Text("Connecting to GitHub...")
.font(.subheadline)
updateHeader( .foregroundStyle(.secondary)
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")
}
} }
} Spacer()
ProgressView()
.controlSize(.small)
.padding(.trailing, 16)
case .failed(let message, let recovery): case .updateAvailable(let update):
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 2) {
updateHeader( Text("Firelink \(update.version) is available!")
systemImage: "exclamationmark.triangle.fill", .font(.headline)
tint: .orange, Text("You currently have version \(appVersion).")
title: message, .font(.subheadline)
subtitle: recovery .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) { case .upToDate(let latestVersion, _):
Button { VStack(alignment: .leading, spacing: 2) {
updateChecker.checkForUpdates() Text("Firelink is up to date")
} label: { .font(.headline)
Label("Check Again", systemImage: "arrow.clockwise") Text("Version \(latestVersion) is the newest stable release.")
} .font(.subheadline)
.buttonStyle(.borderedProminent) .foregroundStyle(.secondary)
}
Spacer()
Button("Check Again") {
updateChecker.checkForUpdates()
}
Button { case .failed(let message, let recovery):
NSWorkspace.shared.open(releasesURL) VStack(alignment: .leading, spacing: 2) {
} label: { Text(message)
Label("Open Releases", systemImage: "safari") .font(.headline)
} Text(recovery)
.font(.subheadline)
.foregroundStyle(.secondary)
.lineLimit(2)
}
Spacer()
Button("Try Again") {
updateChecker.checkForUpdates()
} }
} }
} }
} .padding(.vertical, 4)
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)
}
}
} }
} }