From e3a349c811cea1ed5f6aaf5a9ea72defb449d4f2 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 8 Jul 2021 09:17:07 +0300 Subject: [PATCH] Support token login --- lib/redux/auth/auth_actions.dart | 5 + lib/ui/auth/login_view.dart | 30 ++++ lib/ui/auth/login_vm.dart | 244 ++++++++++++++++--------------- 3 files changed, 163 insertions(+), 116 deletions(-) diff --git a/lib/redux/auth/auth_actions.dart b/lib/redux/auth/auth_actions.dart index b664ccae2..0d31e4b5a 100644 --- a/lib/redux/auth/auth_actions.dart +++ b/lib/redux/auth/auth_actions.dart @@ -37,6 +37,11 @@ class OAuthLoginRequest implements StartLoading { final String oneTimePassword; } +class TokenLogin { + TokenLogin(this.token); + final String token; +} + class UserLoadUrl { UserLoadUrl({this.url}); diff --git a/lib/ui/auth/login_view.dart b/lib/ui/auth/login_view.dart index 70659e5a6..fa33861df 100644 --- a/lib/ui/auth/login_view.dart +++ b/lib/ui/auth/login_view.dart @@ -6,6 +6,7 @@ import 'package:flutter/services.dart'; import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/redux/ui/pref_state.dart'; import 'package:invoiceninja_flutter/ui/app/buttons/app_text_button.dart'; +import 'package:invoiceninja_flutter/ui/app/buttons/elevated_button.dart'; import 'package:invoiceninja_flutter/ui/app/dialogs/alert_dialog.dart'; import 'package:invoiceninja_flutter/ui/app/forms/app_toggle_buttons.dart'; import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart'; @@ -46,6 +47,7 @@ class _LoginState extends State { final _urlController = TextEditingController(); final _secretController = TextEditingController(); final _oneTimePasswordController = TextEditingController(); + final _tokenController = TextEditingController(); final _buttonController = RoundedLoadingButtonController(); @@ -53,6 +55,7 @@ class _LoginState extends State { String _loginError = ''; + bool _tokenLogin = false; bool _emailLogin = false; bool _isSelfHosted = false; bool _createAccount = true; @@ -110,6 +113,8 @@ class _LoginState extends State { _passwordController.dispose(); _urlController.dispose(); _secretController.dispose(); + _oneTimePasswordController.dispose(); + _tokenController.dispose(); super.dispose(); } @@ -294,9 +299,34 @@ class _LoginState extends State { onTap: () { launch(kSiteUrl, forceSafariVC: false, forceWebView: false); }, + onLongPress: () { + if (kReleaseMode) { + return; + } + + setState(() => _tokenLogin = !_tokenLogin); + }, ), ), ), + if (_tokenLogin) + FormCard( + forceNarrow: calculateLayout(context) != AppLayout.mobile, + children: [ + DecoratedFormField( + label: localization.token, + controller: _tokenController, + ), + AppButton( + label: localization.submit.toUpperCase(), + onPressed: () { + final Completer completer = Completer(); + viewModel.onTokenLoginPressed(context, completer, + token: _tokenController.text); + }, + ) + ], + ), AnimatedOpacity( duration: Duration(milliseconds: 500), opacity: viewModel.authState.isAuthenticated ? 0 : 1, diff --git a/lib/ui/auth/login_vm.dart b/lib/ui/auth/login_vm.dart index 5c427e857..1e3e09b28 100644 --- a/lib/ui/auth/login_vm.dart +++ b/lib/ui/auth/login_vm.dart @@ -48,6 +48,7 @@ class LoginVM { @required this.onSignUpPressed, @required this.onGoogleLoginPressed, @required this.onGoogleSignUpPressed, + @required this.onTokenLoginPressed, }); AppState state; @@ -79,6 +80,12 @@ class LoginVM { @required String password, }) onSignUpPressed; + final Function( + BuildContext, + Completer completer, { + @required String token, + }) onTokenLoginPressed; + final Function(BuildContext, Completer completer, {String url, String secret, String oneTimePassword}) onGoogleLoginPressed; final Function(BuildContext, Completer completer) onGoogleSignUpPressed; @@ -120,132 +127,137 @@ class LoginVM { } return LoginVM( - state: store.state, - isLoading: store.state.isLoading, - authState: store.state.authState, - onGoogleLoginPressed: ( - BuildContext context, - Completer completer, { - @required String url, - @required String secret, - @required String oneTimePassword, - }) async { - try { - await GoogleOAuth.signOut(); - final signedIn = await GoogleOAuth.signIn((idToken, accessToken) { - if (idToken.isEmpty || accessToken.isEmpty) { - GoogleOAuth.signOut(); - completer.completeError( - AppLocalization.of(context).anErrorOccurredTryAgain); - } else { - store.dispatch(OAuthLoginRequest( - completer: completer, - idToken: idToken, - accessToken: accessToken, - url: _formatApiUrl(url), - secret: secret.trim(), - platform: getPlatform(context), - oneTimePassword: oneTimePassword, - )); - completer.future.then((_) => _handleLogin(context: context)); - } - }); - if (!signedIn) { + state: store.state, + isLoading: store.state.isLoading, + authState: store.state.authState, + onGoogleLoginPressed: ( + BuildContext context, + Completer completer, { + @required String url, + @required String secret, + @required String oneTimePassword, + }) async { + try { + await GoogleOAuth.signOut(); + final signedIn = await GoogleOAuth.signIn((idToken, accessToken) { + if (idToken.isEmpty || accessToken.isEmpty) { + GoogleOAuth.signOut(); completer.completeError( AppLocalization.of(context).anErrorOccurredTryAgain); + } else { + store.dispatch(OAuthLoginRequest( + completer: completer, + idToken: idToken, + accessToken: accessToken, + url: _formatApiUrl(url), + secret: secret.trim(), + platform: getPlatform(context), + oneTimePassword: oneTimePassword, + )); + completer.future.then((_) => _handleLogin(context: context)); } - } catch (error) { - completer.completeError(error); - print('## onGoogleLoginPressed: $error'); + }); + if (!signedIn) { + completer.completeError( + AppLocalization.of(context).anErrorOccurredTryAgain); } - }, - onGoogleSignUpPressed: - (BuildContext context, Completer completer) async { - try { - await GoogleOAuth.signOut(); - final signedIn = await GoogleOAuth.signUp((idToken, accessToken) { - if (idToken.isEmpty || accessToken.isEmpty) { - GoogleOAuth.signOut(); - completer.completeError( - AppLocalization.of(context).anErrorOccurredTryAgain); - } else { - store.dispatch(OAuthSignUpRequest( - completer: completer, - idToken: idToken, - accessToken: accessToken, - )); - completer.future.then( - (_) => _handleLogin(context: context, isSignUp: true)); - } - }); - if (!signedIn) { + } catch (error) { + completer.completeError(error); + print('## onGoogleLoginPressed: $error'); + } + }, + onGoogleSignUpPressed: + (BuildContext context, Completer completer) async { + try { + await GoogleOAuth.signOut(); + final signedIn = await GoogleOAuth.signUp((idToken, accessToken) { + if (idToken.isEmpty || accessToken.isEmpty) { + GoogleOAuth.signOut(); completer.completeError( AppLocalization.of(context).anErrorOccurredTryAgain); + } else { + store.dispatch(OAuthSignUpRequest( + completer: completer, + idToken: idToken, + accessToken: accessToken, + )); + completer.future + .then((_) => _handleLogin(context: context, isSignUp: true)); } - } catch (error) { - completer.completeError(error); - print('## onGoogleSignUpPressed: $error'); - } - }, - onSignUpPressed: ( - BuildContext context, - Completer completer, { - @required String email, - @required String password, - }) async { - if (store.state.isLoading) { - return; + }); + if (!signedIn) { + completer.completeError( + AppLocalization.of(context).anErrorOccurredTryAgain); } + } catch (error) { + completer.completeError(error); + print('## onGoogleSignUpPressed: $error'); + } + }, + onSignUpPressed: ( + BuildContext context, + Completer completer, { + @required String email, + @required String password, + }) async { + if (store.state.isLoading) { + return; + } - store.dispatch(UserSignUpRequest( - completer: completer, - email: email.trim(), - password: password.trim(), - )); - completer.future - .then((_) => _handleLogin(context: context, isSignUp: true)); - }, - onRecoverPressed: ( - BuildContext context, - Completer completer, { - @required String email, - @required String url, - @required String secret, - }) async { - if (store.state.isLoading) { - return; - } + store.dispatch(UserSignUpRequest( + completer: completer, + email: email.trim(), + password: password.trim(), + )); + completer.future + .then((_) => _handleLogin(context: context, isSignUp: true)); + }, + onRecoverPressed: ( + BuildContext context, + Completer completer, { + @required String email, + @required String url, + @required String secret, + }) async { + if (store.state.isLoading) { + return; + } - store.dispatch(RecoverPasswordRequest( - completer: completer, - email: email.trim(), - url: _formatApiUrl(url), - secret: secret.trim(), - )); - }, - onLoginPressed: ( - BuildContext context, - Completer completer, { - @required String email, - @required String password, - @required String url, - @required String secret, - @required String oneTimePassword, - }) async { - if (store.state.isLoading) { - return; - } + store.dispatch(RecoverPasswordRequest( + completer: completer, + email: email.trim(), + url: _formatApiUrl(url), + secret: secret.trim(), + )); + }, + onLoginPressed: ( + BuildContext context, + Completer completer, { + @required String email, + @required String password, + @required String url, + @required String secret, + @required String oneTimePassword, + }) async { + if (store.state.isLoading) { + return; + } - store.dispatch(UserLoginRequest( - completer: completer, - email: email.trim(), - password: password.trim(), - url: _formatApiUrl(url), - secret: secret.trim(), - platform: getPlatform(context), - oneTimePassword: oneTimePassword.trim(), - )); - completer.future.then((_) => _handleLogin(context: context)); - }); + store.dispatch(UserLoginRequest( + completer: completer, + email: email.trim(), + password: password.trim(), + url: _formatApiUrl(url), + secret: secret.trim(), + platform: getPlatform(context), + oneTimePassword: oneTimePassword.trim(), + )); + completer.future.then((_) => _handleLogin(context: context)); + }, + onTokenLoginPressed: (BuildContext context, Completer completer, + {@required String token}) { + // + }, + ); } }