import 'package:flutter/material.dart'; import 'package:flutter/services.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 { late String _arl; String? _error; //Initialize deezer etc Future _init() async { deezerAPI.arl = settings.arl; //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: [ 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: [ 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: [ 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 { 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); }, ) ], ); } }