diff --git a/android/app/src/main/java/f/f/freezer/Deezer.java b/android/app/src/main/java/f/f/freezer/Deezer.java index b4b2ba2..cc22354 100644 --- a/android/app/src/main/java/f/f/freezer/Deezer.java +++ b/android/app/src/main/java/f/f/freezer/Deezer.java @@ -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 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) diff --git a/android/app/src/main/java/f/f/freezer/DownloadService.java b/android/app/src/main/java/f/f/freezer/DownloadService.java index 311fdbd..8d11cb8 100644 --- a/android/app/src/main/java/f/f/freezer/DownloadService.java +++ b/android/app/src/main/java/f/f/freezer/DownloadService.java @@ -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"); diff --git a/lib/api/cache.dart b/lib/api/cache.dart index f09af8d..32eae24 100644 --- a/lib/api/cache.dart +++ b/lib/api/cache.dart @@ -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) diff --git a/lib/api/player.dart b/lib/api/player.dart index 58bad1a..9177ba9 100644 --- a/lib/api/player.dart +++ b/lib/api/player.dart @@ -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 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 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; diff --git a/lib/languages/en_us.dart b/lib/languages/en_us.dart index a9d620f..416bf3a 100644 --- a/lib/languages/en_us.dart +++ b/lib/languages/en_us.dart @@ -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.", + } }; diff --git a/lib/ui/cached_image.dart b/lib/ui/cached_image.dart index 9368bac..5cae8f2 100644 --- a/lib/ui/cached_image.dart +++ b/lib/ui/cached_image.dart @@ -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 { 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( diff --git a/lib/ui/details_screens.dart b/lib/ui/details_screens.dart index c020cd6..e0eed59 100644 --- a/lib/ui/details_screens.dart +++ b/lib/ui/details_screens.dart @@ -127,7 +127,7 @@ class _AlbumDetailsState extends State { children: [ Row( children: [ - 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 { ), Row( children: [ - 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 { ), Row( children: [ - 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: [ - 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 { '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 { 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 { 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 { 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 { }, ), 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 { ], ), 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 { 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); diff --git a/lib/ui/downloads_screen.dart b/lib/ui/downloads_screen.dart index 599ea48..699316c 100644 --- a/lib/ui/downloads_screen.dart +++ b/lib/ui/downloads_screen.dart @@ -74,7 +74,7 @@ class _DownloadsScreenState extends State { '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 { ), 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) diff --git a/lib/ui/error.dart b/lib/ui/error.dart index 578066f..1130762 100644 --- a/lib/ui/error.dart +++ b/lib/ui/error.dart @@ -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 { + + 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, + ), + ), + ) ], ), ); diff --git a/lib/ui/importer_screen.dart b/lib/ui/importer_screen.dart index cc2c4a8..8923407 100644 --- a/lib/ui/importer_screen.dart +++ b/lib/ui/importer_screen.dart @@ -98,7 +98,7 @@ class _SpotifyImporterV1State extends State { ), ), IconButton( - icon: Icon(Icons.search), + icon: Icon(Icons.search, semanticLabel: "Search".i18n,), onPressed: () => _load(), ) ], diff --git a/lib/ui/library.dart b/lib/ui/library.dart index b63a6b0..7db2a57 100644 --- a/lib/ui/library.dart +++ b/lib/ui/library.dart @@ -33,7 +33,7 @@ class LibraryAppBar extends StatelessWidget implements PreferredSizeWidget { 'Library'.i18n, actions: [ 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 { '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 { '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 { '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 { '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 { 'History'.i18n, actions: [ IconButton( - icon: Icon(Icons.delete_sweep), + icon: Icon(Icons.delete_sweep, semanticLabel: "Clear all".i18n,), onPressed: () { setState(() => cache.history = []); cache.save(); diff --git a/lib/ui/menu.dart b/lib/ui/menu.dart index cb2220d..ad13201 100644 --- a/lib/ui/menu.dart +++ b/lib/ui/menu.dart @@ -67,10 +67,14 @@ class MenuSheet { mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - 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, diff --git a/lib/ui/player_bar.dart b/lib/ui/player_bar.dart index ad188c7..e7bd400 100644 --- a/lib/ui/player_bar.dart +++ b/lib/ui/player_bar.dart @@ -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 with SingleTickerProv icon: AnimatedIcon( icon: AnimatedIcons.play_pause, progress: _animation, + semanticLabel: _playing ? "Pause".i18n : "Play".i18n, ), iconSize: widget.size, onPressed: _playing diff --git a/lib/ui/player_screen.dart b/lib/ui/player_screen.dart index 791fb3e..03e3426 100644 --- a/lib/ui/player_screen.dart +++ b/lib/ui/player_screen.dart @@ -263,7 +263,8 @@ class _PlayerScreenHorizontalState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ 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 { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ 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 { }, ), 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 { 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 { 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 { 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 { IconButton( icon: Icon( Icons.shuffle, + semanticLabel: "Shuffle".i18n, ), onPressed: () async { await playerHelper.toggleShuffle(); @@ -848,7 +855,7 @@ class _QueueScreenState extends State { }, 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(() {}); diff --git a/lib/ui/search.dart b/lib/ui/search.dart index c0f032e..f1d4588 100644 --- a/lib/ui/search.dart +++ b/lib/ui/search.dart @@ -111,7 +111,7 @@ class _SearchScreenState extends State { 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 { 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 { data, onTap: () { List queue = cache.searchHistory.where((h) => h.type == SearchHistoryItemType.TRACK).map((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); diff --git a/lib/ui/settings_screen.dart b/lib/ui/settings_screen.dart index a8ce1ad..9523bcb 100644 --- a/lib/ui/settings_screen.dart +++ b/lib/ui/settings_screen.dart @@ -1226,8 +1226,8 @@ class _GeneralSettingsState extends State { 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 { 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 { 'Pick-a-Path'.i18n, actions: [ IconButton( - icon: Icon(Icons.sd_card), + icon: Icon(Icons.sd_card, semanticLabel: 'Select storage'.i18n,), onPressed: () { String path = ''; //Chose storage diff --git a/pubspec.lock b/pubspec.lock index 5a28f1a..a9085e4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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" diff --git a/pubspec.yaml b/pubspec.yaml index 9f81226..1035fb6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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"