Yattee v2 rewrite

This commit is contained in:
Arkadiusz Fal
2026-02-08 18:31:16 +01:00
parent 20d0cfc0c7
commit 05f921d605
1043 changed files with 163875 additions and 68430 deletions

View File

@@ -0,0 +1,144 @@
//
// 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
.onAppear {
syncFromConfiguration(config)
}
} else {
ContentUnavailableView(
String(localized: "settings.playerControls.buttonNotFound"),
systemImage: "exclamationmark.triangle"
)
}
}
// 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()
)
)
}
}