search: change chip functionality +
wip: fix resuming audio handler after stop
This commit is contained in:
parent
6816bdc112
commit
bb4448731e
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
)),
|
),
|
||||||
),
|
)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
));
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue