Skip to content

Commit f8c715f

Browse files
committed
refactor: redesign playlists folder page
1 parent d47c466 commit f8c715f

File tree

1 file changed

+171
-91
lines changed

1 file changed

+171
-91
lines changed

lib/screens/playlist_folder_page.dart

Lines changed: 171 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -59,89 +59,192 @@ class _PlaylistFolderPageState extends State<PlaylistFolderPage> {
5959
builder: (context, _, __) {
6060
final playlists = getPlaylistsInFolder(widget.folderId);
6161
return Scaffold(
62-
appBar: AppBar(
63-
title: Text(_folderName),
64-
actions: [
65-
PopupMenuButton<String>(
66-
shape: RoundedRectangleBorder(
67-
borderRadius: BorderRadius.circular(12),
62+
body: CustomScrollView(
63+
slivers: [
64+
SliverAppBar(
65+
pinned: true,
66+
expandedHeight: 300,
67+
flexibleSpace: FlexibleSpaceBar(
68+
collapseMode: CollapseMode.pin,
69+
background: _buildHeader(context, playlists.length),
6870
),
69-
color: Theme.of(context).colorScheme.surface,
70-
itemBuilder: (context) => [
71-
PopupMenuItem<String>(
72-
value: 'add',
73-
child: Row(
74-
mainAxisSize: MainAxisSize.min,
75-
children: [
76-
Icon(
77-
FluentIcons.add_24_regular,
78-
color: Theme.of(context).colorScheme.primary,
79-
size: 18,
80-
),
81-
const SizedBox(width: 10),
82-
Text(context.l10n!.addPlaylist),
83-
],
71+
actions: [
72+
PopupMenuButton<String>(
73+
shape: RoundedRectangleBorder(
74+
borderRadius: BorderRadius.circular(12),
8475
),
85-
),
86-
PopupMenuItem<String>(
87-
value: 'rename',
88-
child: Row(
89-
mainAxisSize: MainAxisSize.min,
90-
children: [
91-
Icon(
92-
FluentIcons.edit_24_regular,
93-
color: Theme.of(context).colorScheme.primary,
94-
size: 18,
76+
color: Theme.of(context).colorScheme.surface,
77+
itemBuilder: (context) => [
78+
PopupMenuItem<String>(
79+
value: 'add',
80+
child: Row(
81+
mainAxisSize: MainAxisSize.min,
82+
children: [
83+
Icon(
84+
FluentIcons.add_24_regular,
85+
color: Theme.of(context).colorScheme.primary,
86+
size: 18,
87+
),
88+
const SizedBox(width: 10),
89+
Text(context.l10n!.addPlaylist),
90+
],
9591
),
96-
const SizedBox(width: 10),
97-
Text(context.l10n!.editFolder),
98-
],
99-
),
100-
),
101-
PopupMenuItem<String>(
102-
value: 'delete',
103-
child: Row(
104-
mainAxisSize: MainAxisSize.min,
105-
children: [
106-
Icon(
107-
FluentIcons.delete_24_regular,
108-
color: Theme.of(context).colorScheme.error,
109-
size: 18,
92+
),
93+
PopupMenuItem<String>(
94+
value: 'rename',
95+
child: Row(
96+
mainAxisSize: MainAxisSize.min,
97+
children: [
98+
Icon(
99+
FluentIcons.edit_24_regular,
100+
color: Theme.of(context).colorScheme.primary,
101+
size: 18,
102+
),
103+
const SizedBox(width: 10),
104+
Text(context.l10n!.editFolder),
105+
],
110106
),
111-
const SizedBox(width: 10),
112-
Text(
113-
context.l10n!.deleteFolder,
114-
style: TextStyle(
115-
color: Theme.of(context).colorScheme.error,
116-
),
107+
),
108+
PopupMenuItem<String>(
109+
value: 'delete',
110+
child: Row(
111+
mainAxisSize: MainAxisSize.min,
112+
children: [
113+
Icon(
114+
FluentIcons.delete_24_regular,
115+
color: Theme.of(context).colorScheme.error,
116+
size: 18,
117+
),
118+
const SizedBox(width: 10),
119+
Text(
120+
context.l10n!.deleteFolder,
121+
style: TextStyle(
122+
color: Theme.of(context).colorScheme.error,
123+
),
124+
),
125+
],
117126
),
118-
],
119-
),
127+
),
128+
],
129+
onSelected: (value) {
130+
if (value == 'add') {
131+
_showAddPlaylistDialog();
132+
} else if (value == 'rename') {
133+
_showRenameFolderDialog();
134+
} else if (value == 'delete') {
135+
_showDeleteFolderDialog();
136+
}
137+
},
120138
),
121139
],
122-
onSelected: (value) {
123-
if (value == 'add') {
124-
_showAddPlaylistDialog();
125-
} else if (value == 'rename') {
126-
_showRenameFolderDialog();
127-
} else if (value == 'delete') {
128-
_showDeleteFolderDialog();
129-
}
130-
},
131140
),
141+
if (playlists.isEmpty)
142+
SliverFillRemaining(child: _buildEmptyState())
143+
else
144+
SliverPadding(
145+
padding: commonListViewBottomPadding,
146+
sliver: SliverList.builder(
147+
itemCount: playlists.length,
148+
itemBuilder: (context, index) {
149+
final playlist = playlists[index];
150+
final borderRadius = getItemBorderRadius(
151+
index,
152+
playlists.length,
153+
);
154+
return PlaylistBar(
155+
key: ValueKey(playlist['ytid']),
156+
playlist['title'],
157+
playlistId: playlist['ytid'],
158+
playlistArtwork: playlist['image'],
159+
playlistData: playlist,
160+
onDelete: () => _showRemovePlaylistDialog(playlist),
161+
borderRadius: borderRadius,
162+
);
163+
},
164+
),
165+
),
132166
],
133167
),
134-
body: playlists.isEmpty
135-
? _buildEmptyState()
136-
: SingleChildScrollView(
137-
padding: commonSingleChildScrollViewPadding,
138-
child: Column(children: [_buildPlaylistList(playlists)]),
139-
),
140168
);
141169
},
142170
);
143171
}
144172

173+
Widget _buildHeader(BuildContext context, int playlistCount) {
174+
final colorScheme = Theme.of(context).colorScheme;
175+
final theme = Theme.of(context);
176+
return Container(
177+
alignment: Alignment.center,
178+
padding: const EdgeInsets.fromLTRB(24, 20, 24, 16),
179+
child: Column(
180+
mainAxisAlignment: MainAxisAlignment.center,
181+
children: [
182+
ClipPath(
183+
clipper: const ShapeBorderClipper(
184+
shape: StarBorder(
185+
points: 8,
186+
pointRounding: 0.8,
187+
valleyRounding: 0.2,
188+
innerRadiusRatio: 0.6,
189+
),
190+
),
191+
child: Container(
192+
width: 130,
193+
height: 130,
194+
color: colorScheme.surfaceContainerHighest,
195+
child: Icon(
196+
FluentIcons.folder_24_filled,
197+
size: 64,
198+
color: colorScheme.onSurfaceVariant,
199+
),
200+
),
201+
),
202+
const SizedBox(height: 20),
203+
Text(
204+
_folderName,
205+
style: theme.textTheme.headlineSmall?.copyWith(
206+
fontWeight: FontWeight.w700,
207+
color: colorScheme.onSurface,
208+
letterSpacing: -0.3,
209+
),
210+
overflow: TextOverflow.ellipsis,
211+
maxLines: 2,
212+
textAlign: TextAlign.center,
213+
),
214+
const SizedBox(height: 10),
215+
Container(
216+
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 7),
217+
decoration: BoxDecoration(
218+
color: colorScheme.secondaryContainer,
219+
borderRadius: BorderRadius.circular(20),
220+
),
221+
child: Row(
222+
mainAxisSize: MainAxisSize.min,
223+
children: [
224+
Icon(
225+
FluentIcons.music_note_2_24_regular,
226+
size: 14,
227+
color: colorScheme.onSecondaryContainer,
228+
),
229+
const SizedBox(width: 6),
230+
Text(
231+
playlistCount == 1
232+
? '1 ${context.l10n!.playlist.toLowerCase()}'
233+
: '$playlistCount ${context.l10n!.playlists.toLowerCase()}',
234+
style: theme.textTheme.labelMedium?.copyWith(
235+
color: colorScheme.onSecondaryContainer,
236+
fontWeight: FontWeight.w600,
237+
letterSpacing: 0.2,
238+
),
239+
),
240+
],
241+
),
242+
),
243+
],
244+
),
245+
);
246+
}
247+
145248
Widget _buildEmptyState() {
146249
return Center(
147250
child: Padding(
@@ -169,29 +272,6 @@ class _PlaylistFolderPageState extends State<PlaylistFolderPage> {
169272
);
170273
}
171274

172-
Widget _buildPlaylistList(List<Map> playlists) {
173-
return ListView.builder(
174-
shrinkWrap: true,
175-
physics: const NeverScrollableScrollPhysics(),
176-
itemCount: playlists.length,
177-
padding: commonListViewBottomPadding,
178-
itemBuilder: (context, index) {
179-
final playlist = playlists[index];
180-
final borderRadius = getItemBorderRadius(index, playlists.length);
181-
182-
return PlaylistBar(
183-
key: ValueKey(playlist['ytid']),
184-
playlist['title'],
185-
playlistId: playlist['ytid'],
186-
playlistArtwork: playlist['image'],
187-
playlistData: playlist,
188-
onDelete: () => _showRemovePlaylistDialog(playlist),
189-
borderRadius: borderRadius,
190-
);
191-
},
192-
);
193-
}
194-
195275
Future<void> _showAddPlaylistDialog() async {
196276
final customCandidates = getPlaylistsNotInFolders();
197277
final youtubeCandidates = await getUserPlaylistsNotInFolders();

0 commit comments

Comments
 (0)