From 8f356a4ee31b66feaa254aec1c5e8b89fb6a2eb8 Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 22 Feb 2022 16:04:12 +0700 Subject: [PATCH] TF-233 Add mailbox name validator --- .../views/text/input_decoration_builder.dart | 2 +- .../exceptions/verify_name_exception.dart | 33 ++++++++++++ .../extensions/list_validator_extension.dart | 18 +++++++ .../name_validator_string_extension.dart | 6 +++ .../composite_name_validator.dart | 21 ++++++++ .../duplicate_name_validator.dart | 28 ++++++++++ .../verification/empty_name_validator.dart | 19 +++++++ .../model/verification/new_name_request.dart | 11 ++++ .../special_character_validator.dart | 20 +++++++ .../domain/model/verification/validator.dart | 7 +++ .../domain/state/verify_name_view_state.dart | 17 ++++++ .../usecases/verify_name_interactor.dart | 17 ++++++ .../validator_failure_extension.dart | 20 +++++++ .../mailbox_creator_bindings.dart | 7 ++- .../mailbox_creator_controller.dart | 52 ++++++++++++++++++- .../presentation/mailbox_creator_view.dart | 29 +++++++---- .../app_bar_mailbox_creator_widget.dart | 2 +- ...mailbox_name_input_decoration_builder.dart | 3 ++ lib/main/localizations/app_localizations.dart | 21 ++++++++ 19 files changed, 318 insertions(+), 15 deletions(-) create mode 100644 lib/features/mailbox_creator/domain/exceptions/verify_name_exception.dart create mode 100644 lib/features/mailbox_creator/domain/extensions/list_validator_extension.dart create mode 100644 lib/features/mailbox_creator/domain/extensions/name_validator_string_extension.dart create mode 100644 lib/features/mailbox_creator/domain/model/verification/composite_name_validator.dart create mode 100644 lib/features/mailbox_creator/domain/model/verification/duplicate_name_validator.dart create mode 100644 lib/features/mailbox_creator/domain/model/verification/empty_name_validator.dart create mode 100644 lib/features/mailbox_creator/domain/model/verification/new_name_request.dart create mode 100644 lib/features/mailbox_creator/domain/model/verification/special_character_validator.dart create mode 100644 lib/features/mailbox_creator/domain/model/verification/validator.dart create mode 100644 lib/features/mailbox_creator/domain/state/verify_name_view_state.dart create mode 100644 lib/features/mailbox_creator/domain/usecases/verify_name_interactor.dart create mode 100644 lib/features/mailbox_creator/presentation/extensions/validator_failure_extension.dart diff --git a/core/lib/presentation/views/text/input_decoration_builder.dart b/core/lib/presentation/views/text/input_decoration_builder.dart index f25f5ef58..82223eb69 100644 --- a/core/lib/presentation/views/text/input_decoration_builder.dart +++ b/core/lib/presentation/views/text/input_decoration_builder.dart @@ -44,7 +44,7 @@ abstract class InputDecorationBuilder { errorBorder = newErrorBorder; } - void setErrorText(String newText) { + void setErrorText(String? newText) { errorText = newText; } diff --git a/lib/features/mailbox_creator/domain/exceptions/verify_name_exception.dart b/lib/features/mailbox_creator/domain/exceptions/verify_name_exception.dart new file mode 100644 index 000000000..484b2debf --- /dev/null +++ b/lib/features/mailbox_creator/domain/exceptions/verify_name_exception.dart @@ -0,0 +1,33 @@ + +import 'package:equatable/equatable.dart'; + +abstract class VerifyNameException extends Equatable implements Exception { + static const EmptyName = 'The name cannot be empty!'; + static const DuplicatedName = 'The name already exists!'; + static const NameContainSpecialCharacter = 'The name cannot contain special characters'; + + final String? message; + + VerifyNameException(this.message); +} + +class EmptyNameException extends VerifyNameException { + EmptyNameException() : super(VerifyNameException.EmptyName); + + @override + List get props => []; +} + +class DuplicatedNameException extends VerifyNameException { + DuplicatedNameException() : super(VerifyNameException.DuplicatedName); + + @override + List get props => []; +} + +class SpecialCharacterException extends VerifyNameException { + SpecialCharacterException() : super(VerifyNameException.NameContainSpecialCharacter); + + @override + List get props => []; +} \ No newline at end of file diff --git a/lib/features/mailbox_creator/domain/extensions/list_validator_extension.dart b/lib/features/mailbox_creator/domain/extensions/list_validator_extension.dart new file mode 100644 index 000000000..4cf40b54f --- /dev/null +++ b/lib/features/mailbox_creator/domain/extensions/list_validator_extension.dart @@ -0,0 +1,18 @@ + +import 'package:core/core.dart'; +import 'package:dartz/dartz.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/new_name_request.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/validator.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/state/verify_name_view_state.dart'; + +extension ListValidatorExtension on List { + Either getValidatorNameViewState(NewNameRequest newNameRequest) { + for (var validator in this) { + final either = validator.validate(newNameRequest); + if (either.isLeft()) { + return either; + } + } + return Right(VerifyNameViewState()); + } +} \ No newline at end of file diff --git a/lib/features/mailbox_creator/domain/extensions/name_validator_string_extension.dart b/lib/features/mailbox_creator/domain/extensions/name_validator_string_extension.dart new file mode 100644 index 000000000..a519d0b58 --- /dev/null +++ b/lib/features/mailbox_creator/domain/extensions/name_validator_string_extension.dart @@ -0,0 +1,6 @@ + +extension NameValidatorStringExtension on String { + bool hasSpecialCharactersInName() { + return RegExp(r'^([@#%^$~!%&*()+=\|{}/<>:;?`])').hasMatch(this); + } +} \ No newline at end of file diff --git a/lib/features/mailbox_creator/domain/model/verification/composite_name_validator.dart b/lib/features/mailbox_creator/domain/model/verification/composite_name_validator.dart new file mode 100644 index 000000000..eb7e678b4 --- /dev/null +++ b/lib/features/mailbox_creator/domain/model/verification/composite_name_validator.dart @@ -0,0 +1,21 @@ + +import 'package:core/core.dart'; +import 'package:dartz/dartz.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/extensions/list_validator_extension.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/new_name_request.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/validator.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/state/verify_name_view_state.dart'; + +class CompositeNameValidator extends Validator { + + final List _listValidator; + + CompositeNameValidator(this._listValidator); + + @override + Either validate(NewNameRequest newNameRequest) { + return _listValidator.isNotEmpty + ? _listValidator.getValidatorNameViewState(newNameRequest) + : Right(VerifyNameViewState()); + } +} \ No newline at end of file diff --git a/lib/features/mailbox_creator/domain/model/verification/duplicate_name_validator.dart b/lib/features/mailbox_creator/domain/model/verification/duplicate_name_validator.dart new file mode 100644 index 000000000..7df0496e0 --- /dev/null +++ b/lib/features/mailbox_creator/domain/model/verification/duplicate_name_validator.dart @@ -0,0 +1,28 @@ +import 'package:core/core.dart'; +import 'package:dartz/dartz.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/exceptions/verify_name_exception.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/new_name_request.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/validator.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/state/verify_name_view_state.dart'; + +class DuplicateNameValidator extends Validator { + final List _listName; + + DuplicateNameValidator(this._listName); + + @override + Either validate(NewNameRequest newNameRequest) { + if (newNameRequest.value != null) { + final nameExist = _listName + .map((nameItem) => nameItem.toLowerCase()) + .contains(newNameRequest.value!.toLowerCase()); + if (nameExist) { + return Left(VerifyNameFailure(DuplicatedNameException())); + } else { + return Right(VerifyNameViewState()); + } + } else { + return Right(VerifyNameViewState()); + } + } +} \ No newline at end of file diff --git a/lib/features/mailbox_creator/domain/model/verification/empty_name_validator.dart b/lib/features/mailbox_creator/domain/model/verification/empty_name_validator.dart new file mode 100644 index 000000000..6822abb39 --- /dev/null +++ b/lib/features/mailbox_creator/domain/model/verification/empty_name_validator.dart @@ -0,0 +1,19 @@ + +import 'package:core/core.dart'; +import 'package:dartz/dartz.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/exceptions/verify_name_exception.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/new_name_request.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/validator.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/state/verify_name_view_state.dart'; + +class EmptyNameValidator extends Validator { + + @override + Either validate(NewNameRequest newNameRequest) { + if (newNameRequest.value == null || newNameRequest.value!.isEmpty) { + return Left(VerifyNameFailure(EmptyNameException())); + } else { + return Right(VerifyNameViewState()); + } + } +} \ No newline at end of file diff --git a/lib/features/mailbox_creator/domain/model/verification/new_name_request.dart b/lib/features/mailbox_creator/domain/model/verification/new_name_request.dart new file mode 100644 index 000000000..6e3f9e849 --- /dev/null +++ b/lib/features/mailbox_creator/domain/model/verification/new_name_request.dart @@ -0,0 +1,11 @@ + +import 'package:equatable/equatable.dart'; + +class NewNameRequest with EquatableMixin { + final String? value; + + NewNameRequest(this.value); + + @override + List get props => [value]; +} \ No newline at end of file diff --git a/lib/features/mailbox_creator/domain/model/verification/special_character_validator.dart b/lib/features/mailbox_creator/domain/model/verification/special_character_validator.dart new file mode 100644 index 000000000..7242bfff6 --- /dev/null +++ b/lib/features/mailbox_creator/domain/model/verification/special_character_validator.dart @@ -0,0 +1,20 @@ + +import 'package:core/core.dart'; +import 'package:dartz/dartz.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/exceptions/verify_name_exception.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/extensions/name_validator_string_extension.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/new_name_request.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/validator.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/state/verify_name_view_state.dart'; + +class SpecialCharacterValidator extends Validator { + + @override + Either validate(NewNameRequest newNameRequest) { + if (newNameRequest.value != null && newNameRequest.value!.hasSpecialCharactersInName()) { + return Left(VerifyNameFailure(SpecialCharacterException())); + } else { + return Right(VerifyNameViewState()); + } + } +} \ No newline at end of file diff --git a/lib/features/mailbox_creator/domain/model/verification/validator.dart b/lib/features/mailbox_creator/domain/model/verification/validator.dart new file mode 100644 index 000000000..00cf05f0e --- /dev/null +++ b/lib/features/mailbox_creator/domain/model/verification/validator.dart @@ -0,0 +1,7 @@ + +import 'package:core/core.dart'; +import 'package:dartz/dartz.dart'; + +abstract class Validator { + Either validate(T value); +} \ No newline at end of file diff --git a/lib/features/mailbox_creator/domain/state/verify_name_view_state.dart b/lib/features/mailbox_creator/domain/state/verify_name_view_state.dart new file mode 100644 index 000000000..1522423d0 --- /dev/null +++ b/lib/features/mailbox_creator/domain/state/verify_name_view_state.dart @@ -0,0 +1,17 @@ + +import 'package:core/core.dart'; + +class VerifyNameViewState extends UIState { + VerifyNameViewState(); + + @override + List get props => []; +} + +class VerifyNameFailure extends FeatureFailure { + final exception; + + VerifyNameFailure(this.exception); + @override + List get props => [exception]; +} \ No newline at end of file diff --git a/lib/features/mailbox_creator/domain/usecases/verify_name_interactor.dart b/lib/features/mailbox_creator/domain/usecases/verify_name_interactor.dart new file mode 100644 index 000000000..9bd3871ae --- /dev/null +++ b/lib/features/mailbox_creator/domain/usecases/verify_name_interactor.dart @@ -0,0 +1,17 @@ + +import 'package:core/core.dart'; +import 'package:dartz/dartz.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/composite_name_validator.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/new_name_request.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/validator.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/state/verify_name_view_state.dart'; + +class VerifyNameInteractor { + Either execute(String? newName, List listValidator) { + try { + return CompositeNameValidator(listValidator).validate(NewNameRequest(newName)); + } catch (exception) { + return Left(VerifyNameFailure(exception)); + } + } +} \ No newline at end of file diff --git a/lib/features/mailbox_creator/presentation/extensions/validator_failure_extension.dart b/lib/features/mailbox_creator/presentation/extensions/validator_failure_extension.dart new file mode 100644 index 000000000..2d99d3474 --- /dev/null +++ b/lib/features/mailbox_creator/presentation/extensions/validator_failure_extension.dart @@ -0,0 +1,20 @@ + +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/exceptions/verify_name_exception.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/state/verify_name_view_state.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +extension ValicatorFailureExtension on VerifyNameFailure { + + String getMessage(BuildContext context) { + if (exception is EmptyNameException) { + return AppLocalizations.of(context).name_of_mailbox_is_required; + } else if (exception is DuplicatedNameException) { + return AppLocalizations.of(context).this_folder_name_is_already_taken; + } else if (exception is SpecialCharacterException) { + return AppLocalizations.of(context).mailbox_name_cannot_contain_special_characters; + } else { + return ''; + } + } +} \ No newline at end of file diff --git a/lib/features/mailbox_creator/presentation/mailbox_creator_bindings.dart b/lib/features/mailbox_creator/presentation/mailbox_creator_bindings.dart index a416a4899..107d6020b 100644 --- a/lib/features/mailbox_creator/presentation/mailbox_creator_bindings.dart +++ b/lib/features/mailbox_creator/presentation/mailbox_creator_bindings.dart @@ -1,10 +1,15 @@ import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart'; import 'package:tmail_ui_user/features/mailbox_creator/presentation/mailbox_creator_controller.dart'; class MailboxCreatorBindings extends Bindings { @override void dependencies() { - Get.lazyPut(() => MailboxCreatorController()); + Get.lazyPut(() => VerifyNameInteractor()); + + Get.lazyPut(() => MailboxCreatorController( + Get.find(), + )); } } \ No newline at end of file diff --git a/lib/features/mailbox_creator/presentation/mailbox_creator_controller.dart b/lib/features/mailbox_creator/presentation/mailbox_creator_controller.dart index 1fadca7e6..746e95a85 100644 --- a/lib/features/mailbox_creator/presentation/mailbox_creator_controller.dart +++ b/lib/features/mailbox_creator/presentation/mailbox_creator_controller.dart @@ -1,20 +1,34 @@ import 'package:core/core.dart'; import 'package:dartz/dartz.dart'; +import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/duplicate_name_validator.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/empty_name_validator.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/model/verification/special_character_validator.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/state/verify_name_view_state.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/presentation/extensions/validator_failure_extension.dart'; import 'package:tmail_ui_user/features/mailbox_creator/presentation/model/mailbox_creator_arguments.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/main/utils/app_logger.dart'; class MailboxCreatorController extends BaseController { + final VerifyNameInteractor _verifyNameInteractor; + final selectedMailbox = Rxn(); + final newNameMailbox = Rxn(); List allMailboxes = []; - MailboxCreatorController(); + MailboxCreatorController( + this._verifyNameInteractor, + ); + + void setNewNameMailbox(String newName) => newNameMailbox.value = newName; @override void onReady() { @@ -38,7 +52,41 @@ class MailboxCreatorController extends BaseController { @override void onError(error) {} - void closeMailboxCreator() { + bool isCreateMailboxValidated(BuildContext context) { + final nameValidated = getErrorInputNameString(context); + if (nameValidated?.isNotEmpty == true) { + return false; + } + return true; + } + + String? getErrorInputNameString(BuildContext context) { + final nameMailbox = newNameMailbox.value; + final listName = allMailboxes + .where((mailbox) => !(mailbox.name.isBlank == true)) + .map((mailbox) => mailbox.name!.name).toList(); + + return _verifyNameInteractor.execute( + nameMailbox, + [ + EmptyNameValidator(), + DuplicateNameValidator(listName), + SpecialCharacterValidator(), + ] + ).fold( + (failure) { + if (failure is VerifyNameFailure) { + return failure.getMessage(context); + } else { + return null; + } + }, + (success) => null + ); + } + + void closeMailboxCreator(BuildContext context) { + FocusScope.of(context).unfocus(); popBack(); } } \ No newline at end of file diff --git a/lib/features/mailbox_creator/presentation/mailbox_creator_view.dart b/lib/features/mailbox_creator/presentation/mailbox_creator_view.dart index 8fda9a0b9..209233e5a 100644 --- a/lib/features/mailbox_creator/presentation/mailbox_creator_view.dart +++ b/lib/features/mailbox_creator/presentation/mailbox_creator_view.dart @@ -19,7 +19,7 @@ class MailboxCreatorView extends GetWidget { borderOnForeground: false, color: Colors.transparent, child: GestureDetector( - onTap: () => controller.closeMailboxCreator(), + onTap: () => controller.closeMailboxCreator(context), child: ResponsiveWidget( responsiveUtils: _responsiveUtils, mobile: Container( @@ -92,22 +92,31 @@ class MailboxCreatorView extends GetWidget { Widget _buildAppBar(BuildContext context) { return Padding( - padding: EdgeInsets.only(top: 10, bottom: 4), - child: (AppBarMailboxCreatorWidget(context) - ..addOnCancelActionClick(() => controller.closeMailboxCreator()) - ..addOnCreateActionClick(() {})) - .build()); + padding: EdgeInsets.only(top: 5), + child: Obx(() => (AppBarMailboxCreatorWidget( + context, + isValidated: controller.isCreateMailboxValidated(context)) + ..addOnCancelActionClick(() => controller.closeMailboxCreator(context)) + ..addOnCreateActionClick(() => {})) + .build()) + ); } Widget _buildCreateMailboxNameInput(BuildContext context) { return Padding( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 14), - child: (TextFieldBuilder() + child: Obx(() => (TextFieldBuilder() ..key(Key('create_mailbox_name_input')) - ..onChange((value) => {}) + ..onChange((value) => controller.setNewNameMailbox(value)) ..keyboardType(TextInputType.visiblePassword) - ..textDecoration((CreateMailboxNameInputDecorationBuilder()).build())) - .build()); + ..cursorColor(AppColor.colorTextButton) + ..autoFocus(true) + ..textStyle(TextStyle(color: AppColor.colorNameEmail, fontSize: 16)) + ..textDecoration((CreateMailboxNameInputDecorationBuilder() + ..setErrorText(controller.getErrorInputNameString(context))) + .build())) + .build()) + ); } Widget _buildMailboxLocation(BuildContext context) { diff --git a/lib/features/mailbox_creator/presentation/widgets/app_bar_mailbox_creator_widget.dart b/lib/features/mailbox_creator/presentation/widgets/app_bar_mailbox_creator_widget.dart index c678bfc7c..43b30e2f6 100644 --- a/lib/features/mailbox_creator/presentation/widgets/app_bar_mailbox_creator_widget.dart +++ b/lib/features/mailbox_creator/presentation/widgets/app_bar_mailbox_creator_widget.dart @@ -71,7 +71,7 @@ class AppBarMailboxCreatorWidget { AppLocalizations.of(_context).done, style: TextStyle(fontSize: 17, color: isValidated ? AppColor.colorTextButton : AppColor.colorDisableMailboxCreateButton), ), - onPressed: () => _createActionClick?.call() + onPressed: () => isValidated ? _createActionClick?.call() : null ) ); } diff --git a/lib/features/mailbox_creator/presentation/widgets/create_mailbox_name_input_decoration_builder.dart b/lib/features/mailbox_creator/presentation/widgets/create_mailbox_name_input_decoration_builder.dart index a55741da5..89993199a 100644 --- a/lib/features/mailbox_creator/presentation/widgets/create_mailbox_name_input_decoration_builder.dart +++ b/lib/features/mailbox_creator/presentation/widgets/create_mailbox_name_input_decoration_builder.dart @@ -16,6 +16,9 @@ class CreateMailboxNameInputDecorationBuilder extends InputDecorationBuilder { errorBorder: errorBorder ?? OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(10)), borderSide: BorderSide(width: 1, color: AppColor.colorInputBorderErrorVerifyName)), + focusedErrorBorder: errorBorder ?? OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + borderSide: BorderSide(width: 1, color: AppColor.colorInputBorderErrorVerifyName)), prefixText: prefixText, labelText: labelText, floatingLabelBehavior: FloatingLabelBehavior.never, diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index c279a7b27..c8299e7e1 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -613,4 +613,25 @@ class AppLocalizations { name: 'default_mailbox', ); } + + String get name_of_mailbox_is_required { + return Intl.message( + 'Name of mailbox is required', + name: 'name_of_mailbox_is_required', + ); + } + + String get mailbox_name_cannot_contain_special_characters { + return Intl.message( + 'Mailbox name cannot contain special characters', + name: 'mailbox_name_cannot_contain_special_characters', + ); + } + + String get this_folder_name_is_already_taken { + return Intl.message( + 'This folder name is already taken', + name: 'this_folder_name_is_already_taken', + ); + } } \ No newline at end of file