430 lines
14 KiB
Dart
430 lines
14 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:fluttertoast/fluttertoast.dart';
|
|
import 'package:freezer/api/deezer.dart';
|
|
import 'package:freezer/api/player.dart';
|
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
|
import 'package:freezer/translations.i18n.dart';
|
|
|
|
import '../settings.dart';
|
|
import '../api/definitions.dart';
|
|
import 'home_screen.dart';
|
|
|
|
class LoginWidget extends StatefulWidget {
|
|
final Function? callback;
|
|
LoginWidget({this.callback, Key? key}) : super(key: key);
|
|
|
|
@override
|
|
_LoginWidgetState createState() => _LoginWidgetState();
|
|
}
|
|
|
|
class _LoginWidgetState extends State<LoginWidget> {
|
|
late String _arl;
|
|
String? _error;
|
|
|
|
//Initialize deezer etc
|
|
Future _init() async {
|
|
deezerAPI.arl = settings.arl;
|
|
await playerHelper.start();
|
|
|
|
//Pre-cache homepage
|
|
if (!await HomePage().exists()) {
|
|
await deezerAPI.authorize();
|
|
settings.offlineMode = false;
|
|
HomePage hp = await deezerAPI.homePage();
|
|
await hp.save();
|
|
}
|
|
}
|
|
|
|
//Call _init()
|
|
void _start() async {
|
|
if (settings.arl != null) {
|
|
_init().then((_) {
|
|
if (widget.callback != null) widget.callback!();
|
|
});
|
|
}
|
|
}
|
|
|
|
//Check if deezer available in current country
|
|
void _checkAvailability() async {
|
|
bool? available = await DeezerAPI.chceckAvailability();
|
|
if (!(available ?? true)) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: Text("Deezer is unavailable".i18n),
|
|
content: Text(
|
|
"Deezer is unavailable in your country, Freezer might not work properly. Please use a VPN"
|
|
.i18n),
|
|
actions: [
|
|
TextButton(
|
|
child: Text('Continue'.i18n),
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
)
|
|
],
|
|
));
|
|
}
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(LoginWidget oldWidget) {
|
|
_start();
|
|
super.didUpdateWidget(oldWidget);
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
_start();
|
|
_checkAvailability();
|
|
super.initState();
|
|
}
|
|
|
|
void errorDialog() {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
return AlertDialog(
|
|
title: Text('Error'.i18n),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
'Error logging in! Please check your token and internet connection and try again.'
|
|
.i18n),
|
|
if (_error != null) Text('\n\n$_error')
|
|
],
|
|
),
|
|
actions: <Widget>[
|
|
TextButton(
|
|
child: Text('Dismiss'.i18n),
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
)
|
|
],
|
|
);
|
|
});
|
|
}
|
|
|
|
void _update() async {
|
|
setState(() => {});
|
|
|
|
//Try logging in
|
|
try {
|
|
deezerAPI.arl = settings.arl;
|
|
bool resp = await deezerAPI.rawAuthorize(
|
|
onError: (e) => setState(() => _error = e.toString()));
|
|
if (resp == false) {
|
|
//false, not null
|
|
if (settings.arl!.length != 192) {
|
|
if (_error == null) _error = '';
|
|
_error = 'Invalid ARL length!';
|
|
}
|
|
setState(() => settings.arl = null);
|
|
errorDialog();
|
|
}
|
|
//On error show dialog and reset to null
|
|
} catch (e) {
|
|
_error = e.toString();
|
|
print('Login error: ' + e.toString());
|
|
setState(() => settings.arl = null);
|
|
errorDialog();
|
|
}
|
|
|
|
await settings.save();
|
|
_start();
|
|
}
|
|
|
|
// ARL auth: called on "Save" click, Enter and DPAD_Center press
|
|
void goARL(FocusNode? node, TextEditingController _controller) {
|
|
if (node != null) {
|
|
node.unfocus();
|
|
}
|
|
_controller.clear();
|
|
settings.arl = _arl.trim();
|
|
Navigator.of(context).pop();
|
|
_update();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
//If arl non null, show loading
|
|
if (settings.arl != null)
|
|
return Scaffold(
|
|
body: Center(
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
);
|
|
TextEditingController _controller = new TextEditingController();
|
|
// For "DPAD center" key handling on remote controls
|
|
FocusNode focusNode = FocusNode(
|
|
skipTraversal: true,
|
|
descendantsAreFocusable: false,
|
|
onKey: (node, event) {
|
|
if (event.logicalKey == LogicalKeyboardKey.select) {
|
|
goARL(node, _controller);
|
|
}
|
|
return KeyEventResult.handled;
|
|
});
|
|
if (settings.arl == null)
|
|
return Scaffold(
|
|
body: Padding(
|
|
padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 8.0),
|
|
child: Theme(
|
|
data: Theme.of(context).copyWith(
|
|
outlinedButtonTheme: OutlinedButtonThemeData(
|
|
style: ButtonStyle(
|
|
foregroundColor:
|
|
MaterialStateProperty.all(Colors.white)))),
|
|
//data: ThemeData(
|
|
// outlinedButtonTheme: OutlinedButtonThemeData(
|
|
// style: ButtonStyle(
|
|
// foregroundColor:
|
|
// MaterialStateProperty.all(Colors.white)))),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: <Widget>[
|
|
Expanded(
|
|
child: Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 32.0),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
FreezerTitle(),
|
|
Container(height: 16.0),
|
|
Text(
|
|
"Please login using your Deezer account.".i18n,
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(fontSize: 16.0),
|
|
),
|
|
Container(
|
|
height: 16.0,
|
|
),
|
|
//Email login dialog
|
|
OutlinedButton(
|
|
child: Text(
|
|
'Login using email'.i18n,
|
|
),
|
|
onPressed: () {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => EmailLogin(_update));
|
|
},
|
|
),
|
|
OutlinedButton(
|
|
child: Text('Login using browser'.i18n),
|
|
onPressed: () {
|
|
Navigator.of(context).pushRoute(
|
|
builder: (context) =>
|
|
LoginBrowser(_update));
|
|
},
|
|
),
|
|
OutlinedButton(
|
|
child: Text('Login using token'.i18n),
|
|
onPressed: () {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
Future.delayed(
|
|
Duration(seconds: 1),
|
|
() => {
|
|
focusNode.requestFocus()
|
|
}); // autofocus doesn't work - it's replacement
|
|
return AlertDialog(
|
|
title: Text('Enter ARL'.i18n),
|
|
content: Container(
|
|
child: TextField(
|
|
onChanged: (String s) => _arl = s,
|
|
decoration: InputDecoration(
|
|
labelText: 'Token (ARL)'.i18n),
|
|
focusNode: focusNode,
|
|
controller: _controller,
|
|
onSubmitted: (String s) {
|
|
goARL(focusNode, _controller);
|
|
},
|
|
),
|
|
),
|
|
actions: <Widget>[
|
|
TextButton(
|
|
child: Text('Save'.i18n),
|
|
onPressed: () =>
|
|
goARL(null, _controller),
|
|
)
|
|
],
|
|
);
|
|
});
|
|
},
|
|
),
|
|
]))),
|
|
Container(height: 16.0),
|
|
Text(
|
|
"If you don't have account, you can register on deezer.com for free."
|
|
.i18n,
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(fontSize: 16.0),
|
|
),
|
|
Container(height: 8.0),
|
|
Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 32.0),
|
|
child: OutlinedButton(
|
|
child: Text('Open in browser'.i18n),
|
|
onPressed: () {
|
|
InAppBrowser.openWithSystemBrowser(
|
|
url: Uri.parse('https://deezer.com/register'));
|
|
},
|
|
),
|
|
),
|
|
Container(
|
|
height: 8.0,
|
|
),
|
|
Divider(),
|
|
Container(
|
|
height: 8.0,
|
|
),
|
|
Text(
|
|
"By using this app, you don't agree with the Deezer ToS".i18n,
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(fontSize: 14.0),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
));
|
|
return const SizedBox();
|
|
}
|
|
}
|
|
|
|
class LoginBrowser extends StatelessWidget {
|
|
final Function updateParent;
|
|
LoginBrowser(this.updateParent);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return SafeArea(
|
|
child: InAppWebView(
|
|
initialUrlRequest:
|
|
URLRequest(url: Uri.parse('https://deezer.com/login')),
|
|
onLoadStart: (InAppWebViewController controller, Uri? uri) async {
|
|
//Offers URL
|
|
if (!uri!.path.contains('/login') &&
|
|
!uri.path.contains('/register')) {
|
|
controller.evaluateJavascript(
|
|
source: 'window.location.href = "/open_app"');
|
|
}
|
|
print('scheme ${uri.scheme}, host: ${uri.host}');
|
|
//Parse arl from url
|
|
if (uri.scheme == 'intent' && uri.host == 'deezer.page.link') {
|
|
try {
|
|
//Actual url is in `link` query parameter
|
|
Uri linkUri = Uri.parse(uri.queryParameters['link']!);
|
|
String? arl = linkUri.queryParameters['arl'];
|
|
if (arl != null) {
|
|
settings.arl = arl;
|
|
Navigator.of(context).pop();
|
|
updateParent();
|
|
}
|
|
} catch (e) {
|
|
print(e);
|
|
}
|
|
}
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class EmailLogin extends StatefulWidget {
|
|
final Function callback;
|
|
EmailLogin(this.callback, {Key? key}) : super(key: key);
|
|
|
|
@override
|
|
_EmailLoginState createState() => _EmailLoginState();
|
|
}
|
|
|
|
class _EmailLoginState extends State<EmailLogin> {
|
|
String? _email;
|
|
String? _password;
|
|
bool _loading = false;
|
|
|
|
Future _login() async {
|
|
setState(() => _loading = true);
|
|
//Try logging in
|
|
String? arl;
|
|
String? exception;
|
|
try {
|
|
arl = await DeezerAPI.getArlByEmail(_email, _password!);
|
|
} catch (e, st) {
|
|
exception = e.toString();
|
|
print(e);
|
|
print(st);
|
|
}
|
|
setState(() => _loading = false);
|
|
|
|
//Success
|
|
if (arl != null) {
|
|
settings.arl = arl;
|
|
Navigator.of(context).pop();
|
|
widget.callback();
|
|
return;
|
|
}
|
|
|
|
//Error
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: Text("Error logging in!".i18n),
|
|
content: Text(
|
|
"Error logging in using email, please check your credentials.\nError: " +
|
|
(exception ?? 'Unknown')),
|
|
actions: [
|
|
TextButton(
|
|
child: Text('Dismiss'.i18n),
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
)
|
|
],
|
|
));
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AlertDialog(
|
|
title: Text('Email Login'.i18n),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: _loading
|
|
? [CircularProgressIndicator()]
|
|
: [
|
|
TextField(
|
|
decoration: InputDecoration(labelText: 'Email'.i18n),
|
|
onChanged: (s) => _email = s,
|
|
),
|
|
Container(
|
|
height: 8.0,
|
|
),
|
|
TextField(
|
|
obscureText: true,
|
|
decoration: InputDecoration(labelText: "Password".i18n),
|
|
onChanged: (s) => _password = s,
|
|
)
|
|
],
|
|
),
|
|
actions: [
|
|
if (!_loading)
|
|
TextButton(
|
|
child: Text('Login'),
|
|
onPressed: () async {
|
|
if (_email != null && _password != null)
|
|
await _login();
|
|
else
|
|
ScaffoldMessenger.of(context)
|
|
.snack("Missing email or password!".i18n);
|
|
},
|
|
)
|
|
],
|
|
);
|
|
}
|
|
}
|