add authentication logic

This commit is contained in:
Godwin Asuquo 2022-06-01 03:47:00 +02:00
parent ec3e5d449e
commit 93b7ea29e5
11 changed files with 360 additions and 41 deletions

View file

@ -0,0 +1,11 @@
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/generated/i18n.dart';
class EmailValidator extends TextValidator {
EmailValidator()
: super(
errorMessage: 'Invalid email address',
pattern:
'^[^@]+@[^@]+\.[^@]+',
);
}

View file

@ -1,6 +1,8 @@
import 'package:cake_wallet/core/yat_service.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/wake_lock.dart';
import 'package:cake_wallet/ionia/ionia.dart';
import 'package:cake_wallet/ionia/ionia_api.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/haven/haven.dart';
@ -8,8 +10,10 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/src/screens/cake_pay/auth/create_account_page.dart';
import 'package:cake_wallet/src/screens/cake_pay/auth/forgot_password_page.dart';
import 'package:cake_wallet/src/screens/cake_pay/auth/login_page.dart';
import 'package:cake_wallet/src/screens/cake_pay/auth/verify_otp_page.dart';
import 'package:cake_wallet/src/screens/cake_pay/cake_pay.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart';
import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cake_wallet/core/backup_service.dart';
import 'package:cw_core/wallet_service.dart';
@ -645,13 +649,23 @@ Future setup(
getIt.registerFactory(() => AddressResolver(yatService: getIt.get<YatService>()));
getIt.registerFactory(() => WelcomePage());
getIt.registerFactory(() => LoginPage());
getIt.registerFactory(() => CreateAccountPage());
getIt.registerFactory(() => ForgotPassword());
getIt.registerFactory(() => IoniaApi());
getIt.registerFactory<IoniaService>(
() => IoniaService(getIt.get<FlutterSecureStorage>(), getIt.get<IoniaApi>()));
getIt.registerFactory(() => IoniaViewModel(ioniaService: getIt.get<IoniaService>()));
getIt.registerFactory(() => CreateAccountPage(getIt.get<IoniaViewModel>()));
getIt.registerFactory(() => LoginPage(getIt.get<IoniaViewModel>()));
getIt.registerFactory(() => VerifyIoniaOtp(getIt.get<IoniaViewModel>()));
getIt.registerFactory(() => WelcomePage(getIt.get<IoniaViewModel>()));
_isSetupFinished = true;
}

View file

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
abstract class IoniaCreateState {}
class IoniaCreateStateSuccess extends IoniaCreateState {}
class IoniaCreateStateLoading extends IoniaCreateState {}
class IoniaCreateStateFailure extends IoniaCreateState {
IoniaCreateStateFailure({@required this.error});
final String error;
}
abstract class IoniaOtpState {}
class IoniaOtpValidating extends IoniaOtpState {}
class IoniaOtpSuccess extends IoniaOtpState {}
class IoniaOtpSendDisabled extends IoniaOtpState {}
class IoniaOtpSendEnabled extends IoniaOtpState {}
class IoniaOtpFailure extends IoniaOtpState {
IoniaOtpFailure({@required this.error});
final String error;
}

View file

@ -8,6 +8,7 @@ import 'package:cake_wallet/src/screens/buy/pre_order_page.dart';
import 'package:cake_wallet/src/screens/cake_pay/auth/create_account_page.dart';
import 'package:cake_wallet/src/screens/cake_pay/auth/forgot_password_page.dart';
import 'package:cake_wallet/src/screens/cake_pay/auth/login_page.dart';
import 'package:cake_wallet/src/screens/cake_pay/auth/verify_otp_page.dart';
import 'package:cake_wallet/src/screens/cake_pay/cake_pay.dart';
import 'package:cake_wallet/src/screens/order_details/order_details_page.dart';
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
@ -417,6 +418,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.cakePayForgotPasswordPage:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<ForgotPassword>());
case Routes.verifyIoniaOtpPage:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<VerifyIoniaOtp>());
default:
return MaterialPageRoute<void>(

View file

@ -64,4 +64,5 @@ class Routes {
static const cakePayCreateAccountPage = '/cake_pay_create_account_page';
static const cakePayLoginPage = '/cake_pay_login_page';
static const cakePayForgotPasswordPage = '/cake_pay_forgot_password_page';
static const verifyIoniaOtpPage = '/cake_pay_verify_otp_page';
}

View file

@ -1,15 +1,34 @@
import 'package:cake_wallet/core/email_validator.dart';
import 'package:cake_wallet/ionia/ionia_create_state.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
class CreateAccountPage extends BasePage {
CreateAccountPage() : _formKey = GlobalKey<FormState>();
final IoniaViewModel _ioniaViewModel;
CreateAccountPage(this._ioniaViewModel)
: _emailFocus = FocusNode(),
_emailController = TextEditingController(),
_formKey = GlobalKey<FormState>() {
_emailController.text = _ioniaViewModel.email;
_emailController.addListener(() => _ioniaViewModel.email = _emailController.text);
}
final GlobalKey<FormState> _formKey;
final FocusNode _emailFocus;
final TextEditingController _emailController;
@override
Widget middle(BuildContext context) {
return Text(
@ -24,21 +43,24 @@ class CreateAccountPage extends BasePage {
@override
Widget body(BuildContext context) {
reaction((_) => _ioniaViewModel.createUserState, (IoniaCreateState state) {
if (state is IoniaCreateStateFailure) {
_onCreateUserFailure(context, state.error);
}
if (state is IoniaCreateStateSuccess) {
_onCreateSuccessful(context);
}
});
return ScrollableWithBottomSection(
contentPadding: EdgeInsets.all(24),
content: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
BaseTextFormField(
hintText: 'Email Address *',
),
SizedBox(height: 20),
BaseTextFormField(
hintText: 'Password *',
),
],
child: BaseTextFormField(
hintText: 'Email Address',
focusNode: _emailFocus,
validator: EmailValidator(),
controller: _emailController,
),
),
bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24),
@ -47,11 +69,19 @@ class CreateAccountPage extends BasePage {
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
PrimaryButton(
text: S.of(context).create_account,
onPressed: () {},
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
Observer(
builder: (_) => LoadingPrimaryButton(
text: S.of(context).create_account,
onPressed: () async {
if (!_formKey.currentState.validate()) {
return;
}
await _ioniaViewModel.createUser(_emailController.text);
},
isLoading: _ioniaViewModel.createUserState is IoniaCreateStateLoading,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
),
),
SizedBox(
height: 20,
@ -92,3 +122,17 @@ class CreateAccountPage extends BasePage {
);
}
}
void _onCreateUserFailure(BuildContext context, String error) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.current.create_account,
alertContent: error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
}
void _onCreateSuccessful(BuildContext context) => Navigator.pushNamed(context, Routes.verifyIoniaOtpPage);

View file

@ -1,19 +1,36 @@
import 'package:cake_wallet/core/email_validator.dart';
import 'package:cake_wallet/ionia/ionia_create_state.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
class LoginPage extends BasePage {
LoginPage() : _formKey = GlobalKey<FormState>();
final IoniaViewModel _ioniaViewModel;
LoginPage(this._ioniaViewModel)
: _formKey = GlobalKey<FormState>(),
_emailFocus = FocusNode(),
_emailController = TextEditingController() {
_emailController.text = _ioniaViewModel.email;
_emailController.addListener(() => _ioniaViewModel.email = _emailController.text);
}
final GlobalKey<FormState> _formKey;
@override
Color get titleColor => Colors.black;
final FocusNode _emailFocus;
final TextEditingController _emailController;
@override
Widget middle(BuildContext context) {
return Text(
@ -28,21 +45,22 @@ class LoginPage extends BasePage {
@override
Widget body(BuildContext context) {
reaction((_) => _ioniaViewModel.createUserState, (IoniaCreateState state) {
if (state is IoniaCreateStateFailure) {
_onLoginUserFailure(context, state.error);
}
if (state is IoniaCreateStateSuccess) {
_onLoginSuccessful(context);
}
});
return ScrollableWithBottomSection(
contentPadding: EdgeInsets.all(24),
content: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
BaseTextFormField(
hintText: 'Email Address',
),
SizedBox(height: 20),
BaseTextFormField(
hintText: 'Password',
),
],
child: BaseTextFormField(
hintText: 'Email Address',
validator: EmailValidator(),
controller: _emailController,
),
),
bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24),
@ -51,11 +69,19 @@ class LoginPage extends BasePage {
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
PrimaryButton(
text: S.of(context).login,
onPressed: () {},
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
Observer(
builder: (_) => LoadingPrimaryButton(
text: S.of(context).login,
onPressed: () async {
if (!_formKey.currentState.validate()) {
return;
}
await _ioniaViewModel.createUser(_emailController.text);
},
isLoading: _ioniaViewModel.createUserState is IoniaCreateStateLoading,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
),
),
SizedBox(
height: 20,
@ -78,3 +104,17 @@ class LoginPage extends BasePage {
);
}
}
void _onLoginUserFailure(BuildContext context, String error) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.current.login,
alertContent: error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
}
void _onLoginSuccessful(BuildContext context) => Navigator.pushNamed(context, Routes.verifyIoniaOtpPage);

View file

@ -0,0 +1,103 @@
import 'package:cake_wallet/ionia/ionia_create_state.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class VerifyIoniaOtp extends BasePage {
final IoniaViewModel _ioniaViewModel;
VerifyIoniaOtp(this._ioniaViewModel)
: _codeController = TextEditingController(),
_codeFocus = FocusNode() {
_codeController.addListener(() {
final otp = _codeController.text;
_ioniaViewModel.otp = otp;
if (otp.length > 3) {
_ioniaViewModel.otpState = IoniaOtpSendEnabled();
} else {
_ioniaViewModel.otpState = IoniaOtpSendDisabled();
}
});
}
@override
Widget middle(BuildContext context) {
return Text(
S.current.verification,
style: TextStyle(
fontSize: 22,
fontFamily: 'Lato',
fontWeight: FontWeight.w900,
),
);
}
final TextEditingController _codeController;
final FocusNode _codeFocus;
@override
Widget body(BuildContext context) {
return ScrollableWithBottomSection(
contentPadding: EdgeInsets.all(24),
content: Column(
children: [
BaseTextFormField(
hintText: 'Enter code',
focusNode: _codeFocus,
controller: _codeController,
),
SizedBox(height: 14),
Text(
S.of(context).fill_code,
style: TextStyle(color: Color(0xff7A93BA), fontSize: 12),
),
SizedBox(height: 34),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(S.of(context).dont_get_code),
SizedBox(width: 20),
Text(
S.of(context).resend_code,
style: TextStyle(
color: Palette.blueCraiola,
fontSize: 14,
fontWeight: FontWeight.w400,
),
),
],
),
],
),
bottomSectionPadding: EdgeInsets.symmetric(vertical: 36, horizontal: 24),
bottomSection: Column(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Observer(
builder: (_) => LoadingPrimaryButton(
text: S.of(context).continue_text,
onPressed: () async => await _ioniaViewModel.verifyEmail(_codeController.text),
isDisabled: _ioniaViewModel.otpState is IoniaOtpSendDisabled,
isLoading: _ioniaViewModel.otpState is IoniaOtpValidating,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
),
),
SizedBox(
height: 20,
),
],
),
],
),
);
}
}

View file

@ -2,11 +2,17 @@ import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/view_model/ionia/ionia_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:mobx/mobx.dart';
class WelcomePage extends BasePage {
final IoniaViewModel _ioniaViewModel;
WelcomePage(this._ioniaViewModel);
@override
Color get titleColor => Colors.black;
@ -24,6 +30,11 @@ class WelcomePage extends BasePage {
@override
Widget body(BuildContext context) {
reaction((_) => _ioniaViewModel.isLoggedIn, (bool state) {
if (state) {
// TODO:: Navigate to main page
}
});
return Padding(
padding: const EdgeInsets.all(24.0),
child: Column(

View file

@ -0,0 +1,58 @@
import 'package:cake_wallet/ionia/ionia.dart';
import 'package:cake_wallet/ionia/ionia_create_state.dart';
import 'package:mobx/mobx.dart';
part 'ionia_view_model.g.dart';
class IoniaViewModel = IoniaViewModelBase with _$IoniaViewModel;
abstract class IoniaViewModelBase with Store {
IoniaViewModelBase({this.ioniaService})
: createUserState = IoniaCreateStateSuccess(),
otpState = IoniaOtpSendDisabled() {
_getAuthStatus().then((value) => isLoggedIn = value);
}
final IoniaService ioniaService;
@observable
IoniaCreateState createUserState;
@observable
IoniaOtpState otpState;
@observable
String email;
@observable
String otp;
@observable
bool isLoggedIn;
@action
Future<void> createUser(String email) async {
createUserState = IoniaCreateStateLoading();
try {
await ioniaService.createUser(email);
createUserState = IoniaCreateStateSuccess();
} catch (e) {
createUserState = IoniaCreateStateFailure(error: 'Something went wrong!');
}
}
@action
Future<void> verifyEmail(String code) async {
try {
otpState = IoniaOtpValidating();
await ioniaService.verifyEmail(code);
otpState = IoniaOtpSuccess();
} catch (_) {
otpState = IoniaOtpFailure(error: 'Invalid OTP. Try again');
}
}
Future<bool> _getAuthStatus() async {
return await ioniaService.isLogined();
}
}

View file

@ -546,5 +546,9 @@
"manage_cards": "Manage Cards",
"setup_your_debit_card": "Set up your debit card",
"no_id_required": "No ID required. Top up and spend anywhere",
"how_to_use_card": "How to use this card"
"how_to_use_card": "How to use this card",
"verification": "Verification",
"fill_code": "Please fill in the verification code provided to your email",
"dont_get_code": "Don't get code",
"resend_code": "Please resend it"
}