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.
This commit is contained in:
Arkadiusz Fal
2026-09-21 23:25:10 +02:00
parent d5285b2b83
commit c33d125553
15 changed files with 794 additions and 35 deletions

View File

@@ -95,6 +95,7 @@ struct WideScreenPlayerLayout<PlayerContent: View>: View {
withAnimation(.easeInOut(duration: 0.3)) {
isPanelVisible.toggle()
settingsManager?.landscapeDetailsPanelVisible = isPanelVisible
settingsManager?.detailsPanelLastVisible = isPanelVisible
}
}
@@ -102,6 +103,7 @@ struct WideScreenPlayerLayout<PlayerContent: View>: View {
private func hidePanel() {
isPanelVisible = false
settingsManager?.landscapeDetailsPanelVisible = false
settingsManager?.detailsPanelLastVisible = false
}
/// Get safe area insets from window scene (geometry reader insets are 0 when .ignoresSafeArea is used)
@@ -296,6 +298,14 @@ struct WideScreenPlayerLayout<PlayerContent: View>: View {
// Persist panel width changes to settings
settingsManager?.floatingDetailsPanelWidth = newWidth
}
.onChange(of: settingsManager?.landscapeDetailsPanelVisible) { _, newValue in
// External writes (e.g. the preset's default visibility applied
// after the async layout load) must reach the local state too.
guard let newValue, newValue != isPanelVisible else { return }
withAnimation(.easeInOut(duration: 0.3)) {
isPanelVisible = newValue
}
}
.ignoresSafeArea(.all)
#if os(iOS)
.persistentSystemOverlays(.hidden)
@@ -337,6 +347,7 @@ struct WideScreenPlayerLayout<PlayerContent: View>: View {
// Pinning - ensure panel is visible first
isPanelVisible = true
settingsManager?.landscapeDetailsPanelVisible = true
settingsManager?.detailsPanelLastVisible = true
// Animate panscan to zero and pin concurrently for smooth transition
appEnvironment?.navigationCoordinator.animatePanscanToZero(completion: nil)