diff options
Diffstat (limited to 'Sora/Views/Settings/Section')
8 files changed, 134 insertions, 100 deletions
diff --git a/Sora/Views/Settings/Section/SettingsSectionContentRatingsView.swift b/Sora/Views/Settings/Section/SettingsSectionContentRatingsView.swift index 85b28af..353824a 100644 --- a/Sora/Views/Settings/Section/SettingsSectionContentRatingsView.swift +++ b/Sora/Views/Settings/Section/SettingsSectionContentRatingsView.swift @@ -1,11 +1,12 @@ import SwiftUI struct SettingsSectionContentRatingsView: View { - @EnvironmentObject var settings: SettingsManager + @Environment(SettingsManager.self) + private var settings var body: some View { List { - Section(header: Text("Display Content Ratings")) { + Section("Show Ratings") { ForEach(BooruRating.allCases, id: \.self) { rating in Toggle( rating.rawValue, @@ -25,7 +26,7 @@ struct SettingsSectionContentRatingsView: View { } } - Section(header: Text("Blur Content Ratings")) { + Section("Blur Ratings") { ForEach(BooruRating.allCases, id: \.self) { rating in Toggle( rating.rawValue, @@ -43,7 +44,7 @@ struct SettingsSectionContentRatingsView: View { } } } - .navigationTitle(Text("Content Filtering")) + .navigationTitle(Text("Sensitive Content")) #if !os(macOS) .navigationBarTitleDisplayMode(.inline) #endif @@ -70,5 +71,5 @@ struct SettingsSectionContentRatingsView: View { #Preview { SettingsSectionContentRatingsView() - .environmentObject(SettingsManager()) + .environment(SettingsManager()) } diff --git a/Sora/Views/Settings/Section/SettingsSectionDebugView.swift b/Sora/Views/Settings/Section/SettingsSectionDebugView.swift index 83acb81..c97b545 100644 --- a/Sora/Views/Settings/Section/SettingsSectionDebugView.swift +++ b/Sora/Views/Settings/Section/SettingsSectionDebugView.swift @@ -1,7 +1,8 @@ import SwiftUI struct SettingsSectionDebugView: View { - @EnvironmentObject private var settingsManager: SettingsManager + @Environment(SettingsManager.self) + private var settingsManager var body: some View { Button(action: { diff --git a/Sora/Views/Settings/Section/SettingsSectionDetailsView.swift b/Sora/Views/Settings/Section/SettingsSectionDetailsView.swift index 9634297..c386634 100644 --- a/Sora/Views/Settings/Section/SettingsSectionDetailsView.swift +++ b/Sora/Views/Settings/Section/SettingsSectionDetailsView.swift @@ -1,11 +1,14 @@ import SwiftUI struct SettingsSectionDetailsView: View { - @EnvironmentObject var settings: SettingsManager + @Environment(SettingsManager.self) + private var settings var body: some View { + @Bindable var settings = settings + Form { - Section(header: Text("Image Quality")) { + Section("Image Quality") { Picker("Image Quality", selection: $settings.detailViewQuality) { ForEach(BooruPostFileType.allCases, id: \.self) { type in Text(type.rawValue.capitalized).tag(type) @@ -13,32 +16,32 @@ struct SettingsSectionDetailsView: View { } } - Section(header: Text("Display Options")) { - Toggle("Enable \"Share Image\" Shortcut", isOn: $settings.enableShareShortcut) + Section("Appearance") { + Toggle("Show Share Action", isOn: $settings.enableShareShortcut) - Toggle("Display Information Bar", isOn: $settings.displayDetailsInformationBar) + Toggle("Show Information Bar", isOn: $settings.displayDetailsInformationBar) } #if os(macOS) - Section(header: Text("File Management")) { + Section("Saved Files") { Toggle(isOn: $settings.saveTagsToFile) { - Text("Save Tags to File") + Text("Save Tags Alongside Images") Text("Saves post tags in a file alongside the downloaded image.") } } #endif - Section(header: Text("Performance")) { + Section("Loading") { let preloadRange = 0...10 #if os(macOS) - Picker("Preloaded Images", selection: $settings.preloadedCarouselImages) { + Picker("Preload Nearby Images", selection: $settings.preloadedCarouselImages) { ForEach(preloadRange, id: \.self) { columns in Text("\(columns)") } } #else Stepper( - "Preloaded Images: \(settings.preloadedCarouselImages)", + "Preload Nearby Images: \(settings.preloadedCarouselImages)", value: $settings.preloadedCarouselImages, in: preloadRange ) @@ -48,7 +51,7 @@ struct SettingsSectionDetailsView: View { #if os(macOS) .formStyle(.grouped) #endif - .navigationTitle("Details") + .navigationTitle("Viewer") #if !os(macOS) .navigationBarTitleDisplayMode(.large) #endif @@ -58,6 +61,6 @@ struct SettingsSectionDetailsView: View { #Preview { NavigationStack { SettingsSectionDetailsView() - .environmentObject(SettingsManager()) + .environment(SettingsManager()) } } diff --git a/Sora/Views/Settings/Section/SettingsSectionImportExportView.swift b/Sora/Views/Settings/Section/SettingsSectionImportExportView.swift index b74fe4b..846e9e5 100644 --- a/Sora/Views/Settings/Section/SettingsSectionImportExportView.swift +++ b/Sora/Views/Settings/Section/SettingsSectionImportExportView.swift @@ -2,9 +2,12 @@ import SwiftUI import UniformTypeIdentifiers struct SettingsSectionImportExportView: View { - @EnvironmentObject private var settings: SettingsManager + @Environment(SettingsManager.self) + private var settings @State private var isFileExporterPresented = false @State private var isFileImporterPresented = false + @State private var bookmarksExportDocument: JSONFileDocument? + @State private var bookmarksExportFilename = "sora_bookmarks.json" @State private var exportError: Error? @State private var importError: Error? private let dateFormatter: DateFormatter = { @@ -20,26 +23,30 @@ struct SettingsSectionImportExportView: View { } Button("Export Bookmarks") { - exportBookmarksToFile() + prepareBookmarksExport() } } #if os(macOS) .trailingFrame() - .fileExporter( - isPresented: $isFileExporterPresented, - document: try? JSONFileDocument(settings.exportBookmarks()), - contentType: .json, - defaultFilename: "sora_bookmarks.json" - ) { result in - switch result { - case .success: - break + #endif + .fileExporter( + isPresented: $isFileExporterPresented, + document: bookmarksExportDocument, + contentType: .json, + defaultFilename: bookmarksExportFilename + ) { result in + bookmarksExportDocument = nil - case .failure(let error): + switch result { + case .success: + break + + case .failure(let error): + if !isUserCancelled(error) { exportError = error } } - #endif + } .fileImporter( isPresented: $isFileImporterPresented, allowedContentTypes: [.json], @@ -71,38 +78,14 @@ struct SettingsSectionImportExportView: View { } } - private func exportBookmarksToFile() { + private func prepareBookmarksExport() { do { let data = try settings.exportBookmarks() let timestamp = dateFormatter.string(from: Date()) - #if os(macOS) - _ = data - isFileExporterPresented = true - #elseif os(iOS) - let temporaryURL = FileManager.default.temporaryDirectory - .appendingPathComponent("sora_bookmarks_\(timestamp).json") - - try data.write(to: temporaryURL) - - let activityController = UIActivityViewController( - activityItems: [temporaryURL], - applicationActivities: nil - ) - - if let windowScene = UIApplication.shared.connectedScenes - .first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene, - let rootViewController = windowScene.windows.first?.rootViewController - { - activityController.popoverPresentationController?.sourceView = rootViewController.view - - rootViewController.present(activityController, animated: true) - } - - activityController.completionWithItemsHandler = { _, _, _, _ in - try? FileManager.default.removeItem(at: temporaryURL) - } - #endif + bookmarksExportDocument = JSONFileDocument(data) + bookmarksExportFilename = "sora_bookmarks_\(timestamp).json" + isFileExporterPresented = true } catch { exportError = error } @@ -125,6 +108,13 @@ struct SettingsSectionImportExportView: View { } } + private func isUserCancelled(_ error: Error) -> Bool { + let error = error as NSError + + return error.domain == NSCocoaErrorDomain + && error.code == CocoaError.userCancelled.rawValue + } + private enum ImportError: Error { case accessDenied } diff --git a/Sora/Views/Settings/Section/SettingsSectionProviderView.swift b/Sora/Views/Settings/Section/SettingsSectionProviderView.swift index cbfae37..02a8be6 100644 --- a/Sora/Views/Settings/Section/SettingsSectionProviderView.swift +++ b/Sora/Views/Settings/Section/SettingsSectionProviderView.swift @@ -1,16 +1,19 @@ import SwiftUI struct SettingsSectionProviderView: View { - @EnvironmentObject var settings: SettingsManager + @Environment(SettingsManager.self) + private var settings @State private var showingCustomBooruSheet = false @State private var newDomain: String = "" @State private var newFlavor: BooruProviderFlavor = .danbooru @State private var domainError: String? var body: some View { + @Bindable var settings = settings + Form { - Section(header: Text("Provider Selection")) { - Picker("Provider", selection: $settings.preferredBooru) { + Section("Source") { + Picker("Website", selection: $settings.preferredBooru) { ForEach(BooruProvider.allCases, id: \.self) { type in Text(type.rawValue).tag(type) } @@ -22,11 +25,13 @@ struct SettingsSectionProviderView: View { } } - Section(header: Text("Moebooru Feed")) { - Toggle("Show Held Posts", isOn: $settings.showHeldMoebooruPosts) + if BooruProviderFlavor(provider: settings.preferredBooru) == .moebooru { + Section("Hidden Posts") { + Toggle("Show Hidden Posts", isOn: $settings.showHeldMoebooruPosts) + } } - Section(header: Text("API Credentials")) { + Section { SecureField( "API Key", text: Binding( @@ -65,23 +70,40 @@ struct SettingsSectionProviderView: View { #if os(iOS) .keyboardType(isDanbooruProvider ? .default : .numberPad) #endif + } header: { + Text("Account") + } footer: { + Text("Add credentials only if your selected source supports them.") } - Section(header: Text("Custom Providers")) { - Button("Add Custom Provider") { + Section { + Toggle("Send User Agent", isOn: $settings.sendBooruUserAgent) + + if settings.sendBooruUserAgent { + TextField("Custom User Agent", text: $settings.customBooruUserAgent) + .autocorrectionDisabled(true) + } + } header: { + Text("Advanced") + } footer: { + Text("Only change these options if a source requires them.") + } + + Section { + Button("Add Source") { showingCustomBooruSheet = true } .trailingFrame() if case .custom(let provider) = settings.preferredBooru { - Button("Remove Custom Provider") { + Button("Remove Current Source") { settings.customProviders.removeAll { $0.id == provider.id } settings.preferredBooru = .safebooru } .disabled(!settings.customProviders.contains { $0.id == provider.id }) } - Button("Remove All Custom Providers") { + Button("Remove All Custom Sources") { if case .custom = settings.preferredBooru { settings.preferredBooru = .safebooru } @@ -91,23 +113,27 @@ struct SettingsSectionProviderView: View { } } .trailingFrame() + } header: { + Text("Custom Sources") + } footer: { + Text("Use custom sources when you want Sora to browse a compatible site.") } } #if os(macOS) .formStyle(.grouped) #endif - .navigationTitle("Provider") + .navigationTitle("Source") #if !os(macOS) .navigationBarTitleDisplayMode(.large) #endif .sheet(isPresented: $showingCustomBooruSheet) { NavigationStack { Form { - Section(header: Text("Provider Details")) { - TextField("Domain", text: $newDomain) + Section("Source Details") { + TextField("Website", text: $newDomain) .autocorrectionDisabled(true) - Picker("Provider Type", selection: $newFlavor) { + Picker("Type", selection: $newFlavor) { ForEach(BooruProviderFlavor.allCases, id: \.self) { flavor in Text(flavor.rawValue).tag(flavor) } @@ -117,7 +143,7 @@ struct SettingsSectionProviderView: View { #if os(macOS) .formStyle(.grouped) #endif - .navigationTitle("Add Custom Provider") + .navigationTitle("Add Source") #if !os(macOS) .navigationBarTitleDisplayMode(.inline) #endif @@ -142,7 +168,7 @@ struct SettingsSectionProviderView: View { } } .alert( - "Invalid Domain", + "Invalid Website", isPresented: Binding( get: { domainError != nil }, set: { if !$0 { domainError = nil } } @@ -150,7 +176,9 @@ struct SettingsSectionProviderView: View { ) { Button("OK", role: .cancel) { () } } message: { - Text(domainError ?? "An unknown error occurred while validating the domain.") + if let domainError { + Text(domainError) + } } } @@ -183,19 +211,19 @@ struct SettingsSectionProviderView: View { "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*\\.[a-z]{2,}$" guard NSPredicate(format: "SELF MATCHES %@", domainRegex).evaluate(with: domain) else { - domainError = "Please enter a valid domain name, such as yande.re." + domainError = "Enter a valid website, such as yande.re." return false } guard !domain.contains("://"), !domain.contains("/"), !domain.contains("?") else { - domainError = "Only enter the domain name—leave out 'http://' or extra details." + domainError = "Enter only the website name, without 'http://' or extra details." return false } guard domain.count <= 253 else { // RFC 1035 - domainError = "This domain name is too long. It must be 253 characters or fewer." + domainError = "This website name is too long. It must be 253 characters or fewer." return false } @@ -203,7 +231,7 @@ struct SettingsSectionProviderView: View { let labels = domain.split(separator: ".") guard labels.allSatisfy({ $0.count <= 63 }) else { - domainError = "Each section of the domain name must be 63 characters or fewer." + domainError = "Each section of the website name must be 63 characters or fewer." return false } @@ -248,6 +276,6 @@ struct SettingsSectionProviderView: View { #Preview { NavigationStack { SettingsSectionProviderView() - .environmentObject(SettingsManager()) + .environment(SettingsManager()) } } diff --git a/Sora/Views/Settings/Section/SettingsSectionSearchView.swift b/Sora/Views/Settings/Section/SettingsSectionSearchView.swift index 6249c4a..e82f872 100644 --- a/Sora/Views/Settings/Section/SettingsSectionSearchView.swift +++ b/Sora/Views/Settings/Section/SettingsSectionSearchView.swift @@ -1,16 +1,19 @@ import SwiftUI struct SettingsSectionSearchView: View { - @EnvironmentObject var settings: SettingsManager + @Environment(SettingsManager.self) + private var settings var body: some View { - Picker("Suggestion Mode", selection: $settings.searchSuggestionsMode) { + @Bindable var settings = settings + + Picker("Suggestions", selection: $settings.searchSuggestionsMode) { ForEach(SettingsSearchSuggestionsMode.allCases, id: \.self) { type in Text(type.rawValue.capitalized).tag(type) } } - Button("Clear History") { + Button("Clear Search History") { settings.searchHistory.removeAll() } .trailingFrame() diff --git a/Sora/Views/Settings/Section/SettingsSectionSettingsView.swift b/Sora/Views/Settings/Section/SettingsSectionSettingsView.swift index 273cc82..ad7fe83 100644 --- a/Sora/Views/Settings/Section/SettingsSectionSettingsView.swift +++ b/Sora/Views/Settings/Section/SettingsSectionSettingsView.swift @@ -1,20 +1,25 @@ import SwiftUI struct SettingsSectionSettingsView: View { - @EnvironmentObject var settings: SettingsManager + @Environment(SettingsManager.self) + private var settings var body: some View { + @Bindable var settings = settings + Toggle(isOn: $settings.enableSync) { Text("Sync with iCloud") - Text("Keep bookmarks, search history, and providers consistent across all your devices.") + Text("Keep bookmarks, collections, search history, and sources in sync across your devices.") .font(.caption) } - .onChange(of: settings.enableSync) { _, _ in - settings.triggerSyncIfNeededForAll() + .onChange(of: settings.enableSync) { _, isEnabled in + if isEnabled { + settings.triggerSyncIfNeededForAll() + } } - Button("Reset to Defaults") { + Button("Reset Settings") { settings.resetToDefaults() } .trailingFrame() diff --git a/Sora/Views/Settings/Section/SettingsSectionThumbnailsView.swift b/Sora/Views/Settings/Section/SettingsSectionThumbnailsView.swift index ddfbdcc..dc9d87e 100644 --- a/Sora/Views/Settings/Section/SettingsSectionThumbnailsView.swift +++ b/Sora/Views/Settings/Section/SettingsSectionThumbnailsView.swift @@ -1,12 +1,15 @@ import SwiftUI struct SettingsSectionThumbnailsView: View { - @EnvironmentObject var settings: SettingsManager + @Environment(SettingsManager.self) + private var settings @State private var isShowingContentFiltering = false var body: some View { + @Bindable var settings = settings + Form { - Section(header: Text("Thumbnail Quality")) { + Section("Image Quality") { Picker("Thumbnail Quality", selection: $settings.thumbnailQuality) { ForEach(BooruPostFileType.allCases, id: \.self) { type in Text(type.rawValue.capitalized).tag(type) @@ -14,27 +17,27 @@ struct SettingsSectionThumbnailsView: View { } } - Section(header: Text("Grid Layout")) { + Section("Layout") { #if os(macOS) - Picker("Thumbnail Grid Columns", selection: $settings.thumbnailGridColumns) { + Picker("Columns", selection: $settings.thumbnailGridColumns) { ForEach(1...10, id: \.self) { columns in Text("\(columns)") } } #else Stepper( - "Thumbnail Grid Columns: \(settings.thumbnailGridColumns)", + "Columns: \(settings.thumbnailGridColumns)", value: $settings.thumbnailGridColumns, in: 1...10 ) #endif - Toggle("Uniform Thumbnail Size", isOn: $settings.uniformThumbnailGrid) + Toggle("Square Thumbnails", isOn: $settings.uniformThumbnailGrid) - Toggle("Lazy Thumbnail Loading", isOn: $settings.alternativeThumbnailGrid) + Toggle("Prioritize Scrolling Performance", isOn: $settings.alternativeThumbnailGrid) } - Section(header: Text("Content Filtering")) { + Section("Sensitive Content") { #if os(macOS) - Button("Content Filtering") { + Button("Sensitive Content") { isShowingContentFiltering.toggle() } .sheet(isPresented: $isShowingContentFiltering) { @@ -44,7 +47,7 @@ struct SettingsSectionThumbnailsView: View { .trailingFrame() #else NavigationLink(destination: SettingsSectionContentRatingsView()) { - Text("Content Filtering") + Text("Sensitive Content") } #endif } @@ -62,6 +65,6 @@ struct SettingsSectionThumbnailsView: View { #Preview { NavigationStack { SettingsSectionThumbnailsView() - .environmentObject(SettingsManager()) + .environment(SettingsManager()) } } |