search: change chip functionality +

wip: fix resuming audio handler after stop
This commit is contained in:
Pato05 2024-02-18 18:12:59 +01:00
parent 6816bdc112
commit bb4448731e
No known key found for this signature in database
GPG key ID: F53CA394104BA0CB
7 changed files with 320 additions and 316 deletions

View file

@ -266,12 +266,12 @@ class DeezerAPI {
//Tracks //Tracks
if (uri.pathSegments[0] == 'track') { if (uri.pathSegments[0] == 'track') {
String id = await SpotifyScrapper.convertTrack(spotifyUri); String id = await SpotifyScrapper.convertTrack(spotifyUri);
return DeezerLinkResponse(type: DeezerLinkType.TRACK, id: id); return DeezerLinkResponse(type: DeezerMediaType.track, id: id);
} }
//Albums //Albums
if (uri.pathSegments[0] == 'album') { if (uri.pathSegments[0] == 'album') {
String id = await SpotifyScrapper.convertAlbum(spotifyUri); String id = await SpotifyScrapper.convertAlbum(spotifyUri);
return DeezerLinkResponse(type: DeezerLinkType.ALBUM, id: id); return DeezerLinkResponse(type: DeezerMediaType.album, id: id);
} }
} catch (e) { } catch (e) {
// we don't care about errors apparently // we don't care about errors apparently

View file

@ -1196,22 +1196,31 @@ enum HomePageSectionLayout {
enum RepeatType { NONE, LIST, TRACK } enum RepeatType { NONE, LIST, TRACK }
enum DeezerLinkType { TRACK, ALBUM, ARTIST, PLAYLIST } enum DeezerMediaType {
track,
album,
artist,
playlist,
show,
episode,
}
class DeezerLinkResponse { class DeezerLinkResponse {
DeezerLinkType? type; DeezerMediaType? type;
String? id; String? id;
DeezerLinkResponse({this.type, this.id}); DeezerLinkResponse({this.type, this.id});
//String to DeezerLinkType //String to DeezerLinkType
static typeFromString(String t) { static typeFromString(String t) {
t = t.toLowerCase().trim(); return const <String, DeezerMediaType>{
if (t == 'album') return DeezerLinkType.ALBUM; 'album': DeezerMediaType.album,
if (t == 'artist') return DeezerLinkType.ARTIST; 'artist': DeezerMediaType.artist,
if (t == 'playlist') return DeezerLinkType.PLAYLIST; 'playlist': DeezerMediaType.playlist,
if (t == 'track') return DeezerLinkType.TRACK; 'track': DeezerMediaType.track,
return null; 'show': DeezerMediaType.show,
'episode': DeezerMediaType.episode,
}[t.toLowerCase().trim()];
} }
} }

View file

@ -171,7 +171,7 @@ class AudioPlayerTask extends BaseAudioHandler {
await session.configure(const AudioSessionConfiguration.music()); await session.configure(const AudioSessionConfiguration.music());
_box = await Hive.openLazyBox('playback', path: await Paths.cacheDir()); _box = await Hive.openLazyBox('playback', path: await Paths.cacheDir());
_init(); _init(shouldLoadQueue: false);
await _loadQueueFile(); await _loadQueueFile();
@ -185,7 +185,7 @@ class AudioPlayerTask extends BaseAudioHandler {
} }
} }
Future<void> _init() async { Future<void> _init({required bool shouldLoadQueue}) async {
_player = AudioPlayer( _player = AudioPlayer(
handleInterruptions: !_ignoreInterruptions, handleInterruptions: !_ignoreInterruptions,
androidApplyAudioAttributes: true, androidApplyAudioAttributes: true,
@ -270,12 +270,17 @@ class AudioPlayerTask extends BaseAudioHandler {
.onConnectivityChanged .onConnectivityChanged
.listen(_determineAudioQualityByResult)); .listen(_determineAudioQualityByResult));
} }
if (shouldLoadQueue) {
await _loadQueue(preload: true);
}
} }
Future<void> _maybeResume() { Future<void> _maybeResume() {
if (!_disposed) return Future.value(); if (!_disposed) return Future.value();
return Future.value();
return _init(); _logger.fine('resuming audioHandler.');
return _init(shouldLoadQueue: true);
} }
/// Determine the [AudioQuality] to use according to current connection /// Determine the [AudioQuality] to use according to current connection
@ -340,7 +345,8 @@ class AudioPlayerTask extends BaseAudioHandler {
@override @override
Future play() async { Future play() async {
await _maybeResume(); await _maybeResume();
_player.play(); _logger.fine('playing...');
await _player.play();
//Restore position and queue index on play //Restore position and queue index on play
if (_lastPosition != null) { if (_lastPosition != null) {
_player.seek(_lastPosition, index: _lastQueueIndex); _player.seek(_lastPosition, index: _lastQueueIndex);
@ -773,9 +779,13 @@ class AudioPlayerTask extends BaseAudioHandler {
Future<void> stop() async { Future<void> stop() async {
await _saveQueue(); await _saveQueue();
_disposed = true; _disposed = true;
_player.dispose(); // save state
_lastPosition = _player.position;
_lastQueueIndex = _queueIndex;
await _player.stop();
// await _player.dispose();
for (final subscription in _subscriptions) { for (final subscription in _subscriptions) {
subscription.cancel(); await subscription.cancel();
} }
await super.stop(); await super.stop();
} }
@ -952,27 +962,29 @@ class AudioPlayerTask extends BaseAudioHandler {
if (parsed == null) return; if (parsed == null) return;
switch (parsed.type!) { switch (parsed.type!) {
case DeezerLinkType.TRACK: case DeezerMediaType.track:
final track = await _deezerAPI.track(parsed.id!); final track = await _deezerAPI.track(parsed.id!);
_logger.fine('playing from track mix: ${jsonEncode(track)}'); _logger.fine('playing from track mix: ${jsonEncode(track)}');
unawaited(playerHelper.playSearchMix(track.id, track.title!)); unawaited(playerHelper.playSearchMix(track.id, track.title!));
break; break;
case DeezerLinkType.ALBUM: case DeezerMediaType.album:
final album = await _deezerAPI.album(parsed.id!); final album = await _deezerAPI.album(parsed.id!);
_logger.fine('playing from album: ${album.title}'); _logger.fine('playing from album: ${album.title}');
unawaited(playerHelper.playFromAlbum(album)); unawaited(playerHelper.playFromAlbum(album));
break; break;
case DeezerLinkType.ARTIST: case DeezerMediaType.artist:
final artist = await _deezerAPI.artist(parsed.id!); final artist = await _deezerAPI.artist(parsed.id!);
_logger.fine('playing from artist top: ${artist.name!}'); _logger.fine('playing from artist top: ${artist.name!}');
unawaited( unawaited(
playerHelper.playFromTopTracks(artist.topTracks!, null, artist)); playerHelper.playFromTopTracks(artist.topTracks!, null, artist));
break; break;
case DeezerLinkType.PLAYLIST: case DeezerMediaType.playlist:
final fullPlaylist = await _deezerAPI.playlist(parsed.id!); final fullPlaylist = await _deezerAPI.playlist(parsed.id!);
_logger.fine('playing from playlist: ${fullPlaylist.title!}'); _logger.fine('playing from playlist: ${fullPlaylist.title!}');
unawaited(playerHelper.playFromPlaylist(fullPlaylist)); unawaited(playerHelper.playFromPlaylist(fullPlaylist));
break; break;
default:
break;
} }
} }

View file

@ -71,17 +71,7 @@ class FancyScaffoldState extends State<FancyScaffold>
begin: widget.bottomPanelHeight / MediaQuery.of(context).size.height, begin: widget.bottomPanelHeight / MediaQuery.of(context).size.height,
end: 1.0, end: 1.0,
).animate(dragController); ).animate(dragController);
return WillPopScope( return Stack(
onWillPop: () {
if (statusNotifier.value == AnimationStatus.completed ||
statusNotifier.value == AnimationStatus.reverse) {
dragController.fling(velocity: -1.0);
return Future.value(false);
}
return Future.value(true);
},
child: Stack(
children: [ children: [
Positioned.fill( Positioned.fill(
child: Scaffold( child: Scaffold(
@ -150,10 +140,15 @@ class FancyScaffoldState extends State<FancyScaffold>
builder: (context, state, _) => Stack( builder: (context, state, _) => Stack(
children: [ children: [
if (state != AnimationStatus.dismissed) if (state != AnimationStatus.dismissed)
Positioned.fill( PopScope(
canPop: false,
onPopInvoked: (_) =>
dragController.fling(velocity: -1.0),
child: Positioned.fill(
key: const Key('player_screen'), key: const Key('player_screen'),
child: widget.expandedPanel, child: widget.expandedPanel,
), ),
),
if (state != AnimationStatus.completed) if (state != AnimationStatus.completed)
Positioned( Positioned(
top: 0, top: 0,
@ -176,7 +171,6 @@ class FancyScaffoldState extends State<FancyScaffold>
), ),
), ),
], ],
),
); );
} }

View file

@ -552,8 +552,8 @@ class _SpotifyImporterV2MainState extends State<SpotifyImporterV2Main> {
showDialog( showDialog(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
builder: (context) => WillPopScope( builder: (context) => PopScope(
onWillPop: () => Future.value(false), canPop: false,
child: AlertDialog( child: AlertDialog(
title: Text("Please wait...".i18n), title: Text("Please wait...".i18n),
content: const Row( content: const Row(

View file

@ -26,7 +26,7 @@ FutureOr openScreenByURL(BuildContext context, String url) async {
if (res == null) return; if (res == null) return;
switch (res.type) { switch (res.type) {
case DeezerLinkType.TRACK: case DeezerMediaType.track:
Track t = await deezerAPI.track(res.id!); Track t = await deezerAPI.track(res.id!);
MenuSheet(context).defaultTrackMenu(t, optionsTop: [ MenuSheet(context).defaultTrackMenu(t, optionsTop: [
MenuSheetOption(Text('Play'.i18n), MenuSheetOption(Text('Play'.i18n),
@ -34,15 +34,15 @@ FutureOr openScreenByURL(BuildContext context, String url) async {
onTap: () => playerHelper.playSearchMixDeferred(t)), onTap: () => playerHelper.playSearchMixDeferred(t)),
]); ]);
break; break;
case DeezerLinkType.ALBUM: case DeezerMediaType.album:
Album a = await deezerAPI.album(res.id); Album a = await deezerAPI.album(res.id);
return Navigator.of(context) return Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => AlbumDetails(a))); .push(MaterialPageRoute(builder: (context) => AlbumDetails(a)));
case DeezerLinkType.ARTIST: case DeezerMediaType.artist:
Artist a = await deezerAPI.artist(res.id); Artist a = await deezerAPI.artist(res.id);
return Navigator.of(context) return Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => ArtistDetails(a))); .push(MaterialPageRoute(builder: (context) => ArtistDetails(a)));
case DeezerLinkType.PLAYLIST: case DeezerMediaType.playlist:
Playlist p = await deezerAPI.playlist(res.id); Playlist p = await deezerAPI.playlist(res.id);
if (p.tracks == null || p.tracks!.isEmpty) { if (p.tracks == null || p.tracks!.isEmpty) {
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context)
@ -446,15 +446,9 @@ class SearchResultsScreen extends StatefulWidget {
} }
class _SearchResultsScreenState extends State<SearchResultsScreen> { class _SearchResultsScreenState extends State<SearchResultsScreen> {
final _tracksKey = GlobalKey();
final _albumsKey = GlobalKey();
final _artistsKey = GlobalKey();
final _playlistsKey = GlobalKey();
final _showsKey = GlobalKey();
final _episodesKey = GlobalKey();
SearchResults? _results; SearchResults? _results;
Object? _error; Object? _error;
DeezerMediaType? _page;
Future _search() async { Future _search() async {
try { try {
@ -474,6 +468,24 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
} }
} }
Widget buildListFor(DeezerMediaType page) {
switch (page) {
case DeezerMediaType.track:
return TrackListScreen(_results!.tracks, null);
case DeezerMediaType.album:
return AlbumListScreen(_results!.albums);
case DeezerMediaType.playlist:
return PlaylistListScreen(_results!.playlists);
case DeezerMediaType.episode:
return EpisodeListScreen(_results!.episodes);
case DeezerMediaType.show:
return ShowListScreen(_results!.shows);
case DeezerMediaType.artist:
return const Placeholder();
}
}
Widget buildFromDeezerItem(DeezerMediaItem item) { Widget buildFromDeezerItem(DeezerMediaItem item) {
switch (item) { switch (item) {
case final Track track: case final Track track:
@ -539,62 +551,62 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
ListView(scrollDirection: Axis.horizontal, children: [ ListView(scrollDirection: Axis.horizontal, children: [
if (_results!.tracks != null && if (_results!.tracks != null &&
_results!.tracks!.isNotEmpty) ...[ _results!.tracks!.isNotEmpty) ...[
ActionChip( FilterChip(
elevation: 1.0, elevation: 1.0,
label: Text('Tracks'.i18n), label: Text('Tracks'.i18n),
onPressed: () => Scrollable.ensureVisible( selected: _page == DeezerMediaType.track,
_tracksKey.currentContext!, onSelected: (selected) => setState(() => _page =
duration: const Duration(milliseconds: 500))), selected ? DeezerMediaType.track : null)),
const SizedBox(width: 8.0), const SizedBox(width: 8.0),
], ],
if (_results!.albums != null && if (_results!.albums != null &&
_results!.albums!.isNotEmpty) ...[ _results!.albums!.isNotEmpty) ...[
ActionChip( FilterChip(
elevation: 1.0, elevation: 1.0,
label: Text('Albums'.i18n), label: Text('Albums'.i18n),
onPressed: () => Scrollable.ensureVisible( selected: _page == DeezerMediaType.album,
_albumsKey.currentContext!, onSelected: (selected) => setState(() => _page =
duration: const Duration(milliseconds: 500))), selected ? DeezerMediaType.album : null)),
const SizedBox(width: 8.0),
],
if (_results!.artists != null &&
_results!.artists!.isNotEmpty) ...[
ActionChip(
elevation: 1.0,
label: Text('Artists'.i18n),
onPressed: () => Scrollable.ensureVisible(
_artistsKey.currentContext!,
duration: const Duration(milliseconds: 500))),
const SizedBox(width: 8.0), const SizedBox(width: 8.0),
], ],
// if (_results!.artists != null &&
// _results!.artists!.isNotEmpty) ...[
// FilterChip(
// elevation: 1.0,
// label: Text('Artists'.i18n),
// selected: _page == DeezerMediaType.artist,
// onSelected: (selected) => setState(() => _page =
// selected ? DeezerMediaType.artist : null)),
// const SizedBox(width: 8.0),
// ],
if (_results!.playlists != null && if (_results!.playlists != null &&
_results!.playlists!.isNotEmpty) ...[ _results!.playlists!.isNotEmpty) ...[
ActionChip( FilterChip(
elevation: 1.0, elevation: 1.0,
label: Text('Playlists'.i18n), label: Text('Playlists'.i18n),
onPressed: () => Scrollable.ensureVisible( selected: _page == DeezerMediaType.playlist,
_playlistsKey.currentContext!, onSelected: (selected) => setState(() => _page =
duration: const Duration(milliseconds: 500))), selected ? DeezerMediaType.playlist : null)),
const SizedBox(width: 8.0), const SizedBox(width: 8.0),
], ],
if (_results!.shows != null && if (_results!.shows != null &&
_results!.shows!.isNotEmpty) ...[ _results!.shows!.isNotEmpty) ...[
ActionChip( FilterChip(
elevation: 1.0, elevation: 1.0,
label: Text('Shows'.i18n), label: Text('Shows'.i18n),
onPressed: () => Scrollable.ensureVisible( selected: _page == DeezerMediaType.show,
_showsKey.currentContext!, onSelected: (selected) => setState(() => _page =
duration: const Duration(milliseconds: 500))), selected ? DeezerMediaType.show : null)),
const SizedBox(width: 8.0), const SizedBox(width: 8.0),
], ],
if (_results!.episodes != null && if (_results!.episodes != null &&
_results!.episodes!.isNotEmpty) ...[ _results!.episodes!.isNotEmpty) ...[
ActionChip( FilterChip(
elevation: 1.0, elevation: 1.0,
label: Text('Episodes'.i18n), label: Text('Episodes'.i18n),
onPressed: () => Scrollable.ensureVisible( selected: _page == DeezerMediaType.episode,
_episodesKey.currentContext!, onSelected: (selected) => setState(() => _page =
duration: const Duration(milliseconds: 500))), selected ? DeezerMediaType.episode : null)),
const SizedBox(width: 8.0), const SizedBox(width: 8.0),
], ],
]), ]),
@ -606,7 +618,14 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
? ErrorScreen(message: _error.toString()) ? ErrorScreen(message: _error.toString())
: _results == null : _results == null
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: Builder( : PopScope(
canPop: _page == null,
onPopInvoked: (didPop) {
if (_page != null) {
setState(() => _page = null);
}
},
child: Builder(
builder: (context) { builder: (context) {
final results = _results!; final results = _results!;
if (results.empty) { if (results.empty) {
@ -624,9 +643,11 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
); );
} }
return SingleChildScrollView( if (_page != null) {
child: Column( return buildListFor(_page!);
crossAxisAlignment: CrossAxisAlignment.start, }
return ListView(
children: <Widget>[ children: <Widget>[
if (results.topResult != null && if (results.topResult != null &&
results.topResult!.isNotEmpty) ...[ results.topResult!.isNotEmpty) ...[
@ -657,7 +678,6 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
if (results.tracks != null && if (results.tracks != null &&
results.tracks!.isNotEmpty) ...[ results.tracks!.isNotEmpty) ...[
Padding( Padding(
key: _tracksKey,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 4.0), horizontal: 16.0, vertical: 4.0),
child: Text( child: Text(
@ -679,11 +699,8 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
}), }),
ListTile( ListTile(
title: Text('Show all tracks'.i18n), title: Text('Show all tracks'.i18n),
onTap: () { onTap: () => setState(
Navigator.of(context).pushRoute( () => _page = DeezerMediaType.track),
builder: (context) => TrackListScreen(
results.tracks, null));
},
), ),
const FreezerDivider(), const FreezerDivider(),
], ],
@ -691,7 +708,6 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
results.albums!.isNotEmpty) ...[ results.albums!.isNotEmpty) ...[
const SizedBox(height: 8.0), const SizedBox(height: 8.0),
Padding( Padding(
key: _albumsKey,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 4.0), horizontal: 16.0, vertical: 4.0),
child: Text( child: Text(
@ -715,11 +731,8 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
}), }),
ListTile( ListTile(
title: Text('Show all albums'.i18n), title: Text('Show all albums'.i18n),
onTap: () { onTap: () => setState(
Navigator.of(context).pushRoute( () => _page = DeezerMediaType.album),
builder: (context) =>
AlbumListScreen(results.albums));
},
), ),
const FreezerDivider() const FreezerDivider()
], ],
@ -727,7 +740,6 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
results.artists!.isNotEmpty) ...[ results.artists!.isNotEmpty) ...[
const SizedBox(height: 8.0), const SizedBox(height: 8.0),
Padding( Padding(
key: _artistsKey,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 4.0, horizontal: 16.0), vertical: 4.0, horizontal: 16.0),
child: Text( child: Text(
@ -778,7 +790,6 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
results.playlists!.isNotEmpty) ...[ results.playlists!.isNotEmpty) ...[
const SizedBox(height: 8.0), const SizedBox(height: 8.0),
Padding( Padding(
key: _playlistsKey,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 4.0, horizontal: 16.0), vertical: 4.0, horizontal: 16.0),
child: Text( child: Text(
@ -808,12 +819,8 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
), ),
ListTile( ListTile(
title: Text('Show all playlists'.i18n), title: Text('Show all playlists'.i18n),
onTap: () { onTap: () => setState(
Navigator.of(context).pushRoute( () => _page = DeezerMediaType.playlist),
builder: (context) =>
SearchResultPlaylists(
results.playlists));
},
), ),
const FreezerDivider(), const FreezerDivider(),
], ],
@ -821,7 +828,6 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
results.shows!.isNotEmpty) ...[ results.shows!.isNotEmpty) ...[
const SizedBox(height: 8.0), const SizedBox(height: 8.0),
Padding( Padding(
key: _showsKey,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 4.0, horizontal: 16.0), vertical: 4.0, horizontal: 16.0),
child: Text( child: Text(
@ -843,11 +849,8 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
), ),
ListTile( ListTile(
title: Text('Show all shows'.i18n), title: Text('Show all shows'.i18n),
onTap: () { onTap: () => setState(
Navigator.of(context).pushRoute( () => _page = DeezerMediaType.show),
builder: (context) =>
ShowListScreen(results.shows));
},
), ),
const FreezerDivider() const FreezerDivider()
], ],
@ -855,7 +858,6 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
results.episodes!.isNotEmpty) ...[ results.episodes!.isNotEmpty) ...[
const SizedBox(height: 8.0), const SizedBox(height: 8.0),
Padding( Padding(
key: _episodesKey,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 4.0, horizontal: 16.0), vertical: 4.0, horizontal: 16.0),
child: Text( child: Text(
@ -894,16 +896,14 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
), ),
ListTile( ListTile(
title: Text('Show all episodes'.i18n), title: Text('Show all episodes'.i18n),
onTap: () { onTap: () => setState(
Navigator.of(context).pushRoute( () => _page = DeezerMediaType.episode),
builder: (context) => EpisodeListScreen( )
results.episodes));
})
] ]
], ],
),
); );
}, },
),
)); ));
} }
} }
@ -917,9 +917,7 @@ class TrackListScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return ListView.builder(
appBar: AppBar(title: Text('Tracks'.i18n)),
body: ListView.builder(
itemCount: tracks!.length, itemCount: tracks!.length,
itemBuilder: (BuildContext context, int i) { itemBuilder: (BuildContext context, int i) {
Track t = tracks![i]; Track t = tracks![i];
@ -939,7 +937,6 @@ class TrackListScreen extends StatelessWidget {
}, },
); );
}, },
),
); );
} }
} }
@ -951,9 +948,7 @@ class AlbumListScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return ListView.builder(
appBar: AppBar(title: Text('Albums'.i18n)),
body: ListView.builder(
itemCount: albums!.length, itemCount: albums!.length,
itemBuilder: (context, i) { itemBuilder: (context, i) {
Album? a = albums![i]; Album? a = albums![i];
@ -969,20 +964,17 @@ class AlbumListScreen extends StatelessWidget {
}, },
); );
}, },
),
); );
} }
} }
class SearchResultPlaylists extends StatelessWidget { class PlaylistListScreen extends StatelessWidget {
final List<Playlist?>? playlists; final List<Playlist?>? playlists;
const SearchResultPlaylists(this.playlists, {super.key}); const PlaylistListScreen(this.playlists, {super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return ListView.builder(
appBar: AppBar(title: Text('Playlists'.i18n)),
body: ListView.builder(
itemCount: playlists!.length, itemCount: playlists!.length,
itemBuilder: (context, i) { itemBuilder: (context, i) {
Playlist? p = playlists![i]; Playlist? p = playlists![i];
@ -998,7 +990,6 @@ class SearchResultPlaylists extends StatelessWidget {
}, },
); );
}, },
),
); );
} }
} }
@ -1009,9 +1000,7 @@ class ShowListScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return ListView.builder(
appBar: AppBar(title: Text('Shows'.i18n)),
body: ListView.builder(
itemCount: shows!.length, itemCount: shows!.length,
itemBuilder: (context, i) { itemBuilder: (context, i) {
Show s = shows![i]; Show s = shows![i];
@ -1023,7 +1012,6 @@ class ShowListScreen extends StatelessWidget {
}, },
); );
}, },
),
); );
} }
} }
@ -1034,9 +1022,7 @@ class EpisodeListScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return ListView.builder(
appBar: AppBar(title: Text('Episodes'.i18n)),
body: ListView.builder(
itemCount: episodes!.length, itemCount: episodes!.length,
itemBuilder: (context, i) { itemBuilder: (context, i) {
ShowEpisode e = episodes![i]; ShowEpisode e = episodes![i];
@ -1061,6 +1047,6 @@ class EpisodeListScreen extends StatelessWidget {
}, },
); );
}, },
)); );
} }
} }

View file

@ -1528,6 +1528,9 @@ class _GeneralSettingsState extends State<GeneralSettings> {
ScaffoldMessenger.of(context).snack('Copied'.i18n); ScaffoldMessenger.of(context).snack('Copied'.i18n);
}, },
), ),
ListTile(
title: const Text('DEBUG: stop audioHandler'),
onTap: () => audioHandler.stop()),
], ],
), ),
); );