From 8772f3c24ac8e278a5f65de2b8cdd9371e3237bb Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 08:28:56 +0000 Subject: [PATCH] Remove the Music Player Studio's Mini Bar half It configured a widget that does not exist. The studio's "Mini Bar" target let you pick a preset, drag components into an order and watch a live preview, and then told you it was "set as active bottom bar" -- but this fork replaced upstream's per-page mini players with the shared UniversalPlayBar, which reads none of MusicSettings. None of it ever reached the screen. Wiring it up was the other option and was rejected: the bar is shared across music, video, audiobooks and podcasts, so a music-only component order would make it rearrange itself whenever the source type changed. One shared bar is the fork's design; a music-only mini-player customiser contradicts it. Removes the Fullscreen/Mini switcher, the mini preview card and its component builder, MusicMiniPlayerPreset, selectedMiniPreset, componentOrderMini, reorderMiniComponents and setSelectedMiniPreset -- 280 lines. The Fullscreen studio is untouched; it genuinely works, since music_page.dart reads selectedFullscreenPreset. settings_are_wired_test.dart no longer needs its two exemptions, so the rule is unconditional again: every persisted setting is read by something. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01B62qvJ25JCrwnV2uiDrnCQ --- docs/CHANGELOG.md | 6 + docs/ROADMAP.md | 1 - .../appearance/music_player_studio_page.dart | 242 ++---------------- lib/services/music/music_settings.dart | 54 ---- test/services/settings_are_wired_test.dart | 2 - 5 files changed, 24 insertions(+), 281 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 8d053d8e..be2efb99 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to PlayTorrio V3 will be documented in this file. ## [unreleased] — 2026-08-22 +### Remove the Music Player Studio's Mini Bar half — 2026-08-31 +- **It configured a widget that does not exist.** The studio's "Mini Bar" target let you pick a preset, drag components into an order and watch a live preview, then told you it was "set as active bottom bar". This fork replaced upstream's per-page mini players with the shared `UniversalPlayBar`, which reads none of `MusicSettings` — so none of it ever reached the screen. +- Wiring it up instead was the other option and was rejected: the bar is shared across music, video, audiobooks and podcasts, so a music-only component order would make it rearrange itself whenever you switched source type. The one-shared-bar architecture is the fork's design; a music-only mini-player customiser contradicts it. +- Removed the Fullscreen/Mini switcher, the mini preview card and its component builder, `MusicMiniPlayerPreset`, `selectedMiniPreset`, `componentOrderMini`, `reorderMiniComponents` and `setSelectedMiniPreset` — 280 lines. The Fullscreen studio, which genuinely works (`music_page.dart` reads `selectedFullscreenPreset`), is untouched. +- `settings_are_wired_test.dart` no longer needs its two exemptions, so the rule is now unconditional: every persisted setting is read by something. + ### One answer to "is this desktop?" (AUDIT: breakpoint values disagree) — 2026-08-31 - **Eight pages asked the question against four different literals** — `>= 900`, `>= 800`, `> 800`, `>= 750`, `> 700`. At an 820px window the nav chrome rendered tablet while Discover, Catalog, Anime, IPTV, the details page and the Arabic anime details page rendered desktop: a visible split down the middle of one screen. - All eight now ask `AppBreakpoints.of(context)`. Its 900 was already canonical and already what the nav chrome used, so the outliers moved up to it rather than the shared value moving down for them. Pages in the 800–899 band now get the tablet layout they were meant to have instead of a desktop one that disagreed with the chrome around it. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 6fd2ed8c..b4440b83 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -48,7 +48,6 @@ Once a build has actually been through this, clear `AppInfo.channel` and the | 5 | **Anime and Movies/Series now share one row; the pages still differ** | The row is done: `BrowseRowView` is the single implementation, `BrowseScaffold` builds its rows from it and `AnimeSliderSection` wraps it, so card size, spacing, header and arrows cannot drift. Migrating the anime *page* itself onto `BrowseScaffold` was **dropped as not worth it**: `AnimeSliderSection` is also used by `anime_search_page`, so converting only the anime page would leave two row implementations on adjacent screens — worse than before. Converting both is two large pages of churn for a layout that now already matches. What the anime page still has of its own is a hero carousel and a `ContinueWatchingSlider` slot; revisit only if a third page wants that arrangement. | | 6 | **35 files do ad-hoc `MediaQuery.sizeOf(context).width`** instead of `AppBreakpoints.of(context)` | 35 independent call sites, no shared risk, no user-visible bug. Migrate opportunistically when a file is touched for another reason, not as a batch pass. | | 7 | **`music_page.dart` is 5,220 lines** | Four other files are over 2,000. Splitting is worthwhile but is a refactor with no user-visible payoff, so it waits behind anything that a user would notice. | -| 7b | **The Music Player Studio's "Mini Player" tab configures a widget that does not exist** | This fork replaced upstream's per-page mini players with the shared `UniversalPlayBar`, which reads no `MusicSettings`. So the studio's preset picker, drag-to-reorder and live preview all persist state (`selectedMiniPreset`, `componentOrderMini`) that nothing consumes — a settings screen that lies. Either remove the Mini Player half of the studio (consistent with one-shared-bar, loses nothing that works today) or teach `UniversalPlayBar` to honour it, which first needs an answer for what a *music* preset means when the bar is showing a video or an audiobook. Found 2026-08-31. | | 8 | **`actions/download-artifact` is still on v4** | Checked 2026-08-31 and there is **nothing to fix**, recorded so it is not re-raised. The only documented incompatibility is that v4-and-above cannot read artifacts from `upload-artifact@v3` or below, so v4 download alongside v5 upload is a supported pair. Nor is it a runtime issue: `upload-artifact@v5`, `download-artifact@v5` and `download-artifact@v6` all declare `using: node20` in their own `action.yml`, so none of the v5 bumps moved anything off Node 20. Bumping it would be churn. Revisit only if a release run actually warns. | ## Content sources diff --git a/lib/pages/settings/appearance/music_player_studio_page.dart b/lib/pages/settings/appearance/music_player_studio_page.dart index e58ed1a2..8e648eb6 100644 --- a/lib/pages/settings/appearance/music_player_studio_page.dart +++ b/lib/pages/settings/appearance/music_player_studio_page.dart @@ -16,8 +16,6 @@ class MusicPlayerStudioPage extends StatefulWidget { } class _MusicPlayerStudioPageState extends State with SingleTickerProviderStateMixin { - // Studio Player Mode: 0 = Fullscreen Player Studio, 1 = Mini Player Studio - int _studioPlayerTarget = 0; // Live Studio Preview State bool _previewIsPlaying = true; @@ -120,62 +118,6 @@ class _MusicPlayerStudioPageState extends State with Sing ], ), actions: [ - // Target Switcher Pill (Fullscreen vs Mini Player) - Container( - margin: const EdgeInsets.symmetric(vertical: 8), - decoration: BoxDecoration( - color: const Color(0xFF141724), - borderRadius: BorderRadius.circular(10), - border: Border.all(color: Colors.white.withValues(alpha: 0.1)), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - InkWell( - onTap: () => setState(() => _studioPlayerTarget = 0), - borderRadius: BorderRadius.circular(9), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), - decoration: BoxDecoration( - color: _studioPlayerTarget == 0 ? palette.primaryColor : Colors.transparent, - borderRadius: BorderRadius.circular(9), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.fullscreen_rounded, size: 14, color: _studioPlayerTarget == 0 ? Colors.white : Colors.white60), - if (screenW >= 480) ...[ - const SizedBox(width: 4), - Text('Fullscreen', style: TextStyle(color: _studioPlayerTarget == 0 ? Colors.white : Colors.white60, fontSize: 11, fontWeight: FontWeight.bold)), - ], - ], - ), - ), - ), - InkWell( - onTap: () => setState(() => _studioPlayerTarget = 1), - borderRadius: BorderRadius.circular(9), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), - decoration: BoxDecoration( - color: _studioPlayerTarget == 1 ? palette.primaryColor : Colors.transparent, - borderRadius: BorderRadius.circular(9), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.dock_rounded, size: 14, color: _studioPlayerTarget == 1 ? Colors.white : Colors.white60), - if (screenW >= 480) ...[ - const SizedBox(width: 4), - Text('Mini Bar', style: TextStyle(color: _studioPlayerTarget == 1 ? Colors.white : Colors.white60, fontSize: 11, fontWeight: FontWeight.bold)), - ], - ], - ), - ), - ), - ], - ), - ), const SizedBox(width: 8), if (screenW < 520) @@ -346,25 +288,14 @@ class _MusicPlayerStudioPageState extends State with Sing void _applyAsActivePlayer() { final palette = AppThemeService.currentPalette.value; - if (_studioPlayerTarget == 0) { - MusicSettings.setSelectedFullscreenPreset(MusicFullscreenPreset.customStudio); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: const Text('Custom Fullscreen Player Studio layout set as active player!'), - backgroundColor: palette.primaryColor, - behavior: SnackBarBehavior.floating, - ), - ); - } else { - MusicSettings.setSelectedMiniPreset(MusicMiniPlayerPreset.customStudio); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: const Text('Custom Mini Player Studio layout set as active bottom bar!'), - backgroundColor: palette.primaryColor, - behavior: SnackBarBehavior.floating, - ), - ); - } + MusicSettings.setSelectedFullscreenPreset(MusicFullscreenPreset.customStudio); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text('Custom Fullscreen Player Studio layout set as active player!'), + backgroundColor: palette.primaryColor, + behavior: SnackBarBehavior.floating, + ), + ); } // ═══════════════════════════════════════════════════════════════ @@ -395,15 +326,13 @@ class _MusicPlayerStudioPageState extends State with Sing ), ), - // Player Container Sandbox (Fullscreen vs Mini) + // Player Container Sandbox Center( child: SingleChildScrollView( padding: EdgeInsets.symmetric(horizontal: isDesktop ? 24 : 12, vertical: 16), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 440), - child: _studioPlayerTarget == 0 - ? _buildFullscreenPlayerCard(palette, isDesktop) - : _buildMiniPlayerCard(palette, isDesktop), + child: _buildFullscreenPlayerCard(palette, isDesktop), ), ), ), @@ -655,136 +584,6 @@ class _MusicPlayerStudioPageState extends State with Sing } } - Widget _buildMiniPlayerCard(AppThemePalette palette, bool isDesktop) { - final order = MusicSettings.componentOrderMini.value; - - return Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - decoration: BoxDecoration( - color: const Color(0xFF0F121C).withValues(alpha: 0.95), - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: palette.primaryColor.withValues(alpha: 0.35), - width: 1.2, - ), - boxShadow: [ - BoxShadow( - color: palette.primaryColor.withValues(alpha: 0.25), - blurRadius: 30, - offset: const Offset(0, 8), - ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Header - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), - margin: const EdgeInsets.only(bottom: 10), - decoration: BoxDecoration( - color: palette.primaryColor.withValues(alpha: 0.2), - borderRadius: BorderRadius.circular(6), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.dock_rounded, color: palette.primaryColor, size: 12), - const SizedBox(width: 4), - Text( - 'MINI PLAYER DOCK PREVIEW', - style: TextStyle(color: palette.primaryColor, fontSize: 9.5, fontWeight: FontWeight.bold), - ), - ], - ), - ), - - Row( - children: order.map((key) => _buildMiniPreviewComponent(key, palette)).toList(), - ), - ], - ), - ); - } - - Widget _buildMiniPreviewComponent(String key, AppThemePalette palette) { - switch (key) { - case 'artwork': - return Padding( - padding: const EdgeInsets.only(right: 12), - child: Container( - width: 44, - height: 44, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: const Color(0xFF181C2E), - border: Border.all(color: Colors.white.withValues(alpha: 0.15)), - boxShadow: [ - BoxShadow( - color: palette.primaryColor.withValues(alpha: 0.3), - blurRadius: 10, - ), - ], - ), - child: const Icon(Icons.music_note_rounded, color: Colors.white, size: 22), - ), - ); - - case 'trackInfo': - return const Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Starboy (feat. Daft Punk)', - style: TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.bold), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - SizedBox(height: 2), - Text( - 'The Weeknd • Lossless', - style: TextStyle(color: Colors.white54, fontSize: 11), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], - ), - ); - - case 'mainControls': - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - _buildCanvasIconButton(Icons.skip_previous_rounded, 20, palette, () {}), - const SizedBox(width: 4), - _buildCustomPlayPauseButton(palette, mini: true), - const SizedBox(width: 4), - _buildCanvasIconButton(Icons.skip_next_rounded, 20, palette, () {}), - ], - ); - - case 'extraActions': - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: Icon( - _previewIsLiked ? Icons.favorite_rounded : Icons.favorite_border_rounded, - color: _previewIsLiked ? const Color(0xFFE50914) : Colors.white60, - size: 18, - ), - onPressed: () => setState(() => _previewIsLiked = !_previewIsLiked), - ), - ], - ); - - default: - return const SizedBox.shrink(); - } - } - Widget _buildPreviewArtwork(AppThemePalette palette, MusicArtworkStyle artStyle) { if (artStyle == MusicArtworkStyle.vinylSpinningDisc) { return AnimatedBuilder( @@ -1220,17 +1019,16 @@ class _MusicPlayerStudioPageState extends State with Sing // ── Tab 0: Drag & Drop Reorderable Components ── Widget _buildDragAndDropLayoutSection(AppThemePalette palette) { - final isMini = _studioPlayerTarget == 1; final componentNames = { - 'artwork': isMini ? 'Mini Cover Art' : 'Album Art & Vinyl Turntable', + 'artwork': 'Album Art & Vinyl Turntable', 'title': 'Track, Artist & Album Titles', 'trackInfo': 'Track Title & Quality Badge', 'qualityBadge': 'Lossless Hi-Res Audio Quality Badge', 'seekbar': 'Seek Bar Scrubber Canvas', 'mainControls': 'Primary Controls (Play, Pause, Skip, Shuffle, Repeat)', 'secondaryControls': 'Secondary Controls (Volume Slider)', - 'extraActions': isMini ? 'Like Track Action' : 'Synced Lyrics & Queue Quick Buttons', + 'extraActions': 'Synced Lyrics & Queue Quick Buttons', }; final componentIcons = { @@ -1245,7 +1043,7 @@ class _MusicPlayerStudioPageState extends State with Sing }; return ValueListenableBuilder>( - valueListenable: isMini ? MusicSettings.componentOrderMini : MusicSettings.componentOrderFullscreen, + valueListenable: MusicSettings.componentOrderFullscreen, builder: (context, order, _) { return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -1254,15 +1052,15 @@ class _MusicPlayerStudioPageState extends State with Sing children: [ Icon(Icons.drag_indicator_rounded, color: palette.primaryColor, size: 18), const SizedBox(width: 8), - Text( - isMini ? 'Mini Player Drag & Drop Arranger' : 'Fullscreen Player Drag & Drop Arranger', - style: const TextStyle(color: Colors.white, fontSize: 14.5, fontWeight: FontWeight.w800), + const Text( + 'Fullscreen Player Drag & Drop Arranger', + style: TextStyle(color: Colors.white, fontSize: 14.5, fontWeight: FontWeight.w800), ), ], ), const SizedBox(height: 6), Text( - 'Hold and drag any block to reorder the layout of your ${isMini ? "mini player bar" : "fullscreen player"} in real-time.', + 'Hold and drag any block to reorder the layout of your fullscreen player in real-time.', style: TextStyle(color: Colors.white.withValues(alpha: 0.55), fontSize: 12), ), const SizedBox(height: 16), @@ -1272,11 +1070,7 @@ class _MusicPlayerStudioPageState extends State with Sing physics: const NeverScrollableScrollPhysics(), buildDefaultDragHandles: false, onReorder: (oldIdx, newIdx) { - if (isMini) { - MusicSettings.reorderMiniComponents(oldIdx, newIdx); - } else { - MusicSettings.reorderFullscreenComponents(oldIdx, newIdx); - } + MusicSettings.reorderFullscreenComponents(oldIdx, newIdx); }, children: List.generate(order.length, (index) { final key = order[index]; diff --git a/lib/services/music/music_settings.dart b/lib/services/music/music_settings.dart index 97509a0c..f76d0af8 100644 --- a/lib/services/music/music_settings.dart +++ b/lib/services/music/music_settings.dart @@ -1,17 +1,6 @@ import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; -enum MusicMiniPlayerPreset { - floatingGlassIsland('Floating Glass Island', 'Sleek floating glassmorphic dock with ambient glow and lossless badge'), - compactPill('Compact Neon Capsule', 'Streamlined compact rounded pill with essential playback controls'), - gradientWave('Gradient Wave Dock', 'Dynamic audio frequency gradient wave background with glowing controls'), - minimalistLine('Minimalist Edge Rail', 'Ultra-thin edge-to-edge floating sleek rail'), - customStudio('Custom Drag & Drop Mini Player', 'Your fully customized, arranged and styled mini player'); - - final String label; - final String description; - const MusicMiniPlayerPreset(this.label, this.description); -} enum MusicFullscreenPreset { vinylStudio('Vinyl Turntable Studio', 'Rotating 33rpm vinyl disc with tonearm and warm studio atmosphere'), @@ -85,8 +74,6 @@ abstract final class MusicSettings { static const _keyCardHoverGlow = 'music_card_hover_glow'; // Mini Player Keys - static const _keySelectedMiniPreset = 'music_selected_mini_preset'; - static const _keyComponentOrderMini = 'music_component_order_mini'; // Fullscreen Player Keys static const _keySelectedFullscreenPreset = 'music_selected_fullscreen_preset'; @@ -103,14 +90,6 @@ abstract final class MusicSettings { static final ValueNotifier cardHoverGlow = ValueNotifier(true); // Mini Player Notifiers - static final ValueNotifier selectedMiniPreset = - ValueNotifier(MusicMiniPlayerPreset.floatingGlassIsland); - static final ValueNotifier> componentOrderMini = ValueNotifier>([ - 'artwork', - 'trackInfo', - 'mainControls', - 'extraActions', - ]); // Fullscreen Player Notifiers static final ValueNotifier selectedFullscreenPreset = @@ -142,16 +121,7 @@ abstract final class MusicSettings { enableSpotlight.value = prefs.getBool(_keyEnableSpotlight) ?? true; cardHoverGlow.value = prefs.getBool(_keyCardHoverGlow) ?? true; - final miniPresetStr = prefs.getString(_keySelectedMiniPreset); - selectedMiniPreset.value = MusicMiniPlayerPreset.values.firstWhere( - (p) => p.name == miniPresetStr, - orElse: () => MusicMiniPlayerPreset.floatingGlassIsland, - ); - final miniOrderList = prefs.getStringList(_keyComponentOrderMini); - if (miniOrderList != null && miniOrderList.isNotEmpty) { - componentOrderMini.value = miniOrderList; - } final fullPresetStr = prefs.getString(_keySelectedFullscreenPreset); selectedFullscreenPreset.value = MusicFullscreenPreset.values.firstWhere( @@ -211,12 +181,6 @@ abstract final class MusicSettings { _notify(); } - static Future setSelectedMiniPreset(MusicMiniPlayerPreset preset) async { - selectedMiniPreset.value = preset; - final prefs = await SharedPreferences.getInstance(); - await prefs.setString(_keySelectedMiniPreset, preset.name); - _notify(); - } static Future setSelectedFullscreenPreset(MusicFullscreenPreset preset) async { selectedFullscreenPreset.value = preset; @@ -254,20 +218,6 @@ abstract final class MusicSettings { } - static Future reorderMiniComponents(int oldIndex, int newIndex) async { - var index = newIndex; - if (oldIndex < index) { - index -= 1; - } - final list = List.from(componentOrderMini.value); - final item = list.removeAt(oldIndex); - list.insert(index, item); - componentOrderMini.value = list; - - final prefs = await SharedPreferences.getInstance(); - await prefs.setStringList(_keyComponentOrderMini, list); - _notify(); - } static Future reorderFullscreenComponents(int oldIndex, int newIndex) async { var index = newIndex; @@ -288,8 +238,6 @@ abstract final class MusicSettings { final prefs = await SharedPreferences.getInstance(); await prefs.remove(_keyEnableSpotlight); await prefs.remove(_keyCardHoverGlow); - await prefs.remove(_keySelectedMiniPreset); - await prefs.remove(_keyComponentOrderMini); await prefs.remove(_keySelectedFullscreenPreset); await prefs.remove(_keyCustomSeekbarStyle); await prefs.remove(_keyCustomPlayButtonStyle); @@ -299,8 +247,6 @@ abstract final class MusicSettings { enableSpotlight.value = true; cardHoverGlow.value = true; - selectedMiniPreset.value = MusicMiniPlayerPreset.floatingGlassIsland; - componentOrderMini.value = ['artwork', 'trackInfo', 'mainControls', 'extraActions']; selectedFullscreenPreset.value = MusicFullscreenPreset.vinylStudio; customSeekbarStyle.value = MusicSeekbarStyle.waveformEqualizer; customPlayButtonStyle.value = MusicPlayButtonStyle.liquidGlassNeo; diff --git a/test/services/settings_are_wired_test.dart b/test/services/settings_are_wired_test.dart index 78a5a32f..14a68fa4 100644 --- a/test/services/settings_are_wired_test.dart +++ b/test/services/settings_are_wired_test.dart @@ -19,8 +19,6 @@ const _appliedInOwnFile = { 'changeNotifier', 'audioDelayDefault', // applied in PlayerSettings.applyPreOpenProperties 'hardwareAudioClock', // ditto, since 2026-08-31 - 'selectedMiniPreset', // see ROADMAP: the mini player it configures is gone - 'componentOrderMini', // ditto }; List _notifierNames(String source) => RegExp(