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 { 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 } #if os(macOS) .frame(maxWidth: .infinity, alignment: .trailing) #endif .sheet(isPresented: $showingCustomBooruSheet) { Form { 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 .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 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() } } #if os(macOS) .frame(maxWidth: .infinity, alignment: .trailing) #endif } .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 }) #if os(macOS) .frame(maxWidth: .infinity, alignment: .trailing) #endif } }