0.6.10 - Spotify is ass

This commit is contained in:
exttex 2021-04-05 22:22:32 +02:00
parent 3105ed6c1d
commit 676d5d45cc
23 changed files with 1000 additions and 339 deletions

View file

@ -114,7 +114,7 @@ class _ZoomableImageState extends State<ZoomableImage> {
@override
Widget build(BuildContext context) {
ctx = context;
return FlatButton(
return TextButton(
child: CachedImage(
url: widget.url,
rounded: widget.rounded,

View file

@ -21,7 +21,7 @@ import 'menu.dart';
class AlbumDetails extends StatefulWidget {
Album album;
final Album album;
AlbumDetails(this.album, {Key key}): super(key: key);
@override
@ -165,7 +165,7 @@ class _AlbumDetailsState extends State<AlbumDetails> {
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
FlatButton(
TextButton(
child: Row(
children: <Widget>[
Icon((album.library??false)? Icons.favorite : Icons.favorite_border, size: 32),
@ -196,7 +196,7 @@ class _AlbumDetailsState extends State<AlbumDetails> {
},
),
MakeAlbumOffline(album: album),
FlatButton(
TextButton(
child: Row(
children: <Widget>[
Icon(Icons.file_download, size: 32.0,),
@ -248,7 +248,7 @@ class _AlbumDetailsState extends State<AlbumDetails> {
class MakeAlbumOffline extends StatefulWidget {
Album album;
final Album album;
MakeAlbumOffline({Key key, this.album}): super(key: key);
@override
@ -399,7 +399,7 @@ class ArtistDetails extends StatelessWidget {
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
FlatButton(
TextButton(
child: Row(
children: <Widget>[
Icon(Icons.favorite, size: 32),
@ -417,7 +417,7 @@ class ArtistDetails extends StatelessWidget {
},
),
if ((artist.radio??false))
FlatButton(
TextButton(
child: Row(
children: <Widget>[
Icon(Icons.radio, size: 32),
@ -714,7 +714,7 @@ class _DiscographyScreenState extends State<DiscographyScreen> {
class PlaylistDetails extends StatefulWidget {
Playlist playlist;
final Playlist playlist;
PlaylistDetails(this.playlist, {Key key}): super(key: key);
@override

View file

@ -239,11 +239,11 @@ class DownloadTile extends StatelessWidget {
title: Text('Delete'.i18n),
content: Text('Are you sure you want to delete this download?'.i18n),
actions: [
FlatButton(
TextButton(
child: Text('Cancel'.i18n),
onPressed: () => Navigator.of(context).pop(),
),
FlatButton(
TextButton(
child: Text('Delete'.i18n),
onPressed: () async {
await downloadManager.removeDownload(download.id);

View file

@ -50,6 +50,7 @@ class FreezerAppBar extends StatelessWidget implements PreferredSizeWidget {
return Theme(
data: ThemeData(primaryColor: (Theme.of(context).brightness == Brightness.light)?Colors.white:Colors.black),
child: AppBar(
brightness: Theme.of(context).brightness,
elevation: 0.0,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
title: Text(

View file

@ -180,7 +180,7 @@ class HomepageSectionWidget extends StatelessWidget {
//Has more items
if (j == section.items.length) {
if (section.hasMore ?? false) {
return FlatButton(
return TextButton(
child: Text(
'Show more'.i18n,
textAlign: TextAlign.center,

View file

@ -1,40 +1,44 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:freezer/api/deezer.dart';
import 'package:freezer/api/definitions.dart';
import 'package:freezer/api/download.dart';
import 'package:freezer/api/spotify.dart';
import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:freezer/settings.dart';
import 'package:freezer/ui/elements.dart';
import 'package:freezer/ui/menu.dart';
import 'package:freezer/api/importer.dart';
import 'package:freezer/api/spotify.dart';
import 'package:freezer/ui/elements.dart';
import 'package:freezer/translations.i18n.dart';
import 'package:spotify/spotify.dart' as spotify;
import 'package:url_launcher/url_launcher.dart';
class ImporterScreen extends StatefulWidget {
import 'dart:async';
class SpotifyImporterV1 extends StatefulWidget {
@override
_ImporterScreenState createState() => _ImporterScreenState();
_SpotifyImporterV1State createState() => _SpotifyImporterV1State();
}
class _ImporterScreenState extends State<ImporterScreen> {
class _SpotifyImporterV1State extends State<SpotifyImporterV1> {
String _url;
bool _error = false;
bool _loading = false;
SpotifyPlaylist _data;
//Load URL
Future _load() async {
setState(() {
_error = false;
_loading = true;
});
try {
String uri = await spotify.resolveUrl(_url);
String uri = await SpotifyScrapper.resolveUrl(_url);
//Error/NonPlaylist
if (uri == null || uri.split(':')[1] != 'playlist') {
throw Exception();
}
//Load
SpotifyPlaylist data = await spotify.playlist(uri);
SpotifyPlaylist data = await SpotifyScrapper.playlist(uri);
setState(() => _data = data);
return;
@ -46,7 +50,12 @@ class _ImporterScreenState extends State<ImporterScreen> {
});
return;
}
}
//Start importing
Future _start() async {
List<ImporterTrack> tracks = _data.toImporter();
await importer.start(context, _data.name, _data.description, tracks);
}
@override
@ -109,98 +118,96 @@ class _ImporterScreenState extends State<ImporterScreen> {
title: Text('Error loading URL!'.i18n),
leading: Icon(Icons.error, color: Colors.red,),
),
//Playlist
if (_data != null)
ImporterWidget(_data)
...[
FreezerDivider(),
ListTile(
title: Text(_data.name),
subtitle: Text((_data.description ?? '') == '' ? '${_data.tracks.length} tracks' : _data.description),
leading: Image.network(_data.image??'http://cdn-images.deezer.com/images/cover//256x256-000000-80-0-0.jpg')
),
ImporterSettings(),
Padding(
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0),
child: ElevatedButton(
child: Text('Start import'.i18n),
onPressed: () async {
await _start();
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => ImporterStatusScreen()
));
},
),
),
]
],
),
);
}
}
class ImporterWidget extends StatefulWidget {
final SpotifyPlaylist playlist;
ImporterWidget(this.playlist, {Key key}): super(key: key);
class ImporterSettings extends StatefulWidget {
@override
_ImporterWidgetState createState() => _ImporterWidgetState();
_ImporterSettingsState createState() => _ImporterSettingsState();
}
class _ImporterWidgetState extends State<ImporterWidget> {
class _ImporterSettingsState extends State<ImporterSettings> {
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
FreezerDivider(),
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
title: Text(widget.playlist.name),
subtitle: Text(widget.playlist.description),
//Default image
leading: Image.network(widget.playlist.image??'http://cdn-images.deezer.com/images/cover//256x256-000000-80-0-0.jpg'),
title: Text('Download imported tracks'.i18n),
leading: Switch(
value: importer.download,
onChanged: (v) => setState(() => importer.download = v),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
RaisedButton(
child: Text('Convert'.i18n),
color: Theme.of(context).primaryColor,
onPressed: () {
spotify.convertPlaylist(widget.playlist);
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => CurrentlyImportingScreen()
));
},
),
RaisedButton(
child: Text('Download only'.i18n),
color: Theme.of(context).primaryColor,
onPressed: () async {
//Ask for quality
AudioQuality quality;
if (settings.downloadQuality == AudioQuality.ASK) {
quality = await downloadManager.qualitySelect(context);
if (quality == null) return;
}
spotify.convertPlaylist(widget.playlist, downloadOnly: true, context: context, quality: quality);
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => CurrentlyImportingScreen()
));
},
),
],
),
...List.generate(widget.playlist.tracks.length, (i) {
SpotifyTrack t = widget.playlist.tracks[i];
return ListTile(
title: Text(
t.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: Text(
t.artists,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
);
})
],
);
}
}
class CurrentlyImportingScreen extends StatelessWidget {
class ImporterStatusScreen extends StatefulWidget {
@override
_ImporterStatusScreenState createState() => _ImporterStatusScreenState();
}
Widget _stateIcon(TrackImportState s) {
switch (s) {
case TrackImportState.ERROR:
return Icon(Icons.error, color: Colors.red,);
case TrackImportState.OK:
return Icon(Icons.done, color: Colors.green);
default:
return Container(width: 0, height: 0);
class _ImporterStatusScreenState extends State<ImporterStatusScreen> {
bool _done = false;
StreamSubscription _subscription;
@override
void initState() {
//If import done mark as not done, to prevent double routing
if (importer.done) {
_done = true;
importer.done = false;
}
//Update
_subscription = importer.updateStream.listen((event) {
setState(() {
//Unset done so this page doesn't reopen
if (importer.done) {
_done = true;
importer.done = false;
};
});
});
super.initState();
}
@override
void dispose() {
if (_subscription != null)
_subscription.cancel();
super.dispose();
}
@ -208,87 +215,450 @@ class CurrentlyImportingScreen extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: FreezerAppBar('Importing...'.i18n),
body: StreamBuilder(
stream: spotify.importingStream.stream,
builder: (context, snapshot) {
//If not in progress
if (spotify.importingSpotifyPlaylist == null || spotify.importingSpotifyPlaylist.tracks == null || spotify.doneImporting == null)
return Center(child: CircularProgressIndicator(),);
if (spotify.doneImporting) spotify.doneImporting = null;
//Cont OK, error, total
int ok = spotify.importingSpotifyPlaylist.tracks.where((t) => t.state == TrackImportState.OK).length;
int err = spotify.importingSpotifyPlaylist.tracks.where((t) => t.state == TrackImportState.ERROR).length;
int count = spotify.importingSpotifyPlaylist.tracks.length;
return ListView(
children: <Widget>[
if (!(spotify.doneImporting??true)) Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator()
],
),
body: ListView(
children: [
// Spinner
if (!_done)
Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator()
],
),
Container(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.import_export, size: 24.0,),
Container(width: 4.0,),
Text('${ok+err}/$count', style: TextStyle(fontSize: 24.0),)
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.done, size: 24.0,),
Container(width: 4.0,),
Text('$ok', style: TextStyle(fontSize: 24.0),)
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.error, size: 24.0,),
Container(width: 4.0,),
Text('$err', style: TextStyle(fontSize: 24.0),),
],
),
if (snapshot.data != null)
FlatButton(
child: Text('Playlist menu'.i18n),
onPressed: () async {
Playlist p = await deezerAPI.playlist(snapshot.data);
p.library = true;
MenuSheet m = MenuSheet(context);
m.defaultPlaylistMenu(p);
},
)
],
),
),
// Progress indicator
Container(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.import_export, size: 24.0,),
Container(width: 4.0,),
Text('${importer.ok+importer.error}/${importer.tracks.length}', style: TextStyle(fontSize: 24.0),)
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.done, size: 24.0,),
Container(width: 4.0,),
Text('${importer.ok}', style: TextStyle(fontSize: 24.0),)
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.error, size: 24.0,),
Container(width: 4.0,),
Text('${importer.error}', style: TextStyle(fontSize: 24.0),),
],
),
//When Done
if (_done)
TextButton(
child: Text('Playlist menu'.i18n),
onPressed: () {
MenuSheet m = MenuSheet(context);
m.defaultPlaylistMenu(importer.playlist);
},
)
],
),
),
Container(height: 8.0),
FreezerDivider(),
//Tracks
...List.generate(importer.tracks.length, (i) {
ImporterTrack t = importer.tracks[i];
return ListTile(
leading: t.state.icon,
title: Text(t.title),
subtitle: Text(
t.artists.join(", "),
maxLines: 1,
),
Container(height: 8.0),
FreezerDivider(),
...List.generate(spotify.importingSpotifyPlaylist.tracks.length, (i) {
SpotifyTrack t = spotify.importingSpotifyPlaylist.tracks[i];
return ListTile(
title: Text(t.title),
subtitle: Text(t.artists),
leading: _stateIcon(t.state),
);
})
],
);
},
);
})
],
),
);
}
}
class SpotifyImporterV2 extends StatefulWidget {
@override
_SpotifyImporterV2State createState() => _SpotifyImporterV2State();
}
class _SpotifyImporterV2State extends State<SpotifyImporterV2> {
bool _authorizing = false;
String _clientId;
String _clientSecret;
SpotifyAPIWrapper spotify;
//Spotify authorization flow
Future _authorize() async {
setState(() => _authorizing = true);
spotify = SpotifyAPIWrapper();
await spotify.authorize(_clientId, _clientSecret);
//Save credentials
settings.spotifyClientId = _clientId;
settings.spotifyClientSecret = _clientSecret;
await settings.save();
setState(() => _authorizing = false);
//Redirect
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => SpotifyImporterV2Main(spotify)
));
}
@override
void initState() {
_clientId = settings.spotifyClientId;
_clientSecret = settings.spotifyClientSecret;
super.initState();
}
@override
void dispose() {
//Stop server
if (spotify != null) {
spotify.cancelAuthorize();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: FreezerAppBar("Spotify Importer v2".i18n),
body: ListView(
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 12.0, horizontal: 8.0),
child: Text(
"This importer requires Spotify Client ID and Client Secret. To obtain them:".i18n,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18.0,
),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
"1. Go to: developer.spotify.com/dashboard and create an app.".i18n,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16.0,
),
)
),
Padding(
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0),
child: ElevatedButton(
child: Text("Open in Browser".i18n),
onPressed: () {
launch("https://developer.spotify.com/dashboard");
},
),
),
Container(height: 16.0),
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
"2. In the app you just created go to settings, and set the Redirect URL to: ".i18n + "http://localhost:42069",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16.0,
),
)
),
Padding(
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0),
child: ElevatedButton(
child: Text("Copy the Redirect URL".i18n),
onPressed: () async {
await Clipboard.setData(new ClipboardData(text: "http://localhost:42069"));
Fluttertoast.showToast(msg: "Copied".i18n, gravity: ToastGravity.BOTTOM, toastLength: Toast.LENGTH_SHORT);
},
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
Flexible(
child: TextField(
controller: TextEditingController(text: _clientId),
decoration: InputDecoration(
labelText: "Client ID".i18n
),
onChanged: (v) => setState(() => _clientId = v),
),
),
Container(width: 16.0),
Flexible(
child: TextField(
controller: TextEditingController(text: _clientSecret),
obscureText: true,
decoration: InputDecoration(
labelText: "Client Secret".i18n
),
onChanged: (v) => setState(() => _clientSecret = v),
),
),
],
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0),
child: ElevatedButton(
child: Text("Authorize".i18n),
onPressed: (_clientId != null && _clientSecret != null && !_authorizing)
? () => _authorize()
: null
),
),
if (_authorizing)
Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator()
],
),
)
],
),
);
}
}
class SpotifyImporterV2Main extends StatefulWidget {
SpotifyAPIWrapper spotify;
SpotifyImporterV2Main(this.spotify, {Key key}): super(key: key);
@override
_SpotifyImporterV2MainState createState() => _SpotifyImporterV2MainState();
}
class _SpotifyImporterV2MainState extends State<SpotifyImporterV2Main> {
String _url;
bool _urlLoading = false;
spotify.Playlist _urlPlaylist;
bool _playlistsLoading = true;
List<spotify.PlaylistSimple> _playlists;
@override
void initState() {
_loadPlaylists();
super.initState();
}
//Load playlists
Future _loadPlaylists() async {
var pages = widget.spotify.spotify.users.playlists(widget.spotify.me.id);
_playlists = List.from(await pages.all());
setState(() => _playlistsLoading = false);
}
Future _loadUrl() async {
setState(() => _urlLoading = true);
//Resolve URL
try {
String uri = await SpotifyScrapper.resolveUrl(_url);
//Error/NonPlaylist
if (uri == null || uri.split(':')[1] != 'playlist') {
throw Exception();
}
//Get playlist
spotify.Playlist playlist = await widget.spotify.spotify.playlists.get(uri.split(":")[2]);
setState(() {
_urlLoading = false;
_urlPlaylist = playlist;
});
} catch (e) {
Fluttertoast.showToast(msg: "Invalid/Unsupported URL".i18n, gravity: ToastGravity.BOTTOM, toastLength: Toast.LENGTH_SHORT);
setState(() => _urlLoading = false);
return;
}
}
Future _startImport(String title, String description, String id) async {
//Show loading dialog
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => WillPopScope(
onWillPop: () => Future.value(false),
child: AlertDialog(
title: Text("Please wait...".i18n),
content: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [CircularProgressIndicator()],
)
)
)
);
try {
//Fetch entire playlist
var pages = widget.spotify.spotify.playlists.getTracksByPlaylistId(id);
var all = await pages.all();
//Map to importer track
List<ImporterTrack> tracks = all.map((t) => ImporterTrack(t.name, t.artists.map((a) => a.name).toList(), isrc: t.externalIds.isrc)).toList();
await importer.start(context, title, description, tracks);
//Route
Navigator.of(context).pop();
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => ImporterStatusScreen()
));
} catch (e) {
Fluttertoast.showToast(msg: e.toString(), gravity: ToastGravity.BOTTOM, toastLength: Toast.LENGTH_SHORT);
Navigator.of(context).pop();
return;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: FreezerAppBar("Spotify Importer v2".i18n),
body: ListView(
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: Text(
'Logged in as: '.i18n + widget.spotify.me.displayName,
maxLines: 1,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold
)
),
),
FreezerDivider(),
Container(height: 4.0),
Text(
"Options".i18n,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold
),
),
ImporterSettings(),
FreezerDivider(),
Container(height: 4.0),
Text(
"Import playlists by URL".i18n,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row(
children: [
Expanded(
child: TextField(
decoration: InputDecoration(
hintText: "URL".i18n
),
onChanged: (v) => setState(() => _url = v)
),
),
IconButton(
icon: Icon(Icons.search),
onPressed: () => _loadUrl(),
)
],
)
),
if (_urlLoading)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 4.0),
child: CircularProgressIndicator(),
)
],
),
if (_urlPlaylist != null)
ListTile(
title: Text(_urlPlaylist.name),
subtitle: Text(_urlPlaylist.description ?? ''),
leading: Image.network(_urlPlaylist.images.first?.url??"http://cdn-images.deezer.com/images/cover//256x256-000000-80-0-0.jpg")
),
if (_urlPlaylist != null)
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: ElevatedButton(
child: Text("Import".i18n),
onPressed: () {
_startImport(_urlPlaylist.name, _urlPlaylist.description, _urlPlaylist.id);
}
)
),
// Playlists
FreezerDivider(),
Container(height: 4.0),
Text(
"Playlists".i18n,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold
)
),
Container(height: 4.0),
if (_playlistsLoading)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 4.0),
child: CircularProgressIndicator(),
)
],
),
if (!_playlistsLoading && _playlists != null)
...List.generate(_playlists.length, (i) {
spotify.PlaylistSimple p = _playlists[i];
return ListTile(
title: Text(p.name, maxLines: 1),
subtitle: Text(p.owner.displayName, maxLines: 1),
leading: Image.network(p.images.first?.url??"http://cdn-images.deezer.com/images/cover//256x256-000000-80-0-0.jpg"),
onTap: () {
_startImport(p.name, "", p.id);
},
);
})
],
)
);
}
}

View file

@ -5,6 +5,7 @@ import 'package:fluttertoast/fluttertoast.dart';
import 'package:freezer/api/cache.dart';
import 'package:freezer/api/deezer.dart';
import 'package:freezer/api/definitions.dart';
import 'package:freezer/api/importer.dart';
import 'package:freezer/api/player.dart';
import 'package:freezer/settings.dart';
import 'package:freezer/ui/details_screens.dart';
@ -138,16 +139,44 @@ class LibraryScreen extends StatelessWidget {
leading: LeadingIcon(Icons.import_export, color: Color(0xff2ba766)),
subtitle: Text('Import playlists from Spotify'.i18n),
onTap: () {
if (spotify.doneImporting != null) {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => CurrentlyImportingScreen())
);
if (spotify.doneImporting) spotify.doneImporting = null;
//Show progress
if (importer.done || importer.busy) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ImporterStatusScreen()
));
return;
}
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => ImporterScreen())
//Pick importer dialog
showDialog(
context: context,
builder: (context) => SimpleDialog(
title: Text('Importer'.i18n),
children: [
ListTile (
leading: Icon(FontAwesome5.spotify),
title: Text('Spotify v1'.i18n),
subtitle: Text('Import Spotify playlists up to 100 tracks without any login.'.i18n),
onTap: () {
Navigator.of(context).pop();
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => SpotifyImporterV1()
));
},
),
ListTile (
leading: Icon(FontAwesome5.spotify),
title: Text('Spotify v2'.i18n),
subtitle: Text('Import any Spotify playlist, import from own Spotify library. Requires free account.'.i18n),
onTap: () {
Navigator.of(context).pop();
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => SpotifyImporterV2()
));
},
)
],
)
);
},
),
@ -444,7 +473,7 @@ class _LibraryTracksState extends State<LibraryTracks> {
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
MakePlaylistOffline(_playlist),
FlatButton(
TextButton(
child: Row(
children: <Widget>[
Icon(Icons.file_download, size: 32.0,),

View file

@ -13,7 +13,7 @@ import 'home_screen.dart';
class LoginWidget extends StatefulWidget {
Function callback;
final Function callback;
LoginWidget({this.callback, Key key}): super(key: key);
@override
@ -95,7 +95,7 @@ class _LoginWidgetState extends State<LoginWidget> {
],
),
actions: <Widget>[
FlatButton(
TextButton(
child: Text('Dismiss'.i18n),
onPressed: () {
Navigator.of(context).pop();
@ -169,14 +169,6 @@ class _LoginWidgetState extends State<LoginWidget> {
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: ListView(
children: <Widget>[
Container(height: 16.0,),
Text(
'Welcome to'.i18n,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16.0
),
),
FreezerTitle(),
Container(height: 8.0,),
Text(
@ -190,7 +182,7 @@ class _LoginWidgetState extends State<LoginWidget> {
//Email login dialog
Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0),
child: OutlineButton(
child: OutlinedButton(
child: Text(
'Login using email'.i18n,
),
@ -204,7 +196,7 @@ class _LoginWidgetState extends State<LoginWidget> {
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0),
child: OutlineButton(
child: OutlinedButton(
child: Text('Login using browser'.i18n),
onPressed: () {
Navigator.of(context).push(
@ -215,7 +207,7 @@ class _LoginWidgetState extends State<LoginWidget> {
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0),
child: OutlineButton(
child: OutlinedButton(
child: Text('Login using token'.i18n),
onPressed: () {
showDialog(
@ -238,7 +230,7 @@ class _LoginWidgetState extends State<LoginWidget> {
),
),
actions: <Widget>[
FlatButton(
TextButton(
child: Text('Save'.i18n),
onPressed: () => goARL(null, _controller),
)
@ -259,7 +251,7 @@ class _LoginWidgetState extends State<LoginWidget> {
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 32.0),
child: OutlineButton(
child: OutlinedButton(
child: Text('Open in browser'.i18n),
onPressed: () {
InAppBrowser.openWithSystemBrowser(url: 'https://deezer.com/register');
@ -287,7 +279,7 @@ class _LoginWidgetState extends State<LoginWidget> {
class LoginBrowser extends StatelessWidget {
Function updateParent;
final Function updateParent;
LoginBrowser(this.updateParent);
@override
@ -331,7 +323,7 @@ class LoginBrowser extends StatelessWidget {
class EmailLogin extends StatefulWidget {
Function callback;
final Function callback;
EmailLogin(this.callback, {Key key}): super(key: key);
@override

View file

@ -697,14 +697,14 @@ class _SleepTimerDialogState extends State<SleepTimerDialog> {
],
),
actions: [
FlatButton(
TextButton(
child: Text('Dismiss'.i18n),
onPressed: () {
Navigator.of(context).pop();
},
),
if (cache.sleepTimer != null)
FlatButton(
TextButton(
child: Text('Cancel current timer'.i18n),
onPressed: () {
cache.sleepTimer.cancel();
@ -714,7 +714,7 @@ class _SleepTimerDialogState extends State<SleepTimerDialog> {
},
),
FlatButton(
TextButton(
child: Text('Save'.i18n),
onPressed: () {
Duration duration = Duration(hours: hours, minutes: minutes);
@ -891,11 +891,11 @@ class _CreatePlaylistDialogState extends State<CreatePlaylistDialog> {
],
),
actions: <Widget>[
FlatButton(
TextButton(
child: Text('Cancel'.i18n),
onPressed: () => Navigator.of(context).pop(),
),
FlatButton(
TextButton(
child: Text(edit ? 'Update'.i18n : 'Create'.i18n),
onPressed: () async {
if (edit) {

View file

@ -152,50 +152,71 @@ class PrevNextButton extends StatelessWidget {
}
class PlayPauseButton extends StatelessWidget {
class PlayPauseButton extends StatefulWidget {
final double size;
PlayPauseButton(this.size);
PlayPauseButton(this.size, {Key key}): super(key: key);
@override
_PlayPauseButtonState createState() => _PlayPauseButtonState();
}
class _PlayPauseButtonState extends State<PlayPauseButton> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
@override
void initState() {
_controller = AnimationController(vsync: this, duration: Duration(milliseconds: 200));
_animation = Tween<double>(begin: 0, end: 1).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: AudioService.playbackStateStream,
builder: (context, snapshot) {
//Playing
if (AudioService.playbackState?.playing??false) {
return IconButton(
iconSize: this.size,
icon: Icon(Icons.pause),
onPressed: () => AudioService.pause()
);
}
//Animated icon by pato05
bool _playing = AudioService.playbackState?.playing ?? false;
if (_playing || AudioService.playbackState?.processingState == AudioProcessingState.ready ||
AudioService.playbackState?.processingState == AudioProcessingState.none) {
if (_playing)
_controller.forward();
else
_controller.reverse();
//Paused
if ((!(AudioService.playbackState?.playing??false) &&
AudioService.playbackState.processingState == AudioProcessingState.ready) ||
//None state (stopped)
AudioService.playbackState.processingState == AudioProcessingState.none) {
return IconButton(
iconSize: this.size,
icon: Icon(Icons.play_arrow),
onPressed: () => AudioService.play()
splashRadius: widget.size,
icon: AnimatedIcon(
icon: AnimatedIcons.play_pause,
progress: _animation,
),
iconSize: widget.size,
onPressed: _playing
? () => AudioService.pause()
: () => AudioService.play()
);
}
switch (AudioService.playbackState.processingState) {
//Stopped/Error
//Stopped/Error
case AudioProcessingState.error:
case AudioProcessingState.none:
case AudioProcessingState.stopped:
return Container(width: this.size, height: this.size);
//Loading, connecting, rewinding...
return Container(width: widget.size, height: widget.size);
//Loading, connecting, rewinding...
default:
return Container(
width: this.size,
height: this.size,
width: widget.size,
height: widget.size,
child: CircularProgressIndicator(),
);
}
@ -203,3 +224,6 @@ class PlayPauseButton extends StatelessWidget {
);
}
}

View file

@ -439,7 +439,7 @@ class _QualityInfoWidgetState extends State<QualityInfoWidget> {
@override
Widget build(BuildContext context) {
return FlatButton(
return TextButton(
child: Text(value),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => QualitySettings()));

View file

@ -106,7 +106,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
title: Text('Language'.i18n),
content: Text('Language changed, please restart Freezer to apply!'.i18n),
actions: [
FlatButton(
TextButton(
child: Text('OK'),
onPressed: () {
Navigator.of(context).pop();
@ -387,7 +387,7 @@ class _FontSelectorState extends State<FontSelector> {
title: Text('Warning'.i18n),
content: Text("This app isn't made for supporting many fonts, it can break layouts and overflow. Use at your own risk!".i18n),
actions: [
FlatButton(
TextButton(
onPressed: () async {
setState(() => settings.font = font);
await settings.save();
@ -398,7 +398,7 @@ class _FontSelectorState extends State<FontSelector> {
},
child: Text('Apply'.i18n),
),
FlatButton(
TextButton(
onPressed: () {
Navigator.of(context).pop();
widget.callback();
@ -701,11 +701,11 @@ class _DeezerSettingsState extends State<DeezerSettings> {
// ),
// ),
// actions: [
// FlatButton(
// TextButton(
// child: Text('Cancel'.i18n),
// onPressed: () => Navigator.of(context).pop(),
// ),
// FlatButton(
// TextButton(
// child: Text('Reset'.i18n),
// onPressed: () async {
// setState(() {
@ -715,7 +715,7 @@ class _DeezerSettingsState extends State<DeezerSettings> {
// Navigator.of(context).pop();
// },
// ),
// FlatButton(
// TextButton(
// child: Text('Save'.i18n),
// onPressed: () async {
// setState(() {
@ -739,8 +739,8 @@ class _DeezerSettingsState extends State<DeezerSettings> {
class FilenameTemplateDialog extends StatefulWidget {
String initial;
Function onSave;
final String initial;
final Function onSave;
FilenameTemplateDialog(this.initial, this.onSave, {Key key}): super(key: key);
@override
@ -782,22 +782,22 @@ class _FilenameTemplateDialogState extends State<FilenameTemplateDialog> {
],
),
actions: [
FlatButton(
TextButton(
child: Text('Cancel'.i18n),
onPressed: () => Navigator.of(context).pop(),
),
FlatButton(
TextButton(
child: Text('Reset'.i18n),
onPressed: () {
_controller.value = _controller.value.copyWith(text: '%artist% - %title%');
_new = '%artist% - %title%';
},
),
FlatButton(
TextButton(
child: Text('Clear'.i18n),
onPressed: () => _controller.clear(),
),
FlatButton(
TextButton(
child: Text('Save'.i18n),
onPressed: () async {
widget.onSave(_new);
@ -907,7 +907,7 @@ class _DownloadsSettingsState extends State<DownloadsSettings> {
title: Text('Warning'.i18n),
content: Text('Using too many concurrent downloads on older/weaker devices might cause crashes!'.i18n),
actions: [
FlatButton(
TextButton(
child: Text('Dismiss'.i18n),
onPressed: () => Navigator.of(context).pop(),
)
@ -1251,18 +1251,18 @@ class _GeneralSettingsState extends State<GeneralSettings> {
// content: Text('Due to plugin incompatibility, login using browser is unavailable without restart.'.i18n),
content: Text('Restart of app is required to properly log out!'.i18n),
actions: <Widget>[
FlatButton(
TextButton(
child: Text('Cancel'.i18n),
onPressed: () => Navigator.of(context).pop(),
),
// FlatButton(
// TextButton(
// child: Text('(ARL ONLY) Continue'.i18n),
// onPressed: () async {
// await logOut();
// Navigator.of(context).pop();
// },
// ),
FlatButton(
TextButton(
child: Text('Log out & Exit'.i18n),
onPressed: () async {
try {AudioService.stop();} catch (e) {}
@ -1329,11 +1329,11 @@ class _LastFMLoginState extends State<LastFMLogin> {
],
),
actions: [
FlatButton(
TextButton(
child: Text('Cancel'.i18n),
onPressed: () => Navigator.of(context).pop(),
),
FlatButton(
TextButton(
child: Text('Login'.i18n),
onPressed: () async {
LastFM last;
@ -1577,7 +1577,7 @@ class _CreditsScreenState extends State<CreditsScreen> {
subtitle: Text('To get latest releases'.i18n),
leading: Icon(FontAwesome5.telegram, color: Color(0xFF27A2DF), size: 36.0),
onTap: () {
launch('https://t.me/freezereleases');
launch('https://t.me/joinchat/Se4zLEBvjS1NCiY9');
},
),
ListTile(
@ -1601,7 +1601,7 @@ class _CreditsScreenState extends State<CreditsScreen> {
subtitle: Text('Source code, report issues there.'.i18n),
leading: Icon(Icons.code, color: Colors.green, size: 36.0),
onTap: () {
launch('https://git.rip/freezer/');
launch('https://git.freezer.life/exttex/freezer');
},
),
ListTile(

View file

@ -168,7 +168,7 @@ class _UpdaterScreenState extends State<UpdaterScreen> {
//Available download
if (_versionDownload != null)
Column(children: [
RaisedButton(
ElevatedButton(
child: Text('Download'.i18n + ' (${_versionDownload.version})'),
onPressed: _buttonEnabled ? () {
setState(() => _buttonEnabled = false);