From 777e7e3f0d23d8caaa8ee3c3720512575f92e6d9 Mon Sep 17 00:00:00 2001 From: Frederik Benoist Date: Tue, 7 Mar 2023 22:39:58 +0100 Subject: [PATCH] apiProvider --- assets/images/logo.png | Bin 10220 -> 15025 bytes assets/images/logo_dark.png | Bin 14692 -> 18832 bytes ios/Podfile.lock | 6 + lib/bloc/authentication/login/login_bloc.dart | 28 ++ .../authentication/login/login_event.dart | 11 + .../authentication/login/login_state.dart | 23 ++ lib/bloc/example/bloc.dart | 3 + lib/bloc/example/example_bloc.dart | 38 +++ lib/bloc/example/example_event.dart | 15 + lib/bloc/example/example_state.dart | 26 ++ lib/bloc/product_grid/bloc.dart | 3 + lib/bloc/product_grid/product_grid_bloc.dart | 26 ++ lib/bloc/product_grid/product_grid_event.dart | 10 + lib/bloc/product_grid/product_grid_state.dart | 22 ++ lib/bloc/product_listview/bloc.dart | 3 + .../product_listview_bloc.dart | 27 ++ .../product_listview_event.dart | 10 + .../product_listview_state.dart | 22 ++ lib/bloc/student/bloc.dart | 3 + lib/bloc/student/student_bloc.dart | 121 +++++++ lib/bloc/student/student_event.dart | 32 ++ lib/bloc/student/student_state.dart | 99 ++++++ lib/config/constant.dart | 17 + lib/model/login.dart | 57 ++++ lib/model/user_profile.dart | 20 ++ lib/network/api_provider.dart | 313 ++++++++++++++++++ lib/service/local_storage.dart | 85 +++++ lib/ui/account/tab_account.dart | 22 +- lib/ui/authentication/forgot_password.dart | 115 ------- lib/ui/authentication/signin.dart | 85 ++--- lib/ui/authentication/signin_old.dart | 250 -------------- lib/ui/authentication/signup.dart | 177 ---------- lib/ui/authentication/verification.dart | 172 ++++++++++ lib/ui/home/tab_home.dart | 9 +- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + macos/Podfile.lock | 17 + pubspec.lock | 76 ++++- pubspec.yaml | 10 +- .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 42 files changed, 1363 insertions(+), 601 deletions(-) create mode 100644 lib/bloc/authentication/login/login_bloc.dart create mode 100644 lib/bloc/authentication/login/login_event.dart create mode 100644 lib/bloc/authentication/login/login_state.dart create mode 100644 lib/bloc/example/bloc.dart create mode 100644 lib/bloc/example/example_bloc.dart create mode 100644 lib/bloc/example/example_event.dart create mode 100644 lib/bloc/example/example_state.dart create mode 100644 lib/bloc/product_grid/bloc.dart create mode 100644 lib/bloc/product_grid/product_grid_bloc.dart create mode 100644 lib/bloc/product_grid/product_grid_event.dart create mode 100644 lib/bloc/product_grid/product_grid_state.dart create mode 100644 lib/bloc/product_listview/bloc.dart create mode 100644 lib/bloc/product_listview/product_listview_bloc.dart create mode 100644 lib/bloc/product_listview/product_listview_event.dart create mode 100644 lib/bloc/product_listview/product_listview_state.dart create mode 100644 lib/bloc/student/bloc.dart create mode 100644 lib/bloc/student/student_bloc.dart create mode 100644 lib/bloc/student/student_event.dart create mode 100644 lib/bloc/student/student_state.dart create mode 100644 lib/model/login.dart create mode 100644 lib/model/user_profile.dart create mode 100644 lib/network/api_provider.dart create mode 100644 lib/service/local_storage.dart delete mode 100644 lib/ui/authentication/forgot_password.dart delete mode 100644 lib/ui/authentication/signin_old.dart delete mode 100644 lib/ui/authentication/signup.dart create mode 100644 lib/ui/authentication/verification.dart diff --git a/assets/images/logo.png b/assets/images/logo.png index 2cc752e09cd674d098235cf17bfb489649f94776..f8d71967f3f0fd993019c74a64af8f1fa5bb4e3f 100644 GIT binary patch literal 15025 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 literal 10220 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 diff --git a/assets/images/logo_dark.png b/assets/images/logo_dark.png index 23c18eece401ed03c9c6006acbdcdd15b485a34a..cf40c0707b54156310b29ae65259a73a8e744022 100644 GIT binary patch literal 18832 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 M>Z*KDvU~pD0AnNSTL1t6 literal 14692 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 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a1618e5..0e6f6ef 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,5 +1,7 @@ PODS: - Flutter (1.0.0) + - flutter_secure_storage (6.0.0): + - Flutter - fluttertoast (0.0.2): - Flutter - Toast @@ -25,6 +27,7 @@ PODS: DEPENDENCIES: - Flutter (from `Flutter`) + - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - objectbox_flutter_libs (from `.symlinks/plugins/objectbox_flutter_libs/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) @@ -41,6 +44,8 @@ SPEC REPOS: EXTERNAL SOURCES: Flutter: :path: Flutter + flutter_secure_storage: + :path: ".symlinks/plugins/flutter_secure_storage/ios" fluttertoast: :path: ".symlinks/plugins/fluttertoast/ios" objectbox_flutter_libs: @@ -56,6 +61,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a ObjectBox: a7900d5335218cd437cbc080b7ccc38a5211f7b4 diff --git a/lib/bloc/authentication/login/login_bloc.dart b/lib/bloc/authentication/login/login_bloc.dart new file mode 100644 index 0000000..f1e9d58 --- /dev/null +++ b/lib/bloc/authentication/login/login_bloc.dart @@ -0,0 +1,28 @@ +import 'package:bloc/bloc.dart'; +import 'package:mobdr/model/integration/login_model.dart'; +import 'package:mobdr/network/api_provider.dart'; +import 'package:meta/meta.dart'; + +part 'login_event.dart'; +part 'login_state.dart'; + +class LoginBloc extends Bloc { + LoginBloc() : super(LoginInitial()) { + on(_login); + } +} + +void _login(Login event, Emitter emit) async { + ApiProvider _apiProvider = ApiProvider(); + + emit(LoginWaiting()); + try { + List data = + await _apiProvider.login2(event.email, event.password, event.apiToken); + emit(LoginSuccess(loginData: data)); + } catch (ex) { + if (ex != 'cancel') { + emit(LoginError(errorMessage: ex.toString())); + } + } +} diff --git a/lib/bloc/authentication/login/login_event.dart b/lib/bloc/authentication/login/login_event.dart new file mode 100644 index 0000000..33bf630 --- /dev/null +++ b/lib/bloc/authentication/login/login_event.dart @@ -0,0 +1,11 @@ +part of 'login_bloc.dart'; + +@immutable +abstract class LoginEvent {} + +class Login extends LoginEvent { + final String email; + final String password; + final apiToken; + Login({required this.email, required this.password, required this.apiToken}); +} \ No newline at end of file diff --git a/lib/bloc/authentication/login/login_state.dart b/lib/bloc/authentication/login/login_state.dart new file mode 100644 index 0000000..5fb56a5 --- /dev/null +++ b/lib/bloc/authentication/login/login_state.dart @@ -0,0 +1,23 @@ +part of 'login_bloc.dart'; + +@immutable +abstract class LoginState {} + +class LoginInitial extends LoginState {} + +class InitialLoginState extends LoginState {} + +class LoginError extends LoginState { + final String errorMessage; + + LoginError({ + required this.errorMessage, + }); +} + +class LoginWaiting extends LoginState {} + +class LoginSuccess extends LoginState { + final List loginData; + LoginSuccess({required this.loginData}); +} \ No newline at end of file diff --git a/lib/bloc/example/bloc.dart b/lib/bloc/example/bloc.dart new file mode 100644 index 0000000..fdf7254 --- /dev/null +++ b/lib/bloc/example/bloc.dart @@ -0,0 +1,3 @@ +export 'example_bloc.dart'; +export 'example_event.dart'; +export 'example_state.dart'; \ No newline at end of file diff --git a/lib/bloc/example/example_bloc.dart b/lib/bloc/example/example_bloc.dart new file mode 100644 index 0000000..8b8854c --- /dev/null +++ b/lib/bloc/example/example_bloc.dart @@ -0,0 +1,38 @@ +import 'package:bloc/bloc.dart'; +import 'package:mobdr/network/api_provider.dart'; +import './bloc.dart'; + +class ExampleBloc extends Bloc { + ExampleBloc() : super(InitialExampleState()) { + on(_getExample); + on(_postExample); + } +} + +void _getExample(GetExample event, Emitter emit) async { + ApiProvider _apiProvider = ApiProvider(); + + emit(ExampleWaiting()); + try { + String data = await _apiProvider.getExample(event.apiToken); + emit(GetExampleSuccess(exampleData: data)); + } catch (ex) { + if (ex != 'cancel') { + emit(ExampleError(errorMessage: ex.toString())); + } + } +} + +void _postExample(PostExample event, Emitter emit) async { + ApiProvider _apiProvider = ApiProvider(); + + emit(ExampleWaiting()); + try { + String data = await _apiProvider.postExample(event.id, event.apiToken); + emit(PostExampleSuccess(exampleData: data)); + } catch (ex) { + if (ex != 'cancel') { + emit(ExampleError(errorMessage: ex.toString())); + } + } +} diff --git a/lib/bloc/example/example_event.dart b/lib/bloc/example/example_event.dart new file mode 100644 index 0000000..8b16fc3 --- /dev/null +++ b/lib/bloc/example/example_event.dart @@ -0,0 +1,15 @@ +import 'package:meta/meta.dart'; + +@immutable +abstract class ExampleEvent {} + +class GetExample extends ExampleEvent { + final apiToken; + GetExample({@required this.apiToken}); +} + +class PostExample extends ExampleEvent { + final String id; + final apiToken; + PostExample({required this.id, required this.apiToken}); +} \ No newline at end of file diff --git a/lib/bloc/example/example_state.dart b/lib/bloc/example/example_state.dart new file mode 100644 index 0000000..9cbcc74 --- /dev/null +++ b/lib/bloc/example/example_state.dart @@ -0,0 +1,26 @@ +import 'package:meta/meta.dart'; + +@immutable +abstract class ExampleState {} + +class InitialExampleState extends ExampleState {} + +class ExampleError extends ExampleState { + final String errorMessage; + + ExampleError({ + required this.errorMessage, + }); +} + +class ExampleWaiting extends ExampleState {} + +class GetExampleSuccess extends ExampleState { + final String exampleData; + GetExampleSuccess({required this.exampleData}); +} + +class PostExampleSuccess extends ExampleState { + final String exampleData; + PostExampleSuccess({required this.exampleData}); +} \ No newline at end of file diff --git a/lib/bloc/product_grid/bloc.dart b/lib/bloc/product_grid/bloc.dart new file mode 100644 index 0000000..bfae681 --- /dev/null +++ b/lib/bloc/product_grid/bloc.dart @@ -0,0 +1,3 @@ +export 'product_grid_bloc.dart'; +export 'product_grid_event.dart'; +export 'product_grid_state.dart'; \ No newline at end of file diff --git a/lib/bloc/product_grid/product_grid_bloc.dart b/lib/bloc/product_grid/product_grid_bloc.dart new file mode 100644 index 0000000..cd5eb0f --- /dev/null +++ b/lib/bloc/product_grid/product_grid_bloc.dart @@ -0,0 +1,26 @@ +import 'package:bloc/bloc.dart'; +import 'package:mobdr/model/integration/product_grid_model.dart'; +import 'package:mobdr/network/api_provider.dart'; +import './bloc.dart'; + +class ProductGridBloc extends Bloc { + ProductGridBloc() : super(InitialProductGridState()) { + on(_getProductGrid); + } +} + +void _getProductGrid( + GetProductGrid event, Emitter emit) async { + ApiProvider _apiProvider = ApiProvider(); + + emit(ProductGridWaiting()); + try { + List data = await _apiProvider.getProductGrid( + event.sessionId, event.skip, event.limit, event.apiToken); + emit(GetProductGridSuccess(productGridData: data)); + } catch (ex) { + if (ex != 'cancel') { + emit(ProductGridError(errorMessage: ex.toString())); + } + } +} diff --git a/lib/bloc/product_grid/product_grid_event.dart b/lib/bloc/product_grid/product_grid_event.dart new file mode 100644 index 0000000..005de82 --- /dev/null +++ b/lib/bloc/product_grid/product_grid_event.dart @@ -0,0 +1,10 @@ +import 'package:meta/meta.dart'; + +@immutable +abstract class ProductGridEvent {} + +class GetProductGrid extends ProductGridEvent { + final String sessionId, skip, limit; + final apiToken; + GetProductGrid({required this.sessionId, required this.skip, required this.limit, @required this.apiToken}); +} \ No newline at end of file diff --git a/lib/bloc/product_grid/product_grid_state.dart b/lib/bloc/product_grid/product_grid_state.dart new file mode 100644 index 0000000..961d233 --- /dev/null +++ b/lib/bloc/product_grid/product_grid_state.dart @@ -0,0 +1,22 @@ +import 'package:mobdr/model/integration/product_grid_model.dart'; +import 'package:meta/meta.dart'; + +@immutable +abstract class ProductGridState {} + +class InitialProductGridState extends ProductGridState {} + +class ProductGridError extends ProductGridState { + final String errorMessage; + + ProductGridError({ + required this.errorMessage, + }); +} + +class ProductGridWaiting extends ProductGridState {} + +class GetProductGridSuccess extends ProductGridState { + final List productGridData; + GetProductGridSuccess({required this.productGridData}); +} diff --git a/lib/bloc/product_listview/bloc.dart b/lib/bloc/product_listview/bloc.dart new file mode 100644 index 0000000..4f3f564 --- /dev/null +++ b/lib/bloc/product_listview/bloc.dart @@ -0,0 +1,3 @@ +export 'product_listview_bloc.dart'; +export 'product_listview_event.dart'; +export 'product_listview_state.dart'; \ No newline at end of file diff --git a/lib/bloc/product_listview/product_listview_bloc.dart b/lib/bloc/product_listview/product_listview_bloc.dart new file mode 100644 index 0000000..232c94b --- /dev/null +++ b/lib/bloc/product_listview/product_listview_bloc.dart @@ -0,0 +1,27 @@ +import 'package:bloc/bloc.dart'; +import 'package:mobdr/model/integration/product_listview_model.dart'; +import 'package:mobdr/network/api_provider.dart'; +import './bloc.dart'; + +class ProductListviewBloc + extends Bloc { + ProductListviewBloc() : super(InitialProductListviewState()) { + on(_getProductListview); + } +} + +void _getProductListview( + GetProductListview event, Emitter emit) async { + ApiProvider _apiProvider = ApiProvider(); + + emit(ProductListviewWaiting()); + try { + List data = await _apiProvider.getProductListview( + event.sessionId, event.skip, event.limit, event.apiToken); + emit(GetProductListviewSuccess(productListviewData: data)); + } catch (ex) { + if (ex != 'cancel') { + emit(ProductListviewError(errorMessage: ex.toString())); + } + } +} diff --git a/lib/bloc/product_listview/product_listview_event.dart b/lib/bloc/product_listview/product_listview_event.dart new file mode 100644 index 0000000..e15ec76 --- /dev/null +++ b/lib/bloc/product_listview/product_listview_event.dart @@ -0,0 +1,10 @@ +import 'package:meta/meta.dart'; + +@immutable +abstract class ProductListviewEvent {} + +class GetProductListview extends ProductListviewEvent { + final String sessionId, skip, limit; + final apiToken; + GetProductListview({required this.sessionId, required this.skip, required this.limit, required this.apiToken}); +} \ No newline at end of file diff --git a/lib/bloc/product_listview/product_listview_state.dart b/lib/bloc/product_listview/product_listview_state.dart new file mode 100644 index 0000000..fc24d42 --- /dev/null +++ b/lib/bloc/product_listview/product_listview_state.dart @@ -0,0 +1,22 @@ +import 'package:mobdr/model/integration/product_listview_model.dart'; +import 'package:meta/meta.dart'; + +@immutable +abstract class ProductListviewState {} + +class InitialProductListviewState extends ProductListviewState {} + +class ProductListviewError extends ProductListviewState { + final String errorMessage; + + ProductListviewError({ + required this.errorMessage, + }); +} + +class ProductListviewWaiting extends ProductListviewState {} + +class GetProductListviewSuccess extends ProductListviewState { + final List productListviewData; + GetProductListviewSuccess({required this.productListviewData}); +} diff --git a/lib/bloc/student/bloc.dart b/lib/bloc/student/bloc.dart new file mode 100644 index 0000000..7840f88 --- /dev/null +++ b/lib/bloc/student/bloc.dart @@ -0,0 +1,3 @@ +export 'student_bloc.dart'; +export 'student_event.dart'; +export 'student_state.dart'; \ No newline at end of file diff --git a/lib/bloc/student/student_bloc.dart b/lib/bloc/student/student_bloc.dart new file mode 100644 index 0000000..bbb94b6 --- /dev/null +++ b/lib/bloc/student/student_bloc.dart @@ -0,0 +1,121 @@ +import 'package:bloc/bloc.dart'; +import 'package:mobdr/model/integration/student_model.dart'; +import 'package:mobdr/network/api_provider.dart'; +import './bloc.dart'; + +class StudentBloc extends Bloc { + StudentBloc() : super(InitialStudentState()) { + on(_getStudent); + on(_addStudent); + on(_editStudent); + on(_deleteStudent); + } +} + +void _getStudent(GetStudent event, Emitter emit) async { + ApiProvider _apiProvider = ApiProvider(); + + emit(GetStudentWaiting()); + try { + List data = + await _apiProvider.getStudent(event.sessionId, event.apiToken); + emit(GetStudentSuccess(studentData: data)); + } catch (ex) { + if (ex != 'cancel') { + emit(GetStudentError(errorMessage: ex.toString())); + } + } +} + +void _addStudent(AddStudent event, Emitter emit) async { + ApiProvider _apiProvider = ApiProvider(); + + String errorMessage = ''; + if (event.studentName == '') { + errorMessage = 'Student name cannot be empty'; + } else if (event.studentPhoneNumber == '') { + errorMessage = 'Student phone number can not be empty'; + } else if (event.studentGender == '') { + errorMessage = 'Student gender can not be empty'; + } else if (event.studentAddress == '') { + errorMessage = 'Student address can not be empty'; + } + + if (errorMessage == '') { + emit(AddStudentWaiting()); + try { + List data = await _apiProvider.addStudent( + event.sessionId, + event.studentName, + event.studentPhoneNumber, + event.studentGender, + event.studentAddress, + event.apiToken); + emit(AddStudentSuccess( + msg: data[0], + studentId: data[1], + studentName: event.studentName, + studentPhoneNumber: event.studentPhoneNumber, + studentGender: event.studentGender, + studentAddress: event.studentAddress)); + } catch (ex) { + emit(AddStudentError(errorMessage: ex.toString())); + } + } else { + emit(StudentErrorValidation(errorMessage: errorMessage)); + } +} + +void _editStudent(EditStudent event, Emitter emit) async { + ApiProvider _apiProvider = ApiProvider(); + + String errorMessage = ''; + if (event.studentName == '') { + errorMessage = 'Student name cannot be empty'; + } else if (event.studentPhoneNumber == '') { + errorMessage = 'Student phone number can not be empty'; + } else if (event.studentGender == '') { + errorMessage = 'Student gender can not be empty'; + } else if (event.studentAddress == '') { + errorMessage = 'Student address can not be empty'; + } + + if (errorMessage == '') { + emit(EditStudentWaiting()); + try { + String message = await _apiProvider.editStudent( + event.sessionId, + event.studentId, + event.studentName, + event.studentPhoneNumber, + event.studentGender, + event.studentAddress, + event.apiToken); + emit(EditStudentSuccess( + msg: message, + index: event.index, + studentId: event.studentId, + studentName: event.studentName, + studentPhoneNumber: event.studentPhoneNumber, + studentGender: event.studentGender, + studentAddress: event.studentAddress)); + } catch (ex) { + emit(EditStudentError(errorMessage: ex.toString())); + } + } else { + emit(StudentErrorValidation(errorMessage: errorMessage)); + } +} + +void _deleteStudent(DeleteStudent event, Emitter emit) async { + ApiProvider _apiProvider = ApiProvider(); + + emit(DeleteStudentWaiting()); + try { + String msg = await _apiProvider.deleteStudent( + event.sessionId, event.studentId, event.apiToken); + emit(DeleteStudentSuccess(msg: msg, index: event.index)); + } catch (ex) { + emit(DeleteStudentError(errorMessage: ex.toString())); + } +} diff --git a/lib/bloc/student/student_event.dart b/lib/bloc/student/student_event.dart new file mode 100644 index 0000000..dfd5ac3 --- /dev/null +++ b/lib/bloc/student/student_event.dart @@ -0,0 +1,32 @@ +import 'package:meta/meta.dart'; + +@immutable +abstract class StudentEvent {} + +class GetStudent extends StudentEvent { + final String sessionId; + final apiToken; + GetStudent({required this.sessionId, required this.apiToken}); +} + +class AddStudent extends StudentEvent { + final String sessionId; + final String studentName, studentPhoneNumber, studentGender, studentAddress; + final apiToken; + AddStudent({required this.sessionId, required this.studentName, required this.studentPhoneNumber, required this.studentGender, required this.studentAddress, required this.apiToken}); +} + +class EditStudent extends StudentEvent { + final String sessionId; + final int studentId, index; + final String studentName, studentPhoneNumber, studentGender, studentAddress; + final apiToken; + EditStudent({required this.sessionId, required this.index, required this.studentId, required this.studentName, required this.studentPhoneNumber, required this.studentGender, required this.studentAddress, required this.apiToken}); +} + +class DeleteStudent extends StudentEvent { + final String sessionId; + final int studentId, index; + final apiToken; + DeleteStudent({required this.sessionId, required this.studentId, required this.index, required this.apiToken}); +} \ No newline at end of file diff --git a/lib/bloc/student/student_state.dart b/lib/bloc/student/student_state.dart new file mode 100644 index 0000000..60d225a --- /dev/null +++ b/lib/bloc/student/student_state.dart @@ -0,0 +1,99 @@ +import 'package:mobdr/model/integration/student_model.dart'; +import 'package:meta/meta.dart'; + +@immutable +abstract class StudentState {} + +class InitialStudentState extends StudentState {} + +// get student state +class GetStudentWaiting extends StudentState {} + +class GetStudentError extends StudentState { + final String errorMessage; + GetStudentError({ + required this.errorMessage, + }); +} + +class GetStudentSuccess extends StudentState { + final List studentData; + GetStudentSuccess({required this.studentData}); +} + +// general +class StudentErrorValidation extends StudentState { + final String errorMessage; + StudentErrorValidation({ + required this.errorMessage, + }); +} + +// add student state +class AddStudentWaiting extends StudentState {} + +class AddStudentError extends StudentState { + final String errorMessage; + AddStudentError({ + required this.errorMessage, + }); +} + +class AddStudentSuccess extends StudentState { + final int studentId; + final String msg, + studentName, + studentPhoneNumber, + studentGender, + studentAddress; + AddStudentSuccess( + {required this.msg, + required this.studentId, + required this.studentName, + required this.studentPhoneNumber, + required this.studentGender, + required this.studentAddress}); +} + +// edit student state +class EditStudentWaiting extends StudentState {} + +class EditStudentError extends StudentState { + final String errorMessage; + EditStudentError({ + required this.errorMessage, + }); +} + +class EditStudentSuccess extends StudentState { + final int studentId, index; + final String msg, + studentName, + studentPhoneNumber, + studentGender, + studentAddress; + EditStudentSuccess( + {required this.msg, + required this.index, + required this.studentId, + required this.studentName, + required this.studentPhoneNumber, + required this.studentGender, + required this.studentAddress}); +} + +// delete student state +class DeleteStudentWaiting extends StudentState {} + +class DeleteStudentError extends StudentState { + final String errorMessage; + DeleteStudentError({ + required this.errorMessage, + }); +} + +class DeleteStudentSuccess extends StudentState { + final String msg; + final int index; + DeleteStudentSuccess({required this.msg, required this.index}); +} diff --git a/lib/config/constant.dart b/lib/config/constant.dart index 881b614..7ca8e84 100644 --- a/lib/config/constant.dart +++ b/lib/config/constant.dart @@ -17,6 +17,12 @@ const Color BLACK_GREY = Color(0xff777777); const Color SOFT_GREY = Color(0xFFaaaaaa); const Color SOFT_BLUE = Color(0xff01aed6); +const int STATUS_OK = 200; +const int STATUS_BAD_REQUEST = 400; +const int STATUS_NOT_AUTHORIZED = 403; +const int STATUS_NOT_FOUND = 404; +const int STATUS_INTERNAL_ERROR = 500; + const String ERROR_OCCURED = 'Error occured, please try again later'; const int LIMIT_PAGE = 8; @@ -26,3 +32,14 @@ const String GLOBAL_URL = 'https://ijtechnology.net/assets/images/api/devkit'; //const String GLOBAL_URL = 'http://192.168.100.9/devkit'; const String LOCAL_IMAGES_URL = 'assets/images'; + +class ApiConstants { + static String baseUrl = 'https://mp4.ikksgroup.com'; + static String mobDREndpoint = '/mobdr'; + static String mp4Endpoint = '/MobilePortal4/webresources'; + static String externalEndpoint = '/MobilePortal4_external/webresources'; + static String restEndpoint = '/rest/api'; +} + +const String LOGIN_API = 'https://mp4.ikksgroup.com' + "/authentication/login"; +const String PRODUCT_API = 'https://mp4.ikksgroup.com' + "/example/getProduct"; diff --git a/lib/model/login.dart b/lib/model/login.dart new file mode 100644 index 0000000..6041b1f --- /dev/null +++ b/lib/model/login.dart @@ -0,0 +1,57 @@ +class LoginModel { + int idUtilisateur; + String email; + int expire; + String guid; + String langage; + String lastTraduction; + String login; + String nom; + String prenom; + String version; + String photo; + + LoginModel( + this.idUtilisateur, + this.email, + this.expire, + this.guid, + this.langage, + this.lastTraduction, + this.login, + this.nom, + this.prenom, + this.version, + this.photo); + + LoginModel.fromJson(Map json) + : idUtilisateur = json['id_utilisateur'], + email = json['email'], + expire = json['expire'], + guid = json['guid'], + langage = json['langage'], + lastTraduction = json['last_traduction'], + login = json['login'], + nom = json['nom'], + prenom = json['prenom'], + version = json['version'], + photo = json['photo']; + + Map toJson() { + final Map data = new Map(); + + data['id_utilisateur'] = this.idUtilisateur; + data['email'] = this.email; + data['expire'] = this.expire; + data['guid'] = this.guid; + data['langage'] = this.langage; + data['last_traduction'] = this.lastTraduction; + data['login'] = this.login; + data['nom'] = this.nom; + data['prenom'] = this.prenom; + data['version'] = this.version; + data['photo'] = this.photo; + + return data; + } +} diff --git a/lib/model/user_profile.dart b/lib/model/user_profile.dart new file mode 100644 index 0000000..c24bd5c --- /dev/null +++ b/lib/model/user_profile.dart @@ -0,0 +1,20 @@ +class UserProfileModel { + final String name; + final String accessToken; + final int age; + + UserProfileModel({this.name = '', this.accessToken = '', this.age = 0}); + + UserProfileModel.fromJson(Map json) + : name = json['name'], + accessToken = json['accessToken'], + age = json['age']; + + Map toJson() { + final Map data = new Map(); + data['name'] = this.name; + data['accessToken'] = this.accessToken; + data['age'] = this.age; + return data; + } +} diff --git a/lib/network/api_provider.dart b/lib/network/api_provider.dart new file mode 100644 index 0000000..e83f342 --- /dev/null +++ b/lib/network/api_provider.dart @@ -0,0 +1,313 @@ +/* +This is api provider +This page is used to get data from API + */ + +import 'package:mobdr/config/constant.dart'; +import 'package:mobdr/service/local_storage.dart'; +import 'package:mobdr/model/login.dart'; +import 'package:dio/dio.dart'; +import 'dart:convert'; +import 'package:crypto/crypto.dart'; + +class ApiProvider { + Dio dio = Dio(); + late Response response; + String connErr = 'Please check your internet connection and try again'; + + Future getCrud(url, body) async { + print('url : ' + url.toString()); + try { + dio.options.headers['content-Type'] = 'application/json'; + dio.options.headers['Accept'] = 'application/json'; + dio.options.connectTimeout = 30000; //5s + dio.options.receiveTimeout = 25000; + dio.options.queryParameters = body; + + return await dio.get(url); + } on DioError catch (e) { + //print(e.toString()+' | '+url.toString()); + if (e.type == DioErrorType.response) { + int? statusCode = e.response!.statusCode; + if (statusCode == STATUS_NOT_FOUND) { + throw "Api not found"; + } else if (statusCode == STATUS_INTERNAL_ERROR) { + throw "Internal Server Error"; + } else { + throw e.error.message.toString(); + } + } else if (e.type == DioErrorType.connectTimeout) { + throw e.message.toString(); + } else if (e.type == DioErrorType.cancel) { + throw 'cancel'; + } + throw connErr; + } finally { + //dio.close(); // TODO: Attention pas close + } + } + + Future getConnect(url, apiToken) async { + print('url : ' + url.toString()); + try { + dio.options.headers['content-Type'] = 'application/x-www-form-urlencoded'; + dio.options.connectTimeout = 30000; //5s + dio.options.receiveTimeout = 25000; + + return await dio.post(url, cancelToken: apiToken); + } on DioError catch (e) { + //print(e.toString()+' | '+url.toString()); + if (e.type == DioErrorType.response) { + int? statusCode = e.response!.statusCode; + if (statusCode == STATUS_NOT_FOUND) { + throw "Api not found"; + } else if (statusCode == STATUS_INTERNAL_ERROR) { + throw "Internal Server Error"; + } else { + throw e.error.message.toString(); + } + } else if (e.type == DioErrorType.connectTimeout) { + throw e.message.toString(); + } else if (e.type == DioErrorType.cancel) { + throw 'cancel'; + } + throw connErr; + } finally { + //dio.close(); + } + } + + Future postConnect(url, data, apiToken) async { + print('url : ' + url.toString()); + print('postData : ' + data.toString()); + try { + dio.options.headers['content-Type'] = 'application/x-www-form-urlencoded'; + dio.options.connectTimeout = 30000; //5s + dio.options.receiveTimeout = 25000; + + return await dio.post(url, data: data, cancelToken: apiToken); + } on DioError catch (e) { + //print(e.toString()+' | '+url.toString()); + if (e.type == DioErrorType.response) { + int? statusCode = e.response!.statusCode; + if (statusCode == STATUS_NOT_FOUND) { + throw "Api not found"; + } else if (statusCode == STATUS_INTERNAL_ERROR) { + throw "Internal Server Error"; + } else { + throw e.error.message.toString(); + } + } else if (e.type == DioErrorType.connectTimeout) { + throw e.message.toString(); + } else if (e.type == DioErrorType.cancel) { + throw 'cancel'; + } + throw connErr; + } finally { + dio.close(); + } + } + + /// login function + Future login( + String userName, String pinCode, String securityCode) async { + var body = { + "application": "MobDR ", //+ ExeInfo(exeVersion) + "pin_code": md5.convert(utf8.encode(pinCode)), + "security_code": securityCode, //sCodeSecurite + "langage": "fr", + "screenheight": "0", //SysYRes() + "screenwidth": "0", //SysXRes() + "browser_name": "Natif", + "browser_version": "Application", + "engine_name": "Android", + "device_model": "", + "device_type": "mobile", //SysInfoAppareil(sysModele) WDM 23 + "device_vendor": "", //SysInfoAppareil(sysFabricant) WDM 23 + "ismobile": 1, + "engine_version": "", // SysVersionAndroid(sysVersionApiLevel) + "os_name": "", // SysVersionAndroid(sysVersionPlateForme) + "os_version": "", //SysVersionAndroid(sysVersionNumÈro) + "fingerprint": + "aa" // TODO: on peut mettre un fingerprint sur la version ou sur l'heure + }; + + try { + response = await getCrud( + ApiConstants.baseUrl + + ApiConstants.mp4Endpoint + + ApiConstants.restEndpoint + + '/login/' + + userName + + '/guid', + body); + + print('res : ' + response.toString()); + + switch (response.data['autorisation']) { + case 1: + LocalStorage.saveLoginData(LoginModel.fromJson(response.data)); + return 'OK'; + case -1: + return 'Compte désactivé ...'; + case -2: + return 'SECURITY_CODE'; + case -3: + return 'Téléphone bloqué !'; + case -4: + return 'Code PIN incorrect !'; + default: + return "Une erreur inconnue s''est produite"; + } + } catch (ex) { + return ex.toString(); // return ex.response!.data; + } + } + + Future getExample(apiToken) async { + response = + await getConnect(ApiConstants.baseUrl + '/example/getData', apiToken); + print('res : ' + response.toString()); + return response.data.toString(); + } + + Future postExample(String id, apiToken) async { + var postData = {'id': id}; + response = await postConnect( + ApiConstants.baseUrl + '/example/postData', postData, apiToken); + print('res : ' + response.toString()); + return response.data.toString(); + } + /* + Future> getStudent(String sessionId, apiToken) async { + var postData = {'session_id': sessionId}; + response = await postConnect( + ApiConstants.baseUrl + '/student/getStudent', postData, apiToken); + if (response.data['status'] == STATUS_OK) { + List responseList = response.data['data']; + List listData = + responseList.map((f) => StudentModel.fromJson(f)).toList(); + return listData; + } else { + throw response.data['msg']; + } + } + */ + + Future> addStudent( + String sessionId, + String studentName, + String studentPhoneNumber, + String studentGender, + String studentAddress, + apiToken) async { + var postData = { + 'session_id': sessionId, + 'student_name': studentName, + 'student_phone_number': studentPhoneNumber, + 'student_gender': studentGender, + 'student_address': studentAddress, + }; + response = await postConnect( + ApiConstants.baseUrl + '/student/addStudent', postData, apiToken); + if (response.data['status'] == STATUS_OK) { + List respList = []; + respList.add(response.data['msg']); + respList.add(response.data['data']['id']); + return respList; + } else { + throw response.data['msg']; + } + } + + Future editStudent( + String sessionId, + int studentId, + String studentName, + String studentPhoneNumber, + String studentGender, + String studentAddress, + apiToken) async { + var postData = { + 'session_id': sessionId, + 'student_id': studentId, + 'student_name': studentName, + 'student_phone_number': studentPhoneNumber, + 'student_gender': studentGender, + 'student_address': studentAddress, + }; + response = await postConnect( + ApiConstants.baseUrl + '/student/editStudent', postData, apiToken); + if (response.data['status'] == STATUS_OK) { + return response.data['msg']; + } else { + throw response.data['msg']; + } + } + + Future deleteStudent( + String sessionId, int studentId, apiToken) async { + var postData = { + 'session_id': sessionId, + 'student_id': studentId, + }; + response = await postConnect( + ApiConstants.baseUrl + '/student/deleteStudent', postData, apiToken); + if (response.data['status'] == STATUS_OK) { + return response.data['msg']; + } else { + throw response.data['msg']; + } + } + + Future> login2( + String email, String password, apiToken) async { + var postData = { + 'email': email, + 'password': password, + }; + response = await postConnect(LOGIN_API, postData, apiToken); + if (response.data['status'] == STATUS_OK) { + List responseList = response.data['data']; + List listData = + responseList.map((f) => LoginModel.fromJson(f)).toList(); + return listData; + } else { + throw response.data['msg']; + } + } + + /* + Future> getProductGrid( + String sessionId, String skip, String limit, apiToken) async { + var postData = {'session_id': sessionId, 'skip': skip, 'limit': limit}; + response = await postConnect(PRODUCT_API, postData, apiToken); + if (response.data['status'] == STATUS_OK) { + List responseList = response.data['data']; + //print('data : '+responseList.toString()); + List listData = + responseList.map((f) => ProductGridModel.fromJson(f)).toList(); + return listData; + } else { + throw response.data['msg']; + } + } + */ + + /* + Future> getProductListview( + String sessionId, String skip, String limit, apiToken) async { + var postData = {'session_id': sessionId, 'skip': skip, 'limit': limit}; + response = await postConnect(PRODUCT_API, postData, apiToken); + if (response.data['status'] == STATUS_OK) { + List responseList = response.data['data']; + //print('data : '+responseList.toString()); + List listData = + responseList.map((f) => ProductListviewModel.fromJson(f)).toList(); + return listData; + } else { + throw response.data['msg']; + } + } + */ +} diff --git a/lib/service/local_storage.dart b/lib/service/local_storage.dart new file mode 100644 index 0000000..fa1e79b --- /dev/null +++ b/lib/service/local_storage.dart @@ -0,0 +1,85 @@ +import 'dart:convert'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +//import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:mobdr/model/login.dart'; +import 'package:mobdr/model/user_profile.dart'; + +class LocalStorage { + //static Future get prefs async => + // await SharedPreferences.getInstance(); + + static FlutterSecureStorage get securePref => FlutterSecureStorage(); + + static Future eraseProfileData() async { + var loginData = (await getLoginData()); + loginData?.guid = ''; + //await saveLoginData(LoginModel: loginData); + } + + static Future getLoginData() async { + try { + var loginData = await securePref.read(key: 'loginData'); + return LoginModel.fromJson(json.decode(loginData!)); + } catch (e) { + return null; + } + } + + static Future saveLoginData(LoginModel loginModel) async { + return securePref.write( + key: 'loginData', value: json.encode(loginModel.toJson())); + } + + static Future getProfileData() async { + try { + var profileData = await securePref.read(key: 'profileData'); + return UserProfileModel.fromJson(json.decode(profileData!)); + } catch (e) { + return null; + } + } + + static Future saveProfileData(UserProfileModel profileModel) async { + try { + return securePref.write( + key: 'profileData', value: json.encode(profileModel.toJson())); + } catch (e) { + return null; + } + } + + static Future saveItem({@required item, @required key}) async { + securePref.write(key: key.toString(), value: item); + } + + static Future eraseItem({@required key}) async { + securePref.delete(key: '$key'); + } + + static Future keyExists(String key) async { + return securePref.containsKey(key: key); + } + + /* + static eraseAll() async { + var savedData = await getSavedBooks(); + for (var item in savedData) { + try { + await File(await downloadDir(item.id)).delete(); + } catch (e) {} + } + securePref.deleteAll(); + (await prefs).clear(); + return; + } + + static Future getItemData({@required key}) async { + return await securePref.containsKey(key: '$key') + ? securePref.read(key: '$key') + : null; + } + */ +} diff --git a/lib/ui/account/tab_account.dart b/lib/ui/account/tab_account.dart index 8654ea4..13a2edf 100644 --- a/lib/ui/account/tab_account.dart +++ b/lib/ui/account/tab_account.dart @@ -19,7 +19,7 @@ import 'package:mobdr/ui/general/notification.dart'; import 'package:mobdr/ui/reusable/reusable_widget.dart'; import 'package:mobdr/ui/reusable/cache_image_network.dart'; import 'package:flutter/material.dart'; -import 'package:fluttertoast/fluttertoast.dart'; +import 'package:mobdr/ui/authentication/signin.dart'; class TabAccountPage extends StatefulWidget { @override @@ -97,8 +97,24 @@ class _TabAccountPageState extends State child: GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { - Fluttertoast.showToast( - msg: 'Click Sign Out', toastLength: Toast.LENGTH_LONG); + Navigator.pushAndRemoveUntil( + context, + PageRouteBuilder(pageBuilder: (BuildContext context, + Animation animation, Animation secondaryAnimation) { + return SigninPage(); + }, transitionsBuilder: (BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child) { + return new SlideTransition( + position: new Tween( + begin: const Offset(1.0, 0.0), + end: Offset.zero, + ).animate(animation), + child: child, + ); + }), + (Route route) => false); }, child: Row( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/ui/authentication/forgot_password.dart b/lib/ui/authentication/forgot_password.dart deleted file mode 100644 index 1430c09..0000000 --- a/lib/ui/authentication/forgot_password.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:fluttertoast/fluttertoast.dart'; -import 'package:mobdr/config/constant.dart'; -import 'package:mobdr/config/global_style.dart'; - -class ForgotPasswordPage extends StatefulWidget { - @override - _ForgotPasswordPageState createState() => _ForgotPasswordPageState(); -} - -class _ForgotPasswordPageState extends State { - TextEditingController _etEmail = TextEditingController(); - - @override - void initState() { - _etEmail = TextEditingController(text: 'robert.steven@ijtechnology.net'); - - super.initState(); - } - - @override - void dispose() { - _etEmail.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: ListView( - padding: EdgeInsets.fromLTRB(30, 120, 30, 30), - children: [ - Center(child: Image.asset('assets/images/logo.png', height: 32)), - SizedBox( - height: 80, - ), - TextFormField( - keyboardType: TextInputType.emailAddress, - controller: _etEmail, - style: TextStyle(color: CHARCOAL), - onChanged: (textValue) { - setState(() {}); - }, - decoration: InputDecoration( - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: PRIMARY_COLOR, width: 2.0)), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Color(0xFFCCCCCC)), - ), - labelText: 'Email', - labelStyle: TextStyle(color: BLACK_GREY)), - ), - SizedBox( - height: 10, - ), - Text( - 'We will send the instruction to reset your password through the email', - style: GlobalStyle.resetPasswordNotes, - ), - SizedBox( - height: 40, - ), - Container( - child: TextButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.resolveWith( - (Set states) => PRIMARY_COLOR, - ), - overlayColor: MaterialStateProperty.all(Colors.transparent), - shape: MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(3.0), - )), - ), - onPressed: () { - Fluttertoast.showToast( - msg: 'Click Reset Password', - toastLength: Toast.LENGTH_LONG); - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 5.0), - child: Text( - 'Reset Password', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.white), - textAlign: TextAlign.center, - ), - )), - ), - SizedBox( - height: 50, - ), - // create sign in link - Center( - child: GestureDetector( - onTap: () { - Navigator.pop(context); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - GlobalStyle.iconBack, - Text( - ' Back to login', - style: GlobalStyle.back, - ) - ], - ), - ), - ) - ], - )); - } -} diff --git a/lib/ui/authentication/signin.dart b/lib/ui/authentication/signin.dart index 301d2af..100a24e 100644 --- a/lib/ui/authentication/signin.dart +++ b/lib/ui/authentication/signin.dart @@ -1,8 +1,10 @@ import 'package:universal_io/io.dart'; -import 'package:mobdr/ui/home.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:mobdr/network/api_provider.dart'; import 'package:fluttertoast/fluttertoast.dart'; +import 'package:mobdr/ui/home.dart'; +import 'package:mobdr/ui/authentication/verification.dart'; class SigninPage extends StatefulWidget { @override @@ -12,6 +14,10 @@ class SigninPage extends StatefulWidget { class _SigninPageState extends State { bool _obscureText = true; IconData _iconVisible = Icons.visibility_off; + ApiProvider _apiProvider = ApiProvider(); // TODO: A voir si bien positionné + + TextEditingController _etUserName = TextEditingController(); + TextEditingController _etPinCode = TextEditingController(); Color _gradientTop = Color(0xFF039be6); Color _gradientBottom = Color(0xFF0299e2); @@ -31,11 +37,15 @@ class _SigninPageState extends State { @override void initState() { + _etUserName = TextEditingController(text: 'fbenoist'); + _etPinCode = TextEditingController(text: '9295'); super.initState(); } @override void dispose() { + _etUserName.dispose(); + _etPinCode.dispose(); super.dispose(); } @@ -97,6 +107,7 @@ class _SigninPageState extends State { ), TextField( keyboardType: TextInputType.emailAddress, + controller: _etUserName, decoration: InputDecoration( focusedBorder: UnderlineInputBorder( borderSide: @@ -114,6 +125,7 @@ class _SigninPageState extends State { ), TextField( obscureText: _obscureText, + controller: _etPinCode, decoration: InputDecoration( focusedBorder: UnderlineInputBorder( borderSide: @@ -122,7 +134,7 @@ class _SigninPageState extends State { borderSide: BorderSide(color: _underlineColor), ), - labelText: 'Password', + labelText: 'Pin code', labelStyle: TextStyle(color: Colors.grey[700]), suffixIcon: IconButton( icon: Icon(_iconVisible, @@ -133,24 +145,7 @@ class _SigninPageState extends State { ), ), SizedBox( - height: 20, - ), - Align( - alignment: Alignment.centerRight, - child: GestureDetector( - onTap: () { - Fluttertoast.showToast( - msg: 'Click forgot password', - toastLength: Toast.LENGTH_SHORT); - }, - child: Text( - 'Forgot Password?', - style: TextStyle(fontSize: 13), - ), - ), - ), - SizedBox( - height: 40, + height: 60, ), SizedBox( width: double.maxFinite, @@ -167,12 +162,26 @@ class _SigninPageState extends State { borderRadius: BorderRadius.circular(10), )), ), - onPressed: () { + onPressed: () async { //Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (context) => HomePage()), (Route route) => false); - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => HomePage())); + var apiResponse = await _apiProvider.login( + _etUserName.text, _etPinCode.text, ''); + if (apiResponse == 'OK') { + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (context) => HomePage()), + (Route route) => false); + } else if (apiResponse == 'SECURITY_CODE') { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => + VerificationPage())); + } else { + Fluttertoast.showToast( + msg: apiResponse, + toastLength: Toast.LENGTH_SHORT); + } }, child: Padding( padding: @@ -188,32 +197,6 @@ class _SigninPageState extends State { ], )), ), - SizedBox( - height: 50, - ), - // create sign up link - Center( - child: Wrap( - children: [ - Text('New User? '), - GestureDetector( - onTap: () { - Fluttertoast.showToast( - msg: 'Click signup', - toastLength: Toast.LENGTH_SHORT); - }, - child: Text( - 'Sign Up', - style: TextStyle( - color: _mainColor, fontWeight: FontWeight.w700), - ), - ) - ], - ), - ), - SizedBox( - height: 20, - ), ], ) ], diff --git a/lib/ui/authentication/signin_old.dart b/lib/ui/authentication/signin_old.dart deleted file mode 100644 index 7019d2f..0000000 --- a/lib/ui/authentication/signin_old.dart +++ /dev/null @@ -1,250 +0,0 @@ -import 'package:mobdr/config/global_style.dart'; -import 'package:mobdr/ui/authentication/forgot_password.dart'; -import 'package:mobdr/ui/home.dart'; -import 'package:mobdr/ui/authentication/signup.dart'; -import 'package:flutter/material.dart'; -import 'package:fluttertoast/fluttertoast.dart'; -import 'package:mobdr/config/constant.dart'; - -class SigninPage extends StatefulWidget { - @override - _SigninPageState createState() => _SigninPageState(); -} - -class _SigninPageState extends State { - TextEditingController _etEmail = TextEditingController(); - bool _obscureText = true; - IconData _iconVisible = Icons.visibility_off; - - void _toggleObscureText() { - setState(() { - _obscureText = !_obscureText; - if (_obscureText == true) { - _iconVisible = Icons.visibility_off; - } else { - _iconVisible = Icons.visibility; - } - }); - } - - @override - void initState() { - super.initState(); - } - - @override - void dispose() { - _etEmail.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: ListView( - padding: EdgeInsets.fromLTRB(30, 120, 30, 30), - children: [ - Center(child: Image.asset('assets/images/logo.png', height: 32)), - SizedBox( - height: 80, - ), - Text('Sign In', style: GlobalStyle.authTitle), - TextFormField( - keyboardType: TextInputType.emailAddress, - controller: _etEmail, - style: TextStyle(color: CHARCOAL), - onChanged: (textValue) { - setState(() {}); - }, - decoration: InputDecoration( - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: PRIMARY_COLOR, width: 2.0)), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Color(0xFFCCCCCC)), - ), - labelText: 'Email', - labelStyle: TextStyle(color: BLACK_GREY), - ), - ), - SizedBox( - height: 20, - ), - TextField( - obscureText: _obscureText, - style: TextStyle(color: CHARCOAL), - decoration: InputDecoration( - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: PRIMARY_COLOR, width: 2.0)), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Color(0xFFCCCCCC)), - ), - labelText: 'Password', - labelStyle: TextStyle(color: BLACK_GREY), - suffixIcon: IconButton( - icon: Icon(_iconVisible, color: Colors.grey[400], size: 20), - onPressed: () { - _toggleObscureText(); - }), - ), - ), - SizedBox( - height: 20, - ), - Align( - alignment: Alignment.centerRight, - child: Container( - child: GestureDetector( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ForgotPasswordPage())); - FocusScope.of(context).unfocus(); - }, - child: Text( - 'Forgot Password?', - style: TextStyle(color: PRIMARY_COLOR, fontSize: 13), - ), - ), - )), - SizedBox( - height: 40, - ), - Container( - child: TextButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.resolveWith( - (Set states) => PRIMARY_COLOR, - ), - overlayColor: MaterialStateProperty.all(Colors.transparent), - shape: MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(3.0), - )), - ), - onPressed: () { - //Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (context) => HomePage()), (Route route) => false); - Navigator.pushReplacement(context, - MaterialPageRoute(builder: (context) => HomePage())); - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 5.0), - child: Text( - 'Login', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.white), - textAlign: TextAlign.center, - ), - )), - ), - SizedBox( - height: 40, - ), - Center( - child: Text( - 'Or sign in with', - style: GlobalStyle.authSignWith, - ), - ), - SizedBox( - height: 20, - ), - Container( - margin: EdgeInsets.fromLTRB(20, 0, 20, 0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - GestureDetector( - onTap: () { - //Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (context) => HomePage()), (Route route) => false); - Navigator.pushReplacement(context, - MaterialPageRoute(builder: (context) => HomePage())); - Fluttertoast.showToast( - msg: 'Sign in with Google', - toastLength: Toast.LENGTH_LONG); - }, - child: Image( - image: AssetImage("assets/images/google.png"), - width: 40, - ), - ), - GestureDetector( - onTap: () { - //Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (context) => HomePage()), (Route route) => false); - Navigator.pushReplacement(context, - MaterialPageRoute(builder: (context) => HomePage())); - Fluttertoast.showToast( - msg: 'Sign in with Facebook', - toastLength: Toast.LENGTH_LONG); - }, - child: Image( - image: AssetImage("assets/images/facebook.png"), - width: 40, - ), - ), - GestureDetector( - onTap: () { - //Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (context) => HomePage()), (Route route) => false); - Navigator.pushReplacement(context, - MaterialPageRoute(builder: (context) => HomePage())); - Fluttertoast.showToast( - msg: 'Sign in with Twitter', - toastLength: Toast.LENGTH_LONG); - }, - child: Image( - image: AssetImage("assets/images/twitter.png"), - width: 40, - ), - ) - ], - ), - ), - SizedBox( - height: 20, - ), - Center( - child: GestureDetector( - onTap: () { - Navigator.push(context, - MaterialPageRoute(builder: (context) => SignupPage())); - FocusScope.of(context).unfocus(); - }, - child: Wrap( - children: [ - Text( - 'No account yet? ', - style: GlobalStyle.authBottom1, - ), - Text( - 'Create one', - style: GlobalStyle.authBottom2, - ) - ], - ), - ), - ), - SizedBox( - height: 30, - ), - Center( - child: GestureDetector( - onTap: () { - Navigator.pop(context); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - GlobalStyle.iconBack, - Text( - ' Back', - style: GlobalStyle.back, - ) - ], - ), - ), - ), - ], - )); - } -} diff --git a/lib/ui/authentication/signup.dart b/lib/ui/authentication/signup.dart deleted file mode 100644 index dcedf87..0000000 --- a/lib/ui/authentication/signup.dart +++ /dev/null @@ -1,177 +0,0 @@ -import 'package:mobdr/ui/home.dart'; -import 'package:flutter/material.dart'; -import 'package:mobdr/config/constant.dart'; -import 'package:mobdr/config/global_style.dart'; - -class SignupPage extends StatefulWidget { - final bool fromList; - - const SignupPage({Key? key, this.fromList = false}) : super(key: key); - - @override - _SignupPageState createState() => _SignupPageState(); -} - -class _SignupPageState extends State { - TextEditingController _etEmail = TextEditingController(); - TextEditingController _etName = TextEditingController(); - bool _obscureText = true; - IconData _iconVisible = Icons.visibility_off; - - void _toggleObscureText() { - setState(() { - _obscureText = !_obscureText; - if (_obscureText == true) { - _iconVisible = Icons.visibility_off; - } else { - _iconVisible = Icons.visibility; - } - }); - } - - @override - void initState() { - super.initState(); - } - - @override - void dispose() { - _etEmail.dispose(); - _etName.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: WillPopScope( - onWillPop: () { - FocusScope.of(context).unfocus(); - return Future.value(true); - }, - child: ListView( - padding: EdgeInsets.fromLTRB(30, 120, 30, 30), - children: [ - Center(child: Image.asset('assets/images/logo.png', height: 32)), - SizedBox( - height: 80, - ), - Text('Sign Up', style: GlobalStyle.authTitle), - TextFormField( - keyboardType: TextInputType.emailAddress, - controller: _etEmail, - style: TextStyle(color: CHARCOAL), - onChanged: (textValue) { - setState(() {}); - }, - decoration: InputDecoration( - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: PRIMARY_COLOR, width: 2.0)), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Color(0xFFCCCCCC)), - ), - labelText: 'Email', - labelStyle: TextStyle(color: BLACK_GREY), - ), - ), - SizedBox( - height: 20, - ), - TextFormField( - keyboardType: TextInputType.text, - controller: _etName, - style: TextStyle(color: CHARCOAL), - onChanged: (textValue) { - setState(() {}); - }, - decoration: InputDecoration( - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: PRIMARY_COLOR, width: 2.0)), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Color(0xFFCCCCCC)), - ), - labelText: 'Name', - labelStyle: TextStyle(color: BLACK_GREY), - ), - ), - SizedBox( - height: 20, - ), - TextField( - obscureText: _obscureText, - style: TextStyle(color: CHARCOAL), - decoration: InputDecoration( - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: PRIMARY_COLOR, width: 2.0)), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Color(0xFFCCCCCC)), - ), - labelText: 'Password', - labelStyle: TextStyle(color: BLACK_GREY), - suffixIcon: IconButton( - icon: Icon(_iconVisible, color: Colors.grey[400], size: 20), - onPressed: () { - _toggleObscureText(); - }), - ), - ), - SizedBox( - height: 40, - ), - Container( - child: TextButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.resolveWith( - (Set states) => PRIMARY_COLOR, - ), - overlayColor: MaterialStateProperty.all(Colors.transparent), - shape: MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(3.0), - )), - ), - onPressed: () { - //Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (context) => HomePage()), (Route route) => false); - if (!widget.fromList) { - Navigator.pop(context); - } - Navigator.pushReplacement(context, - MaterialPageRoute(builder: (context) => HomePage())); - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 5.0), - child: Text( - 'Register', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.white), - textAlign: TextAlign.center, - ), - )), - ), - SizedBox( - height: 30, - ), - Center( - child: GestureDetector( - onTap: () { - Navigator.pop(context); - FocusScope.of(context).unfocus(); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - GlobalStyle.iconBack, - Text( - ' Back to login', - style: GlobalStyle.back, - ) - ], - ), - ), - ), - ], - ), - )); - } -} diff --git a/lib/ui/authentication/verification.dart b/lib/ui/authentication/verification.dart new file mode 100644 index 0000000..3dfdfe0 --- /dev/null +++ b/lib/ui/authentication/verification.dart @@ -0,0 +1,172 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:pin_code_fields/pin_code_fields.dart'; +import 'package:mobdr/network/api_provider.dart'; +import 'package:mobdr/ui/home.dart'; +import 'package:mobdr/ui/authentication/signin.dart'; + +class VerificationPage extends StatefulWidget { + @override + _VerificationPageState createState() => _VerificationPageState(); +} + +class _VerificationPageState extends State { + Color _color1 = Color(0xFF07ac12); + Color _color2 = Color(0xFF515151); + Color _color3 = Color(0xff777777); + Color _color4 = Color(0xFFaaaaaa); + + bool _buttonDisabled = true; + String _securityCode = ''; + ApiProvider _apiProvider = ApiProvider(); // TODO: A voir si bien positionné + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ListView( + padding: EdgeInsets.fromLTRB(30, 120, 30, 30), + children: [ + Center(child: Icon(Icons.phone_android, color: _color1, size: 50)), + SizedBox(height: 20), + Center( + child: Text( + 'Enter the Verification Code', + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.bold, color: _color2), + )), + SizedBox( + height: 20, + ), + Container( + width: MediaQuery.of(context).size.width / 1.5, + child: Text( + 'The verification code has been sent by mail', + style: TextStyle(fontSize: 13, color: _color3), + textAlign: TextAlign.center, + ), + ), + SizedBox( + height: 40, + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 50), + child: PinCodeTextField( + autoFocus: true, + appContext: context, + keyboardType: TextInputType.number, + length: 4, + showCursor: false, + obscureText: false, + animationType: AnimationType.fade, + pinTheme: PinTheme( + shape: PinCodeFieldShape.underline, + fieldHeight: 50, + fieldWidth: 40, + inactiveColor: _color4, + activeColor: _color1, + selectedColor: _color1), + animationDuration: Duration(milliseconds: 300), + backgroundColor: Colors.transparent, + onChanged: (value) { + setState(() { + if (value.length == 4) { + _buttonDisabled = false; + } else { + _buttonDisabled = true; + } + _securityCode = value; + }); + }, + beforeTextPaste: (text) { + return false; + }, + ), + ), + SizedBox( + height: 40, + ), + Container( + child: SizedBox( + width: double.maxFinite, + child: TextButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith( + (Set states) => + _buttonDisabled ? Colors.grey[300]! : _color1, + ), + overlayColor: MaterialStateProperty.all(Colors.transparent), + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(3.0), + )), + ), + onPressed: () async { + if (!_buttonDisabled) { + FocusScope.of(context).unfocus(); + + var apiResponse = await _apiProvider.login( + 'fbenoist', '9295', _securityCode); + if (apiResponse == 'OK') { + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute(builder: (context) => HomePage()), + (Route route) => false); + } else { + Fluttertoast.showToast( + msg: apiResponse, toastLength: Toast.LENGTH_SHORT); + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (context) => SigninPage()), + (Route route) => false); + } + } + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 5.0), + child: Text( + 'Verify', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: _buttonDisabled + ? Colors.grey[600] + : Colors.white), + textAlign: TextAlign.center, + ), + ))), + ), + SizedBox( + height: 40, + ), + Center( + child: Wrap( + children: [ + Text( + "Didn't receive the code? ", + style: TextStyle(fontSize: 13, color: _color4), + ), + GestureDetector( + onTap: () { + Fluttertoast.showToast( + msg: 'Click resend', toastLength: Toast.LENGTH_SHORT); + }, + child: Text( + 'Resend', + style: TextStyle(fontSize: 13, color: _color1), + ), + ) + ], + ), + ), + ], + )); + } +} diff --git a/lib/ui/home/tab_home.dart b/lib/ui/home/tab_home.dart index 84d310b..eed776c 100644 --- a/lib/ui/home/tab_home.dart +++ b/lib/ui/home/tab_home.dart @@ -16,6 +16,8 @@ import 'package:mobdr/ui/reusable/reusable_widget.dart'; import 'package:mobdr/ui/reusable/cache_image_network.dart'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:mobdr/service/local_storage.dart'; +import 'package:mobdr/model/login.dart'; class TabHomePage extends StatefulWidget { @override @@ -35,6 +37,7 @@ class _TabHomePageState extends State bool get wantKeepAlive => true; String defaultLang = 'en'; + String UserName = ''; late LanguageCubit _languageCubit; @@ -46,6 +49,9 @@ class _TabHomePageState extends State defaultLang = val!; }); }); + + LocalStorage.getLoginData().then((value) => UserName = value!.prenom); + super.initState(); } @@ -79,7 +85,8 @@ class _TabHomePageState extends State elevation: GlobalStyle.appBarElevation, title: Text( AppLocalizations.of(context)!.translate('i18n_hello')! + - ', Frédérik', + ', ' + + UserName, style: GlobalStyle.appBarTitle, ), backgroundColor: GlobalStyle.appBarBackgroundColor, diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index c58ebc9..b773214 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); + flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); g_autoptr(FlPluginRegistrar) objectbox_flutter_libs_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "ObjectboxFlutterLibsPlugin"); objectbox_flutter_libs_plugin_register_with_registrar(objectbox_flutter_libs_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 94262e7..6d5d836 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_secure_storage_linux objectbox_flutter_libs ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 8bffdec..9ca9b99 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,7 @@ import FlutterMacOS import Foundation +import flutter_secure_storage_macos import objectbox_flutter_libs import package_info_plus import path_provider_foundation @@ -12,6 +13,7 @@ import shared_preferences_foundation import sqflite func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) ObjectboxFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "ObjectboxFlutterLibsPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 16959fc..6bbda76 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -3,40 +3,57 @@ PODS: - FMDB (2.7.5): - FMDB/standard (= 2.7.5) - FMDB/standard (2.7.5) + - ObjectBox (1.8.1) + - objectbox_flutter_libs (0.0.1): + - FlutterMacOS + - ObjectBox (= 1.8.1) - package_info_plus (0.0.1): - FlutterMacOS - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS - sqflite (0.0.2): - FlutterMacOS - FMDB (>= 2.7.5) DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) + - objectbox_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/objectbox_flutter_libs/macos`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/macos`) - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`) SPEC REPOS: trunk: - FMDB + - ObjectBox EXTERNAL SOURCES: FlutterMacOS: :path: Flutter/ephemeral + objectbox_flutter_libs: + :path: Flutter/ephemeral/.symlinks/plugins/objectbox_flutter_libs/macos package_info_plus: :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos path_provider_foundation: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/macos sqflite: :path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a + ObjectBox: a7900d5335218cd437cbc080b7ccc38a5211f7b4 + objectbox_flutter_libs: f89ab4878f0f764a49077cfaa59030be69ae1d7e package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 + shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472 sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 diff --git a/pubspec.lock b/pubspec.lock index 7e2d371..e127f63 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -233,6 +233,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.4" + dio: + dependency: "direct main" + description: + name: dio + sha256: "7d328c4d898a61efc3cd93655a0955858e29a0aa647f0f9e02d59b3bb275e2e8" + url: "https://pub.dev" + source: hosted + version: "4.0.6" fake_async: dependency: transitive description: @@ -315,6 +323,54 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: "98352186ee7ad3639ccc77ad7924b773ff6883076ab952437d20f18a61f0a7c5" + url: "https://pub.dev" + source: hosted + version: "8.0.0" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: "0912ae29a572230ad52d8a4697e5518d7f0f429052fd51df7e5a7952c7efe2a3" + url: "https://pub.dev" + source: hosted + version: "1.1.3" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "083add01847fc1c80a07a08e1ed6927e9acd9618a35e330239d4422cd2a58c50" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: b3773190e385a3c8a382007893d678ae95462b3c2279e987b55d140d3b0cb81b + url: "https://pub.dev" + source: hosted + version: "1.0.1" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: "42938e70d4b872e856e678c423cc0e9065d7d294f45bc41fc1981a4eb4beaffe" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: fc2910ec9b28d60598216c29ea763b3a96c401f0ce1d13cdf69ccb0e5c93c3ee + url: "https://pub.dev" + source: hosted + version: "2.0.0" flutter_test: dependency: "direct dev" description: flutter @@ -341,6 +397,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: "290fde3a86072e4b37dbb03c07bec6126f0ecc28dad403c12ffe2e5a2d751ab7" + url: "https://pub.dev" + source: hosted + version: "7.2.0" glob: dependency: transitive description: @@ -597,6 +661,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.11.1" + pin_code_fields: + dependency: "direct main" + description: + name: pin_code_fields + sha256: c8652519d14688f3fe2a8288d86910a46aa0b9046d728f292d3bf6067c31b4c7 + url: "https://pub.dev" + source: hosted + version: "7.4.0" platform: dependency: transitive description: @@ -665,10 +737,10 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "5949029e70abe87f75cfe59d17bf5c397619c4b74a099b10116baeb34786fad9" + sha256: ee6257848f822b8481691f20c3e6d2bfee2e9eccb2a3d249907fcfb198c55b41 url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.0.18" shared_preferences_android: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 08b4796..685a2dd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,9 +30,12 @@ environment: dependencies: flutter: sdk: flutter + + dio: 4.0.6 objectbox: ^1.7.2 objectbox_flutter_libs: any - + pin_code_fields: 7.4.0 + flutter_localizations: sdk: flutter @@ -43,13 +46,16 @@ dependencies: package_info_plus: 3.0.3 flutter_bloc: 8.1.2 flutter_html: 3.0.0-alpha.6 + flutter_secure_storage: ^8.0.0 #https://pub.dev/packages/intl intl: 0.17.0 carousel_slider: 4.2.1 cached_network_image: 3.2.3 - shared_preferences: 2.0.17 + get_it: ^7.2.0 + shared_preferences: 2.0.18 + universal_io: 2.2.0 dev_dependencies: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index a84779d..0ee502a 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,9 +6,12 @@ #include "generated_plugin_registrant.h" +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); ObjectboxFlutterLibsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ObjectboxFlutterLibsPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 9f0138e..8d486f6 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_secure_storage_windows objectbox_flutter_libs )