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,
|
PictureTypes.DEFAULT_ID,
|
||||||
ImageFormats.MIME_TYPE_JPEG,
|
ImageFormats.MIME_TYPE_JPEG,
|
||||||
"cover",
|
"cover",
|
||||||
1400,
|
settings.albumArtResolution,
|
||||||
1400,
|
settings.albumArtResolution,
|
||||||
24,
|
24,
|
||||||
0
|
0
|
||||||
));
|
));
|
||||||
|
@ -489,7 +489,7 @@ public class DownloadService extends Service {
|
|||||||
File coverFile = new File(outFile.getPath().substring(0, outFile.getPath().lastIndexOf('.')) + ".jpg");
|
File coverFile = new File(outFile.getPath().substring(0, outFile.getPath().lastIndexOf('.')) + ".jpg");
|
||||||
|
|
||||||
try {
|
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();
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
//Set headers
|
//Set headers
|
||||||
connection.setRequestMethod("GET");
|
connection.setRequestMethod("GET");
|
||||||
@ -568,7 +568,7 @@ public class DownloadService extends Service {
|
|||||||
//Create to lock
|
//Create to lock
|
||||||
coverFile.createNewFile();
|
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();
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
//Set headers
|
//Set headers
|
||||||
connection.setRequestMethod("GET");
|
connection.setRequestMethod("GET");
|
||||||
@ -808,8 +808,9 @@ public class DownloadService extends Service {
|
|||||||
boolean albumCover;
|
boolean albumCover;
|
||||||
boolean nomediaFiles;
|
boolean nomediaFiles;
|
||||||
String artistSeparator;
|
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.downloadThreads = downloadThreads;
|
||||||
this.overwriteDownload = overwriteDownload;
|
this.overwriteDownload = overwriteDownload;
|
||||||
this.downloadLyrics = downloadLyrics;
|
this.downloadLyrics = downloadLyrics;
|
||||||
@ -818,6 +819,7 @@ public class DownloadService extends Service {
|
|||||||
this.albumCover = albumCover;
|
this.albumCover = albumCover;
|
||||||
this.nomediaFiles = nomediaFiles;
|
this.nomediaFiles = nomediaFiles;
|
||||||
this.artistSeparator = artistSeparator;
|
this.artistSeparator = artistSeparator;
|
||||||
|
this.albumArtResolution = albumArtResolution;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Parse settings from bundle sent from UI
|
//Parse settings from bundle sent from UI
|
||||||
@ -833,7 +835,8 @@ public class DownloadService extends Service {
|
|||||||
json.getString("arl"),
|
json.getString("arl"),
|
||||||
json.getBoolean("albumCover"),
|
json.getBoolean("albumCover"),
|
||||||
json.getBoolean("nomediaFiles"),
|
json.getBoolean("nomediaFiles"),
|
||||||
json.getString("artistSeparator")
|
json.getString("artistSeparator"),
|
||||||
|
json.getInt("albumArtResolution")
|
||||||
);
|
);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
//Shouldn't happen
|
//Shouldn't happen
|
||||||
|
@ -51,6 +51,9 @@ class Cache {
|
|||||||
@JsonKey(defaultValue: 0)
|
@JsonKey(defaultValue: 0)
|
||||||
int lastUpdateCheck;
|
int lastUpdateCheck;
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
bool wakelock = false;
|
||||||
|
|
||||||
Cache({this.libraryTracks});
|
Cache({this.libraryTracks});
|
||||||
|
|
||||||
//Wrapper to test if track is favorite against cache
|
//Wrapper to test if track is favorite against cache
|
||||||
|
@ -392,7 +392,7 @@ class Playlist {
|
|||||||
id: json['PLAYLIST_ID'].toString(),
|
id: json['PLAYLIST_ID'].toString(),
|
||||||
title: json['TITLE'],
|
title: json['TITLE'],
|
||||||
trackCount: json['NB_SONG']??songsJson['total'],
|
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'],
|
fans: json['NB_FAN'],
|
||||||
duration: Duration(seconds: json['DURATION']??0),
|
duration: Duration(seconds: json['DURATION']??0),
|
||||||
description: json['DESCRIPTION'],
|
description: json['DESCRIPTION'],
|
||||||
@ -464,7 +464,7 @@ class ImageDetails {
|
|||||||
|
|
||||||
//JSON
|
//JSON
|
||||||
factory ImageDetails.fromPrivateString(String art, {String type='cover'}) => ImageDetails(
|
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'
|
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(
|
factory ImageDetails.fromPrivateJson(Map<dynamic, dynamic> json) => ImageDetails.fromPrivateString(
|
||||||
@ -481,22 +481,28 @@ class SearchResults {
|
|||||||
List<Album> albums;
|
List<Album> albums;
|
||||||
List<Artist> artists;
|
List<Artist> artists;
|
||||||
List<Playlist> playlists;
|
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
|
//Check if no search results
|
||||||
bool get empty {
|
bool get empty {
|
||||||
return ((tracks == null || tracks.length == 0) &&
|
return ((tracks == null || tracks.length == 0) &&
|
||||||
(albums == null || albums.length == 0) &&
|
(albums == null || albums.length == 0) &&
|
||||||
(artists == null || artists.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(
|
factory SearchResults.fromPrivateJson(Map<dynamic, dynamic> json) => SearchResults(
|
||||||
tracks: json['TRACK']['data'].map<Track>((dynamic data) => Track.fromPrivateJson(data)).toList(),
|
tracks: json['TRACK']['data'].map<Track>((dynamic data) => Track.fromPrivateJson(data)).toList(),
|
||||||
albums: json['ALBUM']['data'].map<Album>((dynamic data) => Album.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(),
|
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;
|
String url;
|
||||||
Duration duration;
|
Duration duration;
|
||||||
String publishedDate;
|
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')}";
|
String get durationString => "${duration.inMinutes}:${duration.inSeconds.remainder(60).toString().padLeft(2, '0')}";
|
||||||
|
|
||||||
@ -917,7 +925,7 @@ class ShowEpisode {
|
|||||||
},
|
},
|
||||||
displayDescription: description,
|
displayDescription: description,
|
||||||
duration: duration,
|
duration: duration,
|
||||||
artUri: show.art.full
|
artUri: show.art.full,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
factory ShowEpisode.fromMediaItem(MediaItem mi) {
|
factory ShowEpisode.fromMediaItem(MediaItem mi) {
|
||||||
@ -927,6 +935,7 @@ class ShowEpisode {
|
|||||||
description: mi.displayDescription,
|
description: mi.displayDescription,
|
||||||
url: mi.extras['showUrl'],
|
url: mi.extras['showUrl'],
|
||||||
duration: mi.duration,
|
duration: mi.duration,
|
||||||
|
show: Show.fromPrivateJson(mi.extras['show'])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -937,7 +946,8 @@ class ShowEpisode {
|
|||||||
description: json['EPISODE_DESCRIPTION'],
|
description: json['EPISODE_DESCRIPTION'],
|
||||||
url: json['EPISODE_DIRECT_STREAM_URL'],
|
url: json['EPISODE_DIRECT_STREAM_URL'],
|
||||||
duration: Duration(seconds: int.parse(json['DURATION'].toString())),
|
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);
|
factory ShowEpisode.fromJson(Map<String, dynamic> json) => _$ShowEpisodeFromJson(json);
|
||||||
|
@ -453,6 +453,9 @@ ShowEpisode _$ShowEpisodeFromJson(Map<String, dynamic> json) {
|
|||||||
? null
|
? null
|
||||||
: Duration(microseconds: json['duration'] as int),
|
: Duration(microseconds: json['duration'] as int),
|
||||||
publishedDate: json['publishedDate'] as String,
|
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,
|
'url': instance.url,
|
||||||
'duration': instance.duration?.inMicroseconds,
|
'duration': instance.duration?.inMicroseconds,
|
||||||
'publishedDate': instance.publishedDate,
|
'publishedDate': instance.publishedDate,
|
||||||
|
'show': instance.show,
|
||||||
};
|
};
|
||||||
|
@ -45,6 +45,7 @@ class PlayerHelper {
|
|||||||
if (event['action'] == 'onRestore') {
|
if (event['action'] == 'onRestore') {
|
||||||
//Load queueSource from isolate
|
//Load queueSource from isolate
|
||||||
this.queueSource = QueueSource.fromJson(event['queueSource']);
|
this.queueSource = QueueSource.fromJson(event['queueSource']);
|
||||||
|
repeatType = LoopMode.values[event['loopMode']];
|
||||||
}
|
}
|
||||||
if (event['action'] == 'queueEnd') {
|
if (event['action'] == 'queueEnd') {
|
||||||
//If last song is played, load more queue
|
//If last song is played, load more queue
|
||||||
@ -332,6 +333,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
int wifiQuality;
|
int wifiQuality;
|
||||||
QueueSource queueSource;
|
QueueSource queueSource;
|
||||||
Duration _lastPosition;
|
Duration _lastPosition;
|
||||||
|
LoopMode _loopMode = LoopMode.off;
|
||||||
|
|
||||||
Completer _androidAutoCallback;
|
Completer _androidAutoCallback;
|
||||||
|
|
||||||
@ -439,6 +441,19 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
@override
|
@override
|
||||||
Future<void> onSeekBackward(bool begin) async => _seekContinuously(begin, -1);
|
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
|
@override
|
||||||
Future<void> onSkipToNext() async {
|
Future<void> onSkipToNext() async {
|
||||||
if (_queueIndex == _queue.length-1) return;
|
if (_queueIndex == _queue.length-1) return;
|
||||||
@ -630,7 +645,8 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
}
|
}
|
||||||
//Looping
|
//Looping
|
||||||
if (name == 'repeatType') {
|
if (name == 'repeatType') {
|
||||||
_player.setLoopMode(LoopMode.values[args]);
|
_loopMode = LoopMode.values[args];
|
||||||
|
_player.setLoopMode(_loopMode);
|
||||||
}
|
}
|
||||||
if (name == 'saveQueue')
|
if (name == 'saveQueue')
|
||||||
await this._saveQueue();
|
await this._saveQueue();
|
||||||
@ -708,6 +724,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
'queue': _queue.map<Map<String, dynamic>>((mi) => mi.toJson()).toList(),
|
'queue': _queue.map<Map<String, dynamic>>((mi) => mi.toJson()).toList(),
|
||||||
'position': _player.position.inMilliseconds,
|
'position': _player.position.inMilliseconds,
|
||||||
'queueSource': (queueSource??QueueSource()).toJson(),
|
'queueSource': (queueSource??QueueSource()).toJson(),
|
||||||
|
'loopMode': LoopMode.values.indexOf(_loopMode??LoopMode.off)
|
||||||
};
|
};
|
||||||
await f.writeAsString(jsonEncode(data));
|
await f.writeAsString(jsonEncode(data));
|
||||||
}
|
}
|
||||||
@ -721,6 +738,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
this._queueIndex = json['index'] ?? 0;
|
this._queueIndex = json['index'] ?? 0;
|
||||||
this._lastPosition = Duration(milliseconds: json['position']??0);
|
this._lastPosition = Duration(milliseconds: json['position']??0);
|
||||||
this.queueSource = QueueSource.fromJson(json['queueSource']??{});
|
this.queueSource = QueueSource.fromJson(json['queueSource']??{});
|
||||||
|
this._loopMode = LoopMode.values[(json['loopMode']??0)];
|
||||||
//Restore queue
|
//Restore queue
|
||||||
if (_queue != null) {
|
if (_queue != null) {
|
||||||
await AudioServiceBackground.setQueue(_queue);
|
await AudioServiceBackground.setQueue(_queue);
|
||||||
@ -731,7 +749,8 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
//Send restored queue source to ui
|
//Send restored queue source to ui
|
||||||
AudioServiceBackground.sendCustomEvent({
|
AudioServiceBackground.sendCustomEvent({
|
||||||
'action': 'onRestore',
|
'action': 'onRestore',
|
||||||
'queueSource': (queueSource??QueueSource()).toJson()
|
'queueSource': (queueSource??QueueSource()).toJson(),
|
||||||
|
'loopMode': LoopMode.values.indexOf(_loopMode)
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
@ -297,6 +297,16 @@ const language_en_us = {
|
|||||||
//0.6.6
|
//0.6.6
|
||||||
"Restart of app is required to properly log out!": "Restart of app is required to properly log out!",
|
"Restart of app is required to properly log out!": "Restart of app is required to properly log out!",
|
||||||
"Artist separator": "Artist separator",
|
"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;
|
String artistSeparator;
|
||||||
@JsonKey(defaultValue: "%artist% - %title%")
|
@JsonKey(defaultValue: "%artist% - %title%")
|
||||||
String singletonFilename;
|
String singletonFilename;
|
||||||
|
@JsonKey(defaultValue: 1400)
|
||||||
|
int albumArtResolution;
|
||||||
|
|
||||||
//Appearance
|
//Appearance
|
||||||
@JsonKey(defaultValue: Themes.Dark)
|
@JsonKey(defaultValue: Themes.Dark)
|
||||||
|
@ -40,6 +40,7 @@ Settings _$SettingsFromJson(Map<String, dynamic> json) {
|
|||||||
..artistSeparator = json['artistSeparator'] as String ?? ', '
|
..artistSeparator = json['artistSeparator'] as String ?? ', '
|
||||||
..singletonFilename =
|
..singletonFilename =
|
||||||
json['singletonFilename'] as String ?? '%artist% - %title%'
|
json['singletonFilename'] as String ?? '%artist% - %title%'
|
||||||
|
..albumArtResolution = json['albumArtResolution'] as int ?? 1400
|
||||||
..theme =
|
..theme =
|
||||||
_$enumDecodeNullable(_$ThemesEnumMap, json['theme']) ?? Themes.Dark
|
_$enumDecodeNullable(_$ThemesEnumMap, json['theme']) ?? Themes.Dark
|
||||||
..useSystemTheme = json['useSystemTheme'] as bool ?? false
|
..useSystemTheme = json['useSystemTheme'] as bool ?? false
|
||||||
@ -76,6 +77,7 @@ Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
|
|||||||
'nomediaFiles': instance.nomediaFiles,
|
'nomediaFiles': instance.nomediaFiles,
|
||||||
'artistSeparator': instance.artistSeparator,
|
'artistSeparator': instance.artistSeparator,
|
||||||
'singletonFilename': instance.singletonFilename,
|
'singletonFilename': instance.singletonFilename,
|
||||||
|
'albumArtResolution': instance.albumArtResolution,
|
||||||
'theme': _$ThemesEnumMap[instance.theme],
|
'theme': _$ThemesEnumMap[instance.theme],
|
||||||
'useSystemTheme': instance.useSystemTheme,
|
'useSystemTheme': instance.useSystemTheme,
|
||||||
'colorGradientBackground': instance.colorGradientBackground,
|
'colorGradientBackground': instance.colorGradientBackground,
|
||||||
|
@ -28,6 +28,8 @@ const supportedLocales = [
|
|||||||
const Locale('sk', 'SK'),
|
const Locale('sk', 'SK'),
|
||||||
const Locale('cs', 'CZ'),
|
const Locale('cs', 'CZ'),
|
||||||
const Locale('vi', 'VI'),
|
const Locale('vi', 'VI'),
|
||||||
|
const Locale('nl', 'NL'),
|
||||||
|
const Locale('sl', 'SL'),
|
||||||
const Locale('fil', 'PH'),
|
const Locale('fil', 'PH'),
|
||||||
const Locale('uwu', 'UWU')
|
const Locale('uwu', 'UWU')
|
||||||
];
|
];
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:wakelock/wakelock.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.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();
|
void _close() => Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,7 +280,7 @@ class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(
|
Container(
|
||||||
height: ScreenUtil().setSp(64),
|
height: ScreenUtil().setSp(64),
|
||||||
child: AudioService.currentMediaItem.displayTitle.length >= 24 ?
|
child: AudioService.currentMediaItem.displayTitle.length >= 26 ?
|
||||||
Marquee(
|
Marquee(
|
||||||
text: AudioService.currentMediaItem.displayTitle,
|
text: AudioService.currentMediaItem.displayTitle,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -410,12 +410,12 @@ class PlayerMenuButton extends StatelessWidget {
|
|||||||
Track t = Track.fromMediaItem(AudioService.currentMediaItem);
|
Track t = Track.fromMediaItem(AudioService.currentMediaItem);
|
||||||
MenuSheet m = MenuSheet(context);
|
MenuSheet m = MenuSheet(context);
|
||||||
if (AudioService.currentMediaItem.extras['show'] == null)
|
if (AudioService.currentMediaItem.extras['show'] == null)
|
||||||
m.defaultTrackMenu(t, options: [m.sleepTimer()]);
|
m.defaultTrackMenu(t, options: [m.sleepTimer(), m.wakelock()]);
|
||||||
else
|
else
|
||||||
m.defaultShowEpisodeMenu(
|
m.defaultShowEpisodeMenu(
|
||||||
Show.fromJson(jsonDecode(AudioService.currentMediaItem.extras['show'])),
|
Show.fromJson(jsonDecode(AudioService.currentMediaItem.extras['show'])),
|
||||||
ShowEpisode.fromMediaItem(AudioService.currentMediaItem),
|
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();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
key: Key(t.id),
|
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/cache.dart';
|
||||||
import 'package:freezer/api/download.dart';
|
import 'package:freezer/api/download.dart';
|
||||||
import 'package:freezer/api/player.dart';
|
import 'package:freezer/api/player.dart';
|
||||||
|
import 'package:freezer/main.dart';
|
||||||
import 'package:freezer/ui/details_screens.dart';
|
import 'package:freezer/ui/details_screens.dart';
|
||||||
import 'package:freezer/ui/elements.dart';
|
import 'package:freezer/ui/elements.dart';
|
||||||
import 'package:freezer/ui/home_screen.dart';
|
import 'package:freezer/ui/home_screen.dart';
|
||||||
@ -657,6 +658,91 @@ class SearchResultsScreen extends StatelessWidget {
|
|||||||
MaterialPageRoute(builder: (context) => SearchResultPlaylists(results.playlists))
|
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,),
|
Container(height: 8.0,),
|
||||||
...artists,
|
...artists,
|
||||||
Container(height: 8.0,),
|
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)
|
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(
|
ListTile(
|
||||||
title: Text('Create .nomedia files'.i18n),
|
title: Text('Create .nomedia files'.i18n),
|
||||||
subtitle: Text('To prevent gallery being filled with album art'.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),
|
title: Text('Artist separator'.i18n),
|
||||||
leading: Icon(WebSymbols.tag),
|
leading: Icon(WebSymbols.tag),
|
||||||
trailing: Container(
|
trailing: Container(
|
||||||
width: 100.0,
|
width: 75.0,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _artistSeparatorController,
|
controller: _artistSeparatorController,
|
||||||
onChanged: (s) async {
|
onChanged: (s) async {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fluttericon/octicons_icons.dart';
|
||||||
import 'package:freezer/api/deezer.dart';
|
import 'package:freezer/api/deezer.dart';
|
||||||
|
import 'package:freezer/api/download.dart';
|
||||||
import 'package:freezer/translations.i18n.dart';
|
import 'package:freezer/translations.i18n.dart';
|
||||||
|
|
||||||
import '../api/definitions.dart';
|
import '../api/definitions.dart';
|
||||||
@ -25,6 +27,7 @@ class TrackTile extends StatefulWidget {
|
|||||||
class _TrackTileState extends State<TrackTile> {
|
class _TrackTileState extends State<TrackTile> {
|
||||||
|
|
||||||
StreamSubscription _subscription;
|
StreamSubscription _subscription;
|
||||||
|
bool _isOffline = false;
|
||||||
|
|
||||||
bool get nowPlaying {
|
bool get nowPlaying {
|
||||||
if (AudioService.currentMediaItem == null) return false;
|
if (AudioService.currentMediaItem == null) return false;
|
||||||
@ -37,6 +40,9 @@ class _TrackTileState extends State<TrackTile> {
|
|||||||
_subscription = AudioService.currentMediaItemStream.listen((event) {
|
_subscription = AudioService.currentMediaItemStream.listen((event) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
});
|
});
|
||||||
|
//Check if offline
|
||||||
|
downloadManager.checkOffline(track: widget.track).then((b) => setState(() => _isOffline = b));
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,9 +76,18 @@ class _TrackTileState extends State<TrackTile> {
|
|||||||
trailing: Row(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
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)
|
if (widget.track.explicit??false)
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 4.0),
|
padding: EdgeInsets.symmetric(horizontal: 2.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
'E',
|
'E',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -80,9 +95,12 @@ class _TrackTileState extends State<TrackTile> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 2.0),
|
width: 42.0,
|
||||||
child: Text(widget.track.durationString),
|
child: Text(
|
||||||
|
widget.track.durationString,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
widget.trailing??Container(width: 0, height: 0)
|
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 {
|
class ShowEpisodeTile extends StatelessWidget {
|
||||||
|
|
||||||
final ShowEpisode episode;
|
final ShowEpisode episode;
|
||||||
|
28
pubspec.lock
28
pubspec.lock
@ -462,6 +462,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.5"
|
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:
|
infinite_listview:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -985,6 +992,27 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
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:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
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.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 0.6.6+1
|
version: 0.6.7+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.8.0 <3.0.0"
|
sdk: ">=2.8.0 <3.0.0"
|
||||||
@ -74,6 +74,7 @@ dependencies:
|
|||||||
scrobblenaut: ^2.0.4
|
scrobblenaut: ^2.0.4
|
||||||
open_file: ^3.0.3
|
open_file: ^3.0.3
|
||||||
version: ^1.2.0
|
version: ^1.2.0
|
||||||
|
wakelock: ^0.2.1+1
|
||||||
|
|
||||||
audio_session: ^0.0.9
|
audio_session: ^0.0.9
|
||||||
audio_service:
|
audio_service:
|
||||||
|
Loading…
Reference in New Issue
Block a user