Convert YouTube Enhancements settings to macOS-native helpers

Includes SponsorBlock, Return YouTube Dislike, and DeArrow sub-screens.
Extend SettingsNavigationRow with an optional trailing content slot so
rows can show enabled/disabled status next to the chevron.
This commit is contained in:
Arkadiusz Fal
2026-04-21 00:31:04 +02:00
parent 60be0f8b53
commit f173dd1c39
5 changed files with 47 additions and 66 deletions

View File

@@ -11,7 +11,7 @@ struct DeArrowSettingsView: View {
@Environment(\.appEnvironment) private var appEnvironment @Environment(\.appEnvironment) private var appEnvironment
var body: some View { var body: some View {
Form { SettingsFormContainer {
if let settings = appEnvironment?.settingsManager { if let settings = appEnvironment?.settingsManager {
// Enable/Disable toggle // Enable/Disable toggle
EnableSection(settings: settings) EnableSection(settings: settings)
@@ -44,13 +44,11 @@ private struct EnableSection: View {
@Bindable var settings: SettingsManager @Bindable var settings: SettingsManager
var body: some View { var body: some View {
Section { SettingsFormSection(footer: "settings.deArrow.footer") {
Toggle( Toggle(
String(localized: "settings.deArrow.enabled"), String(localized: "settings.deArrow.enabled"),
isOn: $settings.deArrowEnabled isOn: $settings.deArrowEnabled
) )
} footer: {
Text(String(localized: "settings.deArrow.footer"))
} }
} }
} }
@@ -61,7 +59,7 @@ private struct OptionsSection: View {
@Bindable var settings: SettingsManager @Bindable var settings: SettingsManager
var body: some View { var body: some View {
Section(String(localized: "settings.deArrow.options.header")) { SettingsFormSection("settings.deArrow.options.header") {
Toggle( Toggle(
String(localized: "settings.deArrow.replaceTitles"), String(localized: "settings.deArrow.replaceTitles"),
isOn: $settings.deArrowReplaceTitles isOn: $settings.deArrowReplaceTitles
@@ -84,7 +82,7 @@ private struct AdvancedSection: View {
@State private var thumbnailAPIURLText: String = "" @State private var thumbnailAPIURLText: String = ""
var body: some View { var body: some View {
Section { SettingsFormSection("settings.deArrow.advanced.header", footer: "settings.deArrow.apiURL.footer") {
TextField( TextField(
String(localized: "settings.deArrow.apiURL"), String(localized: "settings.deArrow.apiURL"),
text: $apiURLText, text: $apiURLText,
@@ -140,10 +138,6 @@ private struct AdvancedSection: View {
syncAPIURLs() syncAPIURLs()
} }
} }
} header: {
Text(String(localized: "settings.deArrow.advanced.header"))
} footer: {
Text(String(localized: "settings.deArrow.apiURL.footer"))
} }
.onAppear { .onAppear {
let currentAPIURL = settings.deArrowAPIURL let currentAPIURL = settings.deArrowAPIURL
@@ -169,7 +163,7 @@ private struct AdvancedSection: View {
private struct AboutSection: View { private struct AboutSection: View {
var body: some View { var body: some View {
Section(String(localized: "settings.deArrow.about.header")) { SettingsFormSection("settings.deArrow.about.header") {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Text(String(localized: "settings.deArrow.about.description")) Text(String(localized: "settings.deArrow.about.description"))
.font(.callout) .font(.callout)

View File

@@ -139,18 +139,21 @@ struct SettingsFormSection<Content: View>: View {
/// On macOS it renders as a plain full-width list row with a trailing /// On macOS it renders as a plain full-width list row with a trailing
/// chevron, matching the native macOS System Settings look. On iOS/tvOS /// chevron, matching the native macOS System Settings look. On iOS/tvOS
/// it renders as a standard `NavigationLink` with a `Label`. /// it renders as a standard `NavigationLink` with a `Label`.
struct SettingsNavigationRow<Destination: View>: View { struct SettingsNavigationRow<Destination: View, Trailing: View>: View {
let titleKey: LocalizedStringKey let titleKey: LocalizedStringKey
let systemImage: String let systemImage: String
@ViewBuilder var trailing: () -> Trailing
@ViewBuilder var destination: () -> Destination @ViewBuilder var destination: () -> Destination
init( init(
_ titleKey: LocalizedStringKey, _ titleKey: LocalizedStringKey,
systemImage: String, systemImage: String,
@ViewBuilder trailing: @escaping () -> Trailing = { EmptyView() },
@ViewBuilder destination: @escaping () -> Destination @ViewBuilder destination: @escaping () -> Destination
) { ) {
self.titleKey = titleKey self.titleKey = titleKey
self.systemImage = systemImage self.systemImage = systemImage
self.trailing = trailing
self.destination = destination self.destination = destination
} }
@@ -162,6 +165,8 @@ struct SettingsNavigationRow<Destination: View>: View {
HStack(spacing: 8) { HStack(spacing: 8) {
Label(titleKey, systemImage: systemImage) Label(titleKey, systemImage: systemImage)
Spacer() Spacer()
trailing()
.foregroundStyle(.secondary)
Image(systemName: "chevron.right") Image(systemName: "chevron.right")
.font(.caption) .font(.caption)
.foregroundStyle(.tertiary) .foregroundStyle(.tertiary)
@@ -169,7 +174,12 @@ struct SettingsNavigationRow<Destination: View>: View {
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle()) .contentShape(Rectangle())
#else #else
Label(titleKey, systemImage: systemImage) HStack {
Label(titleKey, systemImage: systemImage)
Spacer()
trailing()
.foregroundStyle(.secondary)
}
#endif #endif
} }
#if os(macOS) #if os(macOS)

View File

@@ -11,20 +11,16 @@ struct ReturnYouTubeDislikeSettingsView: View {
@Environment(\.appEnvironment) private var appEnvironment @Environment(\.appEnvironment) private var appEnvironment
var body: some View { var body: some View {
Form { SettingsFormContainer {
if let settings = appEnvironment?.settingsManager { if let settings = appEnvironment?.settingsManager {
// Enable/Disable toggle SettingsFormSection(footer: "settings.returnYouTubeDislike.footer") {
Section {
Toggle( Toggle(
String(localized: "settings.returnYouTubeDislike.enabled"), String(localized: "settings.returnYouTubeDislike.enabled"),
isOn: Bindable(settings).returnYouTubeDislikeEnabled isOn: Bindable(settings).returnYouTubeDislikeEnabled
) )
} footer: {
Text(String(localized: "settings.returnYouTubeDislike.footer"))
} }
// About section SettingsFormSection("settings.returnYouTubeDislike.about.header") {
Section(String(localized: "settings.returnYouTubeDislike.about.header")) {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Text(String(localized: "settings.returnYouTubeDislike.about.description")) Text(String(localized: "settings.returnYouTubeDislike.about.description"))
.font(.callout) .font(.callout)

View File

@@ -11,7 +11,7 @@ struct SponsorBlockSettingsView: View {
@Environment(\.appEnvironment) private var appEnvironment @Environment(\.appEnvironment) private var appEnvironment
var body: some View { var body: some View {
Form { SettingsFormContainer {
if let settings = appEnvironment?.settingsManager { if let settings = appEnvironment?.settingsManager {
// Enable/Disable toggle // Enable/Disable toggle
EnableSection(settings: settings) EnableSection(settings: settings)
@@ -44,13 +44,11 @@ private struct EnableSection: View {
@Bindable var settings: SettingsManager @Bindable var settings: SettingsManager
var body: some View { var body: some View {
Section { SettingsFormSection(footer: "settings.sponsorBlock.footer") {
Toggle( Toggle(
String(localized: "settings.sponsorBlock.enabled"), String(localized: "settings.sponsorBlock.enabled"),
isOn: $settings.sponsorBlockEnabled isOn: $settings.sponsorBlockEnabled
) )
} footer: {
Text(String(localized: "settings.sponsorBlock.footer"))
} }
} }
} }
@@ -61,7 +59,7 @@ private struct CategoriesSection: View {
@Bindable var settings: SettingsManager @Bindable var settings: SettingsManager
var body: some View { var body: some View {
Section { SettingsFormSection("settings.sponsorBlock.categories.header", footer: "settings.sponsorBlock.categories.footer") {
ForEach(SponsorBlockCategory.allCases, id: \.self) { category in ForEach(SponsorBlockCategory.allCases, id: \.self) { category in
CategoryToggleRow( CategoryToggleRow(
category: category, category: category,
@@ -77,10 +75,6 @@ private struct CategoriesSection: View {
} }
) )
} }
} header: {
Text(String(localized: "settings.sponsorBlock.categories.header"))
} footer: {
Text(String(localized: "settings.sponsorBlock.categories.footer"))
} }
} }
} }
@@ -92,7 +86,7 @@ private struct AdvancedSection: View {
@State private var apiURLText: String = "" @State private var apiURLText: String = ""
var body: some View { var body: some View {
Section { SettingsFormSection("settings.sponsorBlock.advanced.header", footer: "settings.sponsorBlock.apiURL.footer") {
TextField( TextField(
String(localized: "settings.sponsorBlock.apiURL"), String(localized: "settings.sponsorBlock.apiURL"),
text: $apiURLText, text: $apiURLText,
@@ -118,10 +112,6 @@ private struct AdvancedSection: View {
settings.sponsorBlockAPIURL = SettingsManager.defaultSponsorBlockAPIURL settings.sponsorBlockAPIURL = SettingsManager.defaultSponsorBlockAPIURL
} }
} }
} header: {
Text(String(localized: "settings.sponsorBlock.advanced.header"))
} footer: {
Text(String(localized: "settings.sponsorBlock.apiURL.footer"))
} }
.onAppear { .onAppear {
let currentURL = settings.sponsorBlockAPIURL let currentURL = settings.sponsorBlockAPIURL
@@ -136,7 +126,7 @@ private struct AdvancedSection: View {
private struct AboutSection: View { private struct AboutSection: View {
var body: some View { var body: some View {
Section(String(localized: "settings.sponsorBlock.about.header")) { SettingsFormSection("settings.sponsorBlock.about.header") {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Text(String(localized: "settings.sponsorBlock.about.description")) Text(String(localized: "settings.sponsorBlock.about.description"))
.font(.callout) .font(.callout)

View File

@@ -11,7 +11,7 @@ struct YouTubeEnhancementsSettingsView: View {
@Environment(\.appEnvironment) private var appEnvironment @Environment(\.appEnvironment) private var appEnvironment
var body: some View { var body: some View {
Form { SettingsFormContainer {
if let settings = appEnvironment?.settingsManager { if let settings = appEnvironment?.settingsManager {
SponsorBlockSection(settings: settings) SponsorBlockSection(settings: settings)
ReturnYouTubeDislikeSection(settings: settings) ReturnYouTubeDislikeSection(settings: settings)
@@ -33,21 +33,18 @@ private struct SponsorBlockSection: View {
@Bindable var settings: SettingsManager @Bindable var settings: SettingsManager
var body: some View { var body: some View {
Section { SettingsFormSection(footer: "settings.youtubeEnhancements.sponsorBlock.footer") {
NavigationLink { SettingsNavigationRow(
SponsorBlockSettingsView() "settings.sponsorBlock.sectionTitle",
} label: { systemImage: "forward",
HStack { trailing: {
Label(String(localized: "settings.sponsorBlock.sectionTitle"), systemImage: "forward")
Spacer()
Text(settings.sponsorBlockEnabled Text(settings.sponsorBlockEnabled
? String(localized: "common.enabled") ? String(localized: "common.enabled")
: String(localized: "common.disabled")) : String(localized: "common.disabled"))
.foregroundStyle(.secondary)
} }
) {
SponsorBlockSettingsView()
} }
} footer: {
Text(String(localized: "settings.youtubeEnhancements.sponsorBlock.footer"))
} }
} }
} }
@@ -58,21 +55,18 @@ private struct ReturnYouTubeDislikeSection: View {
@Bindable var settings: SettingsManager @Bindable var settings: SettingsManager
var body: some View { var body: some View {
Section { SettingsFormSection(footer: "settings.youtubeEnhancements.returnYouTubeDislike.footer") {
NavigationLink { SettingsNavigationRow(
ReturnYouTubeDislikeSettingsView() "settings.returnYouTubeDislike.sectionTitle",
} label: { systemImage: "hand.thumbsdown",
HStack { trailing: {
Label(String(localized: "settings.returnYouTubeDislike.sectionTitle"), systemImage: "hand.thumbsdown")
Spacer()
Text(settings.returnYouTubeDislikeEnabled Text(settings.returnYouTubeDislikeEnabled
? String(localized: "common.enabled") ? String(localized: "common.enabled")
: String(localized: "common.disabled")) : String(localized: "common.disabled"))
.foregroundStyle(.secondary)
} }
) {
ReturnYouTubeDislikeSettingsView()
} }
} footer: {
Text(String(localized: "settings.youtubeEnhancements.returnYouTubeDislike.footer"))
} }
} }
} }
@@ -83,21 +77,18 @@ private struct DeArrowSection: View {
@Bindable var settings: SettingsManager @Bindable var settings: SettingsManager
var body: some View { var body: some View {
Section { SettingsFormSection(footer: "settings.youtubeEnhancements.deArrow.footer") {
NavigationLink { SettingsNavigationRow(
DeArrowSettingsView() "settings.deArrow.sectionTitle",
} label: { systemImage: "textformat",
HStack { trailing: {
Label(String(localized: "settings.deArrow.sectionTitle"), systemImage: "textformat")
Spacer()
Text(settings.deArrowEnabled Text(settings.deArrowEnabled
? String(localized: "common.enabled") ? String(localized: "common.enabled")
: String(localized: "common.disabled")) : String(localized: "common.disabled"))
.foregroundStyle(.secondary)
} }
) {
DeArrowSettingsView()
} }
} footer: {
Text(String(localized: "settings.youtubeEnhancements.deArrow.footer"))
} }
} }
} }