mirror of
https://github.com/nimbold/Firelink.git
synced 2026-07-26 12:08:27 +00:00
feat: overhaul settings panes and add global configuration options
- Overhauled Downloads, Look & Feel, and Network settings panes to match modern UI guidelines. - Implemented global User-Agent spoofing for all download engines to bypass restrictions. - Added new configuration options: dock badge toggle, notification controls, completion sound, auto-retries, and "ask where to save" prompt. - Integrated NSApp.dockTile.badgeLabel for tracking active downloads in the macOS Dock. - Ensured newly added options correctly persist through AppSettings.
This commit is contained in:
@@ -160,6 +160,33 @@ final class AppSettings: ObservableObject {
|
||||
didSet { save() }
|
||||
}
|
||||
|
||||
@Published var showNotifications: Bool {
|
||||
didSet { save() }
|
||||
}
|
||||
|
||||
@Published var customUserAgent: String {
|
||||
didSet { save() }
|
||||
}
|
||||
|
||||
@Published var playCompletionSound: Bool {
|
||||
didSet { save() }
|
||||
}
|
||||
|
||||
@Published var showDockBadge: Bool {
|
||||
didSet { save() }
|
||||
}
|
||||
|
||||
@Published var maxAutomaticRetries: Int {
|
||||
didSet {
|
||||
let clamped = min(max(maxAutomaticRetries, 0), 10)
|
||||
if maxAutomaticRetries != clamped {
|
||||
maxAutomaticRetries = clamped
|
||||
return
|
||||
}
|
||||
save()
|
||||
}
|
||||
}
|
||||
|
||||
@Published var proxySettings: ProxySettings {
|
||||
didSet {
|
||||
let normalized = proxySettings.normalized
|
||||
@@ -197,6 +224,10 @@ final class AppSettings: ObservableObject {
|
||||
|
||||
@Published var showKeychainPrimer = false
|
||||
|
||||
@Published var askWhereToSaveEachFile: Bool {
|
||||
didSet { save() }
|
||||
}
|
||||
|
||||
@Published var message = ""
|
||||
|
||||
private let defaults: UserDefaults
|
||||
@@ -216,12 +247,18 @@ final class AppSettings: ObservableObject {
|
||||
maxConcurrentDownloads = min(max(stored.maxConcurrentDownloads ?? 3, 1), 12)
|
||||
globalSpeedLimitKiBPerSecond = min(max(stored.globalSpeedLimitKiBPerSecond ?? 0, 0), 10_485_760)
|
||||
preventsSleepWhileDownloading = stored.preventsSleepWhileDownloading
|
||||
showNotifications = stored.showNotifications ?? true
|
||||
playCompletionSound = stored.playCompletionSound ?? true
|
||||
showDockBadge = stored.showDockBadge ?? true
|
||||
customUserAgent = stored.customUserAgent ?? ""
|
||||
maxAutomaticRetries = min(max(stored.maxAutomaticRetries ?? 3, 0), 10)
|
||||
proxySettings = stored.proxySettings?.normalized ?? ProxySettings()
|
||||
siteLogins = stored.siteLogins
|
||||
mediaCookieSource = stored.mediaCookieSource ?? .none
|
||||
downloadDirectories = Self.decodeDirectories(stored.downloadDirectories)
|
||||
granted = stored.isKeychainAccessGranted ?? false
|
||||
isKeychainAccessGranted = granted
|
||||
askWhereToSaveEachFile = stored.askWhereToSaveEachFile ?? true
|
||||
} else {
|
||||
appTheme = .system
|
||||
appFontSize = .standard
|
||||
@@ -230,12 +267,18 @@ final class AppSettings: ObservableObject {
|
||||
maxConcurrentDownloads = 3
|
||||
globalSpeedLimitKiBPerSecond = 0
|
||||
preventsSleepWhileDownloading = true
|
||||
showNotifications = true
|
||||
playCompletionSound = true
|
||||
showDockBadge = true
|
||||
customUserAgent = ""
|
||||
maxAutomaticRetries = 3
|
||||
proxySettings = ProxySettings()
|
||||
siteLogins = []
|
||||
mediaCookieSource = .none
|
||||
downloadDirectories = Self.defaultDirectories()
|
||||
granted = false
|
||||
isKeychainAccessGranted = granted
|
||||
askWhereToSaveEachFile = true
|
||||
}
|
||||
|
||||
let currentVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown"
|
||||
@@ -409,11 +452,17 @@ final class AppSettings: ObservableObject {
|
||||
maxConcurrentDownloads: maxConcurrentDownloads,
|
||||
globalSpeedLimitKiBPerSecond: globalSpeedLimitKiBPerSecond,
|
||||
preventsSleepWhileDownloading: preventsSleepWhileDownloading,
|
||||
showNotifications: showNotifications,
|
||||
playCompletionSound: playCompletionSound,
|
||||
showDockBadge: showDockBadge,
|
||||
customUserAgent: customUserAgent,
|
||||
maxAutomaticRetries: maxAutomaticRetries,
|
||||
proxySettings: proxySettings.normalized,
|
||||
downloadDirectories: Dictionary(uniqueKeysWithValues: downloadDirectories.map { ($0.key.rawValue, $0.value) }),
|
||||
siteLogins: siteLogins,
|
||||
mediaCookieSource: mediaCookieSource,
|
||||
isKeychainAccessGranted: isKeychainAccessGranted
|
||||
isKeychainAccessGranted: isKeychainAccessGranted,
|
||||
askWhereToSaveEachFile: askWhereToSaveEachFile
|
||||
)
|
||||
let defaults = self.defaults
|
||||
let storageKey = self.storageKey
|
||||
@@ -490,9 +539,15 @@ private struct StoredSettings: Codable {
|
||||
var maxConcurrentDownloads: Int?
|
||||
var globalSpeedLimitKiBPerSecond: Int?
|
||||
var preventsSleepWhileDownloading: Bool
|
||||
var showNotifications: Bool?
|
||||
var playCompletionSound: Bool?
|
||||
var showDockBadge: Bool?
|
||||
var customUserAgent: String?
|
||||
var maxAutomaticRetries: Int?
|
||||
var proxySettings: ProxySettings?
|
||||
var downloadDirectories: [String: String]
|
||||
var siteLogins: [SiteLogin]
|
||||
var mediaCookieSource: BrowserCookieSource?
|
||||
var isKeychainAccessGranted: Bool?
|
||||
var askWhereToSaveEachFile: Bool?
|
||||
}
|
||||
|
||||
@@ -21,6 +21,15 @@ struct ContentView: View {
|
||||
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("OpenAddDownloadsWindow"))) { _ in
|
||||
openWindow(id: "add-downloads")
|
||||
}
|
||||
.onChange(of: controller.activeCount) { _, newCount in
|
||||
updateDockBadge(count: newCount, show: settings.showDockBadge)
|
||||
}
|
||||
.onChange(of: settings.showDockBadge) { _, show in
|
||||
updateDockBadge(count: controller.activeCount, show: show)
|
||||
}
|
||||
.onAppear {
|
||||
updateDockBadge(count: controller.activeCount, show: settings.showDockBadge)
|
||||
}
|
||||
.onDrop(of: [.url, .fileURL, .plainText], isTargeted: nil) { providers in
|
||||
for provider in providers {
|
||||
if provider.canLoadObject(ofClass: URL.self) {
|
||||
@@ -200,6 +209,14 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func updateDockBadge(count: Int, show: Bool) {
|
||||
if show && count > 0 {
|
||||
NSApp.dockTile.badgeLabel = "\(count)"
|
||||
} else {
|
||||
NSApp.dockTile.badgeLabel = nil
|
||||
}
|
||||
}
|
||||
|
||||
private var selectedItems: [DownloadItem] {
|
||||
controller.downloads.filter { selection.contains($0.id) }
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ final class DownloadController: ObservableObject {
|
||||
private var queuePumpScope: QueuePumpScope = .idle
|
||||
private var sleepActivity: SleepActivityHandle?
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private let maxAutomaticRetries = 3
|
||||
private lazy var storageURL: URL = {
|
||||
let supportDir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first ?? URL(fileURLWithPath: NSHomeDirectory())
|
||||
return supportDir.appendingPathComponent("Firelink").appendingPathComponent("downloads.json")
|
||||
@@ -502,6 +501,17 @@ final class DownloadController: ObservableObject {
|
||||
pruneActiveQueueScopes()
|
||||
}
|
||||
|
||||
private func injectedEngineItem(from item: DownloadItem) -> DownloadItem {
|
||||
var engineItem = item
|
||||
let ua = settings.customUserAgent.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if !ua.isEmpty, !(engineItem.requestHeaders?.contains(where: { $0.name.caseInsensitiveCompare("user-agent") == .orderedSame }) ?? false) {
|
||||
var headers = engineItem.requestHeaders ?? []
|
||||
headers.append(DownloadRequestHeader(name: "User-Agent", value: ua))
|
||||
engineItem.requestHeaders = headers
|
||||
}
|
||||
return engineItem
|
||||
}
|
||||
|
||||
private func start(_ item: DownloadItem) {
|
||||
update(item.id) {
|
||||
$0.status = .downloading
|
||||
@@ -528,7 +538,7 @@ final class DownloadController: ObservableObject {
|
||||
$0.message = "Starting yt-dlp..."
|
||||
}
|
||||
let handle = try await mediaEngine.start(
|
||||
item: liveItem,
|
||||
item: injectedEngineItem(from: liveItem),
|
||||
cookieSource: settings.mediaCookieSource,
|
||||
proxyConfiguration: settings.downloadProxyConfiguration,
|
||||
speedLimitKiBPerSecond: effectiveSpeedLimitKiBPerSecond(for: liveItem),
|
||||
@@ -585,7 +595,7 @@ final class DownloadController: ObservableObject {
|
||||
} else {
|
||||
do {
|
||||
let handle = try engine.start(
|
||||
item: item,
|
||||
item: injectedEngineItem(from: item),
|
||||
proxyConfiguration: settings.downloadProxyConfiguration,
|
||||
speedLimitKiBPerSecond: effectiveSpeedLimitKiBPerSecond(for: item),
|
||||
progress: { [weak self] progress in
|
||||
@@ -810,7 +820,7 @@ final class DownloadController: ObservableObject {
|
||||
|
||||
let retryCount = automaticRetryCounts[itemID] ?? 0
|
||||
|
||||
guard isAutomaticallyRecoverable(error), retryCount < maxAutomaticRetries else {
|
||||
guard isAutomaticallyRecoverable(error), retryCount < settings.maxAutomaticRetries else {
|
||||
automaticRetryCounts[itemID] = nil
|
||||
update(itemID) {
|
||||
$0.status = .failed
|
||||
@@ -833,7 +843,7 @@ final class DownloadController: ObservableObject {
|
||||
$0.speedText = "-"
|
||||
$0.etaText = "-"
|
||||
$0.connectionCount = 0
|
||||
$0.message = "Connection interrupted. Retrying from partial file (\(retryCount + 1)/\(maxAutomaticRetries))."
|
||||
$0.message = "Connection interrupted. Retrying from partial file (\(retryCount + 1)/\(settings.maxAutomaticRetries))."
|
||||
$0.autoResumeOnLaunch = true
|
||||
}
|
||||
saveDownloads()
|
||||
@@ -1102,6 +1112,8 @@ final class DownloadController: ObservableObject {
|
||||
}
|
||||
|
||||
private func showNotification(title: String, body: String) {
|
||||
guard settings.showNotifications || settings.playCompletionSound else { return }
|
||||
|
||||
pendingNotifications.append((title: title, body: body))
|
||||
notificationDebounceTask?.cancel()
|
||||
|
||||
@@ -1110,7 +1122,11 @@ final class DownloadController: ObservableObject {
|
||||
try await Task.sleep(nanoseconds: 1_000_000_000)
|
||||
guard !Task.isCancelled else { return }
|
||||
|
||||
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { [weak self] granted, _ in
|
||||
var options: UNAuthorizationOptions = []
|
||||
if self.settings.showNotifications { options.insert(.alert) }
|
||||
if self.settings.playCompletionSound { options.insert(.sound) }
|
||||
|
||||
UNUserNotificationCenter.current().requestAuthorization(options: options) { [weak self] granted, _ in
|
||||
guard granted, let self else { return }
|
||||
Task { @MainActor in
|
||||
let items = self.pendingNotifications
|
||||
@@ -1118,14 +1134,22 @@ final class DownloadController: ObservableObject {
|
||||
guard !items.isEmpty else { return }
|
||||
|
||||
let content = UNMutableNotificationContent()
|
||||
if items.count == 1 {
|
||||
content.title = items[0].title
|
||||
content.body = items[0].body
|
||||
if self.settings.showNotifications {
|
||||
if items.count == 1 {
|
||||
content.title = items[0].title
|
||||
content.body = items[0].body
|
||||
} else {
|
||||
content.title = "\(items.count) Downloads Completed"
|
||||
content.body = "Multiple items have finished downloading."
|
||||
}
|
||||
} else {
|
||||
content.title = "\(items.count) Downloads Completed"
|
||||
content.body = "Multiple items have finished downloading."
|
||||
content.title = "Download Completed"
|
||||
}
|
||||
content.sound = .default
|
||||
|
||||
if self.settings.playCompletionSound {
|
||||
content.sound = .default
|
||||
}
|
||||
|
||||
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
||||
UNUserNotificationCenter.current().add(request)
|
||||
}
|
||||
|
||||
@@ -185,10 +185,17 @@ final class LocalExtensionServer: @unchecked Sendable {
|
||||
}
|
||||
|
||||
Task { @MainActor in
|
||||
self.downloadController.pendingPasteboardText = validURLs.joined(separator: "\n")
|
||||
self.downloadController.pendingReferer = payload.referer
|
||||
NotificationCenter.default.post(name: NSNotification.Name("OpenAddDownloadsWindow"), object: nil)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
if self.settings.askWhereToSaveEachFile {
|
||||
self.downloadController.pendingPasteboardText = validURLs.joined(separator: "\n")
|
||||
self.downloadController.pendingReferer = payload.referer
|
||||
NotificationCenter.default.post(name: NSNotification.Name("OpenAddDownloadsWindow"), object: nil)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
} else {
|
||||
for validURL in validURLs {
|
||||
self.downloadController.add(urlText: validURL)
|
||||
}
|
||||
self.downloadController.startQueue()
|
||||
}
|
||||
}
|
||||
|
||||
return .ok
|
||||
|
||||
@@ -5,7 +5,7 @@ struct DownloadSettingsPane: View {
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
Section("Connections") {
|
||||
Stepper(
|
||||
"Default connections per server: \(settings.perServerConnections)",
|
||||
value: $settings.perServerConnections,
|
||||
@@ -25,7 +25,7 @@ struct DownloadSettingsPane: View {
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Section {
|
||||
Section("Bandwidth") {
|
||||
LabeledContent("Global speed limit") {
|
||||
HStack {
|
||||
TextField("0", value: $settings.globalSpeedLimitKiBPerSecond, format: .number)
|
||||
@@ -40,6 +40,23 @@ struct DownloadSettingsPane: View {
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Section("Recovery") {
|
||||
Stepper(
|
||||
"Automatic retries: \(settings.maxAutomaticRetries)",
|
||||
value: $settings.maxAutomaticRetries,
|
||||
in: 0...10
|
||||
)
|
||||
Text("Number of times to retry a download automatically if the connection fails.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Section("Notifications") {
|
||||
Toggle("Show notification when download completes", isOn: $settings.showNotifications)
|
||||
Toggle("Play sound when download completes", isOn: $settings.playCompletionSound)
|
||||
.disabled(!settings.showNotifications)
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
}
|
||||
|
||||
@@ -6,18 +6,17 @@ struct LocationsSettingsPane: View {
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
Grid(alignment: .leading, horizontalSpacing: 16, verticalSpacing: 12) {
|
||||
BulkDirectoryPickerRow()
|
||||
Section(footer: Text("When enabled, you can choose the download location each time you add a download.")) {
|
||||
Toggle("Ask where to save each file before downloading", isOn: $settings.askWhereToSaveEachFile)
|
||||
}
|
||||
|
||||
GridRow {
|
||||
Divider()
|
||||
.gridCellColumns(2)
|
||||
}
|
||||
Section(header: Text("Default Locations"), footer: Text("Automatically sets the folder for all categories within the selected base folder.")) {
|
||||
BulkDirectoryPickerRow()
|
||||
}
|
||||
|
||||
ForEach(DownloadCategory.allCases, id: \.self) { category in
|
||||
DirectoryPickerRow(category: category)
|
||||
}
|
||||
Section(header: Text("Category Locations"), footer: Text("Folders will be created automatically when saving.")) {
|
||||
ForEach(DownloadCategory.allCases, id: \.self) { category in
|
||||
DirectoryPickerRow(category: category)
|
||||
}
|
||||
|
||||
HStack {
|
||||
@@ -26,8 +25,6 @@ struct LocationsSettingsPane: View {
|
||||
settings.resetDirectories()
|
||||
}
|
||||
}
|
||||
} footer: {
|
||||
Text("Folders will be created automatically when applied.")
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
@@ -42,13 +39,10 @@ struct DirectoryPickerRow: View {
|
||||
@State private var message = ""
|
||||
|
||||
var body: some View {
|
||||
GridRow(alignment: .firstTextBaseline) {
|
||||
Label(category.rawValue, systemImage: category.symbolName)
|
||||
.gridColumnAlignment(.leading)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
LabeledContent {
|
||||
HStack(spacing: 8) {
|
||||
TextField("", text: $path, prompt: Text("Folder path"))
|
||||
TextField("", text: $path)
|
||||
.labelsHidden()
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.font(.system(.body, design: .monospaced))
|
||||
@@ -56,25 +50,18 @@ struct DirectoryPickerRow: View {
|
||||
applyPath()
|
||||
}
|
||||
|
||||
Button {
|
||||
applyPath()
|
||||
} label: {
|
||||
Label("Apply", systemImage: "checkmark")
|
||||
}
|
||||
.disabled(path.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
|
||||
|
||||
Button {
|
||||
Button("Choose...") {
|
||||
selectFolder()
|
||||
} label: {
|
||||
Label("Select", systemImage: "folder.badge.plus")
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Label(category.rawValue, systemImage: category.symbolName)
|
||||
}
|
||||
|
||||
if let displayMessage = message.isEmpty ? statusMessage(for: path) : message, !displayMessage.isEmpty {
|
||||
Text(displayMessage)
|
||||
.font(.caption)
|
||||
.foregroundStyle(isErrorMessage(displayMessage) ? .red : .secondary)
|
||||
}
|
||||
if let displayMessage = message.isEmpty ? statusMessage(for: path) : message, !displayMessage.isEmpty {
|
||||
Text(displayMessage)
|
||||
.font(.caption)
|
||||
.foregroundStyle(isErrorMessage(displayMessage) ? .red : .secondary)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
@@ -175,13 +162,10 @@ struct BulkDirectoryPickerRow: View {
|
||||
@State private var message = ""
|
||||
|
||||
var body: some View {
|
||||
GridRow(alignment: .firstTextBaseline) {
|
||||
Label("All Categories", systemImage: "folder.fill.badge.plus")
|
||||
.gridColumnAlignment(.leading)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
LabeledContent {
|
||||
HStack(spacing: 8) {
|
||||
TextField("", text: $path, prompt: Text("Base folder path"))
|
||||
TextField("", text: $path)
|
||||
.labelsHidden()
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.font(.system(.body, design: .monospaced))
|
||||
@@ -189,29 +173,18 @@ struct BulkDirectoryPickerRow: View {
|
||||
applyPath()
|
||||
}
|
||||
|
||||
Button {
|
||||
applyPath()
|
||||
} label: {
|
||||
Label("Apply", systemImage: "checkmark")
|
||||
}
|
||||
.disabled(path.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
|
||||
|
||||
Button {
|
||||
Button("Choose...") {
|
||||
selectFolder()
|
||||
} label: {
|
||||
Label("Select", systemImage: "folder.badge.plus")
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Label("All Categories", systemImage: "folder.fill.badge.plus")
|
||||
}
|
||||
|
||||
if !message.isEmpty {
|
||||
Text(message)
|
||||
.font(.caption)
|
||||
.foregroundStyle(isErrorMessage(message) ? .red : .secondary)
|
||||
} else {
|
||||
Text("Automatically creates all category folders in the selected path.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
if !message.isEmpty {
|
||||
Text(message)
|
||||
.font(.caption)
|
||||
.foregroundStyle(isErrorMessage(message) ? .red : .secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,9 +34,13 @@ struct LookAndFeelSettingsPane: View {
|
||||
}
|
||||
}
|
||||
|
||||
Section("Menu Bar") {
|
||||
Toggle("Show menu bar icon", isOn: $showMenuBarIcon)
|
||||
Section("macOS Integration") {
|
||||
Toggle("Show badge on Dock icon", isOn: $settings.showDockBadge)
|
||||
Text("Displays the number of active downloads on the Firelink Dock icon.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
Toggle("Show menu bar icon", isOn: $showMenuBarIcon)
|
||||
Text("Provides quick access to downloads and queues from the macOS menu bar.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
@@ -5,8 +5,8 @@ struct NetworkSettingsPane: View {
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
Picker("Proxy", selection: proxyBinding(\.mode)) {
|
||||
Section("Proxy") {
|
||||
Picker("Mode", selection: proxyBinding(\.mode)) {
|
||||
ForEach(ProxyMode.allCases, id: \.self) { mode in
|
||||
Text(mode.title)
|
||||
.tag(mode)
|
||||
@@ -42,6 +42,16 @@ struct NetworkSettingsPane: View {
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
Section("Identity") {
|
||||
TextField("User Agent", text: $settings.customUserAgent, prompt: Text("e.g. Mozilla/5.0..."))
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.font(.system(.body, design: .monospaced))
|
||||
|
||||
Text("Spoofs the browser User-Agent to bypass download restrictions. Leave blank for default.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
}
|
||||
@@ -49,14 +59,14 @@ struct NetworkSettingsPane: View {
|
||||
private var networkSummary: String {
|
||||
switch settings.proxySettings.mode {
|
||||
case .none:
|
||||
"Downloads ignore configured proxies."
|
||||
return "Downloads ignore configured proxies."
|
||||
case .system:
|
||||
"Downloads use the matching macOS system proxy when one is configured."
|
||||
return "Downloads use the matching macOS system proxy when one is configured."
|
||||
case .custom:
|
||||
if let proxyURI = settings.proxySettings.customProxyURI {
|
||||
"Downloads use \(proxyURI)."
|
||||
return "Downloads use \(proxyURI)."
|
||||
} else {
|
||||
"Enter a proxy host and port to enable the custom proxy."
|
||||
return "Enter a proxy host and port to enable the custom proxy."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user