From 969f172a414a2e95b55732f568d71e486f57b874 Mon Sep 17 00:00:00 2001 From: cevheri Date: Sun, 29 Dec 2024 21:40:05 +0300 Subject: [PATCH] refactor: change responsive form and submit button for web or mobile --- lib/configuration/environment.dart | 4 + lib/data/http_utils.dart | 7 +- .../common_blocs/account/account_bloc.dart | 17 ++++ .../common_blocs/account/account_event.dart | 9 ++ .../screen/account/account_screen.dart | 92 ++++++------------- .../change_password_screen.dart | 2 +- .../screen/account/account_screen_test.dart | 16 ++-- 7 files changed, 75 insertions(+), 72 deletions(-) diff --git a/lib/configuration/environment.dart b/lib/configuration/environment.dart index a4c86a6..9427320 100644 --- a/lib/configuration/environment.dart +++ b/lib/configuration/environment.dart @@ -29,6 +29,10 @@ class ProfileConstants { return _config == _Config.devConstants; } + static bool get isTest { + return _config == _Config.testConstants; + } + static get api { return _config![_Config.api]; } diff --git a/lib/data/http_utils.dart b/lib/data/http_utils.dart index a9a23f0..682c25c 100644 --- a/lib/data/http_utils.dart +++ b/lib/data/http_utils.dart @@ -274,9 +274,14 @@ class HttpUtils { static Future mockRequest(String httpMethod, String endpoint, {String? pathParams, Map? queryParams}) async { debugPrint("BEGIN: Mock Request Method start : $httpMethod $endpoint"); + if (!ProfileConstants.isTest) { + await Future.delayed(const Duration(milliseconds: 500)); + } + var headers = await HttpUtils.headers(); if (!allowedPaths.contains(endpoint)) { - debugPrint("mockRequest: Unauthorized Access. endpoint: $endpoint, httpMethod: $httpMethod, headers: $headers, allowedPaths: $allowedPaths"); + debugPrint( + "mockRequest: Unauthorized Access. endpoint: $endpoint, httpMethod: $httpMethod, headers: $headers, allowedPaths: $allowedPaths"); if (headers['Authorization'] == null) { throw UnauthorizedException("Unauthorized Access"); } diff --git a/lib/presentation/common_blocs/account/account_bloc.dart b/lib/presentation/common_blocs/account/account_bloc.dart index f966fd1..eb2d2b2 100644 --- a/lib/presentation/common_blocs/account/account_bloc.dart +++ b/lib/presentation/common_blocs/account/account_bloc.dart @@ -4,6 +4,7 @@ import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc_advance/configuration/app_logger.dart'; import 'package:flutter_bloc_advance/configuration/local_storage.dart'; +import 'package:flutter_bloc_advance/data/repository/user_repository.dart'; import '../../../data/models/user.dart'; import '../../../data/repository/account_repository.dart'; @@ -22,8 +23,11 @@ class AccountBloc extends Bloc { super(const AccountState()) { on((event, emit) {}); on(_onFetchAccount); + on(_onSubmit); } + final UserRepository _userRepository = UserRepository(); + /// Load the current account. FutureOr _onFetchAccount(AccountFetchEvent event, Emitter emit) async { _log.debug("BEGIN: getAccount bloc: _onLoad"); @@ -41,4 +45,17 @@ class AccountBloc extends Bloc { _log.error("END: getAccount bloc: _onLoad error: {}", [e.toString()]); } } + + FutureOr _onSubmit(AccountSubmitEvent event, Emitter emit) async { + _log.debug("BEGIN: onSubmit AccountSubmitEvent event: {}", [event.data.toString()]); + emit(state.copyWith(status: AccountStatus.loading)); + try { + final user = await _userRepository.update(event.data); + emit(state.copyWith(status: AccountStatus.success, data: user)); + _log.debug("END:onSubmitAccountSubmitEvent event success: {}", [user.toString()]); + } catch (e) { + emit(state.copyWith(status: AccountStatus.failure)); + _log.error("END:onSubmit AccountSubmitEvent event error: {}", [e.toString()]); + } + } } diff --git a/lib/presentation/common_blocs/account/account_event.dart b/lib/presentation/common_blocs/account/account_event.dart index 9655f84..07af53f 100644 --- a/lib/presentation/common_blocs/account/account_event.dart +++ b/lib/presentation/common_blocs/account/account_event.dart @@ -13,3 +13,12 @@ class AccountFetchEvent extends AccountEvent { @override List get props => []; } + +class AccountSubmitEvent extends AccountEvent { + final User data; + + const AccountSubmitEvent(this.data); + + @override + List get props => [data]; +} diff --git a/lib/presentation/screen/account/account_screen.dart b/lib/presentation/screen/account/account_screen.dart index 41a895e..655aa51 100644 --- a/lib/presentation/screen/account/account_screen.dart +++ b/lib/presentation/screen/account/account_screen.dart @@ -6,8 +6,9 @@ import 'package:flutter_bloc_advance/data/models/user.dart'; import 'package:flutter_bloc_advance/generated/l10n.dart'; import 'package:flutter_bloc_advance/presentation/common_blocs/account/account.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/presentation/screen/user/bloc/user.dart'; import 'package:flutter_bloc_advance/routes/app_routes_constants.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:go_router/go_router.dart'; @@ -31,7 +32,7 @@ class AccountScreen extends StatelessWidget { ); } - _buildAppBar(BuildContext context) { + AppBar _buildAppBar(BuildContext context) { return AppBar( title: Text(S.of(context).account), leading: IconButton( @@ -42,43 +43,19 @@ class AccountScreen extends StatelessWidget { ); } - _buildBody(BuildContext context) { + Widget _buildBody(BuildContext context) { return BlocBuilder( - buildWhen: (previous, current) => previous.data != current.data || previous.status != current.status, + buildWhen: (previous, current) => previous.status != current.status, //previous.data != current.data || builder: (context, state) { - if (state.data == null) { - return Center(child: Text(S.of(context).no_data)); - } - if (state.status == AccountStatus.loading) { - return const Center(child: CircularProgressIndicator()); - } - - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Center( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 700), - child: FormBuilder( - key: _formKey, - child: Column( - spacing: 16, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - ..._buildFormFields(context, state), - _submitButton(context, state), - ], - ), - ), - ), - ), - ), + return ResponsiveFormBuilder( + formKey: _formKey, + children: [..._buildFormFields(context, state), _submitButton(context, state)], ); }, ); } - _buildFormFields(BuildContext context, AccountState state) { + List _buildFormFields(BuildContext context, AccountState state) { return [ UserFormFields.usernameField(context, state.data?.login, enabled: false), UserFormFields.firstNameField(context, state.data?.firstName), @@ -88,20 +65,11 @@ class AccountScreen extends StatelessWidget { ]; } - /// Submit button - /// This button is used to submit the form. - /// - /// [context] BuildContext current context - /// [state] AccountState state of the bloc - /// return ElevatedButton Widget _submitButton(BuildContext context, AccountState state) { - return SizedBox( - width: double.infinity, - height: 48, - child: ElevatedButton( - onPressed: () => state.status == AccountStatus.loading ? null : _onSubmit(context, state), - child: Text(S.of(context).save), - ), + debugPrint('state.status: ${state.status}'); + return ResponsiveSubmitButton( + onPressed: () => state.status == AccountStatus.loading ? null : _onSubmit(context, state), + isLoading: state.status == AccountStatus.loading, ); } @@ -115,30 +83,31 @@ class AccountScreen extends StatelessWidget { if (_formKey.currentState?.saveAndValidate() ?? false) { final formData = _formKey.currentState!.value; - final user = _createUserFromFormData(formData, state.data?.id); - - 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 + 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 } } - User _createUserFromFormData(Map formData, String? userId) { - return User( + User _createUserFromData(Map formData, String? userId) => User( id: userId, login: formData['login'], firstName: formData['firstName'], lastName: formData['lastName'], email: formData['email'], - activated: formData['activated'], - ); - } + activated: formData['activated']); void _handleStateChanges(BuildContext context, AccountState state) { const duration = Duration(milliseconds: 1000); @@ -179,5 +148,4 @@ class AccountScreen extends StatelessWidget { context.go(ApplicationRoutesConstants.home); } } - } diff --git a/lib/presentation/screen/change_password/change_password_screen.dart b/lib/presentation/screen/change_password/change_password_screen.dart index c8222bd..496cdd3 100644 --- a/lib/presentation/screen/change_password/change_password_screen.dart +++ b/lib/presentation/screen/change_password/change_password_screen.dart @@ -111,7 +111,7 @@ class ChangePasswordScreen extends StatelessWidget { }, child: FilledButton( key: changePasswordButtonSubmitKey, - child: Text(S.of(context).change_password), + child: Text(S.of(context).save), onPressed: () { //without blocConsumer access to bloc directly final currentState = context.read().state; diff --git a/test/presentation/screen/account/account_screen_test.dart b/test/presentation/screen/account/account_screen_test.dart index 4d4bfef..2aff6e5 100644 --- a/test/presentation/screen/account/account_screen_test.dart +++ b/test/presentation/screen/account/account_screen_test.dart @@ -167,14 +167,14 @@ void main() { expect(find.byType(CircularProgressIndicator), findsNothing); }); - testWidgets('Should display no data message when data is null', (tester) async { - when(mockAccountBloc.state).thenReturn(const AccountState(status: AccountStatus.success)); - - await tester.pumpWidget(buildTestableWidget()); - await tester.pumpAndSettle(); - - expect(find.text(S.current.no_data), findsOneWidget); - }); + // testWidgets('Should display no data message when data is null', (tester) async { + // when(mockAccountBloc.state).thenReturn(const AccountState(status: AccountStatus.success)); + // + // await tester.pumpWidget(buildTestableWidget()); + // await tester.pumpAndSettle(); + // + // expect(find.text(S.current.no_data), findsOneWidget); + // }); }); group('AccountScreen Form Operations', () {