mirror of
https://github.com/yattee/yattee.git
synced 2026-07-20 14:22:02 +00:00
The NavigationStack-level background wasn't enough: macOS draws its own translucent material behind pushed navigation pages, and Form/List containers draw a translucent scroll background on top of anything placed behind them, so several pages kept the wallpaper-tinted look. Bake the opaque background into SettingsFormContainer (covers all pages built on it, pushed or not) and add opaqueSettingsFormBackground(), which hides the container's scroll background before applying the opaque one, to every macOS-reachable Form/List settings page: player controls and its sub-editors, sidebar settings, edit source, legacy data import, subscription/playlist import, customize home and its shortcut style page, and the log viewer. Contributors, translators and the remote control device page get the plain opaque background. Claude-Session: https://claude.ai/code/session_0154KH8RAVAvm6iVanhmoW8o
147 lines
4.4 KiB
Swift
147 lines
4.4 KiB
Swift
//
|
|
// PillButtonConfigurationView.swift
|
|
// Yattee
|
|
//
|
|
// View for configuring individual button settings in the player pill.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
/// View for configuring a single pill button's settings.
|
|
struct PillButtonConfigurationView: View {
|
|
let buttonID: UUID
|
|
@Bindable var viewModel: PlayerControlsSettingsViewModel
|
|
|
|
// Local state for immediate UI updates
|
|
@State private var seekSeconds: Double = 10
|
|
@State private var seekDirection: SeekDirection = .forward
|
|
|
|
/// Look up the current configuration from the view model's pill settings.
|
|
private var configuration: ControlButtonConfiguration? {
|
|
viewModel.pillButtons.first { $0.id == buttonID }
|
|
}
|
|
|
|
var body: some View {
|
|
if let config = configuration {
|
|
Form {
|
|
// Type-specific settings
|
|
if config.buttonType.hasSettings {
|
|
typeSpecificSettings(for: config)
|
|
}
|
|
}
|
|
.navigationTitle(config.buttonType.displayName)
|
|
#if os(iOS)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
#endif
|
|
.opaqueSettingsFormBackground()
|
|
.onAppear {
|
|
syncFromConfiguration(config)
|
|
}
|
|
} else {
|
|
ContentUnavailableView(
|
|
String(localized: "settings.playerControls.buttonNotFound"),
|
|
systemImage: "exclamationmark.triangle"
|
|
)
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
}
|
|
}
|
|
|
|
// MARK: - Sync from Configuration
|
|
|
|
private func syncFromConfiguration(_ config: ControlButtonConfiguration) {
|
|
switch config.settings {
|
|
case .seek(let settings):
|
|
seekSeconds = Double(settings.seconds)
|
|
seekDirection = settings.direction
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
// MARK: - Type-Specific Settings
|
|
|
|
@ViewBuilder
|
|
private func typeSpecificSettings(for config: ControlButtonConfiguration) -> some View {
|
|
switch config.buttonType {
|
|
case .seek:
|
|
seekSettingsSection
|
|
default:
|
|
EmptyView()
|
|
}
|
|
}
|
|
|
|
// MARK: - Seek Settings
|
|
|
|
@ViewBuilder
|
|
private var seekSettingsSection: some View {
|
|
Section {
|
|
// Direction picker
|
|
Picker(
|
|
String(localized: "settings.playerControls.seek.direction"),
|
|
selection: $seekDirection
|
|
) {
|
|
ForEach(SeekDirection.allCases, id: \.self) { direction in
|
|
Text(direction.displayName).tag(direction)
|
|
}
|
|
}
|
|
.onChange(of: seekDirection) { _, newValue in
|
|
updateSettings(.seek(SeekSettings(seconds: Int(seekSeconds), direction: newValue)))
|
|
}
|
|
|
|
#if !os(tvOS)
|
|
HStack {
|
|
Text(String(localized: "settings.playerControls.seek.seconds"))
|
|
Spacer()
|
|
Text("\(Int(seekSeconds))s")
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
Slider(
|
|
value: $seekSeconds,
|
|
in: 1...60,
|
|
step: 1
|
|
)
|
|
.onChange(of: seekSeconds) { _, newValue in
|
|
updateSettings(.seek(SeekSettings(seconds: Int(newValue), direction: seekDirection)))
|
|
}
|
|
#endif
|
|
|
|
// Quick presets
|
|
HStack {
|
|
ForEach([5, 10, 15, 30], id: \.self) { preset in
|
|
Button("\(preset)s") {
|
|
seekSeconds = Double(preset)
|
|
updateSettings(.seek(SeekSettings(seconds: preset, direction: seekDirection)))
|
|
}
|
|
.buttonStyle(.bordered)
|
|
.tint(Int(seekSeconds) == preset ? .accentColor : .secondary)
|
|
}
|
|
}
|
|
} header: {
|
|
Text(String(localized: "settings.playerControls.seek.header"))
|
|
}
|
|
}
|
|
|
|
// MARK: - Update Helpers
|
|
|
|
private func updateSettings(_ settings: ButtonSettings) {
|
|
guard var updated = configuration else { return }
|
|
updated.settings = settings
|
|
viewModel.updatePillButtonConfiguration(updated)
|
|
}
|
|
}
|
|
|
|
// MARK: - Preview
|
|
|
|
#Preview {
|
|
NavigationStack {
|
|
PillButtonConfigurationView(
|
|
buttonID: UUID(),
|
|
viewModel: PlayerControlsSettingsViewModel(
|
|
layoutService: PlayerControlsLayoutService(),
|
|
settingsManager: SettingsManager()
|
|
)
|
|
)
|
|
}
|
|
}
|