Create CakePhone provider to handle cake phone APIs

Add CakePhone Auth View Model to manage the authentication flow
This commit is contained in:
OmarHatem28 2022-07-08 16:49:22 +02:00
parent eed908e6da
commit 9ab2be35d2
6 changed files with 160 additions and 29 deletions

View file

@ -5,10 +5,12 @@ import 'package:cake_wallet/entities/wake_lock.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/providers/cake_phone_provider.dart';
import 'package:cake_wallet/src/screens/cake_phone/phone_number_service/auto_renew_settings_page.dart';
import 'package:cake_wallet/src/screens/cake_phone/phone_number_service/number_settings_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart';
import 'package:cake_wallet/view_model/cake_phone/add_balance_view_model.dart';
import 'package:cake_wallet/view_model/cake_phone/cake_phone_auth_view_model.dart';
import 'package:cake_wallet/view_model/cake_phone/phone_plan_view_model.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cake_wallet/core/backup_service.dart';
@ -672,5 +674,11 @@ Future setup(
return AddBalancePage(addBalanceViewModel: getIt.get<AddBalanceViewModel>());
});
getIt.registerFactory(() {
return CakePhoneAuthViewModel(getIt.get<CakePhoneProvider>(), getIt.get<FlutterSecureStorage>());
});
getIt.registerLazySingleton(() => CakePhoneProvider());
_isSetupFinished = true;
}

View file

@ -22,4 +22,5 @@ class PreferencesKey {
static const bitcoinTransactionPriority = 'current_fee_priority_bitcoin';
static const shouldShowReceiveWarning = 'should_show_receive_warning';
static const shouldShowYatPopup = 'should_show_yat_popup';
static const cakePhoneTokenKey = 'cake_phone_token_key';
}

View file

@ -0,0 +1,54 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class CakePhoneProvider {
final _baseUrl = 'cake-phone.cakewallet.com';
Future<bool> authenticate(String email) async {
try {
final headers = {'Content-Type': 'application/json'};
final body = <String, String>{"email": email};
final uri = Uri.https(_baseUrl, '/email');
final response = await http.post(uri, headers: headers, body: json.encode(body));
if (response.statusCode != 200) {
debugPrint(response.body);
return false;
}
return true;
} catch (err) {
debugPrint(err.toString());
return false;
}
}
Future<String> verifyEmail({@required String email, @required String code}) async {
try {
final headers = {'Content-Type': 'application/json'};
final body = <String, String>{
"code": code,
"email": email,
};
final uri = Uri.https(_baseUrl, '/email/verify');
final response = await http.post(uri, headers: headers, body: json.encode(body));
if (response.statusCode != 200) {
debugPrint(response.body);
return null;
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final String token = responseJSON['token'] as String;
return token;
} catch (err) {
debugPrint(err.toString());
return null;
}
}
}

View file

@ -15,6 +15,7 @@ import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
import 'package:cake_wallet/src/screens/support/support_page.dart';
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
import 'package:cake_wallet/view_model/cake_phone/cake_phone_auth_view_model.dart';
import 'package:cake_wallet/view_model/cake_phone/phone_plan_view_model.dart';
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
import 'package:flutter/cupertino.dart';
@ -351,7 +352,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
return MaterialPageRoute<CakePhoneWelcomePage>(
settings: RouteSettings(name: Routes.cakePhoneAuth),
builder: (_) => CakePhoneAuthPage(isLogin: isLogin),
builder: (_) => CakePhoneAuthPage(
isLogin: isLogin,
cakePhoneAuthViewModel: getIt.get<CakePhoneAuthViewModel>(),
),
);
case Routes.cakePhoneVerification:

View file

@ -1,5 +1,7 @@
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:flushbar/flushbar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/routes.dart';
@ -7,14 +9,18 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/view_model/cake_phone/cake_phone_auth_view_model.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/core/execution_state.dart';
class CakePhoneAuthPage extends BasePage {
CakePhoneAuthPage({@required this.isLogin});
CakePhoneAuthPage({@required this.isLogin, @required this.cakePhoneAuthViewModel});
final bool isLogin;
final CakePhoneAuthViewModel cakePhoneAuthViewModel;
@override
Widget body(BuildContext context) => CakePhoneAuthBody(isLogin);
Widget body(BuildContext context) => CakePhoneAuthBody(isLogin, cakePhoneAuthViewModel);
@override
Widget middle(BuildContext context) {
@ -30,9 +36,10 @@ class CakePhoneAuthPage extends BasePage {
}
class CakePhoneAuthBody extends StatefulWidget {
CakePhoneAuthBody(this.isLogin);
CakePhoneAuthBody(this.isLogin, this.cakePhoneAuthViewModel);
final bool isLogin;
final CakePhoneAuthViewModel cakePhoneAuthViewModel;
@override
CakePhoneAuthBodyState createState() => CakePhoneAuthBodyState();
@ -44,6 +51,36 @@ class CakePhoneAuthBodyState extends State<CakePhoneAuthBody> {
AutovalidateMode _autoValidate = AutovalidateMode.disabled;
ReactionDisposer _reaction;
Flushbar<void> _authBar;
@override
void initState() {
super.initState();
_reaction ??= reaction((_) => widget.cakePhoneAuthViewModel.state, (ExecutionState state) {
if (state is ExecutedSuccessfullyState) {
_authBar?.dismiss();
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.pushNamed(context, Routes.cakePhoneVerification, arguments: _emailController.text);
});
}
if (state is IsExecutingState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_authBar = createBar<void>(S.of(context).authentication, duration: null)..show(context);
});
}
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_authBar?.dismiss();
showBar<void>(context, S.of(context).failed_authentication(state.error));
});
}
});
}
@override
Widget build(BuildContext context) {
return Container(
@ -73,10 +110,12 @@ class CakePhoneAuthBodyState extends State<CakePhoneAuthBody> {
children: <Widget>[
PrimaryButton(
onPressed: () {
if (widget.isLogin) {
_loginCakePhone();
if (_formKey.currentState.validate()) {
widget.cakePhoneAuthViewModel.auth(_emailController.text);
} else {
_registerCakePhone();
setState(() {
_autoValidate = AutovalidateMode.always;
});
}
},
text: widget.isLogin ? S.of(context).login : S.of(context).create_account,
@ -127,26 +166,4 @@ class CakePhoneAuthBodyState extends State<CakePhoneAuthBody> {
),
);
}
void _registerCakePhone() {
// TODO: Add Registration logic
if (_formKey.currentState.validate()) {
Navigator.pushNamed(context, Routes.cakePhoneVerification);
} else {
setState(() {
_autoValidate = AutovalidateMode.always;
});
}
}
void _loginCakePhone() {
// TODO: Add Login logic
if (_formKey.currentState.validate()) {
Navigator.pushNamed(context, Routes.cakePhoneVerification);
} else {
setState(() {
_autoValidate = AutovalidateMode.always;
});
}
}
}

View file

@ -0,0 +1,47 @@
import 'dart:async';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/providers/cake_phone_provider.dart';
import 'package:cake_wallet/core/execution_state.dart';
part 'cake_phone_auth_view_model.g.dart';
class CakePhoneAuthViewModel = CakePhoneAuthViewModelBase with _$CakePhoneAuthViewModel;
abstract class CakePhoneAuthViewModelBase with Store {
CakePhoneAuthViewModelBase(this._cakePhoneProvider, this._secureStorage) {
state = InitialExecutionState();
}
@observable
ExecutionState state;
final CakePhoneProvider _cakePhoneProvider;
final FlutterSecureStorage _secureStorage;
@action
Future<void> auth(String email) async {
state = IsExecutingState();
final isSuccessfullyAuthenticated = await _cakePhoneProvider.authenticate(email);
if (isSuccessfullyAuthenticated) {
state = ExecutedSuccessfullyState();
} else {
state = FailureState("");
}
}
@action
Future<void> verify(String email, String code) async {
final String token = await _cakePhoneProvider.verifyEmail(email: email, code: code);
if (token != null) {
state = ExecutedSuccessfullyState();
await _secureStorage.write(key: PreferencesKey.cakePhoneTokenKey, value: token);
} else {
state = FailureState("");
}
}
}