diff --git a/Shared/Defaults.swift b/Shared/Defaults.swift index 7df4c47d..160a5b6e 100644 --- a/Shared/Defaults.swift +++ b/Shared/Defaults.swift @@ -243,6 +243,7 @@ extension Defaults.Keys { static let sponsorBlockInstance = Key("sponsorBlockInstance", default: "https://sponsor.ajay.app") static let sponsorBlockCategories = Key>("sponsorBlockCategories", default: Set(SponsorBlockAPI.categories)) + static let sponsorBlockColors = Key<[String: String]>("sponsorBlockColors", default: SponsorBlockColors.dictionary) // MARK: GROUP - Locations @@ -580,3 +581,26 @@ enum WidgetListingStyle: String, CaseIterable, Defaults.Serializable { case horizontalCells case list } + +enum SponsorBlockColors: String { + case sponsor = "#00D400" // Green + case selfpromo = "#FFFF00" // Yellow + case interaction = "#CC00FF" // Purple + case intro = "#00FFFF" // Cyan + case outro = "#0202ED" // Dark Blue + case preview = "#008FD6" // Light Blue + case filler = "#7300FF" // Violet + case music_offtopic = "#FF9900" // Orange + + // Define all cases, can be used to iterate over the colors + static let allCases: [SponsorBlockColors] = [.sponsor, .selfpromo, .interaction, .intro, .outro, .preview, .filler, .music_offtopic] + + // Create a dictionary with the category names as keys and colors as values + static let dictionary: [String: String] = { + var dict = [String: String]() + for item in allCases { + dict[String(describing: item)] = item.rawValue + } + return dict + }() +} diff --git a/Shared/Player/Controls/OSD/Seek.swift b/Shared/Player/Controls/OSD/Seek.swift index 3813c7f7..3f102621 100644 --- a/Shared/Player/Controls/OSD/Seek.swift +++ b/Shared/Player/Controls/OSD/Seek.swift @@ -13,6 +13,17 @@ struct Seek: View { @Default(.playerControlsLayout) private var regularPlayerControlsLayout @Default(.fullScreenPlayerControlsLayout) private var fullScreenPlayerControlsLayout + @Default(.sponsorBlockColors) private var sponsorBlockColors + + 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 + } var body: some View { Group { @@ -51,7 +62,8 @@ struct Seek: View { if let segment = projectedSegment { Text(SponsorBlockAPI.categoryDescription(segment.category) ?? "Sponsor") .font(.system(size: playerControlsLayout.segmentFontSize)) - .foregroundColor(Color("AppRedColor")) + .foregroundColor(getColor(for: segment.category)) + .padding(.bottom, 3) } } else { #if !os(tvOS) @@ -69,7 +81,8 @@ struct Seek: View { Divider() Text(SponsorBlockAPI.categoryDescription(category) ?? "Sponsor") .font(.system(size: playerControlsLayout.segmentFontSize)) - .foregroundColor(Color("AppRedColor")) + .foregroundColor(getColor(for: category)) + .padding(.bottom, 3) default: EmptyView() } diff --git a/Shared/Player/Controls/TimelineView.swift b/Shared/Player/Controls/TimelineView.swift index 7f39f4c5..84508ccf 100644 --- a/Shared/Player/Controls/TimelineView.swift +++ b/Shared/Player/Controls/TimelineView.swift @@ -51,11 +51,22 @@ struct TimelineView: View { @Default(.playerControlsLayout) private var regularPlayerControlsLayout @Default(.fullScreenPlayerControlsLayout) private var fullScreenPlayerControlsLayout + @Default(.sponsorBlockColors) private var sponsorBlockColors var playerControlsLayout: PlayerControlsLayout { player.playingFullScreen ? fullScreenPlayerControlsLayout : regularPlayerControlsLayout } + 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 + } + var chapters: [Chapter] { player.currentVideo?.chapters ?? [] } @@ -79,7 +90,7 @@ struct TimelineView: View { Text(description) .font(.system(size: playerControlsLayout.segmentFontSize)) .fixedSize() - .foregroundColor(Color("AppRedColor")) + .foregroundColor(getColor(for: segment.category)) } if let chapter = projectedChapter { Text(chapter.title) @@ -299,7 +310,7 @@ struct TimelineView: View { ForEach(segments, id: \.uuid) { segment in Rectangle() .offset(x: segmentLayerHorizontalOffset(segment)) - .foregroundColor(Color("AppRedColor")) + .foregroundColor(getColor(for: segment.category)) .frame(maxHeight: height) .frame(width: segmentLayerWidth(segment)) } diff --git a/Shared/Settings/SponsorBlockSettings.swift b/Shared/Settings/SponsorBlockSettings.swift index bd7c677a..f51a887a 100644 --- a/Shared/Settings/SponsorBlockSettings.swift +++ b/Shared/Settings/SponsorBlockSettings.swift @@ -1,15 +1,18 @@ 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 { @@ -35,41 +38,63 @@ struct SponsorBlockSettings: View { .labelsHidden() #if !os(macOS) .autocapitalization(.none) + .disableAutocorrection(true) .keyboardType(.URL) #endif } + Section(header: SettingsHeader(text: "Categories to Skip".localized())) { + categoryRows + } + colorSection - Section(header: SettingsHeader(text: "Categories to Skip".localized()), footer: categoriesDetails) { - #if os(macOS) - let list = ForEach(SponsorBlockAPI.categories, id: \.self) { category in - MultiselectRow( - title: SponsorBlockAPI.categoryDescription(category) ?? "Unknown", - selected: sponsorBlockCategories.contains(category) - ) { value in - toggleCategory(category, value: value) - } - } + 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) + } - Group { - if #available(macOS 12.0, *) { - list - .listStyle(.inset(alternatesRowBackgrounds: true)) - } else { - list - .listStyle(.inset) - } - } - Spacer() - #else - ForEach(SponsorBlockAPI.categories, id: \.self) { category in - MultiselectRow( - title: SponsorBlockAPI.categoryDescription(category) ?? "Unknown", - selected: sponsorBlockCategories.contains(category) - ) { value in - toggleCategory(category, value: value) - } - } - #endif + 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) + } } } } @@ -90,7 +115,6 @@ struct SponsorBlockSettings: View { } } .foregroundColor(.secondary) - .padding(.top, 10) } func toggleCategory(_ category: String, value: Bool) { @@ -100,6 +124,40 @@ struct SponsorBlockSettings: View { 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 {