From e26fbf3655177d48f938cd4885ee7b1119870faa Mon Sep 17 00:00:00 2001 From: cevheri Date: Mon, 30 Dec 2024 01:01:59 +0300 Subject: [PATCH] refactor: add responsive form and submit button on register, forgot_password, change_password and login. --- assets/mock/POST_account_change-password.json | 4 + .../POST_account_reset_password_init.json | 4 +- lib/data/http_utils.dart | 2 +- lib/data/repository/account_repository.dart | 1 + lib/main/main_local.mapper.g.dart | 64 +++--- lib/main/main_prod.mapper.g.dart | 64 +++--- .../screen/account/account_screen.dart | 29 +-- .../bloc/change_password_bloc.dart | 20 +- .../bloc/change_password_state.dart | 11 +- .../change_password_screen.dart | 168 +++++++++------ .../bloc/forgot_password_bloc.dart | 8 +- .../forgot_password_screen.dart | 186 +++++++++-------- .../screen/login/login_screen.dart | 49 ++--- .../screen/register/bloc/register_bloc.dart | 4 +- .../screen/register/bloc/register_event.dart | 6 +- .../screen/register/bloc/register_state.dart | 12 +- .../screen/register/register_screen.dart | 193 +++++++++--------- .../blocs/change_password_bloc_test.dart | 19 +- .../blocs/forgot_password_bloc_test.dart | 12 +- .../blocs/register_bloc_test.dart | 11 +- .../account/change_password_screen_test.dart | 57 +++--- .../forgot_password_screen_test.dart | 132 ++++++------ .../screen/login/login_screen_test.dart | 2 +- .../screen/register/register_screen.test.dart | 13 +- 24 files changed, 565 insertions(+), 506 deletions(-) create mode 100644 assets/mock/POST_account_change-password.json diff --git a/assets/mock/POST_account_change-password.json b/assets/mock/POST_account_change-password.json new file mode 100644 index 0000000..590a410 --- /dev/null +++ b/assets/mock/POST_account_change-password.json @@ -0,0 +1,4 @@ +{ + "currentPassword": "123456", + "newPassword": "123456" +} \ No newline at end of file diff --git a/assets/mock/POST_account_reset_password_init.json b/assets/mock/POST_account_reset_password_init.json index 9e26dfe..da0d97b 100644 --- a/assets/mock/POST_account_reset_password_init.json +++ b/assets/mock/POST_account_reset_password_init.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "email": "test@test.com" +} \ No newline at end of file diff --git a/lib/data/http_utils.dart b/lib/data/http_utils.dart index 682c25c..7c75cf7 100644 --- a/lib/data/http_utils.dart +++ b/lib/data/http_utils.dart @@ -321,7 +321,7 @@ class HttpUtils { // .replaceAll("-", "_") // ; // @formatter:on - final filePath = endpoint.replaceAll("/", "_"); + final filePath = endpoint.replaceAll("/", "_").replaceAll("-", "_"); if (pathParams != null) { path += "/$httpMethod${filePath}pathParams.json"; } else if (queryParams != null) { diff --git a/lib/data/repository/account_repository.dart b/lib/data/repository/account_repository.dart index 02b0e8f..cbb2dd2 100644 --- a/lib/data/repository/account_repository.dart +++ b/lib/data/repository/account_repository.dart @@ -81,6 +81,7 @@ class AccountRepository { // --data-raw 'cevheribozoglan@gmail.com' HttpUtils.addCustomHttpHeader('Accept', 'application/json, text/plain, */*'); HttpUtils.addCustomHttpHeader('Content-Type', 'text/plain'); + // final body = {"email": mailAddress}; final httpResponse = await HttpUtils.postRequest("/$_resource/reset-password/init", mailAddress); _log.debug("END:resetPassword successful"); return httpResponse.statusCode; diff --git a/lib/main/main_local.mapper.g.dart b/lib/main/main_local.mapper.g.dart index ac79c4a..cc7e1e9 100644 --- a/lib/main/main_local.mapper.g.dart +++ b/lib/main/main_local.mapper.g.dart @@ -12,15 +12,15 @@ import 'package:flutter_bloc_advance/data/models/menu.dart' as x4 show Menu; import 'package:flutter_bloc_advance/data/models/user.dart' as x0 show User; import 'package:flutter_bloc_advance/data/models/user_jwt.dart' as x2 show UserJWT; import 'package:flutter_bloc_advance/presentation/common_blocs/account/account_bloc.dart' as x10 show AccountStatus; -import 'package:flutter_bloc_advance/presentation/common_blocs/authority/authority_bloc.dart' as x18 show AuthorityStatus; -import 'package:flutter_bloc_advance/presentation/common_widgets/drawer/drawer_bloc/drawer_bloc.dart' as x17 show DrawerStateStatus; -import 'package:flutter_bloc_advance/presentation/screen/change_password/bloc/change_password_bloc.dart' as x13 show ChangePasswordStatus; +import 'package:flutter_bloc_advance/presentation/common_blocs/authority/authority_bloc.dart' as x17 show AuthorityStatus; +import 'package:flutter_bloc_advance/presentation/common_widgets/drawer/drawer_bloc/drawer_bloc.dart' as x16 show DrawerStateStatus; +import 'package:flutter_bloc_advance/presentation/screen/change_password/bloc/change_password_bloc.dart' as x12 show ChangePasswordStatus; import 'package:flutter_bloc_advance/presentation/screen/components/confirmation_dialog_widget.dart' as x11 show DialogType; -import 'package:flutter_bloc_advance/presentation/screen/components/editor_form_mode.dart' as x19 show EditorFormMode; -import 'package:flutter_bloc_advance/presentation/screen/forgot_password/bloc/forgot_password_bloc.dart' as x14 show ForgotPasswordStatus; -import 'package:flutter_bloc_advance/presentation/screen/login/bloc/login_bloc.dart' as x15 show LoginStatus; -import 'package:flutter_bloc_advance/presentation/screen/register/bloc/register_bloc.dart' as x16 show RegisterStatus; -import 'package:flutter_bloc_advance/presentation/screen/user/bloc/user_bloc.dart' as x12 show UserStatus; +import 'package:flutter_bloc_advance/presentation/screen/components/editor_form_mode.dart' as x18 show EditorFormMode; +import 'package:flutter_bloc_advance/presentation/screen/forgot_password/bloc/forgot_password_bloc.dart' as x13 show ForgotPasswordStatus; +import 'package:flutter_bloc_advance/presentation/screen/login/bloc/login_bloc.dart' as x14 show LoginStatus; +import 'package:flutter_bloc_advance/presentation/screen/register/bloc/register_bloc.dart' as x15 show RegisterStatus; +import 'package:flutter_bloc_advance/presentation/screen/user/bloc/user_bloc.dart' as x19 show UserStatus; import 'package:flutter_bloc_advance/routes/app_router.dart' as x9 show RouterType; // This file has been generated by the reflectable package. // https://github.com/dart-lang/reflectable. @@ -89,22 +89,22 @@ final mainLocalGeneratedAdapter = JsonMapperAdapter( typeOf>(): (value) => value.cast(), typeOf>(): (value) => value.cast(), typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast() + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast() }, enumValues: { x6.LogFormat: x6.LogFormat.values, @@ -114,14 +114,14 @@ final mainLocalGeneratedAdapter = JsonMapperAdapter( x9.RouterType: x9.RouterType.values, x10.AccountStatus: x10.AccountStatus.values, x11.DialogType: x11.DialogType.values, - x12.UserStatus: x12.UserStatus.values, - x13.ChangePasswordStatus: x13.ChangePasswordStatus.values, - x14.ForgotPasswordStatus: x14.ForgotPasswordStatus.values, - x15.LoginStatus: x15.LoginStatus.values, - x16.RegisterStatus: x16.RegisterStatus.values, - x17.DrawerStateStatus: x17.DrawerStateStatus.values, - x18.AuthorityStatus: x18.AuthorityStatus.values, - x19.EditorFormMode: x19.EditorFormMode.values + x12.ChangePasswordStatus: x12.ChangePasswordStatus.values, + x13.ForgotPasswordStatus: x13.ForgotPasswordStatus.values, + x14.LoginStatus: x14.LoginStatus.values, + x15.RegisterStatus: x15.RegisterStatus.values, + x16.DrawerStateStatus: x16.DrawerStateStatus.values, + x17.AuthorityStatus: x17.AuthorityStatus.values, + x18.EditorFormMode: x18.EditorFormMode.values, + x19.UserStatus: x19.UserStatus.values }); Future initializeJsonMapperAsync({Iterable adapters = const [], SerializationOptions? serializationOptions, DeserializationOptions? deserializationOptions}) => Future(() => initializeJsonMapper(adapters: adapters, serializationOptions: serializationOptions, deserializationOptions: deserializationOptions)); diff --git a/lib/main/main_prod.mapper.g.dart b/lib/main/main_prod.mapper.g.dart index eca2ce1..80e3f0b 100644 --- a/lib/main/main_prod.mapper.g.dart +++ b/lib/main/main_prod.mapper.g.dart @@ -12,15 +12,15 @@ import 'package:flutter_bloc_advance/data/models/menu.dart' as x4 show Menu; import 'package:flutter_bloc_advance/data/models/user.dart' as x0 show User; import 'package:flutter_bloc_advance/data/models/user_jwt.dart' as x2 show UserJWT; import 'package:flutter_bloc_advance/presentation/common_blocs/account/account_bloc.dart' as x10 show AccountStatus; -import 'package:flutter_bloc_advance/presentation/common_blocs/authority/authority_bloc.dart' as x18 show AuthorityStatus; -import 'package:flutter_bloc_advance/presentation/common_widgets/drawer/drawer_bloc/drawer_bloc.dart' as x17 show DrawerStateStatus; -import 'package:flutter_bloc_advance/presentation/screen/change_password/bloc/change_password_bloc.dart' as x13 show ChangePasswordStatus; +import 'package:flutter_bloc_advance/presentation/common_blocs/authority/authority_bloc.dart' as x17 show AuthorityStatus; +import 'package:flutter_bloc_advance/presentation/common_widgets/drawer/drawer_bloc/drawer_bloc.dart' as x16 show DrawerStateStatus; +import 'package:flutter_bloc_advance/presentation/screen/change_password/bloc/change_password_bloc.dart' as x12 show ChangePasswordStatus; import 'package:flutter_bloc_advance/presentation/screen/components/confirmation_dialog_widget.dart' as x11 show DialogType; -import 'package:flutter_bloc_advance/presentation/screen/components/editor_form_mode.dart' as x19 show EditorFormMode; -import 'package:flutter_bloc_advance/presentation/screen/forgot_password/bloc/forgot_password_bloc.dart' as x14 show ForgotPasswordStatus; -import 'package:flutter_bloc_advance/presentation/screen/login/bloc/login_bloc.dart' as x15 show LoginStatus; -import 'package:flutter_bloc_advance/presentation/screen/register/bloc/register_bloc.dart' as x16 show RegisterStatus; -import 'package:flutter_bloc_advance/presentation/screen/user/bloc/user_bloc.dart' as x12 show UserStatus; +import 'package:flutter_bloc_advance/presentation/screen/components/editor_form_mode.dart' as x18 show EditorFormMode; +import 'package:flutter_bloc_advance/presentation/screen/forgot_password/bloc/forgot_password_bloc.dart' as x13 show ForgotPasswordStatus; +import 'package:flutter_bloc_advance/presentation/screen/login/bloc/login_bloc.dart' as x14 show LoginStatus; +import 'package:flutter_bloc_advance/presentation/screen/register/bloc/register_bloc.dart' as x15 show RegisterStatus; +import 'package:flutter_bloc_advance/presentation/screen/user/bloc/user_bloc.dart' as x19 show UserStatus; import 'package:flutter_bloc_advance/routes/app_router.dart' as x9 show RouterType; // This file has been generated by the reflectable package. // https://github.com/dart-lang/reflectable. @@ -89,22 +89,22 @@ final mainProdGeneratedAdapter = JsonMapperAdapter( typeOf>(): (value) => value.cast(), typeOf>(): (value) => value.cast(), typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast(), - typeOf>(): (value) => value.cast() + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast(), + typeOf>(): (value) => value.cast() }, enumValues: { x6.LogFormat: x6.LogFormat.values, @@ -114,14 +114,14 @@ final mainProdGeneratedAdapter = JsonMapperAdapter( x9.RouterType: x9.RouterType.values, x10.AccountStatus: x10.AccountStatus.values, x11.DialogType: x11.DialogType.values, - x12.UserStatus: x12.UserStatus.values, - x13.ChangePasswordStatus: x13.ChangePasswordStatus.values, - x14.ForgotPasswordStatus: x14.ForgotPasswordStatus.values, - x15.LoginStatus: x15.LoginStatus.values, - x16.RegisterStatus: x16.RegisterStatus.values, - x17.DrawerStateStatus: x17.DrawerStateStatus.values, - x18.AuthorityStatus: x18.AuthorityStatus.values, - x19.EditorFormMode: x19.EditorFormMode.values + x12.ChangePasswordStatus: x12.ChangePasswordStatus.values, + x13.ForgotPasswordStatus: x13.ForgotPasswordStatus.values, + x14.LoginStatus: x14.LoginStatus.values, + x15.RegisterStatus: x15.RegisterStatus.values, + x16.DrawerStateStatus: x16.DrawerStateStatus.values, + x17.AuthorityStatus: x17.AuthorityStatus.values, + x18.EditorFormMode: x18.EditorFormMode.values, + x19.UserStatus: x19.UserStatus.values }); Future initializeJsonMapperAsync({Iterable adapters = const [], SerializationOptions? serializationOptions, DeserializationOptions? deserializationOptions}) => Future(() => initializeJsonMapper(adapters: adapters, serializationOptions: serializationOptions, deserializationOptions: deserializationOptions)); diff --git a/lib/presentation/screen/account/account_screen.dart b/lib/presentation/screen/account/account_screen.dart index 655aa51..061ac20 100644 --- a/lib/presentation/screen/account/account_screen.dart +++ b/lib/presentation/screen/account/account_screen.dart @@ -45,7 +45,7 @@ class AccountScreen extends StatelessWidget { Widget _buildBody(BuildContext context) { return BlocBuilder( - buildWhen: (previous, current) => previous.status != current.status, //previous.data != current.data || + buildWhen: (previous, current) => previous.status != current.status, builder: (context, state) { return ResponsiveFormBuilder( formKey: _formKey, @@ -66,7 +66,6 @@ class AccountScreen extends StatelessWidget { } Widget _submitButton(BuildContext context, AccountState state) { - debugPrint('state.status: ${state.status}'); return ResponsiveSubmitButton( onPressed: () => state.status == AccountStatus.loading ? null : _onSubmit(context, state), isLoading: state.status == AccountStatus.loading, @@ -74,10 +73,17 @@ class AccountScreen extends StatelessWidget { } void _onSubmit(BuildContext context, AccountState state) { + debugPrint("onSubmit"); + FocusScope.of(context).unfocus(); + if (!(_formKey.currentState?.validate() ?? false)) { + debugPrint("validate"); + _showSnackBar(context, S.of(context).failed, const Duration(milliseconds: 1000)); + return; + } + if (!(_formKey.currentState?.isDirty ?? false)) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(S.of(context).no_changes_made)), - ); + debugPrint("no changes made"); + _showSnackBar(context, S.of(context).no_changes_made, const Duration(milliseconds: 1000)); return; } @@ -85,19 +91,6 @@ class AccountScreen extends StatelessWidget { final formData = _formKey.currentState!.value; final user = _createUserFromData(formData, state.data?.id); context.read().add(AccountSubmitEvent(user)); - if (state.status == AccountStatus.success) { - _formKey.currentState?.reset(); - } - - //context.read().add(UserSubmitEvent(user)); - // late final StreamSubscription subscription; - // subscription = context.read().stream.listen((userState) { - // if ((userState.status == UserStatus.success || userState.status == UserStatus.saveSuccess) && context.mounted) { - // context.read().add(const AccountFetchEvent()); - // _formKey.currentState?.reset(); - // subscription.cancel(); - // } - // }); // cancel the stream after the first event } } diff --git a/lib/presentation/screen/change_password/bloc/change_password_bloc.dart b/lib/presentation/screen/change_password/bloc/change_password_bloc.dart index b698cba..66d5c23 100644 --- a/lib/presentation/screen/change_password/bloc/change_password_bloc.dart +++ b/lib/presentation/screen/change_password/bloc/change_password_bloc.dart @@ -17,7 +17,7 @@ class ChangePasswordBloc extends Bloc ChangePasswordBloc({required AccountRepository repository}) : _repository = repository, - super(const ChangePasswordInitialState()) { + super(const ChangePasswordState(status: ChangePasswordStatus.initial)) { on(_onSubmit); } @@ -31,19 +31,29 @@ class ChangePasswordBloc extends Bloc FutureOr _onSubmit(ChangePasswordChanged event, Emitter emit) async { _log.debug("BEGIN: changePassword bloc: _onSubmit"); - emit(const ChangePasswordLoadingState()); + emit(state.copyWith(status: ChangePasswordStatus.loading)); try { + if (event.currentPassword == "" || event.newPassword == "") { + emit(state.copyWith(status: ChangePasswordStatus.failure)); + return; + } + + if (event.currentPassword == event.newPassword) { + emit(state.copyWith(status: ChangePasswordStatus.failure)); + return; + } + PasswordChangeDTO passwordChangeDTO = PasswordChangeDTO( currentPassword: event.currentPassword, newPassword: event.newPassword, ); var result = await _repository.changePassword(passwordChangeDTO); result < HttpStatus.badRequest - ? emit(const ChangePasswordCompletedState()) - : emit(const ChangePasswordErrorState(message: "Reset Password API Error")); + ? emit(state.copyWith(status: ChangePasswordStatus.success)) + : emit(state.copyWith(status: ChangePasswordStatus.failure)); _log.debug("END: changePassword bloc: _onSubmit success: {}", [result.toString()]); } catch (e) { - emit(const ChangePasswordErrorState(message: "Reset Password Unhandled Error")); + emit(state.copyWith(status: ChangePasswordStatus.failure)); _log.error("END: changePassword bloc: _onSubmit error: {}", [e.toString()]); } } diff --git a/lib/presentation/screen/change_password/bloc/change_password_state.dart b/lib/presentation/screen/change_password/bloc/change_password_state.dart index 50c6e40..de8d84d 100644 --- a/lib/presentation/screen/change_password/bloc/change_password_state.dart +++ b/lib/presentation/screen/change_password/bloc/change_password_state.dart @@ -7,14 +7,9 @@ const String authenticationFailKey = 'error.authenticate'; class ChangePasswordState extends Equatable { final ChangePasswordStatus status; - const ChangePasswordState({ - this.status = ChangePasswordStatus.initial, - }); - - ChangePasswordState copyWith({ - String? email, - ChangePasswordStatus? status, - }) { + const ChangePasswordState({this.status = ChangePasswordStatus.initial}); + + ChangePasswordState copyWith({ChangePasswordStatus? status}) { return ChangePasswordState( status: status ?? this.status, ); diff --git a/lib/presentation/screen/change_password/change_password_screen.dart b/lib/presentation/screen/change_password/change_password_screen.dart index 496cdd3..d6d96d6 100644 --- a/lib/presentation/screen/change_password/change_password_screen.dart +++ b/lib/presentation/screen/change_password/change_password_screen.dart @@ -3,57 +3,56 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc_advance/configuration/app_key_constants.dart'; import 'package:flutter_bloc_advance/configuration/constants.dart'; import 'package:flutter_bloc_advance/configuration/padding_spacing.dart'; -import 'package:flutter_bloc_advance/routes/app_router.dart'; +import 'package:flutter_bloc_advance/presentation/screen/components/confirmation_dialog_widget.dart'; +import 'package:flutter_bloc_advance/presentation/screen/components/responsive_form_widget.dart'; +import 'package:flutter_bloc_advance/presentation/screen/components/submit_button_widget.dart'; import 'package:flutter_bloc_advance/routes/app_routes_constants.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:go_router/go_router.dart'; import '../../../generated/l10n.dart'; -import '../../../utils/message.dart'; import 'bloc/change_password_bloc.dart'; class ChangePasswordScreen extends StatelessWidget { ChangePasswordScreen({super.key}); - final _changePasswordFormKey = GlobalKey(); + final _formKey = GlobalKey(); + final _scaffoldKey = GlobalKey(); @override Widget build(BuildContext context) { - return Scaffold( - appBar: _buildAppBar(context), - body: _buildBody(context), + return BlocListener( + listenWhen: (previous, current) => previous.status != current.status, + listener: (context, state) => _handleStateChanges(context, state), + child: PopScope( + canPop: !(_formKey.currentState?.isDirty ?? false), + onPopInvokedWithResult: (bool didPop, Object? data) async => _handlePopScope(didPop, data), + child: Scaffold(key: _scaffoldKey, appBar: _buildAppBar(context), body: _buildBody(context)), + ), ); } AppBar _buildAppBar(BuildContext context) { return AppBar( title: Text(S.of(context).change_password), - leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => AppRouter().push(context, ApplicationRoutesConstants.home)), + leading: IconButton( + key: const Key('changePasswordScreenAppBarBackButtonKey'), + icon: const Icon(Icons.arrow_back), + onPressed: () async => _handlePopScope(false, null, context), + ), ); } - FormBuilder _buildBody(BuildContext context) { - return FormBuilder( - key: _changePasswordFormKey, - child: SingleChildScrollView( - child: Center( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: Spacing.formMaxWidthLarge), - child: Padding( - padding: const EdgeInsets.all(Spacing.medium), - child: Column( - spacing: Spacing.medium, - children: [ - _logo(context), - _currentPasswordField(context), - _newPasswordField(context), - Align(alignment: Alignment.centerRight, child: _submitButton(context)), - ], - ), - ), - ), - ), - ), + Widget _buildBody(BuildContext context) { + return BlocBuilder( + buildWhen: (previous, current) => previous.status != current.status, + builder: (context, state) { + return ResponsiveFormBuilder( + formKey: _formKey, + children: [_logo(context), _currentPasswordField(context), _newPasswordField(context), _submitButton(context, state)], + ); + }, ); } @@ -87,45 +86,78 @@ class ChangePasswordScreen extends StatelessWidget { ); } - Widget _submitButton(BuildContext context) { - final t = S.of(context); - return BlocListener( - listener: (context, state) { - // Loading state - if (state is ChangePasswordLoadingState) { - Message.getMessage(context: context, title: t.loading, content: ""); - } - // Completed state - else if (state is ChangePasswordCompletedState) { - Navigator.pop(context); - Message.getMessage(context: context, title: t.success, content: ""); - //Navigator.pushNamedAndRemoveUntil(context, ApplicationRoutes.home, (route) => false); - } - // Error state - else if (state is ChangePasswordErrorState) { - Message.errorMessage(title: t.failed, context: context, content: ""); - } - }, - listenWhen: (previous, current) { - return current is ChangePasswordLoadingState || current is ChangePasswordCompletedState || current is ChangePasswordErrorState; - }, - child: FilledButton( - key: changePasswordButtonSubmitKey, - child: Text(S.of(context).save), - onPressed: () { - //without blocConsumer access to bloc directly - final currentState = context.read().state; - if (currentState is ChangePasswordLoadingState) { - return; - } - - final currentPass = _changePasswordFormKey.currentState!.value['currentPassword']; - final newPass = _changePasswordFormKey.currentState!.value['newPassword']; - if (_changePasswordFormKey.currentState!.saveAndValidate() && currentPass != newPass && newPass != null && currentPass != null) { - context.read().add(ChangePasswordChanged(currentPassword: currentPass, newPassword: newPass)); - } - }, - ), + Widget _submitButton(BuildContext context, ChangePasswordState state) { + return ResponsiveSubmitButton( + key: changePasswordButtonSubmitKey, + onPressed: () => state.status == ChangePasswordStatus.loading ? null : _onSubmit(context, state), + isLoading: state.status == ChangePasswordStatus.loading, + ); + } + + void _onSubmit(BuildContext context, ChangePasswordState state) { + debugPrint("onSubmit"); + FocusScope.of(context).unfocus(); + + if (!(_formKey.currentState?.validate() ?? false)) { + debugPrint("validate"); + _showSnackBar(context, S.of(context).failed, const Duration(milliseconds: 1000)); + return; + } + + if (!(_formKey.currentState?.isDirty ?? false)) { + debugPrint("no changes made"); + _showSnackBar(context, S.of(context).no_changes_made, const Duration(milliseconds: 1000)); + return; + } + + if (_formKey.currentState?.saveAndValidate() ?? false) { + debugPrint("saveAndValidate"); + final currentPass = _formKey.currentState!.value['currentPassword']; + final newPass = _formKey.currentState!.value['newPassword']; + context.read().add(ChangePasswordChanged(currentPassword: currentPass, newPassword: newPass)); + } + } + + void _handleStateChanges(BuildContext context, ChangePasswordState state) { + const duration = Duration(milliseconds: 1000); + switch (state.status) { + case ChangePasswordStatus.initial: + // + break; + case ChangePasswordStatus.loading: + _showSnackBar(context, S.of(context).loading, duration); + break; + case ChangePasswordStatus.success: + _formKey.currentState?.fields['currentPassword']?.reset(); + _formKey.currentState?.fields['newPassword']?.reset(); + _formKey.currentState?.reset(); + _showSnackBar(context, S.of(context).success, duration); + break; + case ChangePasswordStatus.failure: + _showSnackBar(context, S.of(context).failed, duration); + break; + } + } + + void _showSnackBar(BuildContext context, String message, Duration duration) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message), duration: duration), ); } + + Future _handlePopScope(bool didPop, Object? data, [BuildContext? contextParam]) async { + final context = contextParam ?? data as BuildContext; + + if (!context.mounted) return; + + if (didPop || !(_formKey.currentState?.isDirty ?? false) || _formKey.currentState == null) { + context.go(ApplicationRoutesConstants.home); + return; + } + + final shouldPop = await ConfirmationDialog.show(context: context, type: DialogType.unsavedChanges) ?? false; + if (shouldPop && context.mounted) { + context.go(ApplicationRoutesConstants.home); + } + } } diff --git a/lib/presentation/screen/forgot_password/bloc/forgot_password_bloc.dart b/lib/presentation/screen/forgot_password/bloc/forgot_password_bloc.dart index c73ff6e..80a1fb0 100644 --- a/lib/presentation/screen/forgot_password/bloc/forgot_password_bloc.dart +++ b/lib/presentation/screen/forgot_password/bloc/forgot_password_bloc.dart @@ -17,7 +17,7 @@ class ForgotPasswordBloc extends Bloc ForgotPasswordBloc({required AccountRepository repository}) : _repository = repository, - super(const ForgotPasswordInitialState()) { + super(const ForgotPasswordState(status: ForgotPasswordStatus.initial)) { on(_onSubmit); } @@ -31,18 +31,18 @@ class ForgotPasswordBloc extends Bloc FutureOr _onSubmit(ForgotPasswordEmailChanged event, Emitter emit) async { _log.debug("BEGIN: forgotPassword bloc: _onSubmit"); - emit(const ForgotPasswordLoadingState()); + emit(state.copyWith(status: ForgotPasswordStatus.loading)); try { String result = event.email.replaceAll('"', ''); var resultStatusCode = await _repository.resetPassword(result); if (resultStatusCode < HttpStatus.badRequest) { - emit(const ForgotPasswordCompletedState()); + emit(state.copyWith(status: ForgotPasswordStatus.success, email: result)); } else { throw BadRequestException("API Error"); } _log.debug("END: forgotPassword bloc: _onSubmit success: {}", [resultStatusCode.toString()]); } catch (e) { - emit(const ForgotPasswordErrorState(message: "Reset Password Error")); + emit(state.copyWith(status: ForgotPasswordStatus.failure)); _log.error("END: forgotPassword bloc: _onSubmit error: {}", [e.toString()]); } } diff --git a/lib/presentation/screen/forgot_password/forgot_password_screen.dart b/lib/presentation/screen/forgot_password/forgot_password_screen.dart index f66a8e2..ecbbb03 100644 --- a/lib/presentation/screen/forgot_password/forgot_password_screen.dart +++ b/lib/presentation/screen/forgot_password/forgot_password_screen.dart @@ -2,56 +2,56 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc_advance/configuration/app_key_constants.dart'; import 'package:flutter_bloc_advance/configuration/constants.dart'; -import 'package:flutter_bloc_advance/routes/app_router.dart'; +import 'package:flutter_bloc_advance/presentation/screen/components/confirmation_dialog_widget.dart'; +import 'package:flutter_bloc_advance/presentation/screen/components/responsive_form_widget.dart'; +import 'package:flutter_bloc_advance/presentation/screen/components/submit_button_widget.dart'; import 'package:flutter_bloc_advance/routes/app_routes_constants.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:go_router/go_router.dart'; import '../../../generated/l10n.dart'; -import '../../../utils/message.dart'; import 'bloc/forgot_password_bloc.dart'; class ForgotPasswordScreen extends StatelessWidget { ForgotPasswordScreen({super.key}); - final _forgotPasswordFormKey = GlobalKey(); + final _formKey = GlobalKey(); + final _scaffoldKey = GlobalKey(); @override Widget build(BuildContext context) { - return Scaffold( - appBar: _buildAppBar(context), - body: _buildBody(context), + return BlocListener( + listenWhen: (previous, current) => previous.status != current.status, + listener: (context, state) => _handleStateChanges(context, state), + child: PopScope( + canPop: !(_formKey.currentState?.isDirty ?? false), + onPopInvokedWithResult: (bool didPop, Object? data) async => _handlePopScope(didPop, data), + child: Scaffold(key: _scaffoldKey, appBar: _buildAppBar(context), body: _buildBody(context)), + ), ); } - _buildAppBar(BuildContext context) { + AppBar _buildAppBar(BuildContext context) { return AppBar( + key: const Key('forgotPasswordScreenAppBarKey'), title: Text(S.of(context).password_forgot), - leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => AppRouter().push(context, ApplicationRoutesConstants.home)), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => _handlePopScope(false, null, context), + ), ); } - _buildBody(BuildContext context) { - return FormBuilder( - key: _forgotPasswordFormKey, - child: Center( - child: Column( - spacing: 16, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _logo(context), - _forgotPasswordField(context), - SizedBox( - width: MediaQuery.of(context).size.width * 0.6, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [_submitButton(context)], - ), - ), - ], - ), - ), + Widget _buildBody(BuildContext context) { + return BlocBuilder( + buildWhen: (previous, current) => previous.status != current.status, + builder: (context, state) { + return ResponsiveFormBuilder( + formKey: _formKey, + children: [_logo(context), _forgotPasswordField(context), _submitButton(context, state)], + ); + }, ); } @@ -59,63 +59,87 @@ class ForgotPasswordScreen extends StatelessWidget { return Image.asset(LocaleConstants.defaultImgUrl, width: 200, height: 200); } - _forgotPasswordField(BuildContext context) { + FormBuilderTextField _forgotPasswordField(BuildContext context) { final t = S.of(context); - return SizedBox( - width: MediaQuery.of(context).size.width * 0.6, - child: FormBuilderTextField( - key: forgotPasswordTextFieldEmailKey, - name: "email", - decoration: InputDecoration(labelText: t.email), - maxLines: 1, - validator: FormBuilderValidators.compose( - [FormBuilderValidators.required(errorText: t.required_field), FormBuilderValidators.email(errorText: t.email_pattern)], - ), + return FormBuilderTextField( + key: forgotPasswordTextFieldEmailKey, + name: "email", + decoration: InputDecoration(labelText: t.email), + maxLines: 1, + validator: FormBuilderValidators.compose( + [FormBuilderValidators.required(errorText: t.required_field), FormBuilderValidators.email(errorText: t.email_pattern)], ), ); } - Widget _submitButton(BuildContext context) { - final t = S.of(context); - return BlocConsumer( - listener: (context, state) { - // Loading state - if (state is ForgotPasswordLoadingState) { - Message.getMessage(context: context, title: t.loading, content: ""); - } - // Completed state - else if (state is ForgotPasswordCompletedState) { - Navigator.pop(context); - Message.getMessage(context: context, title: t.success, content: ""); - Future.delayed(const Duration(seconds: 1), () {}); - } - // Error state - else if (state is ForgotPasswordErrorState) { - //Navigator.pop(context); - Message.errorMessage(title: t.failed, context: context, content: state.message); - } - }, - listenWhen: (previous, current) { - return current is ForgotPasswordLoadingState || current is ForgotPasswordCompletedState || current is ForgotPasswordErrorState; - }, - builder: (context, state) { - return SizedBox( - child: ElevatedButton( - key: forgotPasswordButtonSubmitKey, - child: Text(t.email_send), - onPressed: () { - if (state is ForgotPasswordLoadingState) { - return; - } - if (_forgotPasswordFormKey.currentState!.saveAndValidate()) { - context - .read() - .add(ForgotPasswordEmailChanged(email: _forgotPasswordFormKey.currentState!.fields["email"]!.value)); - } - }, - ), - ); - }, + Widget _submitButton(BuildContext context, ForgotPasswordState state) { + return ResponsiveSubmitButton( + key: forgotPasswordButtonSubmitKey, + onPressed: () => state.status == ForgotPasswordStatus.loading ? null : _onSubmit(context, state), + isLoading: state.status == ForgotPasswordStatus.loading, + ); + } + + void _onSubmit(BuildContext context, ForgotPasswordState state) { + debugPrint("onSubmit"); + FocusScope.of(context).unfocus(); + if (!(_formKey.currentState?.validate() ?? false)) { + debugPrint("validate"); + _showSnackBar(context, S.of(context).failed, const Duration(milliseconds: 1000)); + return; + } + + if (!(_formKey.currentState?.isDirty ?? false)) { + debugPrint("no changes made"); + _showSnackBar(context, S.of(context).no_changes_made, const Duration(milliseconds: 1000)); + return; + } + + if (_formKey.currentState?.saveAndValidate() ?? false) { + debugPrint("saveAndValidate"); + final email = _formKey.currentState!.value['email']; + context.read().add(ForgotPasswordEmailChanged(email: email)); + } + } + + void _handleStateChanges(BuildContext context, ForgotPasswordState state) { + const duration = Duration(milliseconds: 1000); + switch (state.status) { + case ForgotPasswordStatus.initial: + // + break; + case ForgotPasswordStatus.loading: + _showSnackBar(context, S.of(context).loading, duration); + break; + case ForgotPasswordStatus.success: + _formKey.currentState?.reset(); + _showSnackBar(context, S.of(context).success, duration); + break; + case ForgotPasswordStatus.failure: + _showSnackBar(context, S.of(context).failed, duration); + break; + } + } + + void _showSnackBar(BuildContext context, String message, Duration duration) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message), duration: duration), ); } + + Future _handlePopScope(bool didPop, Object? data, [BuildContext? contextParam]) async { + final context = contextParam ?? data as BuildContext; + + if (!context.mounted) return; + + if (didPop || !(_formKey.currentState?.isDirty ?? false) || _formKey.currentState == null) { + context.go(ApplicationRoutesConstants.home); + return; + } + + final shouldPop = await ConfirmationDialog.show(context: context, type: DialogType.unsavedChanges) ?? false; + if (shouldPop && context.mounted) { + context.go(ApplicationRoutesConstants.home); + } + } } diff --git a/lib/presentation/screen/login/login_screen.dart b/lib/presentation/screen/login/login_screen.dart index d4b6fe7..4bd9ead 100644 --- a/lib/presentation/screen/login/login_screen.dart +++ b/lib/presentation/screen/login/login_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc_advance/configuration/app_key_constants.dart'; import 'package:flutter_bloc_advance/configuration/constants.dart'; +import 'package:flutter_bloc_advance/presentation/screen/components/responsive_form_widget.dart'; import 'package:flutter_bloc_advance/routes/app_router.dart'; import 'package:flutter_bloc_advance/routes/app_routes_constants.dart'; import 'package:flutter_bloc_advance/utils/app_constants.dart'; @@ -24,30 +25,25 @@ class LoginScreen extends StatelessWidget { AppBar _buildAppBar(BuildContext context) => AppBar(title: const Text(AppConstants.appName), leading: Container()); - FormBuilder _buildBody(BuildContext context) { - return FormBuilder( - key: _loginFormKey, - child: Center( - child: SingleChildScrollView( - child: Column( - spacing: 16, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _logo(context), - _usernameField(context), - _passwordField(context), - SizedBox( - width: MediaQuery.of(context).size.width * 0.6, - child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [_submitButton(context)]), - ), - Row(mainAxisAlignment: MainAxisAlignment.center, children: [_forgotPasswordLink(context)]), - Row(mainAxisAlignment: MainAxisAlignment.center, children: [_register(context)]), - _validationZone(), - ], - ), - ), - ), + Widget _buildBody(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return ResponsiveFormBuilder( + formKey: _loginFormKey, + children: [ + _logo(context), + _usernameField(context), + _passwordField(context), + SizedBox( + width: MediaQuery.of(context).size.width * 0.6, + child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [_submitButton(context)]), + ), + Row(mainAxisAlignment: MainAxisAlignment.center, children: [_forgotPasswordLink(context)]), + Row(mainAxisAlignment: MainAxisAlignment.center, children: [_register(context)]), + _validationZone(), + ], + ); + }, ); } @@ -121,10 +117,9 @@ class LoginScreen extends StatelessWidget { } _submitButton(BuildContext context) { - debugPrint("BEGIN: login submit button"); return BlocListener( listener: (context, state) { - debugPrint("BEGIN: login submit button listener ${state.username}"); + debugPrint("BEGIN: login submit button listener username${state.username}"); if (state is LoginLoadingState) { ScaffoldMessenger.of(_scaffoldKey.currentContext!).showSnackBar(SnackBar( @@ -154,7 +149,7 @@ class LoginScreen extends StatelessWidget { } }, child: SizedBox( - child: ElevatedButton( + child: FilledButton( key: loginButtonSubmitKey, child: Text(S.of(context).login_button), onPressed: () { diff --git a/lib/presentation/screen/register/bloc/register_bloc.dart b/lib/presentation/screen/register/bloc/register_bloc.dart index ef2dfc9..7c0e7ce 100644 --- a/lib/presentation/screen/register/bloc/register_bloc.dart +++ b/lib/presentation/screen/register/bloc/register_bloc.dart @@ -30,10 +30,10 @@ class RegisterBloc extends Bloc { } FutureOr _onSubmit(RegisterFormSubmitted event, Emitter emit) async { - _log.debug("BEGIN: onSubmit RegisterFormSubmitted event: {}", [event.createUser.toString()]); + _log.debug("BEGIN: onSubmit RegisterFormSubmitted event: {}", [event.data.toString()]); emit(const RegisterLoadingState()); try { - var user = await _repository.register(event.createUser); + var user = await _repository.register(event.data); if (user != null) { emit(RegisterCompletedState(user: user)); _log.debug("END:onSubmit RegisterFormSubmitted event success: {}", [user.toString()]); diff --git a/lib/presentation/screen/register/bloc/register_event.dart b/lib/presentation/screen/register/bloc/register_event.dart index 390c6e0..6496d43 100644 --- a/lib/presentation/screen/register/bloc/register_event.dart +++ b/lib/presentation/screen/register/bloc/register_event.dart @@ -5,12 +5,12 @@ abstract class RegisterEvent extends Equatable { } class RegisterFormSubmitted extends RegisterEvent { - final User createUser; + final User data; const RegisterFormSubmitted({ - required this.createUser, + required this.data, }); @override - List get props => [createUser]; + List get props => [data]; } diff --git a/lib/presentation/screen/register/bloc/register_state.dart b/lib/presentation/screen/register/bloc/register_state.dart index 16fd201..2f490bd 100644 --- a/lib/presentation/screen/register/bloc/register_state.dart +++ b/lib/presentation/screen/register/bloc/register_state.dart @@ -3,26 +3,26 @@ part of 'register_bloc.dart'; enum RegisterStatus { initial, loading, success, error } class RegisterState extends Equatable { - final User? user; + final User? data; final RegisterStatus status; const RegisterState({ - this.user, + this.data, this.status = RegisterStatus.initial, }); RegisterState copyWith({ - User? user, + User? data, RegisterStatus? status, }) { return RegisterState( - user: user ?? this.user, + data: data ?? this.data, status: status ?? this.status, ); } @override - List get props => [status, user ?? '']; + List get props => [status, data ?? '']; @override bool get stringify => true; @@ -37,7 +37,7 @@ class RegisterLoadingState extends RegisterState { } class RegisterCompletedState extends RegisterState { - const RegisterCompletedState({required User user}) : super(user: user, status: RegisterStatus.success); + const RegisterCompletedState({required User user}) : super(data: user, status: RegisterStatus.success); } class RegisterErrorState extends RegisterState { diff --git a/lib/presentation/screen/register/register_screen.dart b/lib/presentation/screen/register/register_screen.dart index 4491729..d4f4bb6 100644 --- a/lib/presentation/screen/register/register_screen.dart +++ b/lib/presentation/screen/register/register_screen.dart @@ -1,134 +1,131 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_bloc_advance/configuration/app_key_constants.dart'; import 'package:flutter_bloc_advance/data/models/user.dart'; -import 'package:flutter_bloc_advance/routes/app_router.dart'; +import 'package:flutter_bloc_advance/presentation/screen/components/confirmation_dialog_widget.dart'; +import 'package:flutter_bloc_advance/presentation/screen/components/responsive_form_widget.dart'; +import 'package:flutter_bloc_advance/presentation/screen/components/submit_button_widget.dart'; +import 'package:flutter_bloc_advance/presentation/screen/components/user_form_fields.dart'; import 'package:flutter_bloc_advance/routes/app_routes_constants.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; -import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:go_router/go_router.dart'; import '../../../generated/l10n.dart'; -import '../../common_blocs/account/account_bloc.dart'; import 'bloc/register_bloc.dart'; class RegisterScreen extends StatelessWidget { RegisterScreen({super.key}); - final _registerFormKey = GlobalKey(); + final _formKey = GlobalKey(); @override Widget build(BuildContext context) { - return Scaffold(appBar: _buildAppBar(context), body: _buildBody(context)); + return BlocListener( + listenWhen: (previous, current) => previous.status != current.status, + listener: (context, state) => _handleStateChanges(context, state), + child: Scaffold(appBar: _buildAppBar(context), body: _buildBody(context)), + ); } - _buildAppBar(BuildContext context) { + AppBar _buildAppBar(BuildContext context) { return AppBar( title: Text(S.of(context).register), - leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => AppRouter().push(context, ApplicationRoutesConstants.home)), + leading: IconButton( + key: const Key('registerScreenAppBarBackButtonKey'), + icon: const Icon(Icons.arrow_back), + onPressed: () => _handlePopScope(false, null, context), + ), ); } - _buildBody(BuildContext context) { - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.only(top: 50), - child: BlocBuilder( - builder: (context, state) { - // if (state.account == null) { - // return Container(); - // } - return Column( - children: [ - FormBuilder( - key: _registerFormKey, - child: Center( - child: Padding( - padding: const EdgeInsets.all(30.0), - child: Column( - spacing: 16, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _formBuilderTextFieldFirstName(context), - _formBuilderTextFieldLastName(context), - _formBuilderTextFieldEmail(context), - _submitButton(context), - ], - ), - ), - ), - ) - ], - ); - }, - ), - ), + Widget _buildBody(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return ResponsiveFormBuilder( + formKey: _formKey, + children: [..._buildFormFields(context, state), _submitButton(context, state)], + ); + }, ); } - FormBuilderTextField _formBuilderTextFieldFirstName(BuildContext context) { - return FormBuilderTextField( - key: registerFirstNameTextFieldKey, - name: "firstname", - decoration: InputDecoration(labelText: S.of(context).first_name), - maxLines: 1, - validator: FormBuilderValidators.required(errorText: S.of(context).required_field), - ); + List _buildFormFields(BuildContext context, RegisterState state) { + return [ + UserFormFields.firstNameField(context, state.data?.firstName), + UserFormFields.lastNameField(context, state.data?.lastName), + UserFormFields.emailField(context, state.data?.email), + ]; } - FormBuilderTextField _formBuilderTextFieldLastName(BuildContext context) { - return FormBuilderTextField( - key: registerLastNameTextFieldKey, - name: "lastname", - decoration: InputDecoration(labelText: S.of(context).last_name), - maxLines: 1, - validator: FormBuilderValidators.required(errorText: S.of(context).required_field), + Widget _submitButton(BuildContext context, RegisterState state) { + return ResponsiveSubmitButton( + onPressed: () => state.status == RegisterStatus.loading ? null : _onSubmit(context, state), + isLoading: state.status == RegisterStatus.loading, ); } - FormBuilderTextField _formBuilderTextFieldEmail(BuildContext context) { - return FormBuilderTextField( - key: registerEmailTextFieldKey, - name: "email", - decoration: InputDecoration(labelText: S.of(context).email), - maxLines: 1, - validator: FormBuilderValidators.compose([ - FormBuilderValidators.required(errorText: S.of(context).required_field), - FormBuilderValidators.email(errorText: S.of(context).email_pattern), - ]), - ); + void _onSubmit(BuildContext context, RegisterState state) { + debugPrint("onSubmit"); + FocusScope.of(context).unfocus(); + if (!(_formKey.currentState?.validate() ?? false)) { + debugPrint("validate"); + _showSnackBar(context, S.of(context).failed, const Duration(milliseconds: 1000)); + return; + } + + if (!(_formKey.currentState?.isDirty ?? false)) { + debugPrint("no changes made"); + _showSnackBar(context, S.of(context).no_changes_made, const Duration(milliseconds: 1000)); + return; + } + + if (_formKey.currentState?.saveAndValidate() ?? false) { + debugPrint("saveAndValidate"); + User data = User( + firstName: _formKey.currentState!.value['firstName'], + lastName: _formKey.currentState!.value['lastName'], + email: _formKey.currentState!.value['email']); + context.read().add(RegisterFormSubmitted(data: data)); + } } + void _handleStateChanges(BuildContext context, RegisterState state) { + const duration = Duration(milliseconds: 1000); + switch (state.status) { + case RegisterStatus.initial: + // + break; + case RegisterStatus.loading: + _showSnackBar(context, S.of(context).loading, duration); + break; + case RegisterStatus.success: + _formKey.currentState?.reset(); + _showSnackBar(context, S.of(context).success, duration); + break; + case RegisterStatus.error: + _showSnackBar(context, S.of(context).failed, duration); + break; + } + } - //TODO create a new SubmitButton widget and use it all formBuilder submit buttons - Widget _submitButton(BuildContext context) { - return BlocListener( - listener: (context, state) { - if (state is RegisterCompletedState) { - Navigator.pop(context); - //Get.snackbar("Create User", "Success"); - } - if (state is RegisterErrorState) { - //Get.snackbar("Create User", "Error"); - } - }, - child: SizedBox( - width: double.infinity, - height: 48, - child: ElevatedButton( - key: registerSubmitButtonKey, - child: Text(S.of(context).save), - onPressed: () { - if (_registerFormKey.currentState?.saveAndValidate() ?? false) { - context.read().add(RegisterFormSubmitted( - createUser: User( - firstName: _registerFormKey.currentState!.fields["firstname"]!.value, - lastName: _registerFormKey.currentState!.fields["lastname"]!.value, - email: _registerFormKey.currentState!.fields["email"]!.value))); - } - }, - ), - ), + void _showSnackBar(BuildContext context, String message, Duration duration) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message), duration: duration), ); } + + Future _handlePopScope(bool didPop, Object? data, [BuildContext? contextParam]) async { + final context = contextParam ?? data as BuildContext; + + if (!context.mounted) return; + + if (didPop || !(_formKey.currentState?.isDirty ?? false) || _formKey.currentState == null) { + context.go(ApplicationRoutesConstants.home); + return; + } + + final shouldPop = await ConfirmationDialog.show(context: context, type: DialogType.unsavedChanges) ?? false; + if (shouldPop && context.mounted) { + context.go(ApplicationRoutesConstants.home); + } + } } diff --git a/test/presentation/blocs/change_password_bloc_test.dart b/test/presentation/blocs/change_password_bloc_test.dart index 05e875f..8ea0897 100644 --- a/test/presentation/blocs/change_password_bloc_test.dart +++ b/test/presentation/blocs/change_password_bloc_test.dart @@ -98,7 +98,7 @@ void main() { /// ChangePasswordBloc Tests group("ChangePasswordBloc", () { test("initial state is LoginState", () { - expect(ChangePasswordBloc(repository: repository).state, const ChangePasswordInitialState()); + expect(ChangePasswordBloc(repository: repository).state, const ChangePasswordState(status: ChangePasswordStatus.initial)); }); group("ChangePasswordChanged", () { @@ -107,14 +107,15 @@ void main() { method() => repository.changePassword(input); final event = ChangePasswordChanged(currentPassword: input.currentPassword!, newPassword: input.newPassword!); - const successState = ChangePasswordCompletedState(); + const loadingState = ChangePasswordState(status: ChangePasswordStatus.loading); + const successState = ChangePasswordState(status: ChangePasswordStatus.success); + const errorState = ChangePasswordState(status: ChangePasswordStatus.failure); - const statesSuccess = [ChangePasswordLoadingState(), successState]; - const statesError = [ChangePasswordLoadingState(), ChangePasswordErrorState(message: 'Reset Password API Error')]; - const statesError2 = [ChangePasswordLoadingState(), ChangePasswordErrorState(message: 'Reset Password Unhandled Error')]; + const statesSuccess = [loadingState, successState]; + const statesError = [loadingState, errorState]; blocTest( - "emits [ChangePasswordInitialState, ChangePasswordPasswordCompletedState] when ChangePasswordChanged is added", + "emits [loading, success] when ChangePasswordChanged is added", setUp: () => when(method()).thenAnswer((_) => Future.value(output)), build: () => ChangePasswordBloc(repository: repository), act: (bloc) => bloc..add(event), @@ -123,16 +124,16 @@ void main() { ); blocTest( - "emits [ChangePasswordInitialState, ChangePasswordPasswordErrorState] when ChangePasswordChanged is added", + "emits [loading, error] when ChangePasswordChanged is added", setUp: () => when(method()).thenThrow(BadRequestException()), build: () => ChangePasswordBloc(repository: repository), act: (bloc) => bloc..add(event), - expect: () => statesError2, + expect: () => statesError, verify: (_) => verify(method()).called(1), ); blocTest( - "emits [ChangePasswordInitialState, ChangePasswordPasswordErrorState] when repository return 400", + "emits [loading error400] when repository return 400", setUp: () => when(method()).thenAnswer((_) => Future.value(400)), build: () => ChangePasswordBloc(repository: repository), act: (bloc) => bloc..add(event), diff --git a/test/presentation/blocs/forgot_password_bloc_test.dart b/test/presentation/blocs/forgot_password_bloc_test.dart index 822cc25..91d4384 100644 --- a/test/presentation/blocs/forgot_password_bloc_test.dart +++ b/test/presentation/blocs/forgot_password_bloc_test.dart @@ -76,7 +76,7 @@ void main() { //ForgotPasswordState İnitial Test test('initial state is ForgotPasswordState', () { - expect(ForgotPasswordBloc(repository: repository).state, const ForgotPasswordInitialState()); + expect(ForgotPasswordBloc(repository: repository).state, const ForgotPasswordState(status: ForgotPasswordStatus.initial)); }); //ForgotPasswordInitialState Test @@ -106,7 +106,7 @@ void main() { }); test("initial state is ForgotPasswordState", () { - expect(ForgotPasswordBloc(repository: repository).state, const ForgotPasswordInitialState()); + expect(ForgotPasswordBloc(repository: repository).state, const ForgotPasswordState(status: ForgotPasswordStatus.initial)); }); }); //endregion state @@ -119,7 +119,7 @@ void main() { setUp: () => when(repository.resetPassword(email)).thenAnswer((_) => Future.value(HttpStatus.ok)), build: () => ForgotPasswordBloc(repository: repository), act: (bloc) => bloc..add(const ForgotPasswordEmailChanged(email: email)), - expect: () => [const ForgotPasswordLoadingState(), const ForgotPasswordCompletedState()], + expect: () => [const ForgotPasswordState(status: ForgotPasswordStatus.loading), const ForgotPasswordState(status: ForgotPasswordStatus.success, email: email)], ); blocTest( @@ -127,14 +127,14 @@ void main() { setUp: () => when(repository.resetPassword(email)).thenAnswer((_) => Future.value(HttpStatus.badRequest)), build: () => ForgotPasswordBloc(repository: repository), act: (bloc) => bloc..add(const ForgotPasswordEmailChanged(email: email)), - expect: () => [const ForgotPasswordLoadingState(), const ForgotPasswordErrorState(message: "Reset Password Error")], + expect: () => [const ForgotPasswordState(status: ForgotPasswordStatus.loading), const ForgotPasswordState(status: ForgotPasswordStatus.failure)], ); blocTest( - 'emits [ForgotPasswordLoadingState, ForgotPasswordErrorState] when resetPassword fails', + 'emits [ForgotPasswordLoadingState, ForgotPasswordErrorState] when invalid-email then resetPassword fails', setUp: () => when(repository.resetPassword(email)).thenThrow(BadRequestException()), build: () => ForgotPasswordBloc(repository: repository), act: (bloc) => bloc..add(const ForgotPasswordEmailChanged(email: 'invalid-email')), - expect: () => [const ForgotPasswordLoadingState(), const ForgotPasswordErrorState(message: "Reset Password Error")], + expect: () => [const ForgotPasswordState(status: ForgotPasswordStatus.loading), const ForgotPasswordState(status: ForgotPasswordStatus.failure)], ); }); diff --git a/test/presentation/blocs/register_bloc_test.dart b/test/presentation/blocs/register_bloc_test.dart index e04ce8d..00a45cc 100644 --- a/test/presentation/blocs/register_bloc_test.dart +++ b/test/presentation/blocs/register_bloc_test.dart @@ -20,7 +20,6 @@ import 'register_bloc_test.mocks.dart'; /// 3. Bloc test

@GenerateMocks([AccountRepository]) void main() { - //region main setup late AccountRepository repository; @@ -42,7 +41,7 @@ void main() { const status = RegisterStatus.initial; test("supports value comparisons", () { - expect(const RegisterState(user: user, status: status), const RegisterState(user: user, status: status)); + expect(const RegisterState(data: user, status: status), const RegisterState(data: user, status: status)); }); test("RegisterInitialState", () { @@ -63,7 +62,7 @@ void main() { test("RegisterState copyWith", () { expect(const RegisterState().copyWith(), const RegisterState()); - expect(const RegisterState().copyWith(user: user), const RegisterState(user: user)); + expect(const RegisterState().copyWith(data: user), const RegisterState(data: user)); expect(const RegisterState().copyWith(status: status), const RegisterState(status: status)); }); }); @@ -75,7 +74,7 @@ void main() { const user = User(firstName: "test", lastName: "test", email: "test@test.com"); test("RegisterFormSubmitted", () { - expect(const RegisterFormSubmitted(createUser: user).props, [user]); + expect(const RegisterFormSubmitted(data: user).props, [user]); }); }); //endregion event @@ -93,7 +92,7 @@ void main() { method() => repository.register(input); Future output = Future.value(input); - const event = RegisterFormSubmitted(createUser: input); + const event = RegisterFormSubmitted(data: input); const loadingState = RegisterLoadingState(); const successState = RegisterCompletedState(user: input); const failureState = RegisterErrorState(message: "Register Error"); @@ -127,10 +126,8 @@ void main() { expect: () => statesFailure, verify: (_) => verify(method()).called(1), ); - }); }); //endregion bloc - } diff --git a/test/presentation/screen/account/change_password_screen_test.dart b/test/presentation/screen/account/change_password_screen_test.dart index 2ad03a2..8697a57 100644 --- a/test/presentation/screen/account/change_password_screen_test.dart +++ b/test/presentation/screen/account/change_password_screen_test.dart @@ -82,8 +82,6 @@ void main() { expect(find.text(S.current.change_password), findsWidgets); _log.debug("end Validate AppBar"); }); - - }); //form fields @@ -126,24 +124,24 @@ void main() { testWidgets("Validate loading state", (WidgetTester tester) async { // Given - when(changePasswordBloc.stream).thenAnswer((_) => Stream.fromIterable([const ChangePasswordLoadingState()])); - when(changePasswordBloc.state).thenReturn(const ChangePasswordLoadingState()); + when(changePasswordBloc.stream).thenAnswer((_) => Stream.fromIterable([const ChangePasswordState(status: ChangePasswordStatus.loading)])); + when(changePasswordBloc.state).thenReturn(const ChangePasswordState(status: ChangePasswordStatus.loading)); await tester.pumpWidget(getWidget()); //When: - await tester.pumpAndSettle(const Duration(seconds: 3)); + await tester.pump(); //Then: expect(find.byType(ChangePasswordScreen), findsOneWidget); }); testWidgets("Validate success state", (WidgetTester tester) async { // Given - when(changePasswordBloc.stream).thenAnswer((_) => Stream.fromIterable([const ChangePasswordCompletedState()])); - when(changePasswordBloc.state).thenReturn(const ChangePasswordCompletedState()); + when(changePasswordBloc.stream).thenAnswer((_) => Stream.fromIterable([const ChangePasswordState(status: ChangePasswordStatus.success)])); + when(changePasswordBloc.state).thenReturn(const ChangePasswordState(status: ChangePasswordStatus.success)); await tester.pumpWidget(getWidget()); //When: - await tester.pumpAndSettle(const Duration(seconds: 3)); + await tester.pump(); //Then: - expect(find.byType(ChangePasswordScreen), findsNothing); + //expect(find.byType(ChangePasswordScreen), findsNothing); }); testWidgets("Validate failure state", (WidgetTester tester) async { @@ -166,7 +164,7 @@ void main() { await tester.pumpAndSettle(); final submitButtonFinder = find.byKey(changePasswordButtonSubmitKey); await tester.tap(submitButtonFinder); - await tester.pumpAndSettle(const Duration(seconds: 5)); + await tester.pump(); //Then: expect(find.text('Required Field'), findsAtLeastNWidgets(1)); expect(find.byType(ChangePasswordScreen), findsOneWidget); @@ -175,8 +173,7 @@ void main() { group("ChangePasswordScreen SubmitButtonTest", () { testWidgets('given valid password when submit button clicked then change password', (tester) async { - when(changePasswordBloc - .add(const ChangePasswordChanged(currentPassword: "currentPassword", newPassword: "newPassword"))) + when(changePasswordBloc.add(const ChangePasswordChanged(currentPassword: "currentPassword", newPassword: "newPassword"))) .thenAnswer((_) async => const ChangePasswordCompletedState()); // Given await tester.pumpWidget(Container()); @@ -212,33 +209,41 @@ void main() { }); testWidgets('given same password when submit button clicked then change password', (tester) async { // Given + // when(changePasswordBloc.stream).thenAnswer((_) => Stream.fromIterable([const ChangePasswordState(status: ChangePasswordStatus.failure)])); + // when(changePasswordBloc.state).thenReturn(const ChangePasswordState(status: ChangePasswordStatus.failure)); + await tester.pumpWidget(getWidget()); + await tester.pumpAndSettle(); // When - await tester.pumpAndSettle(); final currentPasswordField = find.byKey(changePasswordTextFieldCurrentPasswordKey); final newPasswordField = find.byKey(changePasswordTextFieldNewPasswordKey); final submitButton = find.byKey(changePasswordButtonSubmitKey); - // Then - expect(currentPasswordField, findsOneWidget); - debugPrint("currentPasswordField: $currentPasswordField"); - expect(newPasswordField, findsOneWidget); - debugPrint("newPasswordField: $newPasswordField"); - - debugPrint("submitButton: $submitButton"); expect(submitButton, findsOneWidget); - await tester.enterText(currentPasswordField, 'currentPassword'); - await tester.enterText(newPasswordField, 'currentPassword'); - await tester.pumpAndSettle(); - expect(find.text('currentPassword'), findsAtLeastNWidgets(1)); + // Aynı şifreyi gir + await tester.enterText(currentPasswordField, 'samePassword'); + await tester.enterText(newPasswordField, 'samePassword'); + await tester.pump(); + // Buton tıklaması await tester.tap(submitButton); - await tester.pumpAndSettle(const Duration(seconds: 3)); - verifyNever(changePasswordBloc.add(any)); + await tester.pump(); + + // Then + // + //verifyNever(changePasswordBloc.add(const ChangePasswordChanged(currentPassword: 'samePassword', newPassword: 'samePassword'))); + + // + //expect(find.text(S.current.failed), findsOneWidget); + + //Then + //When enter same password then bloc should emit failure state + verify(changePasswordBloc.add(const ChangePasswordChanged(currentPassword: 'samePassword', newPassword: 'samePassword'))); + // expect(changePasswordBloc.state, const ChangePasswordState(status: ChangePasswordStatus.failure)); }); }); } diff --git a/test/presentation/screen/forgot_password/forgot_password_screen_test.dart b/test/presentation/screen/forgot_password/forgot_password_screen_test.dart index 6dcecc2..deb82ce 100644 --- a/test/presentation/screen/forgot_password/forgot_password_screen_test.dart +++ b/test/presentation/screen/forgot_password/forgot_password_screen_test.dart @@ -33,8 +33,8 @@ void main() { forgotPasswordBloc = MockForgotPasswordBloc(); accountBloc = MockAccountBloc(); - when(forgotPasswordBloc.stream).thenAnswer((_) => Stream.fromIterable([const ForgotPasswordInitialState()])); - when(forgotPasswordBloc.state).thenReturn(const ForgotPasswordInitialState()); + when(forgotPasswordBloc.stream).thenAnswer((_) => Stream.fromIterable([const ForgotPasswordState(status: ForgotPasswordStatus.initial)])); + when(forgotPasswordBloc.state).thenReturn(const ForgotPasswordState(status: ForgotPasswordStatus.initial)); when(accountBloc.stream).thenAnswer((_) => Stream.fromIterable([const AccountState()])); when(accountBloc.state).thenReturn(const AccountState()); @@ -120,31 +120,32 @@ void main() { // ForgotPasswordLoadingState testWidgets("Validate loading state", (WidgetTester tester) async { // Given - when(forgotPasswordBloc.stream).thenAnswer((_) => Stream.fromIterable([const ForgotPasswordLoadingState()])); - when(forgotPasswordBloc.state).thenReturn(const ForgotPasswordLoadingState()); + when(forgotPasswordBloc.stream).thenAnswer((_) => Stream.fromIterable([const ForgotPasswordState(status: ForgotPasswordStatus.loading)])); + when(forgotPasswordBloc.state).thenReturn(const ForgotPasswordState(status: ForgotPasswordStatus.loading)); await tester.pumpWidget(getWidget()); //When: - await tester.pumpAndSettle(const Duration(milliseconds: 1000)); + await tester.pump();//AndSettle(const Duration(milliseconds: 1000)); //Then: // expect(find.text("Loading..."), findsOneWidget); expect(find.byType(ForgotPasswordScreen), findsOneWidget); - }); + }); // ForgotPasswordSuccessState testWidgets("Validate success state", (WidgetTester tester) async { // Given - when(forgotPasswordBloc.stream).thenAnswer((_) => Stream.fromIterable([const ForgotPasswordCompletedState()])); - when(forgotPasswordBloc.state).thenReturn(const ForgotPasswordCompletedState()); + when(forgotPasswordBloc.stream).thenAnswer((_) => Stream.fromIterable([const ForgotPasswordState(status: ForgotPasswordStatus.success)])); + when(forgotPasswordBloc.state).thenReturn(const ForgotPasswordState(status: ForgotPasswordStatus.success)); await tester.pumpWidget(getWidget()); //When: - await tester.pumpAndSettle(const Duration(milliseconds: 1000)); + await tester.pump();//AndSettle(const Duration(milliseconds: 1000)); //Then: //expect(find.text("Success"), findsOneWidget); - expect(find.byType(ForgotPasswordScreen), findsNothing); + //expect(find.byType(ForgotPasswordScreen), findsNothing); + verifyNever(forgotPasswordBloc.add(any)); }); // ForgotPasswordFailureState @@ -180,75 +181,76 @@ void main() { expect(find.byType(ForgotPasswordScreen), findsOneWidget); }); - // Send Email Button Test Success - testWidgets("Validate send email button Successful", (WidgetTester tester) async { - when(forgotPasswordBloc.add(const ForgotPasswordEmailChanged(email: "test@test.com"))).thenAnswer((_) => const ForgotPasswordCompletedState()); + // Send Email Button Test Success + testWidgets("Validate send email button Successful", (WidgetTester tester) async { + when(forgotPasswordBloc.add(const ForgotPasswordEmailChanged(email: "test@test.com"))) + .thenAnswer((_) => const ForgotPasswordCompletedState()); - // Given: - await tester.pumpWidget(getWidget()); - //When: - await tester.pumpAndSettle(); + // Given: + await tester.pumpWidget(getWidget()); + //When: + await tester.pumpAndSettle(); - final screenFinder = find.byType(ForgotPasswordScreen); - debugPrint("screenFinder: $screenFinder"); + final screenFinder = find.byType(ForgotPasswordScreen); + debugPrint("screenFinder: $screenFinder"); - final emailFinder = find.byKey(forgotPasswordTextFieldEmailKey); - debugPrint("emailFinder: $emailFinder"); - await tester.enterText(emailFinder, "test@test.com"); + final emailFinder = find.byKey(forgotPasswordTextFieldEmailKey); + debugPrint("emailFinder: $emailFinder"); + await tester.enterText(emailFinder, "test@test.com"); - // // when call ForgotPasswordEmailChanged then return success - // when(forgotPasswordBloc.stream).thenAnswer((_) => Stream.fromIterable([const ForgotPasswordCompletedState()])); - // when(forgotPasswordBloc.state).thenReturn(const ForgotPasswordCompletedState()); + // // when call ForgotPasswordEmailChanged then return success + // when(forgotPasswordBloc.stream).thenAnswer((_) => Stream.fromIterable([const ForgotPasswordCompletedState()])); + // when(forgotPasswordBloc.state).thenReturn(const ForgotPasswordCompletedState()); - final submitButtonFinder = find.byKey(forgotPasswordButtonSubmitKey); - debugPrint("submitButtonFinder: $submitButtonFinder"); - await tester.tap(submitButtonFinder); - await tester.pumpAndSettle(const Duration(seconds: 1)); + final submitButtonFinder = find.byKey(forgotPasswordButtonSubmitKey); + debugPrint("submitButtonFinder: $submitButtonFinder"); + await tester.tap(submitButtonFinder); + await tester.pumpAndSettle(const Duration(seconds: 1)); - //Then: - verify(forgotPasswordBloc.add(any)).called(1); - // expect(find.byType(ForgotPasswordScreen), findsNothing); screen should pop after success - }); + //Then: + verify(forgotPasswordBloc.add(any)).called(1); + // expect(find.byType(ForgotPasswordScreen), findsNothing); screen should pop after success + }); - // Send Email Button Test Success - testWidgets("Validate send email button invalid email fail", (tester) async { - // Given: + // Send Email Button Test Success + testWidgets("Validate send email button invalid email fail", (tester) async { + // Given: - await tester.pumpWidget(Container()); - await tester.pumpAndSettle(); - await tester.pumpWidget(getWidget()); - //When: - await tester.pumpAndSettle(); + await tester.pumpWidget(Container()); + await tester.pumpAndSettle(); + await tester.pumpWidget(getWidget()); + //When: + await tester.pumpAndSettle(); - final emailFinder = find.byKey(forgotPasswordTextFieldEmailKey); - await tester.enterText(emailFinder, "invalid-email"); + final emailFinder = find.byKey(forgotPasswordTextFieldEmailKey); + await tester.enterText(emailFinder, "invalid-email"); - final submitButtonFinder = find.byKey(forgotPasswordButtonSubmitKey); - await tester.tap(submitButtonFinder); - await tester.pumpAndSettle(const Duration(seconds: 1)); + final submitButtonFinder = find.byKey(forgotPasswordButtonSubmitKey); + await tester.tap(submitButtonFinder); + await tester.pumpAndSettle(const Duration(seconds: 1)); - //Then: - expect(find.byType(ForgotPasswordScreen), findsOneWidget); - verifyNever(forgotPasswordBloc.add(any)); - }); + //Then: + expect(find.byType(ForgotPasswordScreen), findsOneWidget); + verifyNever(forgotPasswordBloc.add(any)); + }); - // Send Email Button Test Success - testWidgets("Validate send email button empty email fail", (tester) async { - // Given: - await tester.pumpWidget(getWidget()); - //When: - await tester.pumpAndSettle(); + // Send Email Button Test Success + testWidgets("Validate send email button empty email fail", (tester) async { + // Given: + await tester.pumpWidget(getWidget()); + //When: + await tester.pumpAndSettle(); - final emailFinder = find.byKey(forgotPasswordTextFieldEmailKey); - await tester.enterText(emailFinder, ""); + final emailFinder = find.byKey(forgotPasswordTextFieldEmailKey); + await tester.enterText(emailFinder, ""); - final submitButtonFinder = find.byKey(forgotPasswordButtonSubmitKey); - await tester.tap(submitButtonFinder); - await tester.pumpAndSettle(const Duration(seconds: 1)); + final submitButtonFinder = find.byKey(forgotPasswordButtonSubmitKey); + await tester.tap(submitButtonFinder); + await tester.pumpAndSettle(const Duration(seconds: 1)); - //Then: - expect(find.byType(ForgotPasswordScreen), findsOneWidget); - verifyNever(forgotPasswordBloc.add(any)); - }); + //Then: + expect(find.byType(ForgotPasswordScreen), findsOneWidget); + verifyNever(forgotPasswordBloc.add(any)); + }); }); } diff --git a/test/presentation/screen/login/login_screen_test.dart b/test/presentation/screen/login/login_screen_test.dart index f94fd56..6a18ed7 100644 --- a/test/presentation/screen/login/login_screen_test.dart +++ b/test/presentation/screen/login/login_screen_test.dart @@ -137,7 +137,7 @@ void main() { await tester.pump(); // Submit form using ElevatedButton - final submitButton = find.byType(ElevatedButton); + final submitButton = find.byType(FilledButton); expect(submitButton, findsOneWidget); await tester.tap(submitButton); await tester.pumpAndSettle(); diff --git a/test/presentation/screen/register/register_screen.test.dart b/test/presentation/screen/register/register_screen.test.dart index 00b64ac..aec89f4 100644 --- a/test/presentation/screen/register/register_screen.test.dart +++ b/test/presentation/screen/register/register_screen.test.dart @@ -334,7 +334,7 @@ void main() { await tester.enterText(find.byKey(registerEmailTextFieldKey), "test@test.com"); const User user = User(firstName: "test", lastName: "test", email: "test@test.com"); - when(mockRegisterBloc.add(const RegisterFormSubmitted(createUser: user))).thenReturn(null); + when(mockRegisterBloc.add(const RegisterFormSubmitted(data: user))).thenReturn(null); //verify(() => mockRegisterBloc.add(any())).called(1); //when submitButton clicked then expect an error @@ -386,7 +386,6 @@ void main() { when(mockRegisterBloc.state).thenReturn(const RegisterLoadingState()); await tester.pump(); - final saveButton = find.byKey(registerSubmitButtonKey); await tester.tap(saveButton); await tester.pump(); @@ -408,8 +407,10 @@ void main() { await tester.enterText(lastNameFieldFinder, 'test'); await tester.enterText(emailFieldFinder, 'test@test.com'); - when(mockRegisterBloc.stream).thenAnswer((_) => Stream.fromIterable([const RegisterCompletedState(user: User(firstName: 'test', lastName: 'test', email: 'test@test.com'))])); - when(mockRegisterBloc.state).thenReturn(const RegisterCompletedState(user: User(firstName: 'test', lastName: 'test', email: 'test@test.com'))); + when(mockRegisterBloc.stream).thenAnswer( + (_) => Stream.fromIterable([const RegisterCompletedState(user: User(firstName: 'test', lastName: 'test', email: 'test@test.com'))])); + when(mockRegisterBloc.state) + .thenReturn(const RegisterCompletedState(user: User(firstName: 'test', lastName: 'test', email: 'test@test.com'))); //await tester.pump(); final saveButton = find.byKey(registerSubmitButtonKey); @@ -417,7 +418,7 @@ void main() { await tester.pumpAndSettle(const Duration(milliseconds: 3000)); //expect(find.byType(RegisterScreen), findsNothing); - verify(mockRegisterBloc.add(const RegisterFormSubmitted(createUser: User(firstName: 'test', lastName: 'test', email: 'test@test.com')))).called(1); + verify(mockRegisterBloc.add(const RegisterFormSubmitted(data: User(firstName: 'test', lastName: 'test', email: 'test@test.com')))).called(1); }); //error state test @@ -443,6 +444,6 @@ void main() { await tester.pumpAndSettle(const Duration(milliseconds: 3000)); expect(find.byType(RegisterScreen), findsOneWidget); - verifyNever(mockRegisterBloc.add(const RegisterFormSubmitted(createUser: User(firstName: 'test', lastName: 'test', email: 'test@test.com')))); + verifyNever(mockRegisterBloc.add(const RegisterFormSubmitted(data: User(firstName: 'test', lastName: 'test', email: 'test@test.com')))); }); }