0.6.10 - Spotify is ass
This commit is contained in:
parent
3105ed6c1d
commit
676d5d45cc
23 changed files with 1000 additions and 339 deletions
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
);
|
||||
})
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue