From b5ac760af26ab471811132ed83d946b2091c521f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20F=C3=B6rster?= Date: Tue, 23 Apr 2024 17:16:31 +0200 Subject: [PATCH] SponsorBlock set colors for each category Default colors are defined in alignment to upstream. These can be changed by the user. The colors are also used in the Timeline and the seek view. --- Shared/Defaults.swift | 24 ++++ Shared/Player/Controls/OSD/Seek.swift | 17 ++- Shared/Player/Controls/TimelineView.swift | 15 ++- Shared/Settings/SponsorBlockSettings.swift | 122 +++++++++++++++------ 4 files changed, 142 insertions(+), 36 deletions(-) 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 {