mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-24 09:46:29 +00:00
fix: suppress keychain UI prompt, improve media format selector, overhaul update checker UI
This commit is contained in:
@@ -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?
|
||||||
|
|||||||
@@ -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)
|
||||||
)
|
|
||||||
|
|
||||||
HStack(spacing: 12) {
|
Link("View Release Notes", destination: update.releaseURL)
|
||||||
Button {
|
.font(.caption)
|
||||||
updateChecker.checkForUpdates()
|
.padding(.top, 2)
|
||||||
} label: {
|
}
|
||||||
Label("Check Again", systemImage: "arrow.clockwise")
|
Spacer()
|
||||||
}
|
Button("Download Update") {
|
||||||
.buttonStyle(.borderedProminent)
|
NSWorkspace.shared.open(update.releaseURL)
|
||||||
|
}
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
|
|
||||||
Button {
|
case .upToDate(let latestVersion, _):
|
||||||
NSWorkspace.shared.open(releasesURL)
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
} label: {
|
Text("Firelink is up to date")
|
||||||
Label("Open Releases", systemImage: "safari")
|
.font(.headline)
|
||||||
}
|
Text("Version \(latestVersion) is the newest stable release.")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
Button("Check Again") {
|
||||||
|
updateChecker.checkForUpdates()
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user