master #76
							
								
								
									
										7
									
								
								.pre-commit-config.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.pre-commit-config.yaml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| repos: | ||||
|   - repo: https://github.com/compilerla/conventional-pre-commit | ||||
|     rev: v2.1.1 | ||||
|     hooks: | ||||
|       - id: conventional-pre-commit | ||||
|         stages: [commit-msg] | ||||
|         args: [build, ci, docs, feat, fix, perf, refactor, style, test, chore] | ||||
| @ -1,11 +1,18 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: bootstrap.dart | ||||
| // Created Date: 19/08/2022 15:05:17 | ||||
| // Last Modified: Fri Nov 11 2022 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| //  | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| //  | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| //  | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'dart:async'; | ||||
| 
 | ||||
|  | ||||
| @ -1,12 +1,20 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: form_field.dart | ||||
| // Created Date: 19/08/2022 11:52:33 | ||||
| // Last Modified: 19/08/2022 16:35:39 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| //  | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| //  | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| //  | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| abstract class AppFormField { | ||||
|   static const oldPassword = 'oldPassword'; | ||||
|   static const confirmedPassword = 'confirmedPassword'; | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,19 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| //  | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| //  | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| //  | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| abstract class AppFormName { | ||||
|   static const editProfile = 'editProfile'; | ||||
| } | ||||
| @ -1,12 +1,20 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: router.dart | ||||
| // Created Date: 19/08/2022 11:52:22 | ||||
| // Last Modified: 19/08/2022 16:39:07 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:example_router/presentation/features/edit_profile/edit_profile_page.dart'; | ||||
| import 'package:example_router/presentation/features/home/home_page.dart'; | ||||
| import 'package:example_router/presentation/features/sign_in/sign_in_page.dart'; | ||||
| import 'package:example_router/presentation/features/sign_up/sign_up_page.dart'; | ||||
| @ -75,5 +83,14 @@ class AppRouter { | ||||
|         const SubPage(), | ||||
|       ), | ||||
|     ), | ||||
|     GoRoute( | ||||
|       path: '/home/edit', | ||||
|       name: EditProfilePage.pageName, | ||||
|       pageBuilder: (context, state) => defaultTransition( | ||||
|         context, | ||||
|         state, | ||||
|         const EditProfilePage(), | ||||
|       ), | ||||
|     ), | ||||
|   ]; | ||||
| } | ||||
|  | ||||
| @ -1,11 +1,18 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: app_bloc_observer.dart | ||||
| // Created Date: 19/08/2022 12:02:23 | ||||
| // Last Modified: 19/08/2022 12:02:45 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| //  | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| //  | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| //  | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | ||||
|  | ||||
| @ -0,0 +1,32 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:example_router/core/constants/form_field.dart'; | ||||
| import 'package:example_router/core/constants/form_name.dart'; | ||||
| import 'package:example_router/core/utils/custom_password.dart'; | ||||
| import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; | ||||
| import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; | ||||
| 
 | ||||
| abstract class AppForms { | ||||
|   static WyattForm getEditProfileForm() => WyattFormImpl( | ||||
|         [ | ||||
|           FormInput(AuthFormField.email, const Email.pure()), | ||||
|           FormInput(AuthFormField.password, const CustomPassword.pure()), | ||||
|           FormInput(AppFormField.oldPassword, const CustomPassword.pure()), | ||||
|         ], | ||||
|         name: AppFormName.editProfile, | ||||
|       ); | ||||
| } | ||||
| @ -1,3 +1,19 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| //  | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| //  | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| //  | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:example_router/bootstrap.dart'; | ||||
| import 'package:example_router/presentation/features/app/app.dart'; | ||||
| 
 | ||||
|  | ||||
| @ -1,11 +1,18 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: app.dart | ||||
| // Created Date: 19/08/2022 12:05:38 | ||||
| // Last Modified: Sat Dec 03 2022 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'dart:async'; | ||||
| import 'dart:math'; | ||||
| @ -14,6 +21,7 @@ import 'package:example_router/core/constants/form_field.dart'; | ||||
| import 'package:example_router/core/dependency_injection/get_it.dart'; | ||||
| import 'package:example_router/core/routes/router.dart'; | ||||
| import 'package:example_router/core/utils/custom_password.dart'; | ||||
| import 'package:example_router/core/utils/forms.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | ||||
| import 'package:go_router/go_router.dart'; | ||||
| @ -69,7 +77,7 @@ class App extends StatelessWidget { | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     AuthenticationState? previous; | ||||
|     AuthenticationState<int>? previous; | ||||
| 
 | ||||
|     final AuthenticationCubit<int> authenticationCubit = | ||||
|         AuthenticationCubit(authenticationRepository: authenticationRepository); | ||||
| @ -85,29 +93,18 @@ class App extends StatelessWidget { | ||||
|       redirect: (context, state) { | ||||
|         final authState = authenticationCubit.state; | ||||
| 
 | ||||
|         if (authState != previous) { | ||||
|         if (authState.status != previous?.status) { | ||||
|           previous = authState; | ||||
|           // Check if current user is logged in | ||||
|           final loggedIn = | ||||
|               authState.status == AuthenticationStatus.authenticated; | ||||
| 
 | ||||
|           // Checking if current path is onboarding or not | ||||
|           final isOnboarding = AppRouter.publicRoutes.contains(state.subloc); | ||||
| 
 | ||||
|           if (!loggedIn) { | ||||
|           if (authState.status == AuthenticationStatus.unauthenticated) { | ||||
|             debugPrint('Not logged'); | ||||
|             if (isOnboarding) { | ||||
|               return null; | ||||
|             } else { | ||||
|             if (!isOnboarding) { | ||||
|               return '/'; | ||||
|             } | ||||
|           } else { | ||||
|           } else if (authState.status == AuthenticationStatus.authenticated) { | ||||
|             debugPrint('Logged'); | ||||
|             if (isOnboarding) { | ||||
|               return '/home'; | ||||
|             } else { | ||||
|               return null; | ||||
|             } | ||||
|             return '/home'; | ||||
|           } | ||||
|         } | ||||
|         return null; | ||||
| @ -116,9 +113,13 @@ class App extends StatelessWidget { | ||||
| 
 | ||||
|     return MultiRepositoryProvider( | ||||
|       providers: [ | ||||
|         RepositoryProvider<AuthenticationRepository>.value( | ||||
|         RepositoryProvider<AuthenticationRepository<int>>.value( | ||||
|           value: authenticationRepository, | ||||
|         ), | ||||
|         RepositoryProvider<FormRepository>( | ||||
|           create: (context) => | ||||
|               FormRepositoryImpl()..registerForm(AppForms.getEditProfileForm()), | ||||
|         ), | ||||
|       ], | ||||
|       child: MultiBlocProvider( | ||||
|         providers: [ | ||||
|  | ||||
| @ -0,0 +1,74 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| //  | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| //  | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| //  | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:example_router/core/constants/form_field.dart'; | ||||
| import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; | ||||
| import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; | ||||
| import 'package:wyatt_type_utils/wyatt_type_utils.dart'; | ||||
| 
 | ||||
| class EditProfileCubit extends FormDataCubitImpl { | ||||
|   final AuthenticationRepository<int> authenticationRepository; | ||||
| 
 | ||||
|   EditProfileCubit( | ||||
|     super._formRepository, | ||||
|     super._formName, { | ||||
|     required this.authenticationRepository, | ||||
|   }) : super(); | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> submit() async { | ||||
|     emit( | ||||
|       state.copyWith( | ||||
|         status: FormStatus.submissionInProgress, | ||||
|       ), | ||||
|     ); | ||||
|     final user = (await authenticationRepository.getAccount()).ok; | ||||
| 
 | ||||
|     final form = state.form; | ||||
|     final email = form.valueOf<String?>(AuthFormField.email); | ||||
|     final oldPassword = form.valueOf<String?>(AppFormField.oldPassword); | ||||
|     final newPassword = form.valueOf<String?>(AuthFormField.password); | ||||
| 
 | ||||
|     if (email.isNullOrEmpty || | ||||
|         oldPassword.isNullOrEmpty || | ||||
|         newPassword.isNullOrEmpty) { | ||||
|       emit( | ||||
|         state.copyWith( | ||||
|           errorMessage: 'An error occured while retrieving data from the form.', | ||||
|           status: FormStatus.submissionFailure, | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     try { | ||||
|       // await authenticationRepository.signInWithEmailAndPassword( | ||||
|       //   email: user?.email ?? '', | ||||
|       //   password: oldPassword ?? '', | ||||
|       // ); | ||||
|       // await authenticationRepository.reauthenticateWithCredential(); | ||||
|       await authenticationRepository.updateEmail(email: email!); | ||||
|       await authenticationRepository.updatePassword(password: newPassword!); | ||||
|     } on Exception catch (e) { | ||||
|       emit( | ||||
|         state.copyWith( | ||||
|           status: FormStatus.submissionFailure, | ||||
|           errorMessage: e.toString(), | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
|     emit(state.copyWith(status: FormStatus.submissionSuccess)); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,61 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:example_router/core/constants/form_name.dart'; | ||||
| import 'package:example_router/presentation/features/edit_profile/blocs/edit_profile_cubit.dart'; | ||||
| import 'package:example_router/presentation/features/edit_profile/widgets/edit_profile_form.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | ||||
| import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; | ||||
| import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; | ||||
| 
 | ||||
| class EditProfilePage extends StatelessWidget { | ||||
|   const EditProfilePage({Key? key}) : super(key: key); | ||||
| 
 | ||||
|   static String pageName = 'EditProfile'; | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Scaffold( | ||||
|       appBar: AppBar(title: const Text('Edit Profile')), | ||||
|       body: Padding( | ||||
|         padding: const EdgeInsets.all(8), | ||||
|         child: SingleChildScrollView( | ||||
|           child: BlocProvider<EditProfileCubit>( | ||||
|             create: (context) => EditProfileCubit( | ||||
|               context.read<FormRepository>(), | ||||
|               AppFormName.editProfile, | ||||
|               authenticationRepository: | ||||
|                   context.read<AuthenticationRepository<int>>(), | ||||
|             )..dataChanged<String?>( | ||||
|                 AuthFormField.email, | ||||
|                 Email.dirty( | ||||
|                   context | ||||
|                           .read<AuthenticationCubit<int>>() | ||||
|                           .state | ||||
|                           .accountWrapper | ||||
|                           ?.account | ||||
|                           ?.email ?? | ||||
|                       '', | ||||
|                 ), | ||||
|               ), | ||||
|             child: const EditProfileForm(), | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,136 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:example_router/core/constants/form_field.dart'; | ||||
| import 'package:example_router/core/utils/custom_password.dart'; | ||||
| import 'package:example_router/presentation/features/edit_profile/blocs/edit_profile_cubit.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | ||||
| import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; | ||||
| import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; | ||||
| 
 | ||||
| class _EmailInput extends StatelessWidget { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return InputBuilderTextController<EditProfileCubit, String?, void>( | ||||
|       field: AuthFormField.email, | ||||
|       builder: ((context, cubit, state, field, input, controller, extra) { | ||||
|         return TextField( | ||||
|           onChanged: (email) => cubit.dataChanged<String?>( | ||||
|               AuthFormField.email, Email.dirty(email)), | ||||
|           keyboardType: TextInputType.emailAddress, | ||||
|           controller: controller, | ||||
|           decoration: InputDecoration( | ||||
|             labelText: 'Email', | ||||
|             helperText: '', | ||||
|             errorText: input.validator.invalid ? 'Invalid email' : null, | ||||
|           ), | ||||
|         ); | ||||
|       }), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class _OldPasswordInput extends StatelessWidget { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return InputBuilder<EditProfileCubit>( | ||||
|       field: AppFormField.oldPassword, | ||||
|       builder: ((context, cubit, state, field, input) { | ||||
|         return TextField( | ||||
|           onChanged: (pwd) => cubit.dataChanged<String?>( | ||||
|               AppFormField.oldPassword, CustomPassword.dirty(pwd)), | ||||
|           obscureText: true, | ||||
|           decoration: InputDecoration( | ||||
|             labelText: 'Old Password', | ||||
|             helperText: '', | ||||
|             errorText: input.validator.invalid ? 'Invalid password' : null, | ||||
|           ), | ||||
|         ); | ||||
|       }), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class _NewPasswordInput extends StatelessWidget { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return InputBuilder<EditProfileCubit>( | ||||
|       field: AuthFormField.password, | ||||
|       builder: ((context, cubit, state, field, input) { | ||||
|         return TextField( | ||||
|           onChanged: (pwd) => cubit.dataChanged<String?>( | ||||
|               AuthFormField.password, CustomPassword.dirty(pwd)), | ||||
|           obscureText: true, | ||||
|           decoration: InputDecoration( | ||||
|             labelText: 'New Password', | ||||
|             helperText: '', | ||||
|             errorText: input.validator.invalid ? 'Invalid password' : null, | ||||
|           ), | ||||
|         ); | ||||
|       }), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class _EditButton extends StatelessWidget { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return SubmitBuilder<EditProfileCubit>( | ||||
|       builder: ((context, cubit, status) { | ||||
|         return status.isSubmissionInProgress | ||||
|             ? const CircularProgressIndicator() | ||||
|             : ElevatedButton( | ||||
|                 onPressed: status.isValidated ? () => cubit.submit() : null, | ||||
|                 child: const Text('Edit profile'), | ||||
|               ); | ||||
|       }), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class EditProfileForm extends StatelessWidget { | ||||
|   const EditProfileForm({Key? key}) : super(key: key); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return BlocListener<EditProfileCubit, FormDataState>( | ||||
|       listener: (context, state) { | ||||
|         if (state.status == FormStatus.submissionFailure) { | ||||
|           ScaffoldMessenger.of(context) | ||||
|             ..hideCurrentSnackBar() | ||||
|             ..showSnackBar( | ||||
|               SnackBar(content: Text(state.errorMessage ?? 'Edit Failure')), | ||||
|             ); | ||||
|         } | ||||
|       }, | ||||
|       child: SingleChildScrollView( | ||||
|         child: Column( | ||||
|           crossAxisAlignment: CrossAxisAlignment.center, | ||||
|           children: [ | ||||
|             _EmailInput(), | ||||
|             const SizedBox(height: 8), | ||||
|             _OldPasswordInput(), | ||||
|             const SizedBox(height: 8), | ||||
|             _NewPasswordInput(), | ||||
|             const SizedBox(height: 16), | ||||
|             _EditButton(), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -1,12 +1,20 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: home_page.dart | ||||
| // Created Date: 19/08/2022 14:38:24 | ||||
| // Last Modified: Wed Nov 09 2022 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:example_router/presentation/features/edit_profile/edit_profile_page.dart'; | ||||
| import 'package:example_router/presentation/features/sub/sub_page.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | ||||
| @ -25,7 +33,8 @@ class HomePage extends StatelessWidget { | ||||
|         title: const Text('Home'), | ||||
|         actions: [ | ||||
|           IconButton( | ||||
|               onPressed: () => context.read<AuthenticationCubit<int>>().signOut(), | ||||
|               onPressed: () => | ||||
|                   context.read<AuthenticationCubit<int>>().signOut(), | ||||
|               icon: const Icon(Icons.logout_rounded)) | ||||
|         ], | ||||
|       ), | ||||
| @ -35,8 +44,8 @@ class HomePage extends StatelessWidget { | ||||
|           child: Column( | ||||
|             children: [ | ||||
|               AuthenticationBuilder<int>( | ||||
|                 authenticated: (context, accountWrapper) => | ||||
|                     Text('Logged as ${accountWrapper.account?.email} | GeneratedId is ${accountWrapper.data}'), | ||||
|                 authenticated: (context, accountWrapper) => Text( | ||||
|                     'Logged as ${accountWrapper.account?.email} | GeneratedId is ${accountWrapper.data}'), | ||||
|                 unauthenticated: (context) => | ||||
|                     const Text('Not logged (unauthenticated)'), | ||||
|                 unknown: (context) => const Text('Not logged (unknown)'), | ||||
| @ -45,8 +54,13 @@ class HomePage extends StatelessWidget { | ||||
|                 height: 8, | ||||
|               ), | ||||
|               ElevatedButton( | ||||
|                   onPressed: () => context.pushNamed(SubPage.pageName), | ||||
|                   child: const Text('Go to sub page')), | ||||
|                 onPressed: () => context.pushNamed(SubPage.pageName), | ||||
|                 child: const Text('Go to sub page'), | ||||
|               ), | ||||
|               ElevatedButton( | ||||
|                 onPressed: () => context.pushNamed(EditProfilePage.pageName), | ||||
|                 child: const Text('Go to edit profile page'), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|  | ||||
| @ -1,11 +1,18 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: sign_in_page.dart | ||||
| // Created Date: 19/08/2022 12:41:42 | ||||
| // Last Modified: 19/08/2022 15:26:36 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| //  | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| //  | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| //  | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:example_router/presentation/features/sign_in/widgets/sign_in_form.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| @ -1,11 +1,18 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: sign_in_form.dart | ||||
| // Created Date: 19/08/2022 15:24:37 | ||||
| // Last Modified: Sat Dec 03 2022 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| //  | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| //  | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| //  | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:example_router/core/utils/custom_password.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| @ -1,11 +1,18 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: sign_up_page.dart | ||||
| // Created Date: 19/08/2022 12:41:27 | ||||
| // Last Modified: 19/08/2022 14:58:51 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| //  | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| //  | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| //  | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:example_router/presentation/features/sign_up/widgets/sign_up_form.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| @ -1,11 +1,18 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: sign_up_form.dart | ||||
| // Created Date: 19/08/2022 14:41:08 | ||||
| // Last Modified: Sat Dec 03 2022 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| //  | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| //  | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| //  | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:example_router/core/constants/form_field.dart'; | ||||
| import 'package:example_router/core/utils/custom_password.dart'; | ||||
|  | ||||
| @ -1,11 +1,18 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: sub_page.dart | ||||
| // Created Date: 19/08/2022 16:10:05 | ||||
| // Last Modified: Wed Nov 09 2022 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| //  | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| //  | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| //  | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | ||||
| @ -24,7 +31,10 @@ class SubPage extends StatelessWidget { | ||||
|         actions: [ | ||||
|           IconButton( | ||||
|               onPressed: () => context.read<AuthenticationCubit<int>>().signOut(), | ||||
|               icon: const Icon(Icons.logout_rounded)) | ||||
|               icon: const Icon(Icons.logout_rounded)), | ||||
|           IconButton( | ||||
|               onPressed: () => context.read<AuthenticationRepository<int>>().refresh(), | ||||
|               icon: const Icon(Icons.refresh)) | ||||
|         ], | ||||
|       ), | ||||
|       body: const Padding( | ||||
|  | ||||
| @ -1,11 +1,18 @@ | ||||
| // Author: Hugo Pointcheval | ||||
| // Email: git@pcl.ovh | ||||
| // ----- | ||||
| // File: welcome_page.dart | ||||
| // Created Date: 19/08/2022 12:33:21 | ||||
| // Last Modified: Wed Nov 09 2022 | ||||
| // ----- | ||||
| // Copyright (c) 2022 | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| //  | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| //  | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| //  | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:example_router/presentation/features/sign_in/sign_in_page.dart'; | ||||
| import 'package:example_router/presentation/features/sign_up/sign_up_page.dart'; | ||||
|  | ||||
| @ -256,3 +256,24 @@ abstract class GetIdTokenFailureInterface | ||||
| 
 | ||||
|   GetIdTokenFailureInterface.fromCode(super.code) : super.fromCode(); | ||||
| } | ||||
| 
 | ||||
| abstract class ReauthenticateFailureInterface | ||||
|     extends AuthenticationFailureInterface { | ||||
|   ReauthenticateFailureInterface(super.code, super.msg); | ||||
| 
 | ||||
|   ReauthenticateFailureInterface.fromCode(super.code) : super.fromCode(); | ||||
| } | ||||
| 
 | ||||
| abstract class UpdateEmailFailureInterface | ||||
|     extends AuthenticationFailureInterface { | ||||
|   UpdateEmailFailureInterface(super.code, super.msg); | ||||
| 
 | ||||
|   UpdateEmailFailureInterface.fromCode(super.code) : super.fromCode(); | ||||
| } | ||||
| 
 | ||||
| abstract class UpdatePasswordFailureInterface | ||||
|     extends AuthenticationFailureInterface { | ||||
|   UpdatePasswordFailureInterface(super.code, super.msg); | ||||
| 
 | ||||
|   UpdatePasswordFailureInterface.fromCode(super.code) : super.fromCode(); | ||||
| } | ||||
|  | ||||
| @ -277,3 +277,75 @@ class GetIdTokenFailureFirebase extends GetIdTokenFailureInterface { | ||||
| 
 | ||||
|   GetIdTokenFailureFirebase.fromCode(super.code) : super.fromCode(); | ||||
| } | ||||
| 
 | ||||
| class ReauthenticateFailureFirebase extends ReauthenticateFailureInterface { | ||||
|   ReauthenticateFailureFirebase([String? code, String? msg]) | ||||
|       : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); | ||||
|   ReauthenticateFailureFirebase.fromCode(String code) : super.fromCode(code) { | ||||
|     switch (code) { | ||||
|       case 'user-mismatch': | ||||
|         msg = 'Given credential does not correspond to the user.'; | ||||
|         break; | ||||
|       case 'user-not-found': | ||||
|         msg = 'User is not found, please create an account.'; | ||||
|         break; | ||||
|       case 'invalid-credential': | ||||
|         msg = 'The credential received is malformed or has expired.'; | ||||
|         break; | ||||
|       case 'invalid-email': | ||||
|         msg = 'Email is not valid or badly formatted.'; | ||||
|         break; | ||||
|       case 'wrong-password': | ||||
|         msg = 'Incorrect password, please try again.'; | ||||
|         break; | ||||
|       case 'invalid-verification-code': | ||||
|         msg = 'The credential verification code received is invalid.'; | ||||
|         break; | ||||
|       case 'invalid-verification-id': | ||||
|         msg = 'The credential verification ID received is invalid.'; | ||||
|         break; | ||||
|       default: | ||||
|         this.code = 'unknown'; | ||||
|         msg = 'An unknown error occurred.'; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class UpdateEmailFailureFirebase extends UpdateEmailFailureInterface { | ||||
|   UpdateEmailFailureFirebase([String? code, String? msg]) | ||||
|       : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); | ||||
|   UpdateEmailFailureFirebase.fromCode(String code) : super.fromCode(code) { | ||||
|     switch (code) { | ||||
|       case 'invalid-email': | ||||
|         msg = 'Email is not valid or badly formatted.'; | ||||
|         break; | ||||
|       case 'email-already-in-use': | ||||
|         msg = 'An account already exists for that email.'; | ||||
|         break; | ||||
|       case 'requires-recent-login': | ||||
|         msg = "User's last sign-in time does not meet the security threshold."; | ||||
|         break; | ||||
|       default: | ||||
|         this.code = 'unknown'; | ||||
|         msg = 'An unknown error occurred.'; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class UpdatePasswordFailureFirebase extends UpdatePasswordFailureInterface { | ||||
|   UpdatePasswordFailureFirebase([String? code, String? msg]) | ||||
|       : super(code ?? 'unknown', msg ?? 'An unknown error occurred.'); | ||||
|   UpdatePasswordFailureFirebase.fromCode(String code) : super.fromCode(code) { | ||||
|     switch (code) { | ||||
|       case 'weak-password': | ||||
|         msg = 'Please enter a stronger password.'; | ||||
|         break; | ||||
|       case 'requires-recent-login': | ||||
|         msg = "User's last sign-in time does not meet the security threshold."; | ||||
|         break; | ||||
|       default: | ||||
|         this.code = 'unknown'; | ||||
|         msg = 'An unknown error occurred.'; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -21,6 +21,7 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart'; | ||||
| class AuthenticationFirebaseDataSourceImpl | ||||
|     extends AuthenticationRemoteDataSource { | ||||
|   final FirebaseAuth _firebaseAuth; | ||||
|   UserCredential? _latestCreds; | ||||
| 
 | ||||
|   AuthenticationFirebaseDataSourceImpl({FirebaseAuth? firebaseAuth}) | ||||
|       : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance; | ||||
| @ -51,6 +52,7 @@ class AuthenticationFirebaseDataSourceImpl | ||||
|         email: email, | ||||
|         password: password, | ||||
|       ); | ||||
|       _latestCreds = userCredential; | ||||
|       final user = userCredential.user; | ||||
|       if (user.isNotNull) { | ||||
|         return _mapper(user!); | ||||
| @ -76,6 +78,7 @@ class AuthenticationFirebaseDataSourceImpl | ||||
|         email: email, | ||||
|         password: password, | ||||
|       ); | ||||
|       _latestCreds = userCredential; | ||||
|       final user = userCredential.user; | ||||
|       if (user.isNotNull) { | ||||
|         return _mapper(user!); | ||||
| @ -92,6 +95,7 @@ class AuthenticationFirebaseDataSourceImpl | ||||
|   @override | ||||
|   Future<void> signOut() async { | ||||
|     try { | ||||
|       _latestCreds = null; | ||||
|       await _firebaseAuth.signOut(); | ||||
|     } catch (_) { | ||||
|       throw SignOutFailureFirebase(); | ||||
| @ -164,6 +168,7 @@ class AuthenticationFirebaseDataSourceImpl | ||||
|   Future<Account> signInAnonymously() async { | ||||
|     try { | ||||
|       final userCredential = await _firebaseAuth.signInAnonymously(); | ||||
|       _latestCreds = userCredential; | ||||
|       final user = userCredential.user; | ||||
|       if (user.isNotNull) { | ||||
|         return _mapper(user!); | ||||
| @ -199,4 +204,60 @@ class AuthenticationFirebaseDataSourceImpl | ||||
|       throw RefreshFailureFirebase(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<Account> reauthenticateWithCredential() async { | ||||
|     try { | ||||
|       if (_latestCreds?.credential != null) { | ||||
|         await _firebaseAuth.currentUser | ||||
|             ?.reauthenticateWithCredential(_latestCreds!.credential!); | ||||
|       } else { | ||||
|         throw Exception(); // Get caught just after. | ||||
|       } | ||||
|       final user = _firebaseAuth.currentUser; | ||||
|       if (user.isNotNull) { | ||||
|         return _mapper(user!); | ||||
|       } else { | ||||
|         throw Exception(); // Get caught just after. | ||||
|       } | ||||
|     } on FirebaseAuthException catch (e) { | ||||
|       throw ReauthenticateFailureFirebase.fromCode(e.code); | ||||
|     } catch (_) { | ||||
|       throw ReauthenticateFailureFirebase(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<Account> updateEmail({required String email}) async { | ||||
|     try { | ||||
|       await _firebaseAuth.currentUser!.updateEmail(email); | ||||
|       final user = _firebaseAuth.currentUser; | ||||
|       if (user.isNotNull) { | ||||
|         return _mapper(user!); | ||||
|       } else { | ||||
|         throw Exception(); // Get caught just after. | ||||
|       } | ||||
|     } on FirebaseAuthException catch (e) { | ||||
|       throw UpdateEmailFailureFirebase.fromCode(e.code); | ||||
|     } catch (_) { | ||||
|       throw UpdateEmailFailureFirebase(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<Account> updatePassword({required String password}) async { | ||||
|     try { | ||||
|       await _firebaseAuth.currentUser!.updatePassword(password); | ||||
|       final user = _firebaseAuth.currentUser; | ||||
|       if (user.isNotNull) { | ||||
|         return _mapper(user!); | ||||
|       } else { | ||||
|         throw Exception(); // Get caught just after. | ||||
|       } | ||||
|     } on FirebaseAuthException catch (e) { | ||||
|       throw UpdatePasswordFailureFirebase.fromCode(e.code); | ||||
|     } catch (_) { | ||||
|       throw UpdatePasswordFailureFirebase(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -23,6 +23,7 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart'; | ||||
| class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource { | ||||
|   Pair<Account, String>? _connectedMock; | ||||
|   Pair<Account, String>? _registeredMock; | ||||
|   DateTime _lastSignInTime = DateTime.now(); | ||||
|   final StreamController<Account?> _streamAccount = StreamController() | ||||
|     ..add(null); | ||||
| 
 | ||||
| @ -118,6 +119,7 @@ class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource { | ||||
|     ); | ||||
|     _streamAccount.add(mock); | ||||
|     _connectedMock = _connectedMock?.copyWith(left: mock); | ||||
|     _lastSignInTime = DateTime.now(); | ||||
|     return Future.value(mock); | ||||
|   } | ||||
| 
 | ||||
| @ -149,6 +151,7 @@ class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource { | ||||
|       } | ||||
|       _streamAccount.add(_registeredMock!.left); | ||||
|       _connectedMock = _registeredMock!.copyWith(); | ||||
|       _lastSignInTime = DateTime.now(); | ||||
|       return _registeredMock!.left!; | ||||
|     } | ||||
|     throw SignInWithCredentialFailureFirebase(); | ||||
| @ -193,6 +196,8 @@ class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource { | ||||
|     ); | ||||
|     _streamAccount.add(mock); | ||||
|     _registeredMock = Pair(mock, password); | ||||
|     _connectedMock = _registeredMock!.copyWith(); | ||||
|     _lastSignInTime = DateTime.now(); | ||||
|     return Future.value(mock); | ||||
|   } | ||||
| 
 | ||||
| @ -204,4 +209,45 @@ class AuthenticationMockDataSourceImpl extends AuthenticationRemoteDataSource { | ||||
|     await _randomDelay(); | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<Account> reauthenticateWithCredential() async { | ||||
|     await _randomDelay(); | ||||
|     if (_connectedMock.isNull) { | ||||
|       throw ReauthenticateFailureFirebase(); | ||||
|     } | ||||
|     await refresh(); | ||||
|     _lastSignInTime = DateTime.now(); | ||||
|     return Future.value(_connectedMock?.left); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<Account> updateEmail({required String email}) { | ||||
|     final before = DateTime.now().subtract(const Duration(seconds: 10)); | ||||
|     if (_lastSignInTime.isBefore(before)) { | ||||
|       throw UpdateEmailFailureFirebase('requires-recent-login'); | ||||
|     } | ||||
|     final refresh = DateTime.now(); | ||||
|     final mock = (_connectedMock?.left as AccountModel?) | ||||
|         ?.copyWith(lastSignInTime: refresh, email: email); | ||||
|     _connectedMock = _connectedMock?.copyWith(left: mock); | ||||
|     _registeredMock = _registeredMock?.copyWith(left: mock); | ||||
|     _streamAccount.add(mock); | ||||
|     return Future.value(_connectedMock?.left); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<Account> updatePassword({required String password}) { | ||||
|     final before = DateTime.now().subtract(const Duration(seconds: 10)); | ||||
|     if (_lastSignInTime.isBefore(before)) { | ||||
|       throw UpdatePasswordFailureFirebase('requires-recent-login'); | ||||
|     } | ||||
|     final refresh = DateTime.now(); | ||||
|     final mock = (_connectedMock?.left as AccountModel?) | ||||
|         ?.copyWith(lastSignInTime: refresh); | ||||
|     _connectedMock = _connectedMock?.copyWith(left: mock, right: password); | ||||
|     _registeredMock = _registeredMock?.copyWith(left: mock, right: password); | ||||
|     _streamAccount.add(mock); | ||||
|     return Future.value(_connectedMock?.left); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -314,4 +314,38 @@ class AuthenticationRepositoryImpl<T extends Object> | ||||
|         }, | ||||
|         (error) => error, | ||||
|       ); | ||||
| 
 | ||||
|   @override | ||||
|   FutureOrResult<Account> reauthenticateWithCredential() => | ||||
|       Result.tryCatchAsync<Account, AppException, AppException>( | ||||
|         () async { | ||||
|           final account = await _authenticationRemoteDataSource | ||||
|               .reauthenticateWithCredential(); | ||||
|           return account; | ||||
|         }, | ||||
|         (error) => error, | ||||
|       ); | ||||
| 
 | ||||
|   @override | ||||
|   FutureOrResult<Account> updateEmail({required String email}) => | ||||
|       Result.tryCatchAsync<Account, AppException, AppException>( | ||||
|         () async { | ||||
|           final account = | ||||
|               await _authenticationRemoteDataSource.updateEmail(email: email); | ||||
|           return account; | ||||
|         }, | ||||
|         (error) => error, | ||||
|       ); | ||||
| 
 | ||||
|   @override | ||||
|   FutureOrResult<Account> updatePassword({required String password}) => | ||||
|       Result.tryCatchAsync<Account, AppException, AppException>( | ||||
|         () async { | ||||
|           final account = await _authenticationRemoteDataSource.updatePassword( | ||||
|             password: password, | ||||
|           ); | ||||
|           return account; | ||||
|         }, | ||||
|         (error) => error, | ||||
|       ); | ||||
| } | ||||
|  | ||||
| @ -48,4 +48,10 @@ abstract class AuthenticationRemoteDataSource extends BaseRemoteDataSource { | ||||
|   Future<bool> verifyPasswordResetCode({required String code}); | ||||
| 
 | ||||
|   Future<Account> signInAnonymously(); | ||||
| 
 | ||||
|   Future<Account> updateEmail({required String email}); | ||||
| 
 | ||||
|   Future<Account> updatePassword({required String password}); | ||||
| 
 | ||||
|   Future<Account> reauthenticateWithCredential(); | ||||
| } | ||||
|  | ||||
| @ -84,12 +84,45 @@ abstract class AuthenticationRepository<T> extends BaseRepository { | ||||
|     required String password, | ||||
|   }); | ||||
| 
 | ||||
|   /// {@template update_email} | ||||
|   /// Update or add [email]. | ||||
|   /// | ||||
|   /// Throws a UpdateEmailFailureInterface if | ||||
|   /// an exception occurs. | ||||
|   /// {@endtemplate} | ||||
|   FutureOrResult<Account> updateEmail({ | ||||
|     required String email, | ||||
|   }); | ||||
| 
 | ||||
|   /// {@template update_password} | ||||
|   /// Update or add [password]. | ||||
|   /// | ||||
|   /// Throws a UpdatePasswordFailureInterface if | ||||
|   /// an exception occurs. | ||||
|   /// {@endtemplate} | ||||
|   FutureOrResult<Account> updatePassword({ | ||||
|     required String password, | ||||
|   }); | ||||
| 
 | ||||
|   /// {@template reauthenticate} | ||||
|   /// Some security-sensitive actions—such as deleting an account,  | ||||
|   /// setting a primary email address, and changing a password—require that  | ||||
|   /// the user has recently signed in. | ||||
|   /// | ||||
|   /// Throws a ReauthenticateFailureInterface if | ||||
|   /// an exception occurs. | ||||
|   /// {@endtemplate} | ||||
|   FutureOrResult<Account> reauthenticateWithCredential(); | ||||
| 
 | ||||
|   /// {@template signout} | ||||
|   /// Signs out the current user. | ||||
|   /// It also clears the cache and the associated data. | ||||
|   /// {@endtemplate} | ||||
|   FutureOrResult<void> signOut(); | ||||
| 
 | ||||
|   /// {@template refresh} | ||||
|   /// Refreshes the current user, if signed in. | ||||
|   /// {@endtemplate} | ||||
|   FutureOrResult<void> refresh(); | ||||
| 
 | ||||
|   /// {@template stream_account} | ||||
|  | ||||
| @ -48,7 +48,7 @@ class InputBuilderTextController<Cubit extends FormDataCubit, S extends String?, | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final formName = context.watch<Cubit>().formName; | ||||
|     final formName = context.read<Cubit>().formName; | ||||
|     final value = | ||||
|         context.read<FormRepository>().accessForm(formName).valueOf<S>(field); | ||||
|     _controller | ||||
|  | ||||
| @ -0,0 +1,14 @@ | ||||
| import 'package:wyatt_ui_components/wyatt_wyatt_ui_components.dart'; | ||||
| import 'package:wyatt_ui_components_example/components/custom_app_bar.dart'; | ||||
| import 'package:wyatt_ui_components_example/components/custom_bottom_bar.dart'; | ||||
| import 'package:wyatt_ui_components_example/components/custom_error_widget.dart'; | ||||
| import 'package:wyatt_ui_components_example/components/custom_loading_widget.dart'; | ||||
| 
 | ||||
| class AppThemeComponent { | ||||
|   static ComponentThemeData get components => const ComponentThemeData.raw( | ||||
|         appBar: CustomAppBar(), | ||||
|         bottomNavigationBar: CustomBottomNavigationBar(), | ||||
|         errorWidget: CustomErrorWidget(), | ||||
|         loadingWidget: CustomLoadingWidget(), | ||||
|       ); | ||||
| } | ||||
| @ -0,0 +1,16 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:wyatt_ui_components/wyatt_wyatt_ui_components.dart'; | ||||
| 
 | ||||
| class CustomAppBar extends AppBarComponent { | ||||
|   const CustomAppBar({super.title, super.key}); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) => AppBar( | ||||
|         title: Text(super.title ?? ''), | ||||
|       ); | ||||
| 
 | ||||
|   @override | ||||
|   AppBarComponent configure({String? title}) => CustomAppBar( | ||||
|         title: title ?? this.title, | ||||
|       ); | ||||
| } | ||||
| @ -0,0 +1,40 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:wyatt_ui_components/wyatt_wyatt_ui_components.dart'; | ||||
| 
 | ||||
| class CustomBottomNavigationBar extends BottomNavigationBarComponent { | ||||
|   const CustomBottomNavigationBar({ | ||||
|     super.currentIndex, | ||||
|     super.onTap, | ||||
|     super.key, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) => BottomNavigationBar( | ||||
|         currentIndex: currentIndex, | ||||
|         onTap: (index) => super.onTap?.call(context, index), | ||||
|         items: const [ | ||||
|           BottomNavigationBarItem( | ||||
|             label: 'Icon 1', | ||||
|             icon: Icon( | ||||
|               Icons.nearby_off, | ||||
|             ), | ||||
|           ), | ||||
|           BottomNavigationBarItem( | ||||
|             label: 'Icon 2', | ||||
|             icon: Icon( | ||||
|               Icons.dangerous, | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
|       ); | ||||
| 
 | ||||
|   @override | ||||
|   CustomBottomNavigationBar configure({ | ||||
|     void Function(BuildContext, int)? onTap, | ||||
|     int currentIndex = 0, | ||||
|   }) => | ||||
|       CustomBottomNavigationBar( | ||||
|         onTap: onTap ?? this.onTap, | ||||
|         currentIndex: currentIndex, | ||||
|       ); | ||||
| } | ||||
| @ -0,0 +1,16 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:wyatt_ui_components/wyatt_wyatt_ui_components.dart'; | ||||
| 
 | ||||
| class CustomErrorWidget extends ErrorWidgetComponent { | ||||
|   const CustomErrorWidget({super.error, super.key}); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) => ColoredBox( | ||||
|         color: Colors.red, | ||||
|         child: Center(child: Text(error ?? 'Error')), | ||||
|       ); | ||||
| 
 | ||||
|   @override | ||||
|   ErrorWidgetComponent configure({String? error}) => | ||||
|       CustomErrorWidget(error: error ?? this.error); | ||||
| } | ||||
| @ -0,0 +1,18 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:wyatt_ui_components/wyatt_wyatt_ui_components.dart'; | ||||
| 
 | ||||
| class CustomLoadingWidget extends LoadingWidgetComponent { | ||||
|   const CustomLoadingWidget({super.color, super.key}); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) => Center( | ||||
|         child: CircularProgressIndicator( | ||||
|           color: color, | ||||
|         ), | ||||
|       ); | ||||
| 
 | ||||
|   @override | ||||
|   CustomLoadingWidget configure({Color? color}) => CustomLoadingWidget( | ||||
|         color: color ?? this.color, | ||||
|       ); | ||||
| } | ||||
| @ -15,6 +15,8 @@ | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:wyatt_ui_components/wyatt_wyatt_ui_components.dart'; | ||||
| import 'package:wyatt_ui_components_example/component_theme.dart'; | ||||
| 
 | ||||
| void main() { | ||||
|   runApp(const MyApp()); | ||||
| @ -26,17 +28,45 @@ class MyApp extends StatelessWidget { | ||||
|   // This widget is the root of your application. | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return MaterialApp( | ||||
|       title: 'Wyatt Ui Components Example', | ||||
|       theme: ThemeData( | ||||
|         primarySwatch: Colors.blue, | ||||
|       ), | ||||
|       home: Scaffold( | ||||
|         appBar: AppBar( | ||||
|           title: const Text('Wyatt Ui Components Example'), | ||||
|     return ComponentTheme( | ||||
|       componentThemeWidget: AppThemeComponent.components, | ||||
|       child: MaterialApp( | ||||
|         title: 'Wyatt Ui Components Example', | ||||
|         theme: ThemeData( | ||||
|           primarySwatch: Colors.blue, | ||||
|         ), | ||||
|         home: const Scaffold( | ||||
|           body: Home(), | ||||
|         ), | ||||
|         body: const Center(child: Text('')), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class Home extends StatelessWidget { | ||||
|   const Home({super.key}); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) => Scaffold( | ||||
|         appBar: PreferredSize( | ||||
|           preferredSize: const Size.fromHeight(60), | ||||
|           child: context.components.appBar.configure(title: 'Example title'), | ||||
|         ), | ||||
|         body: Column( | ||||
|           children: [ | ||||
|             Expanded( | ||||
|               child: context.components.errorWidget | ||||
|                   .configure(error: 'Example erreur'), | ||||
|             ), | ||||
|             const SizedBox( | ||||
|               height: 10, | ||||
|             ), | ||||
|             Expanded( | ||||
|               child: context.components.loadingWidget | ||||
|                   .configure(color: Colors.green), | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|         bottomNavigationBar: context.components.bottomNavigationBar, | ||||
|       ); | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,7 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| 
 | ||||
| abstract class Component extends StatelessWidget { | ||||
|   const Component({super.key}); | ||||
| 
 | ||||
|   Component configure(); | ||||
| } | ||||
| @ -15,19 +15,17 @@ | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:wyatt_ui_components/src/domain/entities/component.dart'; | ||||
| 
 | ||||
| abstract class AppBarComponent extends PreferredSize { | ||||
| abstract class AppBarComponent extends Component { | ||||
|   final String? title; | ||||
|   const AppBarComponent({this.title, super.key}) | ||||
|       : super( | ||||
|           preferredSize: const Size.fromHeight(60), | ||||
|           child: const SizedBox.shrink(), | ||||
|         ); | ||||
|   const AppBarComponent({this.title, super.key}); | ||||
| 
 | ||||
|   @override | ||||
|   AppBarComponent configure({String? title}); | ||||
| } | ||||
| 
 | ||||
| abstract class BottomNavigationBarComponent extends StatelessWidget { | ||||
| abstract class BottomNavigationBarComponent extends Component { | ||||
|   final int currentIndex; | ||||
|   final void Function(BuildContext, int)? onTap; | ||||
|   const BottomNavigationBarComponent({ | ||||
| @ -36,8 +34,25 @@ abstract class BottomNavigationBarComponent extends StatelessWidget { | ||||
|     super.key, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   BottomNavigationBarComponent configure({ | ||||
|     void Function(BuildContext, int)? onTap, | ||||
|     int currentIndex = 0, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| abstract class ErrorWidgetComponent extends Component { | ||||
|   final String? error; | ||||
|   const ErrorWidgetComponent({required this.error, super.key}); | ||||
| 
 | ||||
|   @override | ||||
|   ErrorWidgetComponent configure({String? error}); | ||||
| } | ||||
| 
 | ||||
| abstract class LoadingWidgetComponent extends Component { | ||||
|   final Color? color; | ||||
|   const LoadingWidgetComponent({required this.color, super.key}); | ||||
| 
 | ||||
|   @override | ||||
|   LoadingWidgetComponent configure({Color? color}); | ||||
| } | ||||
|  | ||||
| @ -19,9 +19,13 @@ import 'package:wyatt_ui_components/src/domain/entities/components.dart'; | ||||
| class ComponentThemeData { | ||||
|   final AppBarComponent appBar; | ||||
|   final BottomNavigationBarComponent bottomNavigationBar; | ||||
|   final ErrorWidgetComponent errorWidget; | ||||
|   final LoadingWidgetComponent loadingWidget; | ||||
| 
 | ||||
|   const ComponentThemeData.raw({ | ||||
|     required this.appBar, | ||||
|     required this.bottomNavigationBar, | ||||
|     required this.errorWidget, | ||||
|     required this.loadingWidget, | ||||
|   }); | ||||
| } | ||||
|  | ||||
| @ -32,3 +32,4 @@ Wyatt Ui Layout for Flutter | ||||
| - Layouts : | ||||
|   - App Bar Layout | ||||
|   - Bottom Navigation Bar Layout | ||||
|   - Frame Layout (wrapp both appbar & bottom bar) | ||||
|  | ||||
| @ -0,0 +1,41 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:wyatt_ui_components/wyatt_wyatt_ui_components.dart'; | ||||
| import 'package:wyatt_ui_layout/src/presentation/layouts/layout.dart'; | ||||
| 
 | ||||
| class FrameLayout extends Layout { | ||||
|   final String title; | ||||
|   final Widget body; | ||||
|   final int currentIndex; | ||||
| 
 | ||||
|   const FrameLayout({ | ||||
|     required this.title, | ||||
|     required this.body, | ||||
|     required this.currentIndex, | ||||
|     super.key, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) => Scaffold( | ||||
|         appBar: context.components.appBar.configure(title: title), | ||||
|         body: body, | ||||
|         bottomNavigationBar: context.components.bottomNavigationBar.configure( | ||||
|           currentIndex: currentIndex, | ||||
|         ), | ||||
|       ); | ||||
| } | ||||
| @ -1,2 +1,3 @@ | ||||
| export 'layouts/app_bar_layout.dart'; | ||||
| export 'layouts/bottom_navigation_bar_layout.dart'; | ||||
| export 'layouts/frame_layout.dart'; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user