0.6.12 - bug fixes

This commit is contained in:
exttex 2021-07-02 18:28:59 +02:00
parent aa7f82b399
commit 972e84c136
18 changed files with 271 additions and 150 deletions

View File

@ -214,7 +214,13 @@ public class Deezer {
original = original.replaceAll("%title%", sanitize(publicTrack.getString("title")));
original = original.replaceAll("%album%", sanitize(publicTrack.getJSONObject("album").getString("title")));
original = original.replaceAll("%artist%", sanitize(publicTrack.getJSONObject("artist").getString("name")));
original = original.replaceAll("%albumArtist%", sanitize(publicAlbum.getJSONObject("artist").getString("name")));
// Album might not be available
try {
original = original.replaceAll("%albumArtist%", sanitize(publicAlbum.getJSONObject("artist").getString("name")));
} catch (Exception e) {
original = original.replaceAll("%albumArtist%", sanitize(publicTrack.getJSONObject("artist").getString("name")));
}
//Artists
String artists = "";
String feats = "";
@ -275,15 +281,16 @@ public class Deezer {
if (!artists.contains(artist))
artists += settings.artistSeparator + artist;
}
boolean albumAvailable = !publicAlbum.has("error");
if (settings.tags.artist) tag.addField(FieldKey.ARTIST, artists.substring(settings.artistSeparator.length()));
if (settings.tags.track) tag.setField(FieldKey.TRACK, String.format("%02d", publicTrack.getInt("track_position")));
if (settings.tags.disc) tag.setField(FieldKey.DISC_NO, Integer.toString(publicTrack.getInt("disk_number")));
if (settings.tags.albumArtist) tag.setField(FieldKey.ALBUM_ARTIST, publicAlbum.getJSONObject("artist").getString("name"));
if (settings.tags.albumArtist && albumAvailable) tag.setField(FieldKey.ALBUM_ARTIST, publicAlbum.getJSONObject("artist").getString("name"));
if (settings.tags.date) tag.setField(FieldKey.YEAR, publicTrack.getString("release_date").substring(0, 4));
if (settings.tags.label) tag.setField(FieldKey.RECORD_LABEL, publicAlbum.getString("label"));
if (settings.tags.label && albumAvailable) tag.setField(FieldKey.RECORD_LABEL, publicAlbum.getString("label"));
if (settings.tags.isrc) tag.setField(FieldKey.ISRC, publicTrack.getString("isrc"));
if (settings.tags.upc) tag.setField(FieldKey.BARCODE, publicAlbum.getString("upc"));
if (settings.tags.trackTotal) tag.setField(FieldKey.TRACK_TOTAL, Integer.toString(publicAlbum.getInt("nb_tracks")));
if (settings.tags.upc && albumAvailable) tag.setField(FieldKey.BARCODE, publicAlbum.getString("upc"));
if (settings.tags.trackTotal && albumAvailable) tag.setField(FieldKey.TRACK_TOTAL, Integer.toString(publicAlbum.getInt("nb_tracks")));
//BPM
if (publicTrack.has("bpm") && (int)publicTrack.getDouble("bpm") > 0)
@ -301,14 +308,16 @@ public class Deezer {
//Genres
String genres = "";
for (int i=0; i<publicAlbum.getJSONObject("genres").getJSONArray("data").length(); i++) {
String genre = publicAlbum.getJSONObject("genres").getJSONArray("data").getJSONObject(0).getString("name");
if (!genres.contains(genre)) {
genres += ", " + genre;
if (albumAvailable) {
for (int i=0; i<publicAlbum.getJSONObject("genres").getJSONArray("data").length(); i++) {
String genre = publicAlbum.getJSONObject("genres").getJSONArray("data").getJSONObject(0).getString("name");
if (!genres.contains(genre)) {
genres += ", " + genre;
}
}
if (genres.length() > 2 && settings.tags.genre)
tag.setField(FieldKey.GENRE, genres.substring(2));
}
if (genres.length() > 2 && settings.tags.genre)
tag.setField(FieldKey.GENRE, genres.substring(2));
//Additional tags from private api
if (settings.tags.contributors)

View File

@ -478,7 +478,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/" + albumJson.getString("md5_image") + "/" + Integer.toString(settings.albumArtResolution) + "x" + Integer.toString(settings.albumArtResolution) + "-000000-80-0-0.jpg");
URL url = new URL("http://e-cdn-images.deezer.com/images/cover/" + trackJson.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");

View File

@ -1,15 +1,11 @@
import 'dart:async';
import 'package:freezer/api/deezer.dart';
import 'package:freezer/api/definitions.dart';
import 'package:freezer/ui/details_screens.dart';
import 'package:freezer/ui/library.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
import 'dart:io';
import 'dart:convert';
import 'dart:async';
part 'cache.g.dart';
@ -68,6 +64,12 @@ class Cache {
if (searchHistory == null)
searchHistory = [];
// Remove duplicate
int i = searchHistory.indexWhere((e) => e.data.id == item.id);
if (i != -1) {
searchHistory.removeAt(i);
}
if (item is Track)
searchHistory.add(SearchHistoryItem(item, SearchHistoryItemType.TRACK));
if (item is Album)

View File

@ -32,7 +32,6 @@ class PlayerHelper {
QueueSource queueSource;
LoopMode repeatType = LoopMode.off;
Timer _timer;
Scrobblenaut scrobblenaut;
int audioSession;
int _prevAudioSession;
bool equalizerOpen = false;
@ -53,6 +52,7 @@ class PlayerHelper {
//After audio_service is loaded, load queue, set quality
await settings.updateAudioServiceQuality();
await AudioService.customAction('load');
await authorizeLastFM();
break;
case 'onRestore':
//Load queueSource from isolate
@ -126,15 +126,6 @@ class PlayerHelper {
if (settings.logListen) {
deezerAPI.logListen(AudioService.currentMediaItem.id);
}
//LastFM
if (scrobblenaut != null) {
await scrobblenaut.track.scrobble(
track: AudioService.currentMediaItem.title,
artist: AudioService.currentMediaItem.artist,
album: AudioService.currentMediaItem.album,
);
}
}
});
@ -163,15 +154,7 @@ class PlayerHelper {
Future authorizeLastFM() async {
if (settings.lastFMUsername == null || settings.lastFMPassword == null) return;
try {
LastFM lastFM = await LastFM.authenticateWithPasswordHash(
apiKey: 'b6ab5ae967bcd8b10b23f68f42493829',
apiSecret: '861b0dff9a8a574bec747f9dab8b82bf',
username: settings.lastFMUsername,
passwordHash: settings.lastFMPassword
);
scrobblenaut = Scrobblenaut(lastFM: lastFM);
} catch (e) {}
await AudioService.customAction("authorizeLastFM", [settings.lastFMUsername, settings.lastFMPassword]);
}
Future toggleShuffle() async {
@ -390,6 +373,10 @@ class AudioPlayerTask extends BackgroundAudioTask {
LoopMode _loopMode = LoopMode.off;
Completer _androidAutoCallback;
Scrobblenaut _scrobblenaut;
bool _scrobblenautReady = false;
// Last logged track id
String _loggedTrackId;
MediaItem get mediaItem => _queue[_queueIndex];
@ -473,13 +460,23 @@ class AudioPlayerTask extends BackgroundAudioTask {
}
@override
Future onPlay() {
Future onPlay() async {
_player.play();
//Restore position on play
if (_lastPosition != null) {
onSeekTo(_lastPosition);
_lastPosition = null;
}
//LastFM
if (_scrobblenautReady && mediaItem.id != _loggedTrackId) {
_loggedTrackId = mediaItem.id;
await _scrobblenaut.track.scrobble(
track: mediaItem.title,
artist: mediaItem.artist,
album: mediaItem.album,
);
}
}
@override
@ -515,6 +512,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
@override
Future<void> onSkipToNext() async {
_lastPosition = null;
if (_queueIndex == _queue.length-1) return;
//Update buffering state
_skipState = AudioProcessingState.skippingToNext;
@ -618,6 +616,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
//Replace current queue
@override
Future onUpdateQueue(List<MediaItem> q) async {
_lastPosition = null;
//just_audio
_shuffle = false;
_originalQueue = null;
@ -793,6 +792,25 @@ class AudioPlayerTask extends BackgroundAudioTask {
_visualizerSubscription = null;
}
break;
//Authorize lastfm
case 'authorizeLastFM':
String username = args[0];
String password = args[1];
try {
LastFM lastFM = await LastFM.authenticateWithPasswordHash(
apiKey: 'b6ab5ae967bcd8b10b23f68f42493829',
apiSecret: '861b0dff9a8a574bec747f9dab8b82bf',
username: username,
passwordHash: password
);
_scrobblenaut = Scrobblenaut(lastFM: lastFM);
_scrobblenautReady = true;
} catch (e) { print(e); }
break;
case 'disableLastFM':
_scrobblenaut = null;
_scrobblenautReady = false;
break;
}
return true;

View File

@ -378,6 +378,35 @@ const language_en_us = {
//0.6.11, offline text OCD lol
"Track removed from offline!": "Track removed from offline!",
"Removed album from offline!": "Removed album from offline!",
"Playlist removed from offline!": "Playlist removed from offline!"
"Playlist removed from offline!": "Playlist removed from offline!",
//0.6.11 - a11y by dangou
"Repeat": "Repeat",
"Repeat one": "Repeat one",
"Repeat off": "Repeat off",
"Love": "Love",
"Unlove": "Unlove",
"Dislike": "Dislike",
"Close": "Close",
"Sort playlist": "Sort playlist",
"Sort ascending": "Sort ascending",
"Sort descending": "Sort descending",
"Stop": "Stop",
"Start": "Start",
"Clear all": "Clear all",
"Play previous": "Play previous",
"Play": "Play",
"Pause": "Pause",
"Remove": "Remove",
"Seekbar": "Seekbar",
"Singles": "Singles",
"Featured": "Featured",
"Fans": "Fans",
"Duration": "Duration",
"Sort": "Sort",
//0.6.12
"Your ARL might be expired, try logging out and logging back in using new ARL or browser.": "Your ARL might be expired, try logging out and logging back in using new ARL or browser.",
}
};

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:palette_generator/palette_generator.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:photo_view/photo_view.dart';
import 'package:freezer/translations.i18n.dart';
ImagesDatabase imagesDatabase = ImagesDatabase();
@ -115,11 +116,14 @@ class _ZoomableImageState extends State<ZoomableImage> {
Widget build(BuildContext context) {
ctx = context;
return TextButton(
child: CachedImage(
url: widget.url,
rounded: widget.rounded,
width: widget.width,
fullThumb: true,
child: Semantics(
child: CachedImage(
url: widget.url,
rounded: widget.rounded,
width: widget.width,
fullThumb: true,
),
label: "Album art".i18n,
),
onPressed: () {
Navigator.of(context).push(PageRouteBuilder(

View File

@ -127,7 +127,7 @@ class _AlbumDetailsState extends State<AlbumDetails> {
children: <Widget>[
Row(
children: <Widget>[
Icon(Icons.audiotrack, size: 32.0,),
Icon(Icons.audiotrack, size: 32.0, semanticLabel: "Tracks".i18n,),
Container(width: 8.0, height: 42.0,), //Height to adjust card height
Text(
album.tracks.length.toString(),
@ -137,7 +137,7 @@ class _AlbumDetailsState extends State<AlbumDetails> {
),
Row(
children: <Widget>[
Icon(Icons.timelapse, size: 32.0,),
Icon(Icons.timelapse, size: 32.0, semanticLabel: "Duration".i18n,),
Container(width: 8.0,),
Text(
album.durationString,
@ -147,7 +147,7 @@ class _AlbumDetailsState extends State<AlbumDetails> {
),
Row(
children: <Widget>[
Icon(Icons.people, size: 32.0,),
Icon(Icons.people, size: 32.0, semanticLabel: "Fans".i18n),
Container(width: 8.0,),
Text(
album.fansString,
@ -361,6 +361,7 @@ class ArtistDetails extends StatelessWidget {
Icon(
Icons.people,
size: 32.0,
semanticLabel: "Fans".i18n,
),
Container(
width: 8,
@ -377,7 +378,7 @@ class ArtistDetails extends StatelessWidget {
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.album, size: 32.0),
Icon(Icons.album, size: 32.0, semanticLabel: "Albums".i18n,),
Container(
width: 8.0,
),
@ -666,9 +667,9 @@ class _DiscographyScreenState extends State<DiscographyScreen> {
'Discography'.i18n,
bottom: TabBar(
tabs: [
Tab(icon: Icon(Icons.album)),
Tab(icon: Icon(Icons.audiotrack)),
Tab(icon: Icon(Icons.recent_actors))
Tab(icon: Icon(Icons.album, semanticLabel: "Albums".i18n,)),
Tab(icon: Icon(Icons.audiotrack, semanticLabel: "Singles".i18n)),
Tab(icon: Icon(Icons.recent_actors, semanticLabel: "Featured".i18n,))
],
),
height: 100.0,
@ -894,6 +895,7 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
Icon(
Icons.audiotrack,
size: 32.0,
semanticLabel: "Tracks".i18n,
),
Container(width: 8.0,),
Text((playlist.trackCount??playlist.tracks.length).toString(), style: TextStyle(fontSize: 16),)
@ -905,6 +907,7 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
Icon(
Icons.timelapse,
size: 32.0,
semanticLabel: "Duration".i18n,
),
Container(width: 8.0,),
Text(playlist.durationString, style: TextStyle(fontSize: 16),)
@ -943,7 +946,8 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
if (playlist.user.name != deezerAPI.userName)
IconButton(
icon: Icon(playlist.library ? Icons.favorite : Icons.favorite_outline, size: 32),
icon: Icon(playlist.library ? Icons.favorite : Icons.favorite_outline, size: 32,
semanticLabel: playlist.library ? "Unlove".i18n : "Love".i18n,),
onPressed: () async {
//Add to library
if (!playlist.library) {
@ -968,14 +972,14 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
},
),
IconButton(
icon: Icon(Icons.file_download, size: 32.0,),
icon: Icon(Icons.file_download, size: 32.0, semanticLabel: "Download".i18n,),
onPressed: () async {
if (await downloadManager.addOfflinePlaylist(playlist, private: false, context: context) != false)
MenuSheet(context).showDownloadStartedToast();
},
),
PopupMenuButton(
child: Icon(Icons.sort, size: 32.0),
child: Icon(Icons.sort, size: 32.0, semanticLabel: "Sort playlist".i18n,),
color: Theme.of(context).scaffoldBackgroundColor,
onSelected: (SortType s) async {
if (playlist.tracks.length < playlist.trackCount) {
@ -1013,7 +1017,8 @@ class _PlaylistDetailsState extends State<PlaylistDetails> {
],
),
IconButton(
icon: Icon(_sort.reverse ? FontAwesome5.sort_alpha_up : FontAwesome5.sort_alpha_down),
icon: Icon(_sort.reverse ? FontAwesome5.sort_alpha_up : FontAwesome5.sort_alpha_down,
semanticLabel: _sort.reverse ? "Sort descending".i18n : "Sort ascending".i18n,),
onPressed: () => _reverse(),
),
Container(width: 4.0)
@ -1230,7 +1235,7 @@ class _ShowScreenState extends State<ShowScreen> {
return ShowEpisodeTile(
e,
trailing: IconButton(
icon: Icon(Icons.more_vert),
icon: Icon(Icons.more_vert, semanticLabel: "Options".i18n,),
onPressed: () {
MenuSheet m = MenuSheet(context);
m.defaultShowEpisodeMenu(_show, e);

View File

@ -74,7 +74,7 @@ class _DownloadsScreenState extends State<DownloadsScreen> {
'Downloads'.i18n,
actions: [
IconButton(
icon: Icon(Icons.delete_sweep),
icon: Icon(Icons.delete_sweep, semanticLabel: "Clear all".i18n,),
onPressed: () async {
await downloadManager.removeDownloads(DownloadState.ERROR);
await downloadManager.removeDownloads(DownloadState.DEEZER_ERROR);
@ -84,7 +84,8 @@ class _DownloadsScreenState extends State<DownloadsScreen> {
),
IconButton(
icon:
Icon(downloadManager.running ? Icons.stop : Icons.play_arrow),
Icon(downloadManager.running ? Icons.stop : Icons.play_arrow,
semanticLabel: downloadManager.running ? "Stop".i18n : "Start".i18n,),
onPressed: () {
setState(() {
if (downloadManager.running)

View File

@ -1,11 +1,36 @@
import 'package:connectivity/connectivity.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:freezer/translations.i18n.dart';
class ErrorScreen extends StatelessWidget {
int counter = 0;
class ErrorScreen extends StatefulWidget {
final String message;
const ErrorScreen({this.message, Key key}) : super(key: key);
ErrorScreen({this.message});
@override
_ErrorScreenState createState() => _ErrorScreenState();
}
class _ErrorScreenState extends State<ErrorScreen> {
bool checkArl = false;
@override
void initState() {
Connectivity().checkConnectivity().then((connectivity) {
if (connectivity != ConnectivityResult.none && counter > 3) {
setState(() {
checkArl = true;
});
}
});
counter += 1;
super.initState();
}
@override
Widget build(BuildContext context) {
@ -19,7 +44,18 @@ class ErrorScreen extends StatelessWidget {
size: 64.0,
),
Container(height: 4.0,),
Text(message ?? 'Please check your connection and try again later...'.i18n)
Text(widget.message ?? 'Please check your connection and try again later...'.i18n),
if (checkArl)
Padding(
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 32.0),
child: Text(
"Your ARL might be expired, try logging out and logging back in using new ARL or browser.".i18n,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12.0,
),
),
)
],
),
);

View File

@ -98,7 +98,7 @@ class _SpotifyImporterV1State extends State<SpotifyImporterV1> {
),
),
IconButton(
icon: Icon(Icons.search),
icon: Icon(Icons.search, semanticLabel: "Search".i18n,),
onPressed: () => _load(),
)
],

View File

@ -33,7 +33,7 @@ class LibraryAppBar extends StatelessWidget implements PreferredSizeWidget {
'Library'.i18n,
actions: <Widget>[
IconButton(
icon: Icon(Icons.file_download),
icon: Icon(Icons.file_download, semanticLabel: "Download".i18n,),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => DownloadsScreen())
@ -41,7 +41,7 @@ class LibraryAppBar extends StatelessWidget implements PreferredSizeWidget {
},
),
IconButton(
icon: Icon(Icons.settings),
icon: Icon(Icons.settings, semanticLabel: "Settings".i18n,),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => SettingsScreen())
@ -420,13 +420,14 @@ class _LibraryTracksState extends State<LibraryTracks> {
'Tracks'.i18n,
actions: [
IconButton(
icon: Icon(_sort.reverse ? FontAwesome5.sort_alpha_up : FontAwesome5.sort_alpha_down),
icon: Icon(_sort.reverse ? FontAwesome5.sort_alpha_up : FontAwesome5.sort_alpha_down,
semanticLabel: _sort.reverse ? "Sort descending".i18n : "Sort ascending".i18n,),
onPressed: () async {
await _reverse();
}
),
PopupMenuButton(
child: Icon(Icons.sort, size: 32.0),
child: Icon(Icons.sort, size: 32.0, semanticLabel: "Sort".i18n,),
color: Theme.of(context).scaffoldBackgroundColor,
onSelected: (SortType s) async {
//Preload for sorting
@ -631,7 +632,8 @@ class _LibraryAlbumsState extends State<LibraryAlbums> {
'Albums'.i18n,
actions: [
IconButton(
icon: Icon(_sort.reverse ? FontAwesome5.sort_alpha_up : FontAwesome5.sort_alpha_down),
icon: Icon(_sort.reverse ? FontAwesome5.sort_alpha_up : FontAwesome5.sort_alpha_down,
semanticLabel: _sort.reverse ? "Sort descending".i18n : "Sort ascending".i18n,),
onPressed: () => _reverse(),
),
PopupMenuButton(
@ -834,7 +836,8 @@ class _LibraryArtistsState extends State<LibraryArtists> {
'Artists'.i18n,
actions: [
IconButton(
icon: Icon(_sort.reverse ? FontAwesome5.sort_alpha_up : FontAwesome5.sort_alpha_down),
icon: Icon(_sort.reverse ? FontAwesome5.sort_alpha_up : FontAwesome5.sort_alpha_down,
semanticLabel: _sort.reverse ? "Sort descending".i18n : "Sort ascending".i18n,),
onPressed: () => _reverse(),
),
PopupMenuButton(
@ -995,7 +998,8 @@ class _LibraryPlaylistsState extends State<LibraryPlaylists> {
'Playlists'.i18n,
actions: [
IconButton(
icon: Icon(_sort.reverse ? FontAwesome5.sort_alpha_up : FontAwesome5.sort_alpha_down),
icon: Icon(_sort.reverse ? FontAwesome5.sort_alpha_up : FontAwesome5.sort_alpha_down,
semanticLabel: _sort.reverse ? "Sort descending".i18n : "Sort ascending".i18n,),
onPressed: () => _reverse(),
),
PopupMenuButton(
@ -1178,7 +1182,7 @@ class _HistoryScreenState extends State<HistoryScreen> {
'History'.i18n,
actions: [
IconButton(
icon: Icon(Icons.delete_sweep),
icon: Icon(Icons.delete_sweep, semanticLabel: "Clear all".i18n,),
onPressed: () {
setState(() => cache.history = []);
cache.save();

View File

@ -67,10 +67,14 @@ class MenuSheet {
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
CachedImage(
url: track.albumArt.full,
height: 128,
width: 128,
Semantics(
child: CachedImage(
url: track.albumArt.full,
height: 128,
width: 128,
),
label: "Album art".i18n,
image: true,
),
Container(
width: 240.0,

View File

@ -2,6 +2,7 @@ import 'package:audio_service/audio_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:freezer/settings.dart';
import 'package:freezer/translations.i18n.dart';
import '../api/player.dart';
import 'cached_image.dart';
@ -122,13 +123,13 @@ class PrevNextButton extends StatelessWidget {
if (!prev) {
if (playerHelper.queueIndex == (AudioService.queue??[]).length - 1) {
return IconButton(
icon: Icon(Icons.skip_next),
icon: Icon(Icons.skip_next, semanticLabel: "Play next".i18n,),
iconSize: size,
onPressed: null,
);
}
return IconButton(
icon: Icon(Icons.skip_next),
icon: Icon(Icons.skip_next, semanticLabel: "Play next".i18n,),
iconSize: size,
onPressed: () => AudioService.skipToNext(),
);
@ -139,13 +140,13 @@ class PrevNextButton extends StatelessWidget {
return Container(height: 0, width: 0,);
}
return IconButton(
icon: Icon(Icons.skip_previous),
icon: Icon(Icons.skip_previous, semanticLabel: "Play previous".i18n,),
iconSize: size,
onPressed: null,
);
}
return IconButton(
icon: Icon(Icons.skip_previous),
icon: Icon(Icons.skip_previous, semanticLabel: "Play previous".i18n,),
iconSize: size,
onPressed: () => AudioService.skipToPrevious(),
);
@ -203,6 +204,7 @@ class _PlayPauseButtonState extends State<PlayPauseButton> with SingleTickerProv
icon: AnimatedIcon(
icon: AnimatedIcons.play_pause,
progress: _animation,
semanticLabel: _playing ? "Pause".i18n : "Play".i18n,
),
iconSize: widget.size,
onPressed: _playing

View File

@ -263,7 +263,8 @@ class _PlayerScreenHorizontalState extends State<PlayerScreenHorizontal> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
icon: Icon(Icons.subtitles, size: ScreenUtil().setWidth(32)),
icon: Icon(Icons.subtitles, size: ScreenUtil().setWidth(32),
semanticLabel: "Lyrics".i18n,),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => LyricsScreen(trackId: AudioService.currentMediaItem.id)
@ -365,7 +366,8 @@ class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
icon: Icon(Icons.subtitles, size: ScreenUtil().setWidth(46)),
icon: Icon(Icons.subtitles, size: ScreenUtil().setWidth(46),
semanticLabel: "Lyrics".i18n,),
onPressed: () async {
//Fix bottom buttons
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
@ -381,7 +383,7 @@ class _PlayerScreenVerticalState extends State<PlayerScreenVertical> {
},
),
IconButton(
icon: Icon(Icons.file_download),
icon: Icon(Icons.file_download, semanticLabel: "Download".i18n,),
onPressed: () async {
Track t = Track.fromMediaItem(AudioService.currentMediaItem);
if (await downloadManager.addOfflineTrack(t, private: false, context: context, isSingleton: true) != false)
@ -466,7 +468,8 @@ class PlayerMenuButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.more_vert, size: ScreenUtil().setWidth(46)),
icon: Icon(Icons.more_vert, size: ScreenUtil().setWidth(46),
semanticLabel: "Options".i18n,),
onPressed: () {
Track t = Track.fromMediaItem(AudioService.currentMediaItem);
MenuSheet m = MenuSheet(context, navigateCallback: () {
@ -502,19 +505,22 @@ class _RepeatButtonState extends State<RepeatButton> {
case LoopMode.off:
return Icon(
Icons.repeat,
size: widget.iconSize
size: widget.iconSize,
semanticLabel: "Repeat off".i18n,
);
case LoopMode.all:
return Icon(
Icons.repeat,
color: Theme.of(context).primaryColor,
size: widget.iconSize
size: widget.iconSize,
semanticLabel: "Repeat".i18n,
);
case LoopMode.one:
return Icon(
Icons.repeat_one,
color: Theme.of(context).primaryColor,
size: widget.iconSize
size: widget.iconSize,
semanticLabel: "Repeat one".i18n,
);
}
}
@ -546,9 +552,9 @@ class _PlaybackControlsState extends State<PlaybackControls> {
Icon get libraryIcon {
if (cache.checkTrackFavorite(Track.fromMediaItem(AudioService.currentMediaItem))) {
return Icon(Icons.favorite, size: widget.iconSize * 0.64);
return Icon(Icons.favorite, size: widget.iconSize * 0.64, semanticLabel: "Unlove".i18n,);
}
return Icon(Icons.favorite_border, size: widget.iconSize * 0.64);
return Icon(Icons.favorite_border, size: widget.iconSize * 0.64, semanticLabel: "Love".i18n,);
}
@override
@ -560,7 +566,7 @@ class _PlaybackControlsState extends State<PlaybackControls> {
mainAxisSize: MainAxisSize.max,
children: [
IconButton(
icon: Icon(Icons.sentiment_very_dissatisfied, size: ScreenUtil().setWidth(46)),
icon: Icon(Icons.sentiment_very_dissatisfied, size: ScreenUtil().setWidth(46), semanticLabel: "Dislike".i18n,),
onPressed: () async {
await deezerAPI.dislikeTrack(AudioService.currentMediaItem.id);
if (playerHelper.queueIndex < (AudioService.queue??[]).length - 1) {
@ -678,7 +684,7 @@ class PlayerScreenTopRow extends StatelessWidget {
),
),
IconButton(
icon: Icon(Icons.menu),
icon: Icon(Icons.menu, semanticLabel: "Queue".i18n,),
iconSize: this.iconSize??ScreenUtil().setSp(52),
splashRadius: this.iconSize??ScreenUtil().setWidth(52),
onPressed: () async {
@ -823,6 +829,7 @@ class _QueueScreenState extends State<QueueScreen> {
IconButton(
icon: Icon(
Icons.shuffle,
semanticLabel: "Shuffle".i18n,
),
onPressed: () async {
await playerHelper.toggleShuffle();
@ -848,7 +855,7 @@ class _QueueScreenState extends State<QueueScreen> {
},
key: Key(i.toString()),
trailing: IconButton(
icon: Icon(Icons.close),
icon: Icon(Icons.close, semanticLabel: "Close".i18n,),
onPressed: () async {
await AudioService.removeQueueItem(t.toMediaItem());
setState(() {});

View File

@ -111,7 +111,7 @@ class _SearchScreenState extends State<SearchScreen> {
Widget _removeHistoryItemWidget(int index) {
return IconButton(
icon: Icon(Icons.close),
icon: Icon(Icons.close, semanticLabel: "Remove".i18n,),
onPressed: () async {
if (cache.searchHistory != null)
cache.searchHistory.removeAt(index);
@ -188,7 +188,7 @@ class _SearchScreenState extends State<SearchScreen> {
width: 40.0,
child: IconButton(
splashRadius: 20.0,
icon: Icon(Icons.clear),
icon: Icon(Icons.clear, semanticLabel: "Clear".i18n,),
onPressed: () {
setState(() {
_suggestions = [];
@ -311,7 +311,7 @@ class _SearchScreenState extends State<SearchScreen> {
data,
onTap: () {
List<Track> queue = cache.searchHistory.where((h) => h.type == SearchHistoryItemType.TRACK).map<Track>((t) => t.data).toList();
playerHelper.playFromTrackList(queue, queue.first.id, QueueSource(
playerHelper.playFromTrackList(queue, data.id, QueueSource(
text: 'Search history'.i18n,
source: 'searchhistory',
id: 'searchhistory'
@ -723,7 +723,7 @@ class SearchResultsScreen extends StatelessWidget {
return ShowEpisodeTile(
e,
trailing: IconButton(
icon: Icon(Icons.more_vert),
icon: Icon(Icons.more_vert, semanticLabel: "Options".i18n,),
onPressed: () {
MenuSheet m = MenuSheet(context);
m.defaultShowEpisodeMenu(e.show, e);
@ -907,7 +907,7 @@ class EpisodeListScreen extends StatelessWidget {
return ShowEpisodeTile(
e,
trailing: IconButton(
icon: Icon(Icons.more_vert),
icon: Icon(Icons.more_vert, semanticLabel: "Options".i18n,),
onPressed: () {
MenuSheet m = MenuSheet(context);
m.defaultShowEpisodeMenu(e.show, e);

View File

@ -1226,8 +1226,8 @@ class _GeneralSettingsState extends State<GeneralSettings> {
if (settings.lastFMPassword != null && settings.lastFMUsername != null) {
settings.lastFMUsername = null;
settings.lastFMPassword = null;
playerHelper.scrobblenaut = null;
await settings.save();
await AudioService.customAction("disableLastFM");
setState(() {});
Fluttertoast.showToast(msg: 'Logged out!'.i18n);
return;
@ -1352,7 +1352,7 @@ class _LastFMLoginState extends State<LastFMLogin> {
settings.lastFMUsername = last.username;
settings.lastFMPassword = last.passwordHash;
await settings.save();
playerHelper.scrobblenaut = Scrobblenaut(lastFM: last);
await playerHelper.authorizeLastFM();
Navigator.of(context).pop();
},
),
@ -1396,7 +1396,7 @@ class _DirectoryPickerState extends State<DirectoryPicker> {
'Pick-a-Path'.i18n,
actions: <Widget>[
IconButton(
icon: Icon(Icons.sd_card),
icon: Icon(Icons.sd_card, semanticLabel: 'Select storage'.i18n,),
onPressed: () {
String path = '';
//Chose storage

View File

@ -21,14 +21,14 @@ packages:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.1.1"
async:
dependency: "direct main"
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.5.0"
version: "2.6.1"
audio_service:
dependency: "direct main"
description:
@ -63,14 +63,14 @@ packages:
name: build_config
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.5"
version: "0.4.6"
build_daemon:
dependency: transitive
description:
name: build_daemon
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.7"
version: "2.1.10"
build_resolvers:
dependency: transitive
description:
@ -84,28 +84,28 @@ packages:
name: build_runner
url: "https://pub.dartlang.org"
source: hosted
version: "1.11.1"
version: "1.11.5"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.7"
version: "6.1.10"
built_collection:
dependency: transitive
description:
name: built_collection
url: "https://pub.dartlang.org"
source: hosted
version: "4.3.2"
version: "5.1.0"
built_value:
dependency: transitive
description:
name: built_value
url: "https://pub.dartlang.org"
source: hosted
version: "7.1.0"
version: "8.1.0"
cached_network_image:
dependency: "direct main"
description:
@ -140,14 +140,14 @@ packages:
name: cli_util
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
version: "0.3.1"
clipboard:
dependency: "direct main"
description:
name: clipboard
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.2+8"
version: "0.1.3"
clock:
dependency: transitive
description:
@ -161,7 +161,7 @@ packages:
name: code_builder
url: "https://pub.dartlang.org"
source: hosted
version: "3.6.0"
version: "3.7.0"
collection:
dependency: "direct main"
description:
@ -310,14 +310,14 @@ packages:
name: ffi
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
version: "1.1.2"
file:
dependency: transitive
description:
name: file
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.0"
version: "6.1.2"
filesize:
dependency: "direct main"
description:
@ -331,7 +331,7 @@ packages:
name: fixnum
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.11"
version: "1.0.0"
flutter:
dependency: "direct main"
description: flutter
@ -371,7 +371,7 @@ packages:
name: flutter_isolate
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0+14"
version: "1.0.0+15"
flutter_local_notifications:
dependency: "direct main"
description:
@ -442,7 +442,7 @@ packages:
name: glob
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "2.0.1"
google_fonts:
dependency: "direct main"
description:
@ -505,7 +505,7 @@ packages:
name: infinite_listview
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1+1"
version: "1.1.0"
intl:
dependency: "direct main"
description:
@ -519,7 +519,7 @@ packages:
name: io
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.4"
version: "0.3.5"
js:
dependency: transitive
description:
@ -568,14 +568,14 @@ packages:
name: logging
url: "https://pub.dartlang.org"
source: hosted
version: "0.11.4"
version: "1.0.1"
marquee:
dependency: "direct main"
description:
name: marquee
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.1"
version: "1.7.0"
matcher:
dependency: transitive
description:
@ -603,21 +603,7 @@ packages:
name: move_to_background
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
node_interop:
dependency: transitive
description:
name: node_interop
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
node_io:
dependency: transitive
description:
name: node_io
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
version: "1.0.2"
numberpicker:
dependency: "direct main"
description:
@ -645,7 +631,7 @@ packages:
name: open_file
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.3"
version: "3.2.1"
package_config:
dependency: transitive
description:
@ -715,7 +701,7 @@ packages:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.2"
version: "1.11.1"
permission_handler:
dependency: "direct main"
description:
@ -764,28 +750,28 @@ packages:
name: pool
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.0"
version: "1.5.0"
process:
dependency: transitive
description:
name: process
url: "https://pub.dartlang.org"
source: hosted
version: "4.1.0"
version: "4.2.1"
pub_semver:
dependency: transitive
description:
name: pub_semver
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.4"
version: "2.0.0"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.7"
version: "0.1.8"
quick_actions:
dependency: "direct main"
description:
@ -820,7 +806,7 @@ packages:
name: scrobblenaut
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.4"
version: "2.0.5"
share:
dependency: "direct main"
description:
@ -841,7 +827,7 @@ packages:
name: shelf_web_socket
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.4"
version: "0.2.4+1"
sky_engine:
dependency: transitive
description: flutter
@ -853,14 +839,14 @@ packages:
name: source_gen
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.10+1"
version: "0.9.10+3"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
version: "1.8.1"
spotify:
dependency: "direct main"
description:
@ -881,14 +867,14 @@ packages:
name: sqflite
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.2+3"
version: "1.3.2+4"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3+1"
version: "1.0.3+3"
stack_trace:
dependency: transitive
description:
@ -909,7 +895,7 @@ packages:
name: stream_transform
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "2.0.0"
string_scanner:
dependency: transitive
description:
@ -937,7 +923,7 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.19"
version: "0.3.0"
timing:
dependency: transitive
description:
@ -959,6 +945,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.0"
universal_io:
dependency: transitive
description:
name: universal_io
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
url_launcher:
dependency: "direct main"
description:
@ -1021,7 +1014,7 @@ packages:
name: version
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.1"
wakelock:
dependency: "direct main"
description:
@ -1049,7 +1042,7 @@ packages:
name: watcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.7+15"
version: "1.0.0"
web_socket_channel:
dependency: transitive
description:
@ -1077,7 +1070,14 @@ packages:
name: yaml
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.1"
version: "3.1.0"
zone_local:
dependency: transitive
description:
name: zone_local
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.2"
sdks:
dart: ">=2.12.0 <3.0.0"
flutter: ">=1.22.2"

View File

@ -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.11+1
version: 0.6.12+1
environment:
sdk: ">=2.8.0 <3.0.0"