Files
yattee/Yattee/Views/Settings/PlayerControls/PanelSettingsView.swift
Arkadiusz Fal c33d125553 Add details panel preset settings UI and default visibility
Phase 2 of the player panels redesign:

- New "Details Panel" screen in Player Controls settings: page
  reorder/enable list with per-page editor (info element toggles),
  and a default panel visibility picker (hidden/visible/remember).
- PlayerControlsSettingsViewModel gains panelSettings bindings with
  the usual debounced-save flow; the last enabled page cannot be
  disabled.
- Built-in presets bumped to v9 with explicit default panelSettings.
- The preset's default visibility now drives the wide-layout panel
  on player open and on entering landscape, replacing the hard
  hidden resets; "remember" restores the new detailsPanelLastVisible
  setting recorded on user toggles. Applied once per expanded player
  session (flag on NavigationCoordinator survives re-mounts).
- WideScreenPlayerLayout picks up external visibility writes so the
  async-loaded default reaches the mounted layout.
- Unit tests for PanelSettings Codable compatibility (round-trip,
  old JSON, unknown page types/discriminators/enum values); removed
  stale PlayerInfoTab tests left from Phase 1.
2026-09-21 23:25:10 +02:00

111 lines
3.2 KiB
Swift

//
// PanelSettingsView.swift
// Yattee
//
// Settings editor for the player details panel pages.
//
import SwiftUI
#if os(iOS) || os(macOS)
struct PanelSettingsView: View {
@Bindable var viewModel: PlayerControlsSettingsViewModel
// Local state for immediate UI updates
@State private var defaultVisibility: PanelDefaultVisibility = .hidden
var body: some View {
List {
visibilitySection
pagesSection
}
.navigationTitle(String(localized: "settings.playerControls.detailsPanel"))
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
#elseif os(macOS)
.formStyle(.grouped)
#endif
.opaqueSettingsFormBackground()
.toolbar {
#if os(iOS)
EditButton()
#endif
}
.onAppear {
syncLocalState()
}
.onChange(of: viewModel.activePreset?.id) { _, _ in
syncLocalState()
}
}
// MARK: - Visibility Section
private var visibilitySection: some View {
Section {
Picker(
String(localized: "panel.settings.defaultVisibility"),
selection: $defaultVisibility
) {
ForEach(PanelDefaultVisibility.allCases, id: \.self) { visibility in
Text(visibility.displayName).tag(visibility)
}
}
.disabled(!viewModel.canEditActivePreset)
.onChange(of: defaultVisibility) { _, newValue in
guard newValue != viewModel.panelDefaultVisibility else { return }
viewModel.syncPanelDefaultVisibility(newValue)
}
} footer: {
Text(String(localized: "panel.settings.defaultVisibility.footer"))
}
}
// MARK: - Pages Section
private var pagesSection: some View {
Section {
ForEach(viewModel.panelPages) { page in
pageRow(for: page)
}
.onMove { source, destination in
viewModel.movePanelPages(fromOffsets: source, toOffset: destination)
}
} header: {
Text(String(localized: "panel.settings.pages.header"))
} footer: {
if viewModel.canEditActivePreset {
Text(String(localized: "panel.settings.pages.footer"))
} else {
Text(String(localized: "settings.playerControls.duplicateToEdit"))
}
}
.disabled(!viewModel.canEditActivePreset)
}
@ViewBuilder
private func pageRow(for page: PanelPageConfiguration) -> some View {
NavigationLink {
PanelPageEditorView(pageType: page.pageType, viewModel: viewModel)
} label: {
HStack {
Label(page.pageType.displayName, systemImage: page.pageType.systemImage)
Spacer()
Text(page.isEnabled
? String(localized: "common.on")
: String(localized: "common.off"))
.foregroundStyle(.secondary)
}
}
}
// MARK: - Helpers
private func syncLocalState() {
defaultVisibility = viewModel.panelDefaultVisibility
}
}
#endif