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,112 +71,106 @@ 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: () { children: [
if (statusNotifier.value == AnimationStatus.completed || Positioned.fill(
statusNotifier.value == AnimationStatus.reverse) { child: Scaffold(
dragController.fling(velocity: -1.0); body: widget.navigationRail != null
return Future.value(false); ? Row(children: [
} widget.navigationRail!,
const VerticalDivider(
return Future.value(true); indent: 0.0,
}, endIndent: 0.0,
child: Stack( width: 2.0,
children: [
Positioned.fill(
child: Scaffold(
body: widget.navigationRail != null
? Row(children: [
widget.navigationRail!,
const VerticalDivider(
indent: 0.0,
endIndent: 0.0,
width: 2.0,
),
Expanded(child: widget.body)
])
: widget.body,
drawer: widget.drawer,
bottomNavigationBar: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: widget.bottomPanelHeight),
if (widget.bottomNavigationBar != null)
SizeTransition(
axisAlignment: -1.0,
sizeFactor:
Tween(begin: 1.0, end: 0.0).animate(sizeAnimation),
child: widget.bottomNavigationBar,
), ),
], Expanded(child: widget.body)
), ])
: widget.body,
drawer: widget.drawer,
bottomNavigationBar: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: widget.bottomPanelHeight),
if (widget.bottomNavigationBar != null)
SizeTransition(
axisAlignment: -1.0,
sizeFactor:
Tween(begin: 1.0, end: 0.0).animate(sizeAnimation),
child: widget.bottomNavigationBar,
),
],
), ),
), ),
Positioned( ),
bottom: 0, Positioned(
left: 0, bottom: 0,
right: 0, left: 0,
child: AnimatedBuilder( right: 0,
animation: sizeAnimation, child: AnimatedBuilder(
builder: (context, child) { animation: sizeAnimation,
final x = 1.0 - sizeAnimation.value; builder: (context, child) {
return Padding( final x = 1.0 - sizeAnimation.value;
padding: EdgeInsets.only( return Padding(
bottom: (defaultBottomPadding /*+ 8.0*/) * x, padding: EdgeInsets.only(
//right: 8.0 * x, bottom: (defaultBottomPadding /*+ 8.0*/) * x,
//left: 8.0 * x, //right: 8.0 * x,
), //left: 8.0 * x,
),
child: child,
);
},
child: ValueListenableBuilder(
valueListenable: statusNotifier,
builder: (context, state, child) {
return GestureDetector(
onVerticalDragEnd: _onVerticalDragEnd,
onVerticalDragUpdate: _onVerticalDragUpdate,
child: child, child: child,
); );
}, },
child: ValueListenableBuilder( child: SizeTransition(
valueListenable: statusNotifier, sizeFactor: sizeAnimation,
builder: (context, state, child) { axisAlignment: -1.0,
return GestureDetector( axis: Axis.vertical,
onVerticalDragEnd: _onVerticalDragEnd, child: SizedBox(
onVerticalDragUpdate: _onVerticalDragUpdate, height: screenHeight,
child: child, width: MediaQuery.of(context).size.width,
); child: ValueListenableBuilder(
}, valueListenable: statusNotifier,
child: SizeTransition( builder: (context, state, _) => Stack(
sizeFactor: sizeAnimation, children: [
axisAlignment: -1.0, if (state != AnimationStatus.dismissed)
axis: Axis.vertical, PopScope(
child: SizedBox( canPop: false,
height: screenHeight, onPopInvoked: (_) =>
width: MediaQuery.of(context).size.width, dragController.fling(velocity: -1.0),
child: ValueListenableBuilder( child: Positioned.fill(
valueListenable: statusNotifier,
builder: (context, state, _) => Stack(
children: [
if (state != AnimationStatus.dismissed)
Positioned.fill(
key: const Key('player_screen'), key: const Key('player_screen'),
child: widget.expandedPanel, child: widget.expandedPanel,
), ),
if (state != AnimationStatus.completed) ),
Positioned( if (state != AnimationStatus.completed)
top: 0, Positioned(
right: 0, top: 0,
left: 0, right: 0,
key: const Key('player_bar'), left: 0,
child: FadeTransition( key: const Key('player_bar'),
opacity: Tween(begin: 1.0, end: 0.0) child: FadeTransition(
.animate(dragController), opacity: Tween(begin: 1.0, end: 0.0)
child: SizedBox( .animate(dragController),
height: widget.bottomPanelHeight, child: SizedBox(
child: widget.bottomPanel), height: widget.bottomPanelHeight,
), child: widget.bottomPanel),
), ),
], ),
), ],
), ),
)), ),
), )),
), ),
), ),
], ),
), ],
); );
} }

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,27 +618,36 @@ 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(
builder: (context) { canPop: _page == null,
final results = _results!; onPopInvoked: (didPop) {
if (results.empty) { if (_page != null) {
return Center( setState(() => _page = null);
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
Icons.warning,
size: 64.sp,
),
Text('No results!'.i18n)
],
),
);
} }
},
child: Builder(
builder: (context) {
final results = _results!;
if (results.empty) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
Icons.warning,
size: 64.sp,
),
Text('No results!'.i18n)
],
),
);
}
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(
@ -893,17 +895,15 @@ 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,29 +917,26 @@ 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)), itemCount: tracks!.length,
body: ListView.builder( itemBuilder: (BuildContext context, int i) {
itemCount: tracks!.length, Track t = tracks![i];
itemBuilder: (BuildContext context, int i) { return TrackTile.fromTrack(
Track t = tracks![i]; t,
return TrackTile.fromTrack( onTap: () {
t, if (queueSource == null) {
onTap: () { playerHelper.playSearchMixDeferred(t);
if (queueSource == null) { return;
playerHelper.playSearchMixDeferred(t); }
return;
}
playerHelper.playFromTrackList(tracks!, t.id, queueSource!); playerHelper.playFromTrackList(tracks!, t.id, queueSource!);
}, },
onSecondary: (details) { onSecondary: (details) {
MenuSheet m = MenuSheet(context); MenuSheet m = MenuSheet(context);
m.defaultTrackMenu(t, details: details); m.defaultTrackMenu(t, details: details);
}, },
); );
}, },
),
); );
} }
} }
@ -951,54 +948,48 @@ 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)), itemCount: albums!.length,
body: ListView.builder( itemBuilder: (context, i) {
itemCount: albums!.length, Album? a = albums![i];
itemBuilder: (context, i) { return AlbumTile(
Album? a = albums![i]; a,
return AlbumTile( onTap: () {
a, Navigator.of(context)
onTap: () { .pushRoute(builder: (context) => AlbumDetails(a));
Navigator.of(context) },
.pushRoute(builder: (context) => AlbumDetails(a)); onSecondary: (details) {
}, MenuSheet m = MenuSheet(context);
onSecondary: (details) { m.defaultAlbumMenu(a!, details: details);
MenuSheet m = MenuSheet(context); },
m.defaultAlbumMenu(a!, details: details); );
}, },
);
},
),
); );
} }
} }
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)), itemCount: playlists!.length,
body: ListView.builder( itemBuilder: (context, i) {
itemCount: playlists!.length, Playlist? p = playlists![i];
itemBuilder: (context, i) { return PlaylistTile(
Playlist? p = playlists![i]; p,
return PlaylistTile( onTap: () {
p, Navigator.of(context)
onTap: () { .pushRoute(builder: (context) => PlaylistDetails(p));
Navigator.of(context) },
.pushRoute(builder: (context) => PlaylistDetails(p)); onSecondary: (details) {
}, MenuSheet m = MenuSheet(context);
onSecondary: (details) { m.defaultPlaylistMenu(p!, details: details);
MenuSheet m = MenuSheet(context); },
m.defaultPlaylistMenu(p!, details: details); );
}, },
);
},
),
); );
} }
} }
@ -1009,21 +1000,18 @@ 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)), itemCount: shows!.length,
body: ListView.builder( itemBuilder: (context, i) {
itemCount: shows!.length, Show s = shows![i];
itemBuilder: (context, i) { return ShowTile(
Show s = shows![i]; s,
return ShowTile( onTap: () {
s, Navigator.of(context)
onTap: () { .push(MaterialPageRoute(builder: (context) => ShowScreen(s)));
Navigator.of(context) },
.push(MaterialPageRoute(builder: (context) => ShowScreen(s))); );
}, },
);
},
),
); );
} }
} }
@ -1034,33 +1022,31 @@ 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)), itemCount: episodes!.length,
body: ListView.builder( itemBuilder: (context, i) {
itemCount: episodes!.length, ShowEpisode e = episodes![i];
itemBuilder: (context, i) { return ShowEpisodeTile(
ShowEpisode e = episodes![i]; e,
return ShowEpisodeTile( trailing: IconButton(
e, icon: Icon(
trailing: IconButton( Icons.more_vert,
icon: Icon( semanticLabel: "Options".i18n,
Icons.more_vert, ),
semanticLabel: "Options".i18n, onPressed: () {
), MenuSheet m = MenuSheet(context);
onPressed: () { m.defaultShowEpisodeMenu(e.show!, e);
MenuSheet m = MenuSheet(context); },
m.defaultShowEpisodeMenu(e.show!, e); ),
}, onTap: () async {
), //Load entire show, then play
onTap: () async { List<ShowEpisode> episodes =
//Load entire show, then play (await deezerAPI.allShowEpisodes(e.show!.id))!;
List<ShowEpisode> episodes = await playerHelper.playShowEpisode(e.show!, episodes,
(await deezerAPI.allShowEpisodes(e.show!.id))!; index: episodes.indexWhere((ep) => e.id == ep.id));
await playerHelper.playShowEpisode(e.show!, episodes,
index: episodes.indexWhere((ep) => e.id == ep.id));
},
);
}, },
)); );
},
);
} }
} }

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()),
], ],
), ),
); );