Skip to content
This repository was archived by the owner on Sep 7, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 0 additions & 1 deletion docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
242 changes: 18 additions & 224 deletions lib/pages/settings/appearance/music_player_studio_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ class MusicPlayerStudioPage extends StatefulWidget {
}

class _MusicPlayerStudioPageState extends State<MusicPlayerStudioPage> with SingleTickerProviderStateMixin {
// Studio Player Mode: 0 = Fullscreen Player Studio, 1 = Mini Player Studio
int _studioPlayerTarget = 0;

// Live Studio Preview State
bool _previewIsPlaying = true;
Expand Down Expand Up @@ -120,62 +118,6 @@ class _MusicPlayerStudioPageState extends State<MusicPlayerStudioPage> 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)
Expand Down Expand Up @@ -346,25 +288,14 @@ class _MusicPlayerStudioPageState extends State<MusicPlayerStudioPage> 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,
),
);
}

// ═══════════════════════════════════════════════════════════════
Expand Down Expand Up @@ -395,15 +326,13 @@ class _MusicPlayerStudioPageState extends State<MusicPlayerStudioPage> 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),
),
),
),
Expand Down Expand Up @@ -655,136 +584,6 @@ class _MusicPlayerStudioPageState extends State<MusicPlayerStudioPage> 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(
Expand Down Expand Up @@ -1220,17 +1019,16 @@ class _MusicPlayerStudioPageState extends State<MusicPlayerStudioPage> 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 = {
Expand All @@ -1245,7 +1043,7 @@ class _MusicPlayerStudioPageState extends State<MusicPlayerStudioPage> with Sing
};

return ValueListenableBuilder<List<String>>(
valueListenable: isMini ? MusicSettings.componentOrderMini : MusicSettings.componentOrderFullscreen,
valueListenable: MusicSettings.componentOrderFullscreen,
builder: (context, order, _) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
Expand All @@ -1254,15 +1052,15 @@ class _MusicPlayerStudioPageState extends State<MusicPlayerStudioPage> 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),
Expand All @@ -1272,11 +1070,7 @@ class _MusicPlayerStudioPageState extends State<MusicPlayerStudioPage> 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];
Expand Down
Loading
Loading