diff options
Diffstat (limited to 'Sora/Views/Settings/Section/SettingsSectionProviderView.swift')
| -rw-r--r-- | Sora/Views/Settings/Section/SettingsSectionProviderView.swift | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/Sora/Views/Settings/Section/SettingsSectionProviderView.swift b/Sora/Views/Settings/Section/SettingsSectionProviderView.swift new file mode 100644 index 0000000..02c5b02 --- /dev/null +++ b/Sora/Views/Settings/Section/SettingsSectionProviderView.swift @@ -0,0 +1,231 @@ +import SwiftUI + +struct SettingsSectionProviderView: 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 { + Form { + Section(header: Text("Provider Selection")) { + 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)) + } + } + } + + Section(header: Text("API Credentials")) { + SecureField( + "API Key", + text: Binding( + get: { settings.providerAPIKeys[settings.preferredBooru] ?? "" }, + set: { updateCredentials(apiKey: $0) } + ) + ) + .autocorrectionDisabled(true) + + TextField( + "User ID", + text: Binding( + get: { + String(settings.providerUserIDs[settings.preferredBooru] ?? 0) + }, + set: { newValue in + let userID = Int(newValue) ?? 0 + + updateCredentials(userID: userID) + } + ) + ) + .autocorrectionDisabled(true) + #if os(iOS) + .keyboardType(.numberPad) + #endif + } + + Section(header: Text("Custom Providers")) { + Button("Add Custom Provider") { + showingCustomBooruSheet = true + } + .trailingFrame() + + if case .custom(let provider) = settings.preferredBooru { + Button("Remove Custom Provider") { + settings.customProviders.removeAll { $0.id == provider.id } + settings.preferredBooru = .safebooru + } + .disabled(!settings.customProviders.contains { $0.id == provider.id }) + } + + Button("Remove All Custom Providers") { + if case .custom = settings.preferredBooru { + settings.preferredBooru = .safebooru + } + + if !settings.customProviders.isEmpty { + settings.customProviders.removeAll() + } + } + .trailingFrame() + } + } + #if os(macOS) + .formStyle(.grouped) + #endif + .navigationTitle("Provider") + #if !os(macOS) + .navigationBarTitleDisplayMode(.large) + #endif + .sheet(isPresented: $showingCustomBooruSheet) { + NavigationStack { + Form { + Section(header: Text("Provider Details")) { + TextField("Domain", text: $newDomain) + .autocorrectionDisabled(true) + + Picker("Flavor", selection: $newFlavor) { + ForEach(BooruProviderFlavor.allCases, id: \.self) { flavor in + Text(flavor.rawValue).tag(flavor) + } + } + } + } + #if os(macOS) + .formStyle(.grouped) + #endif + .navigationTitle("Add Custom Provider") + #if !os(macOS) + .navigationBarTitleDisplayMode(.inline) + #endif + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { + showingCustomBooruSheet = false + resetForm() + } + } + + ToolbarItem(placement: .confirmationAction) { + Button("Add") { + if validateDomain() { + addCustomProvider() + showingCustomBooruSheet = false + } + } + .disabled(newDomain.isEmpty || domainError != nil) + } + } + } + } + .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 updateCredentials(apiKey: String? = nil, userID: Int? = nil) { + var allCredentials = settings.providerCredentials + + if let index = allCredentials.firstIndex(where: { $0.provider == settings.preferredBooru }) { + let credentials = allCredentials[index] + + allCredentials[index] = BooruProviderCredentials( + provider: credentials.provider, + apiKey: apiKey ?? credentials.apiKey, + userID: userID ?? credentials.userID, + id: credentials.id + ) + } else { + allCredentials.append( + BooruProviderCredentials( + provider: settings.preferredBooru, + apiKey: apiKey ?? "", + userID: userID ?? 0 + ) + ) + } + + settings.providerCredentials = allCredentials + } +} + +#Preview { + NavigationStack { + SettingsSectionProviderView() + .environmentObject(SettingsManager()) + } +} |