summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2025-03-05 03:53:34 -0800
committerFuwn <[email protected]>2025-03-05 03:53:34 -0800
commitd10f51d93e35c11f883df536b13b7bb6382d463c (patch)
tree59b786a352ad3adb16a61ca1d858e5e75807db90
parentfeat: Development commit (diff)
downloadsora-testing-d10f51d93e35c11f883df536b13b7bb6382d463c.tar.xz
sora-testing-d10f51d93e35c11f883df536b13b7bb6382d463c.zip
feat: Development commit
-rw-r--r--Localizable.xcstrings24
-rw-r--r--Sora/Data/Booru/Provider/BooruProvider.swift10
-rw-r--r--Sora/Data/Booru/Provider/BooruProviderCustom.swift6
-rw-r--r--Sora/Data/Settings/SettingsManager.swift22
-rw-r--r--Sora/Views/Settings/Section/SettingsProviderView.swift148
5 files changed, 199 insertions, 11 deletions
diff --git a/Localizable.xcstrings b/Localizable.xcstrings
index df67de2..1b601f2 100644
--- a/Localizable.xcstrings
+++ b/Localizable.xcstrings
@@ -4,6 +4,12 @@
"%lld" : {
},
+ "Add" : {
+
+ },
+ "Add Custom Provider" : {
+
+ },
"Add Dummy Bookmarks" : {
},
@@ -51,6 +57,9 @@
"BookmarksAddToView" : {
},
+ "Cancel" : {
+
+ },
"Clear Cached Tags (%@)" : {
},
@@ -103,6 +112,9 @@
}
}
},
+ "Domain" : {
+
+ },
"Enable \"Share Image\" Shortcut" : {
},
@@ -112,6 +124,9 @@
"Export Failed" : {
},
+ "Flavor" : {
+
+ },
"Image Quality" : {
},
@@ -124,6 +139,9 @@
"Import Failed" : {
},
+ "Invalid Domain" : {
+
+ },
"Loading %@…" : {
},
@@ -226,9 +244,15 @@
"Remove All Bookmarks" : {
},
+ "Remove All Custom Providers" : {
+
+ },
"Remove All Searches" : {
},
+ "Remove Custom Provider" : {
+
+ },
"Reset to Defaults" : {
"localizations" : {
"ja" : {
diff --git a/Sora/Data/Booru/Provider/BooruProvider.swift b/Sora/Data/Booru/Provider/BooruProvider.swift
index 84b76c8..49bf18a 100644
--- a/Sora/Data/Booru/Provider/BooruProvider.swift
+++ b/Sora/Data/Booru/Provider/BooruProvider.swift
@@ -9,7 +9,13 @@ enum BooruProvider: CaseIterable, Codable, Hashable, Equatable {
// MARK: - Computed Properties
var domain: String {
- Self.domains[self] ?? rawValue.lowercased()
+ switch self {
+ case .custom(let provider):
+ provider.self.domain
+
+ default:
+ Self.domains[self] ?? rawValue.lowercased()
+ }
}
var asFileNameComponent: String {
@@ -45,7 +51,7 @@ enum BooruProvider: CaseIterable, Codable, Hashable, Equatable {
"yande.re"
case .custom(let provider):
- provider.baseURL
+ provider.domain
}
}
diff --git a/Sora/Data/Booru/Provider/BooruProviderCustom.swift b/Sora/Data/Booru/Provider/BooruProviderCustom.swift
index a2be323..7d8cc28 100644
--- a/Sora/Data/Booru/Provider/BooruProviderCustom.swift
+++ b/Sora/Data/Booru/Provider/BooruProviderCustom.swift
@@ -2,12 +2,12 @@ import Foundation
struct BooruProviderCustom: Identifiable, Codable, Hashable {
let id: UUID
- var baseURL: String
+ var domain: String
var flavor: BooruProviderFlavor
- init(baseURL: String, flavor: BooruProviderFlavor, id: UUID = UUID()) {
+ init(domain: String, flavor: BooruProviderFlavor, id: UUID = UUID()) {
self.id = id
- self.baseURL = baseURL
+ self.domain = domain
self.flavor = flavor
}
}
diff --git a/Sora/Data/Settings/SettingsManager.swift b/Sora/Data/Settings/SettingsManager.swift
index f4273ef..984ffbe 100644
--- a/Sora/Data/Settings/SettingsManager.swift
+++ b/Sora/Data/Settings/SettingsManager.swift
@@ -14,9 +14,6 @@ class SettingsManager: ObservableObject {
@AppStorage("thumbnailGridColumns")
var thumbnailGridColumns = 2
- @AppStorage("preferredBooru")
- var preferredBooruData = Data()
-
@AppStorage("enableShareShortcut")
var enableShareShortcut = false
@@ -44,6 +41,12 @@ class SettingsManager: ObservableObject {
@AppStorage("searchHistory")
private var searchHistoryData = Data()
+ @AppStorage("preferredBooru")
+ private var preferredBooruData = Data()
+
+ @AppStorage("customProviders")
+ private var customProvidersData = Data()
+
// MARK: - Computed Properties
var bookmarks: [SettingsBookmark] {
get {
@@ -85,6 +88,19 @@ class SettingsManager: ObservableObject {
set { preferredBooruData = Self.encode(newValue) ?? preferredBooruData }
}
+ var customProviders: [BooruProviderCustom] {
+ get {
+ Self.decode(
+ [BooruProviderCustom].self,
+ from: customProvidersData
+ ) ?? []
+ }
+
+ set {
+ customProvidersData = Self.encode(newValue) ?? customProvidersData
+ }
+ }
+
// MARK: - Private Helpers
private static func encode<T: Encodable>(_ value: T) -> Data? {
try? JSONEncoder().encode(value)
diff --git a/Sora/Views/Settings/Section/SettingsProviderView.swift b/Sora/Views/Settings/Section/SettingsProviderView.swift
index b561bc3..eb3fb1b 100644
--- a/Sora/Views/Settings/Section/SettingsProviderView.swift
+++ b/Sora/Views/Settings/Section/SettingsProviderView.swift
@@ -2,12 +2,154 @@ import SwiftUI
struct SettingsProviderView: View {
@EnvironmentObject var settings: SettingsManager
+ @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 {
- Picker("Provider", selection: $settings.preferredBooru) {
- ForEach(BooruProvider.allCases, id: \.self) { type in
- Text(type.rawValue).tag(type)
+ Group {
+ Picker("Provider", selection: $settings.preferredBooru) {
+ ForEach(BooruProvider.allCases, id: \.self) { type in
+ Text(type.rawValue).tag(type)
+ }
+
+ ForEach(settings.customProviders, id: \.id) { provider in
+ Text(provider.domain)
+ .tag(BooruProvider.custom(provider))
+ }
+ }
+
+ Button("Add Custom Provider") {
+ showingCustomBooruSheet = true
+ }
+ .sheet(isPresented: $showingCustomBooruSheet) {
+ NavigationView {
+ Form {
+ TextField("Domain", text: $newDomain)
+ .autocorrectionDisabled(true)
+
+ Picker("Flavor", selection: $newFlavor) {
+ ForEach(BooruProviderFlavor.allCases, id: \.self) { flavor in
+ Text(flavor.rawValue).tag(flavor)
+ }
+ }
+ }
+ .toolbar {
+ ToolbarItem(placement: .cancellationAction) {
+ Button("Cancel") {
+ showingCustomBooruSheet = false
+ resetForm()
+ }
+ }
+
+ ToolbarItem(placement: .confirmationAction) {
+ Button("Add") {
+ if validateDomain() {
+ addCustomProvider()
+ showingCustomBooruSheet = false
+ }
+ }
+ .disabled(newDomain.isEmpty || domainError != nil)
+ }
+ }
+ #if os(macOS)
+ .navigationTitle("Add Custom Provider")
+ #endif
+ }
+ }
+
+ if case .custom(let provider) = settings.preferredBooru {
+ removeCustomProviderButtonContent(provider)
+ }
+
+ Button("Remove All Custom Providers") {
+ if case .custom = settings.preferredBooru {
+ settings.preferredBooru = .safebooru
+ }
+
+ if !settings.customProviders.isEmpty {
+ settings.customProviders.removeAll()
+ }
}
}
+ .alert(
+ "Invalid Domain",
+ isPresented: Binding(
+ get: { domainError != nil },
+ set: { if !$0 { domainError = nil } }
+ )
+ ) {
+ Button("OK", role: .cancel) { () }
+ } message: {
+ Text(domainError ?? "An unknown error occurred while validating the domain.")
+ }
+ }
+
+ private func addCustomProvider() {
+ let customProvider = BooruProviderCustom(
+ domain: newDomain.lowercased(),
+ flavor: newFlavor
+ )
+
+ settings.customProviders.append(customProvider)
+ resetForm()
+ }
+
+ private func resetForm() {
+ newDomain = ""
+ newFlavor = .danbooru
+ domainError = nil
+ }
+
+ @discardableResult
+ private func validateDomain() -> Bool {
+ guard !newDomain.isEmpty else {
+ domainError = nil
+
+ return false
+ }
+
+ let domain = newDomain.lowercased().trimmingCharacters(in: .whitespaces)
+ let domainRegex =
+ "^[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."
+
+ return false
+ }
+
+ guard !domain.contains("://"), !domain.contains("/"), !domain.contains("?") else {
+ domainError = "Only enter the domain name—leave out '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."
+
+ return false
+ }
+
+ 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."
+
+ return false
+ }
+
+ domainError = nil
+
+ return true
+ }
+
+ private func removeCustomProviderButtonContent(_ provider: BooruProviderCustom) -> some View {
+ Button("Remove Custom Provider") {
+ settings.customProviders.removeAll { $0.id == provider.id }
+ settings.preferredBooru = .safebooru
+ }
+ .disabled(!settings.customProviders.contains { $0.id == provider.id })
}
}