0.6.7 - shows in search, album art resolution
This commit is contained in:
parent
babd12bae2
commit
c3a26b0e3b
|
@ -400,8 +400,8 @@ public class Deezer {
|
|||
PictureTypes.DEFAULT_ID,
|
||||
ImageFormats.MIME_TYPE_JPEG,
|
||||
"cover",
|
||||
1400,
|
||||
1400,
|
||||
settings.albumArtResolution,
|
||||
settings.albumArtResolution,
|
||||
24,
|
||||
0
|
||||
));
|
||||
|
|
|
@ -489,7 +489,7 @@ public class DownloadService extends Service {
|
|||
File coverFile = new File(outFile.getPath().substring(0, outFile.getPath().lastIndexOf('.')) + ".jpg");
|
||||
|
||||
try {
|
||||
URL url = new URL("http://e-cdn-images.deezer.com/images/cover/" + trackJson.getString("md5_image") + "/1400x1400-000000-80-0-0.jpg");
|
||||
URL url = new URL("http://e-cdn-images.deezer.com/images/cover/" + albumJson.getString("md5_image") + "/" + Integer.toString(settings.albumArtResolution) + "x" + Integer.toString(settings.albumArtResolution) + "-000000-80-0-0.jpg");
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
//Set headers
|
||||
connection.setRequestMethod("GET");
|
||||
|
@ -568,7 +568,7 @@ public class DownloadService extends Service {
|
|||
//Create to lock
|
||||
coverFile.createNewFile();
|
||||
|
||||
URL url = new URL("http://e-cdn-images.deezer.com/images/cover/" + albumJson.getString("md5_image") + "/1400x1400-000000-80-0-0.jpg");
|
||||
URL url = new URL("http://e-cdn-images.deezer.com/images/cover/" + albumJson.getString("md5_image") + "/" + Integer.toString(settings.albumArtResolution) + "x" + Integer.toString(settings.albumArtResolution) + "-000000-80-0-0.jpg");
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
//Set headers
|
||||
connection.setRequestMethod("GET");
|
||||
|
@ -808,8 +808,9 @@ public class DownloadService extends Service {
|
|||
boolean albumCover;
|
||||
boolean nomediaFiles;
|
||||
String artistSeparator;
|
||||
int albumArtResolution;
|
||||
|
||||
private DownloadSettings(int downloadThreads, boolean overwriteDownload, boolean downloadLyrics, boolean trackCover, String arl, boolean albumCover, boolean nomediaFiles, String artistSeparator) {
|
||||
private DownloadSettings(int downloadThreads, boolean overwriteDownload, boolean downloadLyrics, boolean trackCover, String arl, boolean albumCover, boolean nomediaFiles, String artistSeparator, int albumArtResolution) {
|
||||
this.downloadThreads = downloadThreads;
|
||||
this.overwriteDownload = overwriteDownload;
|
||||
this.downloadLyrics = downloadLyrics;
|
||||
|
@ -818,6 +819,7 @@ public class DownloadService extends Service {
|
|||
this.albumCover = albumCover;
|
||||
this.nomediaFiles = nomediaFiles;
|
||||
this.artistSeparator = artistSeparator;
|
||||
this.albumArtResolution = albumArtResolution;
|
||||
}
|
||||
|
||||
//Parse settings from bundle sent from UI
|
||||
|
@ -833,7 +835,8 @@ public class DownloadService extends Service {
|
|||
json.getString("arl"),
|
||||
json.getBoolean("albumCover"),
|
||||
json.getBoolean("nomediaFiles"),
|
||||
json.getString("artistSeparator")
|
||||
json.getString("artistSeparator"),
|
||||
json.getInt("albumArtResolution")
|
||||
);
|
||||
} catch (Exception e) {
|
||||
//Shouldn't happen
|
||||
|
|
|
@ -51,6 +51,9 @@ class Cache {
|
|||
@JsonKey(defaultValue: 0)
|
||||
int lastUpdateCheck;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
bool wakelock = false;
|
||||
|
||||
Cache({this.libraryTracks});
|
||||
|
||||
//Wrapper to test if track is favorite against cache
|
||||
|
|
|
@ -392,7 +392,7 @@ class Playlist {
|
|||
id: json['PLAYLIST_ID'].toString(),
|
||||
title: json['TITLE'],
|
||||
trackCount: json['NB_SONG']??songsJson['total'],
|
||||
image: ImageDetails.fromPrivateString(json['PLAYLIST_PICTURE'], type: 'playlist'),
|
||||
image: ImageDetails.fromPrivateString(json['PLAYLIST_PICTURE'], type: json['PICTURE_TYPE']),
|
||||
fans: json['NB_FAN'],
|
||||
duration: Duration(seconds: json['DURATION']??0),
|
||||
description: json['DESCRIPTION'],
|
||||
|
@ -464,7 +464,7 @@ class ImageDetails {
|
|||
|
||||
//JSON
|
||||
factory ImageDetails.fromPrivateString(String art, {String type='cover'}) => ImageDetails(
|
||||
fullUrl: 'https://e-cdns-images.dzcdn.net/images/$type/$art/1400x1400-000000-80-0-0.jpg',
|
||||
fullUrl: 'https://e-cdns-images.dzcdn.net/images/$type/$art/1000x1000-000000-80-0-0.jpg',
|
||||
thumbUrl: 'https://e-cdns-images.dzcdn.net/images/$type/$art/140x140-000000-80-0-0.jpg'
|
||||
);
|
||||
factory ImageDetails.fromPrivateJson(Map<dynamic, dynamic> json) => ImageDetails.fromPrivateString(
|
||||
|
@ -481,22 +481,28 @@ class SearchResults {
|
|||
List<Album> albums;
|
||||
List<Artist> artists;
|
||||
List<Playlist> playlists;
|
||||
List<Show> shows;
|
||||
List<ShowEpisode> episodes;
|
||||
|
||||
SearchResults({this.tracks, this.albums, this.artists, this.playlists});
|
||||
SearchResults({this.tracks, this.albums, this.artists, this.playlists, this.shows, this.episodes});
|
||||
|
||||
//Check if no search results
|
||||
bool get empty {
|
||||
return ((tracks == null || tracks.length == 0) &&
|
||||
(albums == null || albums.length == 0) &&
|
||||
(artists == null || artists.length == 0) &&
|
||||
(playlists == null || playlists.length == 0));
|
||||
(playlists == null || playlists.length == 0) &&
|
||||
(shows == null || shows.length == 0) &&
|
||||
(episodes == null || episodes.length == 0));
|
||||
}
|
||||
|
||||
factory SearchResults.fromPrivateJson(Map<dynamic, dynamic> json) => SearchResults(
|
||||
tracks: json['TRACK']['data'].map<Track>((dynamic data) => Track.fromPrivateJson(data)).toList(),
|
||||
albums: json['ALBUM']['data'].map<Album>((dynamic data) => Album.fromPrivateJson(data)).toList(),
|
||||
artists: json['ARTIST']['data'].map<Artist>((dynamic data) => Artist.fromPrivateJson(data)).toList(),
|
||||
playlists: json['PLAYLIST']['data'].map<Playlist>((dynamic data) => Playlist.fromPrivateJson(data)).toList()
|
||||
playlists: json['PLAYLIST']['data'].map<Playlist>((dynamic data) => Playlist.fromPrivateJson(data)).toList(),
|
||||
shows: json['SHOW']['data'].map<Show>((dynamic data) => Show.fromPrivateJson(data)).toList(),
|
||||
episodes: json['EPISODE']['data'].map<ShowEpisode>((dynamic data) => ShowEpisode.fromPrivateJson(data)).toList()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -897,8 +903,10 @@ class ShowEpisode {
|
|||
String url;
|
||||
Duration duration;
|
||||
String publishedDate;
|
||||
//Might not be fully available
|
||||
Show show;
|
||||
|
||||
ShowEpisode({this.id, this.title, this.description, this.url, this.duration, this.publishedDate});
|
||||
ShowEpisode({this.id, this.title, this.description, this.url, this.duration, this.publishedDate, this.show});
|
||||
|
||||
String get durationString => "${duration.inMinutes}:${duration.inSeconds.remainder(60).toString().padLeft(2, '0')}";
|
||||
|
||||
|
@ -917,7 +925,7 @@ class ShowEpisode {
|
|||
},
|
||||
displayDescription: description,
|
||||
duration: duration,
|
||||
artUri: show.art.full
|
||||
artUri: show.art.full,
|
||||
);
|
||||
}
|
||||
factory ShowEpisode.fromMediaItem(MediaItem mi) {
|
||||
|
@ -927,6 +935,7 @@ class ShowEpisode {
|
|||
description: mi.displayDescription,
|
||||
url: mi.extras['showUrl'],
|
||||
duration: mi.duration,
|
||||
show: Show.fromPrivateJson(mi.extras['show'])
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -937,7 +946,8 @@ class ShowEpisode {
|
|||
description: json['EPISODE_DESCRIPTION'],
|
||||
url: json['EPISODE_DIRECT_STREAM_URL'],
|
||||
duration: Duration(seconds: int.parse(json['DURATION'].toString())),
|
||||
publishedDate: json['EPISODE_PUBLISHED_TIMESTAMP']
|
||||
publishedDate: json['EPISODE_PUBLISHED_TIMESTAMP'],
|
||||
show: Show.fromPrivateJson(json)
|
||||
);
|
||||
|
||||
factory ShowEpisode.fromJson(Map<String, dynamic> json) => _$ShowEpisodeFromJson(json);
|
||||
|
|
|
@ -453,6 +453,9 @@ ShowEpisode _$ShowEpisodeFromJson(Map<String, dynamic> json) {
|
|||
? null
|
||||
: Duration(microseconds: json['duration'] as int),
|
||||
publishedDate: json['publishedDate'] as String,
|
||||
show: json['show'] == null
|
||||
? null
|
||||
: Show.fromJson(json['show'] as Map<String, dynamic>),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -464,4 +467,5 @@ Map<String, dynamic> _$ShowEpisodeToJson(ShowEpisode instance) =>
|
|||
'url': instance.url,
|
||||
'duration': instance.duration?.inMicroseconds,
|
||||
'publishedDate': instance.publishedDate,
|
||||
'show': instance.show,
|
||||
};
|
||||
|
|
|
@ -45,6 +45,7 @@ class PlayerHelper {
|
|||
if (event['action'] == 'onRestore') {
|
||||
//Load queueSource from isolate
|
||||
this.queueSource = QueueSource.fromJson(event['queueSource']);
|
||||
repeatType = LoopMode.values[event['loopMode']];
|
||||
}
|
||||
if (event['action'] == 'queueEnd') {
|
||||
//If last song is played, load more queue
|
||||
|
@ -332,6 +333,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
int wifiQuality;
|
||||
QueueSource queueSource;
|
||||
Duration _lastPosition;
|
||||
LoopMode _loopMode = LoopMode.off;
|
||||
|
||||
Completer _androidAutoCallback;
|
||||
|
||||
|
@ -439,6 +441,19 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
@override
|
||||
Future<void> onSeekBackward(bool begin) async => _seekContinuously(begin, -1);
|
||||
|
||||
//Remove item from queue
|
||||
@override
|
||||
Future<void> onRemoveQueueItem(MediaItem mediaItem) async {
|
||||
int index = _queue.indexWhere((m) => m.id == mediaItem.id);
|
||||
_queue.removeAt(index);
|
||||
if (index <= _queueIndex) {
|
||||
_queueIndex--;
|
||||
}
|
||||
_audioSource.removeAt(index);
|
||||
|
||||
AudioServiceBackground.setQueue(_queue);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onSkipToNext() async {
|
||||
if (_queueIndex == _queue.length-1) return;
|
||||
|
@ -630,7 +645,8 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
}
|
||||
//Looping
|
||||
if (name == 'repeatType') {
|
||||
_player.setLoopMode(LoopMode.values[args]);
|
||||
_loopMode = LoopMode.values[args];
|
||||
_player.setLoopMode(_loopMode);
|
||||
}
|
||||
if (name == 'saveQueue')
|
||||
await this._saveQueue();
|
||||
|
@ -708,6 +724,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
'queue': _queue.map<Map<String, dynamic>>((mi) => mi.toJson()).toList(),
|
||||
'position': _player.position.inMilliseconds,
|
||||
'queueSource': (queueSource??QueueSource()).toJson(),
|
||||
'loopMode': LoopMode.values.indexOf(_loopMode??LoopMode.off)
|
||||
};
|
||||
await f.writeAsString(jsonEncode(data));
|
||||
}
|
||||
|
@ -721,6 +738,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
this._queueIndex = json['index'] ?? 0;
|
||||
this._lastPosition = Duration(milliseconds: json['position']??0);
|
||||
this.queueSource = QueueSource.fromJson(json['queueSource']??{});
|
||||
this._loopMode = LoopMode.values[(json['loopMode']??0)];
|
||||
//Restore queue
|
||||
if (_queue != null) {
|
||||
await AudioServiceBackground.setQueue(_queue);
|
||||
|
@ -731,7 +749,8 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||
//Send restored queue source to ui
|
||||
AudioServiceBackground.sendCustomEvent({
|
||||
'action': 'onRestore',
|
||||
'queueSource': (queueSource??QueueSource()).toJson()
|
||||
'queueSource': (queueSource??QueueSource()).toJson(),
|
||||
'loopMode': LoopMode.values.indexOf(_loopMode)
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -297,6 +297,16 @@ const language_en_us = {
|
|||
//0.6.6
|
||||
"Restart of app is required to properly log out!": "Restart of app is required to properly log out!",
|
||||
"Artist separator": "Artist separator",
|
||||
"Singleton naming": "Standalone tracks filename"
|
||||
"Singleton naming": "Standalone tracks filename",
|
||||
|
||||
//0.6.7
|
||||
"Keep the screen on": "Keep the screen on",
|
||||
"Wakelock enabled!": "Wakelock enabled!",
|
||||
"Wakelock disabled!": "Wakelock disabled!",
|
||||
"Show all shows": "Show all shows",
|
||||
"Episodes": "Episodes",
|
||||
"Show all episodes": "Show all episodes",
|
||||
"Album cover resolution": "Album cover resolution",
|
||||
"WARNING: Resolutions above 1200 aren't officially supported": "WARNING: Resolutions above 1200 aren't officially supported"
|
||||
}
|
||||
};
|
||||
|
|
|
@ -71,6 +71,8 @@ class Settings {
|
|||
String artistSeparator;
|
||||
@JsonKey(defaultValue: "%artist% - %title%")
|
||||
String singletonFilename;
|
||||
@JsonKey(defaultValue: 1400)
|
||||
int albumArtResolution;
|
||||
|
||||
//Appearance
|
||||
@JsonKey(defaultValue: Themes.Dark)
|
||||
|
|
|
@ -40,6 +40,7 @@ Settings _$SettingsFromJson(Map<String, dynamic> json) {
|
|||
..artistSeparator = json['artistSeparator'] as String ?? ', '
|
||||
..singletonFilename =
|
||||
json['singletonFilename'] as String ?? '%artist% - %title%'
|
||||
..albumArtResolution = json['albumArtResolution'] as int ?? 1400
|
||||
..theme =
|
||||
_$enumDecodeNullable(_$ThemesEnumMap, json['theme']) ?? Themes.Dark
|
||||
..useSystemTheme = json['useSystemTheme'] as bool ?? false
|
||||
|
@ -76,6 +77,7 @@ Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
|
|||
'nomediaFiles': instance.nomediaFiles,
|
||||
'artistSeparator': instance.artistSeparator,
|
||||
'singletonFilename': instance.singletonFilename,
|
||||
'albumArtResolution': instance.albumArtResolution,
|
||||
'theme': _$ThemesEnumMap[instance.theme],
|
||||
'useSystemTheme': instance.useSystemTheme,
|
||||
'colorGradientBackground': instance.colorGradientBackground,
|
||||
|
|
|
@ -28,6 +28,8 @@ const supportedLocales = [
|
|||
const Locale('sk', 'SK'),
|
||||
const Locale('cs', 'CZ'),
|
||||
const Locale('vi', 'VI'),
|
||||
const Locale('nl', 'NL'),
|
||||
const Locale('sl', 'SL'),
|
||||
const Locale('fil', 'PH'),
|
||||
const Locale('uwu', 'UWU')
|
||||
];
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:wakelock/wakelock.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
@ -585,6 +586,33 @@ class MenuSheet {
|
|||
},
|
||||
);
|
||||
|
||||
Widget wakelock() => ListTile(
|
||||
title: Text('Keep the screen on'.i18n),
|
||||
leading: Icon(Icons.screen_lock_portrait),
|
||||
onTap: () async {
|
||||
_close();
|
||||
if (cache.wakelock == null)
|
||||
cache.wakelock = false;
|
||||
//Enable
|
||||
if (!cache.wakelock) {
|
||||
Wakelock.enable();
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Wakelock enabled!'.i18n,
|
||||
gravity: ToastGravity.BOTTOM
|
||||
);
|
||||
cache.wakelock = true;
|
||||
return;
|
||||
}
|
||||
//Disable
|
||||
Wakelock.disable();
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Wakelock disabled!'.i18n,
|
||||
gravity: ToastGravity.BOTTOM
|
||||
);
|
||||
cache.wakelock = false;
|
||||
},
|
||||
);
|
||||
|
||||
void _close() => Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
|
|
|
@ -280,7 +280,7 @@ class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
|
|||
children: <Widget>[
|
||||
Container(
|
||||
height: ScreenUtil().setSp(64),
|
||||
child: AudioService.currentMediaItem.displayTitle.length >= 24 ?
|
||||
child: AudioService.currentMediaItem.displayTitle.length >= 26 ?
|
||||
Marquee(
|
||||
text: AudioService.currentMediaItem.displayTitle,
|
||||
style: TextStyle(
|
||||
|
@ -410,12 +410,12 @@ class PlayerMenuButton extends StatelessWidget {
|
|||
Track t = Track.fromMediaItem(AudioService.currentMediaItem);
|
||||
MenuSheet m = MenuSheet(context);
|
||||
if (AudioService.currentMediaItem.extras['show'] == null)
|
||||
m.defaultTrackMenu(t, options: [m.sleepTimer()]);
|
||||
m.defaultTrackMenu(t, options: [m.sleepTimer(), m.wakelock()]);
|
||||
else
|
||||
m.defaultShowEpisodeMenu(
|
||||
Show.fromJson(jsonDecode(AudioService.currentMediaItem.extras['show'])),
|
||||
ShowEpisode.fromMediaItem(AudioService.currentMediaItem),
|
||||
options: [m.sleepTimer()]
|
||||
options: [m.sleepTimer(), m.wakelock()]
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -771,6 +771,13 @@ class _QueueScreenState extends State<QueueScreen> {
|
|||
Navigator.of(context).pop();
|
||||
},
|
||||
key: Key(t.id),
|
||||
trailing: IconButton(
|
||||
icon: Icon(Icons.close),
|
||||
onPressed: () async {
|
||||
await AudioService.removeQueueItem(t.toMediaItem());
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
)
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:flutter/src/services/keyboard_key.dart';
|
|||
import 'package:freezer/api/cache.dart';
|
||||
import 'package:freezer/api/download.dart';
|
||||
import 'package:freezer/api/player.dart';
|
||||
import 'package:freezer/main.dart';
|
||||
import 'package:freezer/ui/details_screens.dart';
|
||||
import 'package:freezer/ui/elements.dart';
|
||||
import 'package:freezer/ui/home_screen.dart';
|
||||
|
@ -657,6 +658,91 @@ class SearchResultsScreen extends StatelessWidget {
|
|||
MaterialPageRoute(builder: (context) => SearchResultPlaylists(results.playlists))
|
||||
);
|
||||
},
|
||||
),
|
||||
FreezerDivider()
|
||||
];
|
||||
}
|
||||
|
||||
//Shows
|
||||
List<Widget> shows = [];
|
||||
if (results.shows != null && results.shows.length != 0) {
|
||||
shows = [
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0),
|
||||
child: Text(
|
||||
'Shows'.i18n,
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
fontSize: 20.0,
|
||||
fontWeight: FontWeight.bold
|
||||
),
|
||||
),
|
||||
),
|
||||
...List.generate(3, (i) {
|
||||
if (results.shows.length <= i) return Container(height: 0, width: 0,);
|
||||
Show s = results.shows[i];
|
||||
return ShowTile(
|
||||
s,
|
||||
onTap: () async {
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => ShowScreen(s)
|
||||
));
|
||||
},
|
||||
);
|
||||
}),
|
||||
ListTile(
|
||||
title: Text('Show all shows'.i18n),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => ShowListScreen(results.shows))
|
||||
);
|
||||
},
|
||||
),
|
||||
FreezerDivider()
|
||||
];
|
||||
}
|
||||
|
||||
//Episodes
|
||||
List<Widget> episodes = [];
|
||||
if (results.episodes != null && results.episodes.length != 0) {
|
||||
episodes = [
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0),
|
||||
child: Text(
|
||||
'Episodes'.i18n,
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
fontSize: 20.0,
|
||||
fontWeight: FontWeight.bold
|
||||
),
|
||||
),
|
||||
),
|
||||
...List.generate(3, (i) {
|
||||
if (results.episodes.length <= i) return Container(height: 0, width: 0,);
|
||||
ShowEpisode e = results.episodes[i];
|
||||
return ShowEpisodeTile(
|
||||
e,
|
||||
trailing: IconButton(
|
||||
icon: Icon(Icons.more_vert),
|
||||
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));
|
||||
},
|
||||
);
|
||||
}),
|
||||
ListTile(
|
||||
title: Text('Show all episodes'.i18n),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => EpisodeListScreen(results.episodes))
|
||||
);
|
||||
}
|
||||
)
|
||||
];
|
||||
}
|
||||
|
@ -670,7 +756,11 @@ class SearchResultsScreen extends StatelessWidget {
|
|||
Container(height: 8.0,),
|
||||
...artists,
|
||||
Container(height: 8.0,),
|
||||
...playlists
|
||||
...playlists,
|
||||
Container(height: 8.0,),
|
||||
...shows,
|
||||
Container(height: 8.0,),
|
||||
...episodes
|
||||
],
|
||||
);
|
||||
},
|
||||
|
@ -773,3 +863,64 @@ class SearchResultPlaylists extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ShowListScreen extends StatelessWidget {
|
||||
|
||||
final List<Show> shows;
|
||||
ShowListScreen(this.shows);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: FreezerAppBar('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)
|
||||
));
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EpisodeListScreen extends StatelessWidget {
|
||||
|
||||
final List<ShowEpisode> episodes;
|
||||
EpisodeListScreen(this.episodes);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: FreezerAppBar('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),
|
||||
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));
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -856,6 +856,27 @@ class _DownloadsSettingsState extends State<DownloadsSettings> {
|
|||
),
|
||||
leading: Icon(Icons.image)
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Album cover resolution'.i18n),
|
||||
subtitle: Text("WARNING: Resolutions above 1200 aren't officially supported".i18n),
|
||||
leading: Icon(Icons.image),
|
||||
trailing: Container(
|
||||
width: 75.0,
|
||||
child: DropdownButton<int>(
|
||||
value: settings.albumArtResolution,
|
||||
items: [400, 800, 1000, 1200, 1400, 1600, 1800].map<DropdownMenuItem<int>>((int i) => DropdownMenuItem<int>(
|
||||
value: i,
|
||||
child: Text(i.toString()),
|
||||
)).toList(),
|
||||
onChanged: (int n) async {
|
||||
setState(() {
|
||||
settings.albumArtResolution = n;
|
||||
});
|
||||
await settings.save();
|
||||
},
|
||||
)
|
||||
)
|
||||
),
|
||||
ListTile(
|
||||
title: Text('Create .nomedia files'.i18n),
|
||||
subtitle: Text('To prevent gallery being filled with album art'.i18n),
|
||||
|
@ -872,7 +893,7 @@ class _DownloadsSettingsState extends State<DownloadsSettings> {
|
|||
title: Text('Artist separator'.i18n),
|
||||
leading: Icon(WebSymbols.tag),
|
||||
trailing: Container(
|
||||
width: 100.0,
|
||||
width: 75.0,
|
||||
child: TextField(
|
||||
controller: _artistSeparatorController,
|
||||
onChanged: (s) async {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttericon/octicons_icons.dart';
|
||||
import 'package:freezer/api/deezer.dart';
|
||||
import 'package:freezer/api/download.dart';
|
||||
import 'package:freezer/translations.i18n.dart';
|
||||
|
||||
import '../api/definitions.dart';
|
||||
|
@ -25,6 +27,7 @@ class TrackTile extends StatefulWidget {
|
|||
class _TrackTileState extends State<TrackTile> {
|
||||
|
||||
StreamSubscription _subscription;
|
||||
bool _isOffline = false;
|
||||
|
||||
bool get nowPlaying {
|
||||
if (AudioService.currentMediaItem == null) return false;
|
||||
|
@ -37,6 +40,9 @@ class _TrackTileState extends State<TrackTile> {
|
|||
_subscription = AudioService.currentMediaItemStream.listen((event) {
|
||||
setState(() {});
|
||||
});
|
||||
//Check if offline
|
||||
downloadManager.checkOffline(track: widget.track).then((b) => setState(() => _isOffline = b));
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -70,9 +76,18 @@ class _TrackTileState extends State<TrackTile> {
|
|||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if ((_isOffline??false))
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2.0),
|
||||
child: Icon(
|
||||
Octicons.primitive_dot,
|
||||
color: Colors.green,
|
||||
size: 12.0,
|
||||
),
|
||||
),
|
||||
if (widget.track.explicit??false)
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 4.0),
|
||||
padding: EdgeInsets.symmetric(horizontal: 2.0),
|
||||
child: Text(
|
||||
'E',
|
||||
style: TextStyle(
|
||||
|
@ -80,9 +95,12 @@ class _TrackTileState extends State<TrackTile> {
|
|||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2.0),
|
||||
child: Text(widget.track.durationString),
|
||||
Container(
|
||||
width: 42.0,
|
||||
child: Text(
|
||||
widget.track.durationString,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
widget.trailing??Container(width: 0, height: 0)
|
||||
],
|
||||
|
@ -497,6 +515,38 @@ class ShowCard extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class ShowTile extends StatelessWidget {
|
||||
|
||||
final Show show;
|
||||
final Function onTap;
|
||||
final Function onHold;
|
||||
|
||||
ShowTile(this.show, {this.onTap, this.onHold});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
title: Text(
|
||||
show.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
subtitle: Text(
|
||||
show.description,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
onTap: onTap,
|
||||
onLongPress: onHold,
|
||||
leading: CachedImage(
|
||||
url: show.art.thumb,
|
||||
width: 48,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ShowEpisodeTile extends StatelessWidget {
|
||||
|
||||
final ShowEpisode episode;
|
||||
|
|
28
pubspec.lock
28
pubspec.lock
|
@ -462,6 +462,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.5"
|
||||
import_js_library:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: import_js_library
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
infinite_listview:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -985,6 +992,27 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
wakelock:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: wakelock
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.1+1"
|
||||
wakelock_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: wakelock_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.0+1"
|
||||
wakelock_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: wakelock_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.0+3"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 0.6.6+1
|
||||
version: 0.6.7+1
|
||||
|
||||
environment:
|
||||
sdk: ">=2.8.0 <3.0.0"
|
||||
|
@ -74,6 +74,7 @@ dependencies:
|
|||
scrobblenaut: ^2.0.4
|
||||
open_file: ^3.0.3
|
||||
version: ^1.2.0
|
||||
wakelock: ^0.2.1+1
|
||||
|
||||
audio_session: ^0.0.9
|
||||
audio_service:
|
||||
|
|
Loading…
Reference in New Issue