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

View file

@ -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 <String, DeezerMediaType>{
'album': DeezerMediaType.album,
'artist': DeezerMediaType.artist,
'playlist': DeezerMediaType.playlist,
'track': DeezerMediaType.track,
'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());
_box = await Hive.openLazyBox('playback', path: await Paths.cacheDir());
_init();
_init(shouldLoadQueue: false);
await _loadQueueFile();
@ -185,7 +185,7 @@ class AudioPlayerTask extends BaseAudioHandler {
}
}
Future<void> _init() async {
Future<void> _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<void> _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<void> 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;
}
}

View file

@ -71,112 +71,106 @@ class FancyScaffoldState extends State<FancyScaffold>
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),
),
],
),
),
],
),
)),
),
),
)),
),
),
],
),
),
],
);
}

View file

@ -552,8 +552,8 @@ class _SpotifyImporterV2MainState extends State<SpotifyImporterV2Main> {
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(

View file

@ -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<SearchResultsScreen> {
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<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) {
switch (item) {
case final Track track:
@ -539,62 +551,62 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
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<SearchResultsScreen> {
? 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: <Widget>[
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: <Widget>[
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: <Widget>[
if (results.topResult != null &&
results.topResult!.isNotEmpty) ...[
@ -657,7 +678,6 @@ class _SearchResultsScreenState extends State<SearchResultsScreen> {
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<SearchResultsScreen> {
}),
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<SearchResultsScreen> {
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<SearchResultsScreen> {
}),
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<SearchResultsScreen> {
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<SearchResultsScreen> {
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<SearchResultsScreen> {
),
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<SearchResultsScreen> {
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<SearchResultsScreen> {
),
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<SearchResultsScreen> {
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<SearchResultsScreen> {
},
),
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<Playlist?>? 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<ShowEpisode> 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<ShowEpisode> episodes =
(await deezerAPI.allShowEpisodes(e.show!.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);
},
),
ListTile(
title: const Text('DEBUG: stop audioHandler'),
onTap: () => audioHandler.stop()),
],
),
);