import Defaults import SwiftUI import UIKit struct SponsorBlockSettings: View { @ObservedObject private var settings = SettingsModel.shared @Default(.sponsorBlockInstance) private var sponsorBlockInstance @Default(.sponsorBlockCategories) private var sponsorBlockCategories @Default(.sponsorBlockColors) private var sponsorBlockColors var body: some View { Group { #if os(macOS) sections Spacer() #else List { sections } #endif } #if os(tvOS) .frame(maxWidth: 1000) #else .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) #endif .navigationTitle("SponsorBlock") } private var sections: some View { Group { Section(header: SettingsHeader(text: "SponsorBlock API")) { TextField( "SponsorBlock API Instance", text: $sponsorBlockInstance ) .labelsHidden() #if !os(macOS) .autocapitalization(.none) .disableAutocorrection(true) .keyboardType(.URL) #endif } Section(header: SettingsHeader(text: "Categories to Skip".localized())) { categoryRows } colorSection Button { settings.presentAlert( Alert( title: Text("Restore Default Colors?"), message: Text("This action will reset all custom colors back to their original defaults. " + "Any custom color changes you've made will be lost."), primaryButton: .destructive(Text("Restore")) { resetColors() }, secondaryButton: .cancel() ) ) } label: { Text("Restore Default Colors …") .foregroundColor(.red) } Section(footer: categoriesDetails) { EmptyView() } } } private var colorSection: some View { Section(header: SettingsHeader(text: "Colors for Categories")) { ForEach(SponsorBlockAPI.categories, id: \.self) { category in LazyVStack(alignment: .leading) { ColorPicker( SponsorBlockAPI.categoryDescription(category) ?? "Unknown", selection: Binding( get: { getColor(for: category) }, set: { setColor($0, for: category) } ) ) } } } } private var categoryRows: some View { ForEach(SponsorBlockAPI.categories, id: \.self) { category in LazyVStack(alignment: .leading) { MultiselectRow( title: SponsorBlockAPI.categoryDescription(category) ?? "Unknown", selected: sponsorBlockCategories.contains(category) ) { value in toggleCategory(category, value: value) } } } } private var categoriesDetails: some View { VStack(alignment: .leading) { ForEach(SponsorBlockAPI.categories, id: \.self) { category in Text(SponsorBlockAPI.categoryDescription(category) ?? "Category") .fontWeight(.bold) .padding(.bottom, 0.5) #if os(tvOS) .focusable() #endif Text(SponsorBlockAPI.categoryDetails(category) ?? "Details") .padding(.bottom, 10) .fixedSize(horizontal: false, vertical: true) } } .foregroundColor(.secondary) } func toggleCategory(_ category: String, value: Bool) { if let index = sponsorBlockCategories.firstIndex(where: { $0 == category }), !value { sponsorBlockCategories.remove(at: index) } else if value { sponsorBlockCategories.insert(category) } } private func getColor(for category: String) -> Color { if let hexString = sponsorBlockColors[category], let rgbValue = Int(hexString.dropFirst(), radix: 16) { let r = Double((rgbValue >> 16) & 0xFF) / 255.0 let g = Double((rgbValue >> 8) & 0xFF) / 255.0 let b = Double(rgbValue & 0xFF) / 255.0 return Color(red: r, green: g, blue: b) } return Color("AppRedColor") // Fallback color if no match found } private func setColor(_ color: Color, for category: String) { let uiColor = UIColor(color) // swiftlint:disable no_cgfloat var red: CGFloat = 0 var green: CGFloat = 0 var blue: CGFloat = 0 var alpha: CGFloat = 0 // swiftlint:enable no_cgfloat uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha) let r = Int(red * 255.0) let g = Int(green * 255.0) let b = Int(blue * 255.0) let rgbValue = (r << 16) | (g << 8) | b sponsorBlockColors[category] = String(format: "#%06x", rgbValue) } private func resetColors() { sponsorBlockColors = SponsorBlockColors.dictionary } } struct SponsorBlockSettings_Previews: PreviewProvider { static var previews: some View { VStack { SponsorBlockSettings() } .frame(maxHeight: 600) } }