diff --git a/lib/api/deezer.dart b/lib/api/deezer.dart index 7511ed8..d633963 100644 --- a/lib/api/deezer.dart +++ b/lib/api/deezer.dart @@ -266,12 +266,12 @@ class DeezerAPI { //Tracks if (uri.pathSegments[0] == 'track') { String id = await SpotifyScrapper.convertTrack(spotifyUri); - return DeezerLinkResponse(type: DeezerLinkType.TRACK, id: id); + return DeezerLinkResponse(type: DeezerMediaType.track, id: id); } //Albums if (uri.pathSegments[0] == 'album') { String id = await SpotifyScrapper.convertAlbum(spotifyUri); - return DeezerLinkResponse(type: DeezerLinkType.ALBUM, id: id); + return DeezerLinkResponse(type: DeezerMediaType.album, id: id); } } catch (e) { // we don't care about errors apparently diff --git a/lib/api/definitions.dart b/lib/api/definitions.dart index cbee587..d9fa8c5 100644 --- a/lib/api/definitions.dart +++ b/lib/api/definitions.dart @@ -1196,22 +1196,31 @@ enum HomePageSectionLayout { enum RepeatType { NONE, LIST, TRACK } -enum DeezerLinkType { TRACK, ALBUM, ARTIST, PLAYLIST } +enum DeezerMediaType { + track, + album, + artist, + playlist, + show, + episode, +} class DeezerLinkResponse { - DeezerLinkType? type; + DeezerMediaType? type; String? id; DeezerLinkResponse({this.type, this.id}); //String to DeezerLinkType static typeFromString(String t) { - t = t.toLowerCase().trim(); - if (t == 'album') return DeezerLinkType.ALBUM; - if (t == 'artist') return DeezerLinkType.ARTIST; - if (t == 'playlist') return DeezerLinkType.PLAYLIST; - if (t == 'track') return DeezerLinkType.TRACK; - return null; + return const { + 'album': DeezerMediaType.album, + 'artist': DeezerMediaType.artist, + 'playlist': DeezerMediaType.playlist, + 'track': DeezerMediaType.track, + 'show': DeezerMediaType.show, + 'episode': DeezerMediaType.episode, + }[t.toLowerCase().trim()]; } } diff --git a/lib/api/player/audio_handler.dart b/lib/api/player/audio_handler.dart index d889c9c..0b664db 100644 --- a/lib/api/player/audio_handler.dart +++ b/lib/api/player/audio_handler.dart @@ -171,7 +171,7 @@ class AudioPlayerTask extends BaseAudioHandler { await session.configure(const AudioSessionConfiguration.music()); _box = await Hive.openLazyBox('playback', path: await Paths.cacheDir()); - _init(); + _init(shouldLoadQueue: false); await _loadQueueFile(); @@ -185,7 +185,7 @@ class AudioPlayerTask extends BaseAudioHandler { } } - Future _init() async { + Future _init({required bool shouldLoadQueue}) async { _player = AudioPlayer( handleInterruptions: !_ignoreInterruptions, androidApplyAudioAttributes: true, @@ -270,12 +270,17 @@ class AudioPlayerTask extends BaseAudioHandler { .onConnectivityChanged .listen(_determineAudioQualityByResult)); } + + if (shouldLoadQueue) { + await _loadQueue(preload: true); + } } Future _maybeResume() { if (!_disposed) return Future.value(); - - return _init(); + return Future.value(); + _logger.fine('resuming audioHandler.'); + return _init(shouldLoadQueue: true); } /// Determine the [AudioQuality] to use according to current connection @@ -340,7 +345,8 @@ class AudioPlayerTask extends BaseAudioHandler { @override Future play() async { await _maybeResume(); - _player.play(); + _logger.fine('playing...'); + await _player.play(); //Restore position and queue index on play if (_lastPosition != null) { _player.seek(_lastPosition, index: _lastQueueIndex); @@ -773,9 +779,13 @@ class AudioPlayerTask extends BaseAudioHandler { Future stop() async { await _saveQueue(); _disposed = true; - _player.dispose(); + // save state + _lastPosition = _player.position; + _lastQueueIndex = _queueIndex; + await _player.stop(); + // await _player.dispose(); for (final subscription in _subscriptions) { - subscription.cancel(); + await subscription.cancel(); } await super.stop(); } @@ -952,27 +962,29 @@ class AudioPlayerTask extends BaseAudioHandler { if (parsed == null) return; switch (parsed.type!) { - case DeezerLinkType.TRACK: + case DeezerMediaType.track: final track = await _deezerAPI.track(parsed.id!); _logger.fine('playing from track mix: ${jsonEncode(track)}'); unawaited(playerHelper.playSearchMix(track.id, track.title!)); break; - case DeezerLinkType.ALBUM: + case DeezerMediaType.album: final album = await _deezerAPI.album(parsed.id!); _logger.fine('playing from album: ${album.title}'); unawaited(playerHelper.playFromAlbum(album)); break; - case DeezerLinkType.ARTIST: + case DeezerMediaType.artist: final artist = await _deezerAPI.artist(parsed.id!); _logger.fine('playing from artist top: ${artist.name!}'); unawaited( playerHelper.playFromTopTracks(artist.topTracks!, null, artist)); break; - case DeezerLinkType.PLAYLIST: + case DeezerMediaType.playlist: final fullPlaylist = await _deezerAPI.playlist(parsed.id!); _logger.fine('playing from playlist: ${fullPlaylist.title!}'); unawaited(playerHelper.playFromPlaylist(fullPlaylist)); break; + default: + break; } } diff --git a/lib/ui/fancy_scaffold.dart b/lib/ui/fancy_scaffold.dart index 6c2bdc4..93a8d95 100644 --- a/lib/ui/fancy_scaffold.dart +++ b/lib/ui/fancy_scaffold.dart @@ -71,112 +71,106 @@ class FancyScaffoldState extends State begin: widget.bottomPanelHeight / MediaQuery.of(context).size.height, end: 1.0, ).animate(dragController); - return WillPopScope( - 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: [ - 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, + return Stack( + 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, + ), + ], ), ), - Positioned( - bottom: 0, - left: 0, - right: 0, - child: AnimatedBuilder( - animation: sizeAnimation, - builder: (context, child) { - final x = 1.0 - sizeAnimation.value; - return Padding( - padding: EdgeInsets.only( - bottom: (defaultBottomPadding /*+ 8.0*/) * x, - //right: 8.0 * x, - //left: 8.0 * x, - ), + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: AnimatedBuilder( + animation: sizeAnimation, + builder: (context, child) { + final x = 1.0 - sizeAnimation.value; + return Padding( + padding: EdgeInsets.only( + bottom: (defaultBottomPadding /*+ 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: ValueListenableBuilder( - valueListenable: statusNotifier, - builder: (context, state, child) { - return GestureDetector( - onVerticalDragEnd: _onVerticalDragEnd, - onVerticalDragUpdate: _onVerticalDragUpdate, - child: child, - ); - }, - child: SizeTransition( - sizeFactor: sizeAnimation, - axisAlignment: -1.0, - axis: Axis.vertical, - child: SizedBox( - height: screenHeight, - width: MediaQuery.of(context).size.width, - child: ValueListenableBuilder( - valueListenable: statusNotifier, - builder: (context, state, _) => Stack( - children: [ - if (state != AnimationStatus.dismissed) - Positioned.fill( + child: SizeTransition( + sizeFactor: sizeAnimation, + axisAlignment: -1.0, + axis: Axis.vertical, + child: SizedBox( + height: screenHeight, + width: MediaQuery.of(context).size.width, + child: ValueListenableBuilder( + valueListenable: statusNotifier, + builder: (context, state, _) => Stack( + children: [ + if (state != AnimationStatus.dismissed) + PopScope( + canPop: false, + onPopInvoked: (_) => + dragController.fling(velocity: -1.0), + child: Positioned.fill( key: const Key('player_screen'), child: widget.expandedPanel, ), - if (state != AnimationStatus.completed) - Positioned( - top: 0, - right: 0, - left: 0, - key: const Key('player_bar'), - child: FadeTransition( - opacity: Tween(begin: 1.0, end: 0.0) - .animate(dragController), - child: SizedBox( - height: widget.bottomPanelHeight, - child: widget.bottomPanel), - ), + ), + if (state != AnimationStatus.completed) + Positioned( + top: 0, + right: 0, + left: 0, + key: const Key('player_bar'), + child: FadeTransition( + opacity: Tween(begin: 1.0, end: 0.0) + .animate(dragController), + child: SizedBox( + height: widget.bottomPanelHeight, + child: widget.bottomPanel), ), - ], - ), + ), + ], ), - )), - ), + ), + )), ), ), - ], - ), + ), + ], ); } diff --git a/lib/ui/importer_screen.dart b/lib/ui/importer_screen.dart index 6e09e5c..5e9d8b0 100644 --- a/lib/ui/importer_screen.dart +++ b/lib/ui/importer_screen.dart @@ -552,8 +552,8 @@ class _SpotifyImporterV2MainState extends State { showDialog( context: context, barrierDismissible: false, - builder: (context) => WillPopScope( - onWillPop: () => Future.value(false), + builder: (context) => PopScope( + canPop: false, child: AlertDialog( title: Text("Please wait...".i18n), content: const Row( diff --git a/lib/ui/search.dart b/lib/ui/search.dart index e46fc47..63a8469 100644 --- a/lib/ui/search.dart +++ b/lib/ui/search.dart @@ -26,7 +26,7 @@ FutureOr openScreenByURL(BuildContext context, String url) async { if (res == null) return; switch (res.type) { - case DeezerLinkType.TRACK: + case DeezerMediaType.track: Track t = await deezerAPI.track(res.id!); MenuSheet(context).defaultTrackMenu(t, optionsTop: [ MenuSheetOption(Text('Play'.i18n), @@ -34,15 +34,15 @@ FutureOr openScreenByURL(BuildContext context, String url) async { onTap: () => playerHelper.playSearchMixDeferred(t)), ]); break; - case DeezerLinkType.ALBUM: + case DeezerMediaType.album: Album a = await deezerAPI.album(res.id); return Navigator.of(context) .push(MaterialPageRoute(builder: (context) => AlbumDetails(a))); - case DeezerLinkType.ARTIST: + case DeezerMediaType.artist: Artist a = await deezerAPI.artist(res.id); return Navigator.of(context) .push(MaterialPageRoute(builder: (context) => ArtistDetails(a))); - case DeezerLinkType.PLAYLIST: + case DeezerMediaType.playlist: Playlist p = await deezerAPI.playlist(res.id); if (p.tracks == null || p.tracks!.isEmpty) { ScaffoldMessenger.of(context) @@ -446,15 +446,9 @@ class SearchResultsScreen extends StatefulWidget { } class _SearchResultsScreenState extends State { - final _tracksKey = GlobalKey(); - final _albumsKey = GlobalKey(); - final _artistsKey = GlobalKey(); - final _playlistsKey = GlobalKey(); - final _showsKey = GlobalKey(); - final _episodesKey = GlobalKey(); - SearchResults? _results; Object? _error; + DeezerMediaType? _page; Future _search() async { try { @@ -474,6 +468,24 @@ class _SearchResultsScreenState extends State { } } + 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) { switch (item) { case final Track track: @@ -539,62 +551,62 @@ class _SearchResultsScreenState extends State { ListView(scrollDirection: Axis.horizontal, children: [ if (_results!.tracks != null && _results!.tracks!.isNotEmpty) ...[ - ActionChip( + FilterChip( elevation: 1.0, label: Text('Tracks'.i18n), - onPressed: () => Scrollable.ensureVisible( - _tracksKey.currentContext!, - duration: const Duration(milliseconds: 500))), + selected: _page == DeezerMediaType.track, + onSelected: (selected) => setState(() => _page = + selected ? DeezerMediaType.track : null)), const SizedBox(width: 8.0), ], if (_results!.albums != null && _results!.albums!.isNotEmpty) ...[ - ActionChip( + FilterChip( elevation: 1.0, label: Text('Albums'.i18n), - onPressed: () => Scrollable.ensureVisible( - _albumsKey.currentContext!, - duration: const Duration(milliseconds: 500))), - 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))), + selected: _page == DeezerMediaType.album, + onSelected: (selected) => setState(() => _page = + selected ? DeezerMediaType.album : null)), 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 && _results!.playlists!.isNotEmpty) ...[ - ActionChip( + FilterChip( elevation: 1.0, label: Text('Playlists'.i18n), - onPressed: () => Scrollable.ensureVisible( - _playlistsKey.currentContext!, - duration: const Duration(milliseconds: 500))), + selected: _page == DeezerMediaType.playlist, + onSelected: (selected) => setState(() => _page = + selected ? DeezerMediaType.playlist : null)), const SizedBox(width: 8.0), ], if (_results!.shows != null && _results!.shows!.isNotEmpty) ...[ - ActionChip( + FilterChip( elevation: 1.0, label: Text('Shows'.i18n), - onPressed: () => Scrollable.ensureVisible( - _showsKey.currentContext!, - duration: const Duration(milliseconds: 500))), + selected: _page == DeezerMediaType.show, + onSelected: (selected) => setState(() => _page = + selected ? DeezerMediaType.show : null)), const SizedBox(width: 8.0), ], if (_results!.episodes != null && _results!.episodes!.isNotEmpty) ...[ - ActionChip( + FilterChip( elevation: 1.0, label: Text('Episodes'.i18n), - onPressed: () => Scrollable.ensureVisible( - _episodesKey.currentContext!, - duration: const Duration(milliseconds: 500))), + selected: _page == DeezerMediaType.episode, + onSelected: (selected) => setState(() => _page = + selected ? DeezerMediaType.episode : null)), const SizedBox(width: 8.0), ], ]), @@ -606,27 +618,36 @@ class _SearchResultsScreenState extends State { ? ErrorScreen(message: _error.toString()) : _results == null ? const Center(child: CircularProgressIndicator()) - : Builder( - builder: (context) { - final results = _results!; - if (results.empty) { - return Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.warning, - size: 64.sp, - ), - Text('No results!'.i18n) - ], - ), - ); + : PopScope( + canPop: _page == null, + onPopInvoked: (didPop) { + if (_page != null) { + setState(() => _page = null); } + }, + child: Builder( + builder: (context) { + final results = _results!; + if (results.empty) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.warning, + size: 64.sp, + ), + Text('No results!'.i18n) + ], + ), + ); + } - return SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + if (_page != null) { + return buildListFor(_page!); + } + + return ListView( children: [ if (results.topResult != null && results.topResult!.isNotEmpty) ...[ @@ -657,7 +678,6 @@ class _SearchResultsScreenState extends State { if (results.tracks != null && results.tracks!.isNotEmpty) ...[ Padding( - key: _tracksKey, padding: const EdgeInsets.symmetric( horizontal: 16.0, vertical: 4.0), child: Text( @@ -679,11 +699,8 @@ class _SearchResultsScreenState extends State { }), ListTile( title: Text('Show all tracks'.i18n), - onTap: () { - Navigator.of(context).pushRoute( - builder: (context) => TrackListScreen( - results.tracks, null)); - }, + onTap: () => setState( + () => _page = DeezerMediaType.track), ), const FreezerDivider(), ], @@ -691,7 +708,6 @@ class _SearchResultsScreenState extends State { results.albums!.isNotEmpty) ...[ const SizedBox(height: 8.0), Padding( - key: _albumsKey, padding: const EdgeInsets.symmetric( horizontal: 16.0, vertical: 4.0), child: Text( @@ -715,11 +731,8 @@ class _SearchResultsScreenState extends State { }), ListTile( title: Text('Show all albums'.i18n), - onTap: () { - Navigator.of(context).pushRoute( - builder: (context) => - AlbumListScreen(results.albums)); - }, + onTap: () => setState( + () => _page = DeezerMediaType.album), ), const FreezerDivider() ], @@ -727,7 +740,6 @@ class _SearchResultsScreenState extends State { results.artists!.isNotEmpty) ...[ const SizedBox(height: 8.0), Padding( - key: _artistsKey, padding: const EdgeInsets.symmetric( vertical: 4.0, horizontal: 16.0), child: Text( @@ -778,7 +790,6 @@ class _SearchResultsScreenState extends State { results.playlists!.isNotEmpty) ...[ const SizedBox(height: 8.0), Padding( - key: _playlistsKey, padding: const EdgeInsets.symmetric( vertical: 4.0, horizontal: 16.0), child: Text( @@ -808,12 +819,8 @@ class _SearchResultsScreenState extends State { ), ListTile( title: Text('Show all playlists'.i18n), - onTap: () { - Navigator.of(context).pushRoute( - builder: (context) => - SearchResultPlaylists( - results.playlists)); - }, + onTap: () => setState( + () => _page = DeezerMediaType.playlist), ), const FreezerDivider(), ], @@ -821,7 +828,6 @@ class _SearchResultsScreenState extends State { results.shows!.isNotEmpty) ...[ const SizedBox(height: 8.0), Padding( - key: _showsKey, padding: const EdgeInsets.symmetric( vertical: 4.0, horizontal: 16.0), child: Text( @@ -843,11 +849,8 @@ class _SearchResultsScreenState extends State { ), ListTile( title: Text('Show all shows'.i18n), - onTap: () { - Navigator.of(context).pushRoute( - builder: (context) => - ShowListScreen(results.shows)); - }, + onTap: () => setState( + () => _page = DeezerMediaType.show), ), const FreezerDivider() ], @@ -855,7 +858,6 @@ class _SearchResultsScreenState extends State { results.episodes!.isNotEmpty) ...[ const SizedBox(height: 8.0), Padding( - key: _episodesKey, padding: const EdgeInsets.symmetric( vertical: 4.0, horizontal: 16.0), child: Text( @@ -893,17 +895,15 @@ class _SearchResultsScreenState extends State { }, ), ListTile( - title: Text('Show all episodes'.i18n), - onTap: () { - Navigator.of(context).pushRoute( - builder: (context) => EpisodeListScreen( - results.episodes)); - }) + title: Text('Show all episodes'.i18n), + onTap: () => setState( + () => _page = DeezerMediaType.episode), + ) ] ], - ), - ); - }, + ); + }, + ), )); } } @@ -917,29 +917,26 @@ class TrackListScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text('Tracks'.i18n)), - body: ListView.builder( - itemCount: tracks!.length, - itemBuilder: (BuildContext context, int i) { - Track t = tracks![i]; - return TrackTile.fromTrack( - t, - onTap: () { - if (queueSource == null) { - playerHelper.playSearchMixDeferred(t); - return; - } + return ListView.builder( + itemCount: tracks!.length, + itemBuilder: (BuildContext context, int i) { + Track t = tracks![i]; + return TrackTile.fromTrack( + t, + onTap: () { + if (queueSource == null) { + playerHelper.playSearchMixDeferred(t); + return; + } - playerHelper.playFromTrackList(tracks!, t.id, queueSource!); - }, - onSecondary: (details) { - MenuSheet m = MenuSheet(context); - m.defaultTrackMenu(t, details: details); - }, - ); - }, - ), + playerHelper.playFromTrackList(tracks!, t.id, queueSource!); + }, + onSecondary: (details) { + MenuSheet m = MenuSheet(context); + m.defaultTrackMenu(t, details: details); + }, + ); + }, ); } } @@ -951,54 +948,48 @@ class AlbumListScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text('Albums'.i18n)), - body: ListView.builder( - itemCount: albums!.length, - itemBuilder: (context, i) { - Album? a = albums![i]; - return AlbumTile( - a, - onTap: () { - Navigator.of(context) - .pushRoute(builder: (context) => AlbumDetails(a)); - }, - onSecondary: (details) { - MenuSheet m = MenuSheet(context); - m.defaultAlbumMenu(a!, details: details); - }, - ); - }, - ), + return ListView.builder( + itemCount: albums!.length, + itemBuilder: (context, i) { + Album? a = albums![i]; + return AlbumTile( + a, + onTap: () { + Navigator.of(context) + .pushRoute(builder: (context) => AlbumDetails(a)); + }, + onSecondary: (details) { + MenuSheet m = MenuSheet(context); + m.defaultAlbumMenu(a!, details: details); + }, + ); + }, ); } } -class SearchResultPlaylists extends StatelessWidget { +class PlaylistListScreen extends StatelessWidget { final List? playlists; - const SearchResultPlaylists(this.playlists, {super.key}); + const PlaylistListScreen(this.playlists, {super.key}); @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text('Playlists'.i18n)), - body: ListView.builder( - itemCount: playlists!.length, - itemBuilder: (context, i) { - Playlist? p = playlists![i]; - return PlaylistTile( - p, - onTap: () { - Navigator.of(context) - .pushRoute(builder: (context) => PlaylistDetails(p)); - }, - onSecondary: (details) { - MenuSheet m = MenuSheet(context); - m.defaultPlaylistMenu(p!, details: details); - }, - ); - }, - ), + return ListView.builder( + itemCount: playlists!.length, + itemBuilder: (context, i) { + Playlist? p = playlists![i]; + return PlaylistTile( + p, + onTap: () { + Navigator.of(context) + .pushRoute(builder: (context) => PlaylistDetails(p)); + }, + onSecondary: (details) { + MenuSheet m = MenuSheet(context); + m.defaultPlaylistMenu(p!, details: details); + }, + ); + }, ); } } @@ -1009,21 +1000,18 @@ class ShowListScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text('Shows'.i18n)), - body: ListView.builder( - itemCount: shows!.length, - itemBuilder: (context, i) { - Show s = shows![i]; - return ShowTile( - s, - onTap: () { - Navigator.of(context) - .push(MaterialPageRoute(builder: (context) => ShowScreen(s))); - }, - ); - }, - ), + return ListView.builder( + itemCount: shows!.length, + itemBuilder: (context, i) { + Show s = shows![i]; + return ShowTile( + s, + onTap: () { + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => ShowScreen(s))); + }, + ); + }, ); } } @@ -1034,33 +1022,31 @@ class EpisodeListScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text('Episodes'.i18n)), - body: ListView.builder( - itemCount: episodes!.length, - itemBuilder: (context, i) { - ShowEpisode e = episodes![i]; - return ShowEpisodeTile( - e, - trailing: IconButton( - icon: Icon( - Icons.more_vert, - semanticLabel: "Options".i18n, - ), - onPressed: () { - MenuSheet m = MenuSheet(context); - m.defaultShowEpisodeMenu(e.show!, e); - }, - ), - onTap: () async { - //Load entire show, then play - List episodes = - (await deezerAPI.allShowEpisodes(e.show!.id))!; - await playerHelper.playShowEpisode(e.show!, episodes, - index: episodes.indexWhere((ep) => e.id == ep.id)); - }, - ); + return ListView.builder( + itemCount: episodes!.length, + itemBuilder: (context, i) { + ShowEpisode e = episodes![i]; + return ShowEpisodeTile( + e, + trailing: IconButton( + icon: Icon( + Icons.more_vert, + semanticLabel: "Options".i18n, + ), + onPressed: () { + MenuSheet m = MenuSheet(context); + m.defaultShowEpisodeMenu(e.show!, e); + }, + ), + onTap: () async { + //Load entire show, then play + List episodes = + (await deezerAPI.allShowEpisodes(e.show!.id))!; + await playerHelper.playShowEpisode(e.show!, episodes, + index: episodes.indexWhere((ep) => e.id == ep.id)); }, - )); + ); + }, + ); } } diff --git a/lib/ui/settings_screen.dart b/lib/ui/settings_screen.dart index 33f90fa..3bfeda1 100644 --- a/lib/ui/settings_screen.dart +++ b/lib/ui/settings_screen.dart @@ -1528,6 +1528,9 @@ class _GeneralSettingsState extends State { ScaffoldMessenger.of(context).snack('Copied'.i18n); }, ), + ListTile( + title: const Text('DEBUG: stop audioHandler'), + onTap: () => audioHandler.stop()), ], ), );