@@ -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