diff --git a/core/lib/core.dart b/core/lib/core.dart index 14294087b..1229f66ed 100644 --- a/core/lib/core.dart +++ b/core/lib/core.dart @@ -64,6 +64,7 @@ export 'presentation/views/html_viewer/html_content_viewer_on_web_widget.dart'; export 'presentation/views/html_viewer/html_viewer_controller_for_web.dart'; export 'presentation/views/floating_button/scrolling_floating_button_animated.dart'; export 'presentation/views/bottom_popup/cupertino_action_sheet_action_builder.dart'; +export 'presentation/views/bottom_popup/cupertino_action_sheet_no_icon_builder.dart'; export 'presentation/views/bottom_popup/cupertino_action_sheet_builder.dart'; export 'presentation/views/bottom_popup/confirmation_dialog_action_sheet_builder.dart'; export 'presentation/views/modal_sheets/edit_text_modal_sheet_builder.dart'; diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index 1eec437fe..a6f85e759 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -131,6 +131,8 @@ extension AppColor on Color { static const colorSelectedRichTextButton = Color(0x99EBEDF0); static const colorShadowDestinationPicker = Color(0x33000000); static const colorBackgroundSnackBar = Color(0xFF343438); + static const colorBackgroundFieldConditionRulesFilter = Color(0xFFF2F3F5); + static const colorDividerRuleFilter = Color(0xFFE7E8EC); static const mapGradientColor = [ [Color(0xFF21D4FD), Color(0xFFB721FF)], diff --git a/core/lib/presentation/views/bottom_popup/cupertino_action_sheet_no_icon_builder.dart b/core/lib/presentation/views/bottom_popup/cupertino_action_sheet_no_icon_builder.dart new file mode 100644 index 000000000..3ef1fd22d --- /dev/null +++ b/core/lib/presentation/views/bottom_popup/cupertino_action_sheet_no_icon_builder.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +typedef OnCupertinoActionSheetNoIconActionClick = void Function(T data); + +abstract class CupertinoActionSheetNoIconBuilder { + @protected + final Key? key; + @protected + final String actionName; + @protected + OnCupertinoActionSheetNoIconActionClick? onCupertinoActionSheetActionClick; + + CupertinoActionSheetNoIconBuilder( + this.actionName, + { + this.key, + } + ); + + void onActionClick(OnCupertinoActionSheetNoIconActionClick onCupertinoActionSheetActionClick) { + this.onCupertinoActionSheetActionClick = onCupertinoActionSheetActionClick; + } + + TextStyle actionTextStyle({TextStyle? textStyle}) { + return textStyle ?? TextStyle(fontSize: 17, color: Colors.black); + } + + Widget build(); +} \ No newline at end of file diff --git a/lib/features/base/widget/drop_down_button_widget.dart b/lib/features/base/widget/drop_down_button_widget.dart index 47de5d9a3..176d3e1ea 100644 --- a/lib/features/base/widget/drop_down_button_widget.dart +++ b/lib/features/base/widget/drop_down_button_widget.dart @@ -9,8 +9,11 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/identities/identity.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; +import 'package:rule_filter/rule_filter/rule_condition.dart' as rule_condition; import 'package:tmail_ui_user/features/composer/presentation/model/font_name_type.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/language_and_region/extensions/locale_extension.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/extensions/rule_condition_extensions.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/email_rule_filter_action.dart'; class DropDownButtonWidget extends StatelessWidget { @@ -28,6 +31,7 @@ class DropDownButtonWidget extends StatelessWidget { final Color? colorButton; final String tooltip; final double? dropdownWidth; + final double? dropdownMaxHeight; const DropDownButtonWidget({ Key? key, @@ -43,6 +47,7 @@ class DropDownButtonWidget extends StatelessWidget { this.opacity = 1.0, this.iconArrowDown, this.dropdownWidth, + this.dropdownMaxHeight, this.colorButton = Colors.white, this.tooltip = '', }) : super(key: key); @@ -105,7 +110,7 @@ class DropDownButtonWidget extends StatelessWidget { borderRadius: BorderRadius.circular(radiusButton), border: Border.all( color: AppColor.colorInputBorderCreateMailbox, - width: 0.5, + width: 1, ), color: colorButton ?? AppColor.colorInputBackgroundCreateMailbox, ), @@ -132,7 +137,7 @@ class DropDownButtonWidget extends StatelessWidget { borderRadius: BorderRadius.circular(radiusButton), border: Border.all( color: AppColor.colorInputBorderCreateMailbox, - width: 0.5, + width: 1, ), color: colorButton ?? AppColor.colorInputBackgroundCreateMailbox, ), @@ -142,7 +147,7 @@ class DropDownButtonWidget extends StatelessWidget { ? Colors.white : Colors.black12, itemPadding: const EdgeInsets.symmetric(horizontal: 12), - dropdownMaxHeight: 200, + dropdownMaxHeight: dropdownMaxHeight ?? 200, dropdownDecoration: BoxDecoration( borderRadius: BorderRadius.circular(radiusButton), color: Colors.white, @@ -171,6 +176,15 @@ class DropDownButtonWidget extends StatelessWidget { if (item is enough_html_editor.SafeFont) { return item.name; } + if (item is rule_condition.Field) { + return item.getTitle(context); + } + if (item is rule_condition.Comparator) { + return item.getTitle(context); + } + if (item is EmailRuleFilterAction) { + return item.getTitle(context); + } return ''; } } \ No newline at end of file diff --git a/lib/features/destination_picker/presentation/widgets/app_bar_destination_picker_builder.dart b/lib/features/destination_picker/presentation/widgets/app_bar_destination_picker_builder.dart index c3ba06eae..cf502ebc3 100644 --- a/lib/features/destination_picker/presentation/widgets/app_bar_destination_picker_builder.dart +++ b/lib/features/destination_picker/presentation/widgets/app_bar_destination_picker_builder.dart @@ -54,6 +54,7 @@ class AppBarDestinationPickerBuilder extends StatelessWidget { Widget _buildCancelButton(BuildContext context) { if (_mailboxAction == MailboxActions.moveEmail || _mailboxAction == MailboxActions.move || + _mailboxAction == MailboxActions.selectFromRuleFilter || _mailboxAction == MailboxActions.select) { return Padding( padding: const EdgeInsets.only(right: 12), diff --git a/lib/features/mailbox/presentation/model/mailbox_actions.dart b/lib/features/mailbox/presentation/model/mailbox_actions.dart index 1b52f67a0..2f64135cd 100644 --- a/lib/features/mailbox/presentation/model/mailbox_actions.dart +++ b/lib/features/mailbox/presentation/model/mailbox_actions.dart @@ -11,6 +11,7 @@ enum MailboxActions { rename, move, markAsRead, + selectFromRuleFilter, } extension MailboxActionsExtension on MailboxActions { @@ -23,6 +24,7 @@ extension MailboxActionsExtension on MailboxActions { case MailboxActions.move: return AppLocalizations.of(context).moveTo; case MailboxActions.select: + case MailboxActions.selectFromRuleFilter: return AppLocalizations.of(context).selectMailbox; default: return ''; @@ -82,6 +84,7 @@ extension MailboxActionsExtension on MailboxActions { case MailboxActions.moveEmail: case MailboxActions.move: case MailboxActions.select: + case MailboxActions.selectFromRuleFilter: return true; default: return false; @@ -104,6 +107,7 @@ extension MailboxActionsExtension on MailboxActions { case MailboxActions.moveEmail: case MailboxActions.move: case MailboxActions.select: + case MailboxActions.selectFromRuleFilter: return false; default: return true; diff --git a/lib/features/mailbox_creator/presentation/extensions/validator_failure_extension.dart b/lib/features/mailbox_creator/presentation/extensions/validator_failure_extension.dart index ff79e2b21..efdd69196 100644 --- a/lib/features/mailbox_creator/presentation/extensions/validator_failure_extension.dart +++ b/lib/features/mailbox_creator/presentation/extensions/validator_failure_extension.dart @@ -34,4 +34,12 @@ extension ValicatorFailureExtension on VerifyNameFailure { return ''; } } + + String getMessageRulesFilter(BuildContext context) { + if (exception is EmptyNameException) { + return AppLocalizations.of(context).this_field_cannot_be_blank; + } else { + return ''; + } + } } \ No newline at end of file diff --git a/lib/features/rules_filter_creator/presentation/extensions/rule_condition_extensions.dart b/lib/features/rules_filter_creator/presentation/extensions/rule_condition_extensions.dart new file mode 100644 index 000000000..4161d68d1 --- /dev/null +++ b/lib/features/rules_filter_creator/presentation/extensions/rule_condition_extensions.dart @@ -0,0 +1,38 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:rule_filter/rule_filter/rule_condition.dart' as rule_condition; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +extension RuleConditionFieldExtension on rule_condition.Field { + + String getTitle(BuildContext context) { + switch(this) { + case rule_condition.Field.from: + return AppLocalizations.of(context).ruleFilterAddressFromField; + case rule_condition.Field.to: + return AppLocalizations.of(context).ruleFilterAddressToField; + case rule_condition.Field.cc: + return AppLocalizations.of(context).ruleFilterAddressCcField; + case rule_condition.Field.recipient: + return AppLocalizations.of(context).recipient; + case rule_condition.Field.subject: + return AppLocalizations.of(context).subject; + } + } +} + +extension RuleConditionComparatorExtension on rule_condition.Comparator { + + String getTitle(BuildContext context) { + switch(this) { + case rule_condition.Comparator.contains: + return AppLocalizations.of(context).contains; + case rule_condition.Comparator.notContains: + return AppLocalizations.of(context).notContains; + case rule_condition.Comparator.exactlyEquals: + return AppLocalizations.of(context).exactlyEquals; + case rule_condition.Comparator.notExactlyEquals: + return AppLocalizations.of(context).notExactlyEquals; + } + } +} \ No newline at end of file diff --git a/lib/features/rules_filter_creator/presentation/model/creator_action_type.dart b/lib/features/rules_filter_creator/presentation/model/creator_action_type.dart new file mode 100644 index 000000000..689393fff --- /dev/null +++ b/lib/features/rules_filter_creator/presentation/model/creator_action_type.dart @@ -0,0 +1,22 @@ + +import 'package:core/presentation/extensions/capitalize_extension.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +enum CreatorActionType { + create; + + String getTitle(BuildContext context) { + switch(this) { + case CreatorActionType.create: + return AppLocalizations.of(context).createNewRule.inCaps; + } + } + + String getActionName(BuildContext context) { + switch(this) { + case CreatorActionType.create: + return AppLocalizations.of(context).create.inCaps; + } + } +} \ No newline at end of file diff --git a/lib/features/rules_filter_creator/presentation/model/email_rule_filter_action.dart b/lib/features/rules_filter_creator/presentation/model/email_rule_filter_action.dart new file mode 100644 index 000000000..80a7d384a --- /dev/null +++ b/lib/features/rules_filter_creator/presentation/model/email_rule_filter_action.dart @@ -0,0 +1,15 @@ + + +import 'package:flutter/cupertino.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +enum EmailRuleFilterAction { + moveMessage; + + String getTitle(BuildContext context) { + switch(this) { + case EmailRuleFilterAction.moveMessage: + return AppLocalizations.of(context).moveMessage; + } + } +} \ No newline at end of file diff --git a/lib/features/rules_filter_creator/presentation/model/rules_filter_creator_arguments.dart b/lib/features/rules_filter_creator/presentation/model/rules_filter_creator_arguments.dart new file mode 100644 index 000000000..4b2acd1d7 --- /dev/null +++ b/lib/features/rules_filter_creator/presentation/model/rules_filter_creator_arguments.dart @@ -0,0 +1,16 @@ + +import 'package:equatable/equatable.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/creator_action_type.dart'; + +class RulesFilterCreatorArguments with EquatableMixin { + final AccountId accountId; + final CreatorActionType actionType; + + RulesFilterCreatorArguments(this.accountId, { + this.actionType = CreatorActionType.create + }); + + @override + List get props => [accountId, actionType]; +} \ No newline at end of file diff --git a/lib/features/rules_filter_creator/presentation/rules_filter_creator_bindings.dart b/lib/features/rules_filter_creator/presentation/rules_filter_creator_bindings.dart new file mode 100644 index 000000000..5283cf6a7 --- /dev/null +++ b/lib/features/rules_filter_creator/presentation/rules_filter_creator_bindings.dart @@ -0,0 +1,36 @@ +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/base/base_bindings.dart'; +import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/rules_filter_creator_controller.dart'; + +class RulesFilterCreatorBindings extends BaseBindings { + + @override + void bindingsController() { + Get.lazyPut(() => RulesFilterCreatorController( + Get.find() + )); + } + + @override + void bindingsDataSource() { + } + + @override + void bindingsDataSourceImpl() { + + } + + @override + void bindingsInteractor() { + Get.lazyPut(() => VerifyNameInteractor()); + } + + @override + void bindingsRepository() { + } + + @override + void bindingsRepositoryImpl() { + } +} \ No newline at end of file diff --git a/lib/features/rules_filter_creator/presentation/rules_filter_creator_controller.dart b/lib/features/rules_filter_creator/presentation/rules_filter_creator_controller.dart new file mode 100644 index 000000000..b71db6916 --- /dev/null +++ b/lib/features/rules_filter_creator/presentation/rules_filter_creator_controller.dart @@ -0,0 +1,191 @@ + +import 'package:core/utils/app_logger.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:get/get.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:model/model.dart'; +import 'package:rule_filter/rule_filter/rule_action.dart'; +import 'package:rule_filter/rule_filter/rule_append_in.dart'; +import 'package:rule_filter/rule_filter/rule_condition.dart' as rule_condition; +import 'package:rule_filter/rule_filter/tmail_rule.dart'; +import 'package:tmail_ui_user/features/base/base_controller.dart'; +import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.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/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/rules_filter_creator/presentation/model/creator_action_type.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/email_rule_filter_action.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/rules_filter_creator_arguments.dart'; +import 'package:tmail_ui_user/main/routes/app_routes.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; + +class RulesFilterCreatorController extends BaseController { + + final VerifyNameInteractor _verifyNameInteractor; + + final actionType = CreatorActionType.create.obs; + final errorRuleName = Rxn(); + final errorConditionValue = Rxn(); + final ruleConditionFieldSelected = Rxn(); + final ruleConditionComparatorSelected = Rxn(); + final emailRuleFilterActionSelected = Rxn(); + final mailboxSelected = Rxn(); + final isCreateRuleFilterValid = RxBool(false); + + final inputRuleNameController = TextEditingController(); + final inputConditionValueController = TextEditingController(); + + AccountId? _accountId; + String? _newRuleName; + String? _newConditionValue; + + RulesFilterCreatorController(this._verifyNameInteractor); + + @override + void onReady() { + _getArguments(); + _setUpDefaultValueRuleFilter(); + super.onReady(); + } + + @override + void onClose() { + inputRuleNameController.dispose(); + inputConditionValueController.dispose(); + super.onClose(); + } + + @override + void onDone() {} + + @override + void onError(error) {} + + void _getArguments() { + final arguments = Get.arguments; + if (arguments is RulesFilterCreatorArguments) { + _accountId = arguments.accountId; + actionType.value = arguments.actionType; + } + } + + void _setUpDefaultValueRuleFilter() { + if (actionType.value == CreatorActionType.create) { + ruleConditionFieldSelected.value = rule_condition.Field.from; + ruleConditionComparatorSelected.value = rule_condition.Comparator.exactlyEquals; + emailRuleFilterActionSelected.value = EmailRuleFilterAction.moveMessage; + } + + _updateStateCreatorButton(); + } + + void updateRuleName(BuildContext context, String? value) { + _newRuleName = value; + errorRuleName.value = _getErrorStringByInputValue(context, _newRuleName); + _updateStateCreatorButton(); + } + + void updateConditionValue(BuildContext context, String? value) { + _newConditionValue = value; + errorConditionValue.value = _getErrorStringByInputValue(context, _newConditionValue); + _updateStateCreatorButton(); + } + + String? _getErrorStringByInputValue(BuildContext context, String? inputValue) { + return _verifyNameInteractor.execute(inputValue, [EmptyNameValidator()]).fold( + (failure) { + if (failure is VerifyNameFailure) { + return failure.getMessageRulesFilter(context); + } else { + return null; + } + }, + (success) => null + ); + } + + void selectRuleConditionField(rule_condition.Field? newField) { + ruleConditionFieldSelected.value = newField; + _updateStateCreatorButton(); + } + + void selectRuleConditionComparator(rule_condition.Comparator? newComparator) { + ruleConditionComparatorSelected.value = newComparator; + _updateStateCreatorButton(); + } + + void selectEmailRuleFilterAction(EmailRuleFilterAction? newAction) { + emailRuleFilterActionSelected.value = newAction; + _updateStateCreatorButton(); + } + + void selectMailbox() async { + final destinationMailbox = await push( + AppRoutes.DESTINATION_PICKER, + arguments: DestinationPickerArguments( + _accountId!, + MailboxActions.selectForRuleAction)); + + if (destinationMailbox is PresentationMailbox) { + mailboxSelected.value = destinationMailbox; + _updateStateCreatorButton(); + } + } + + void _updateStateCreatorButton() { + isCreateRuleFilterValid.value = _newRuleName?.trim().isNotEmpty == true && + _newConditionValue?.trim().isNotEmpty == true && + mailboxSelected.value != null && + ruleConditionFieldSelected.value != null && + emailRuleFilterActionSelected.value != null && + ruleConditionComparatorSelected.value != null; + } + + void createNewRuleFilter(BuildContext context) async { + FocusScope.of(context).unfocus(); + + final errorName = _getErrorStringByInputValue(context, _newRuleName); + if (errorName?.isNotEmpty == true) { + log('RulesFilterCreatorController::createNewRuleFilter(): errorName: $errorName'); + errorRuleName.value = errorName; + return; + } + + final errorCondition = _getErrorStringByInputValue(context, _newConditionValue); + if (errorCondition?.isNotEmpty == true) { + log('RulesFilterCreatorController::createNewRuleFilter(): errorCondition: $errorCondition'); + errorConditionValue.value = errorName; + return; + } + + final newTMailRule = TMailRule( + name: _newRuleName!, + action: RuleAction( + appendIn: RuleAppendIn( + mailboxIds: [mailboxSelected.value!.id] + ) + ), + condition: rule_condition.RuleCondition( + field: ruleConditionFieldSelected.value!, + comparator: ruleConditionComparatorSelected.value!, + value: _newConditionValue! + )); + + log('RulesFilterCreatorController::newTMailRule(): $newTMailRule'); + + _clearAll(); + popBack(result: newTMailRule); + } + + void _clearAll() { + inputRuleNameController.clear(); + inputConditionValueController.clear(); + } + + void closeView(BuildContext context) { + FocusScope.of(context).unfocus(); + popBack(); + } +} \ No newline at end of file diff --git a/lib/features/rules_filter_creator/presentation/rules_filter_creator_view.dart b/lib/features/rules_filter_creator/presentation/rules_filter_creator_view.dart new file mode 100644 index 000000000..70b1a4d95 --- /dev/null +++ b/lib/features/rules_filter_creator/presentation/rules_filter_creator_view.dart @@ -0,0 +1,723 @@ +import 'package:core/core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; +import 'package:model/model.dart'; +import 'package:rule_filter/rule_filter/rule_condition.dart' as rule_condition; +import 'package:tmail_ui_user/features/base/widget/drop_down_button_widget.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/extensions/rule_condition_extensions.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/email_rule_filter_action.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/rules_filter_creator_controller.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/widgets/rule_action_bottom_sheet_action_tile_builder.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/widgets/rule_condition_comparator_bottom_sheet_action_tile_builder.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/widgets/rule_condition_field_bottom_sheet_action_tile_builder.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/widgets/rule_filter_button_field.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/widgets/rules_filter_input_field_builder.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; + +class RuleFilterCreatorView extends GetWidget { + + final _imagePaths = Get.find(); + final _responsiveUtils = Get.find(); + + RuleFilterCreatorView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ResponsiveWidget( + responsiveUtils: _responsiveUtils, + mobile: Scaffold( + backgroundColor: Colors.black38, + body: GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: SafeArea( + child: Align( + alignment: Alignment.bottomCenter, + child: Card( + color: Colors.transparent, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + )), + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + )), + width: double.infinity, + height: _responsiveUtils.getSizeScreenHeight(context) - 70, + child: ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16)), + child: _buildRulesFilterFormOnMobile(context) + ) + ) + ) + ), + ), + ) + ), + tablet: Scaffold( + backgroundColor: Colors.black38, + body: GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: Align(alignment: Alignment.bottomCenter, child: Card( + color: Colors.transparent, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + )), + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + )), + width: double.infinity, + height: _responsiveUtils.getSizeScreenHeight(context) * 0.7, + child: ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16)), + child: _buildRulesFilterFormOnTablet(context) + ) + ) + )), + ) + ), + desktop: Scaffold( + backgroundColor: Colors.black38, + body: GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: Align(alignment: Alignment.center, child: Card( + color: Colors.transparent, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(16))), + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(16))), + width: _responsiveUtils.getSizeScreenWidth(context) * 0.6, + height: _responsiveUtils.getSizeScreenHeight(context) * 0.7, + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(16)), + child: _buildRulesFilterFormOnDesktop(context) + ) + ) + ) + ), + ) + ) + ); + } + + Widget _buildRulesFilterFormOnDesktop(BuildContext context) { + return Stack( + children: [ + Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Container( + padding: const EdgeInsets.only(top: 16), + alignment: Alignment.center, + child: Obx(() => Text( + controller.actionType.value.getTitle(context), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + color: Colors.black)))), + Expanded(child: SingleChildScrollView( + physics: const ClampingScrollPhysics(), + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Obx(() => RulesFilterInputField( + hintText: AppLocalizations.of(context).rulesNameHintTextInput, + errorText: controller.errorRuleName.value, + editingController: controller.inputRuleNameController, + onChangeAction: (value) => controller.updateRuleName(context, value),)), + const SizedBox(height: 24), + Text(AppLocalizations.of(context).conditionTitleRulesFilter, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + maxLines: 1, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + color: Colors.black)), + const SizedBox(height: 24), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.colorBackgroundFieldConditionRulesFilter, + borderRadius: BorderRadius.circular(12)), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: Obx(() => DropDownButtonWidget( + items: rule_condition.Field.values, + itemSelected: controller.ruleConditionFieldSelected.value, + dropdownMaxHeight: 250, + onChanged: (newField) => + controller.selectRuleConditionField(newField), + supportSelectionIcon: true))), + Container( + width: 220, + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Obx(() => DropDownButtonWidget( + items: rule_condition.Comparator.values, + itemSelected: controller.ruleConditionComparatorSelected.value, + onChanged: (newComparator) => + controller.selectRuleConditionComparator(newComparator), + supportSelectionIcon: true))), + Expanded(child: Obx(() => RulesFilterInputField( + hintText: AppLocalizations.of(context).conditionValueHintTextInput, + errorText: controller.errorConditionValue.value, + editingController: controller.inputConditionValueController, + onChangeAction: (value) => + controller.updateConditionValue(context, value)))) + ] + ) + ), + const SizedBox(height: 24), + Text(AppLocalizations.of(context).actionTitleRulesFilter, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + maxLines: 1, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + color: Colors.black)), + const SizedBox(height: 24), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.colorBackgroundFieldConditionRulesFilter, + borderRadius: BorderRadius.circular(12)), + child: Row(children: [ + Expanded(child: Obx(() => DropDownButtonWidget( + items: EmailRuleFilterAction.values, + itemSelected: controller.emailRuleFilterActionSelected.value, + onChanged: (newAction) => + controller.selectEmailRuleFilterAction(newAction), + supportSelectionIcon: true))), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text( + AppLocalizations.of(context).toMailbox, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + maxLines: 1, + style: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 16, + color: Colors.black)), + ), + Expanded(child: Obx(() => + RuleFilterButtonField( + value: controller.mailboxSelected.value, + tapActionCallback: (value) => controller.selectMailbox()))), + ]) + ), + ] + ), + ), + )), + Container( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24), + alignment: Alignment.centerRight, + color: Colors.white, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + buildTextButton( + AppLocalizations.of(context).cancel, + textStyle: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 17, + color: AppColor.colorTextButton), + backgroundColor: AppColor.emailAddressChipColor, + width: 128, + height: 44, + radius: 10, + onTap: () => controller.closeView(context)), + const SizedBox(width: 12), + Obx(() => buildTextButton( + controller.actionType.value.getActionName(context), + width: 128, + height: 44, + backgroundColor: AppColor.colorTextButton + .withOpacity(controller.isCreateRuleFilterValid.value + ? 1.0 + : 0.4), + radius: 10, + onTap: () { + if (controller.isCreateRuleFilterValid.value) { + controller.createNewRuleFilter(context); + } + })), + ] + ), + ) + ]), + Positioned(top: 8, right: 8, + child: buildIconWeb( + icon: SvgPicture.asset( + _imagePaths.icCloseMailbox, + fit: BoxFit.fill), + tooltip: AppLocalizations.of(context).close, + onTap: () => controller.closeView(context))) + ] + ); + } + + Widget _buildRulesFilterFormOnTablet(BuildContext context) { + return Stack( + children: [ + Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Container( + padding: const EdgeInsets.only(top: 16), + alignment: Alignment.center, + child: Obx(() => Text( + controller.actionType.value.getTitle(context), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + color: Colors.black)))), + Expanded(child: SingleChildScrollView( + physics: const ClampingScrollPhysics(), + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Obx(() => RulesFilterInputField( + hintText: AppLocalizations.of(context).rulesNameHintTextInput, + errorText: controller.errorRuleName.value, + editingController: controller.inputRuleNameController, + onChangeAction: (value) => controller.updateRuleName(context, value),)), + const SizedBox(height: 24), + Text(AppLocalizations.of(context).conditionTitleRulesFilter, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + maxLines: 1, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + color: Colors.black)), + const SizedBox(height: 24), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.colorBackgroundFieldConditionRulesFilter, + borderRadius: BorderRadius.circular(12)), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: Obx(() => DropDownButtonWidget( + items: rule_condition.Field.values, + itemSelected: controller.ruleConditionFieldSelected.value, + dropdownMaxHeight: 250, + onChanged: (newField) => + controller.selectRuleConditionField(newField), + supportSelectionIcon: true))), + Container( + width: 220, + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Obx(() => DropDownButtonWidget( + items: rule_condition.Comparator.values, + itemSelected: controller.ruleConditionComparatorSelected.value, + onChanged: (newComparator) => + controller.selectRuleConditionComparator(newComparator), + supportSelectionIcon: true))), + Expanded(child: Obx(() => RulesFilterInputField( + hintText: AppLocalizations.of(context).conditionValueHintTextInput, + errorText: controller.errorConditionValue.value, + editingController: controller.inputConditionValueController, + onChangeAction: (value) => + controller.updateConditionValue(context, value)))) + ] + ) + ), + const SizedBox(height: 24), + Text(AppLocalizations.of(context).actionTitleRulesFilter, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + maxLines: 1, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + color: Colors.black)), + const SizedBox(height: 24), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.colorBackgroundFieldConditionRulesFilter, + borderRadius: BorderRadius.circular(12)), + child: Row(children: [ + Expanded(child: Obx(() => DropDownButtonWidget( + items: EmailRuleFilterAction.values, + itemSelected: controller.emailRuleFilterActionSelected.value, + onChanged: (newAction) => + controller.selectEmailRuleFilterAction(newAction), + supportSelectionIcon: true))), + Container( + alignment: Alignment.centerLeft, + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text( + AppLocalizations.of(context).toMailbox, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + maxLines: 1, + style: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 16, + color: Colors.black)), + ), + Expanded(child: Obx(() => + RuleFilterButtonField( + value: controller.mailboxSelected.value, + tapActionCallback: (value) => controller.selectMailbox()))), + ]) + ), + ] + ), + ), + )), + Container( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24), + alignment: Alignment.center, + color: Colors.white, + child: Row( + children: [ + Expanded(child: buildTextButton( + AppLocalizations.of(context).cancel, + textStyle: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 17, + color: AppColor.colorTextButton), + backgroundColor: AppColor.emailAddressChipColor, + width: 128, + height: 44, + radius: 10, + onTap: () => controller.closeView(context))), + const SizedBox(width: 12), + Expanded(child: Obx(() => buildTextButton( + controller.actionType.value.getActionName(context), + width: 128, + height: 44, + backgroundColor: AppColor.colorTextButton + .withOpacity(controller.isCreateRuleFilterValid.value + ? 1.0 + : 0.4), + radius: 10, + onTap: () { + if (controller.isCreateRuleFilterValid.value) { + controller.createNewRuleFilter(context); + } + }))), + ] + ), + ) + ]), + Positioned(top: 8, right: 8, + child: buildIconWeb( + icon: SvgPicture.asset( + _imagePaths.icCloseMailbox, + fit: BoxFit.fill), + tooltip: AppLocalizations.of(context).close, + onTap: () => controller.closeView(context))) + ] + ); + } + + Widget _buildRulesFilterFormOnMobile(BuildContext context) { + return Stack( + children: [ + Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Container( + padding: const EdgeInsets.only(top: 16), + alignment: Alignment.center, + child: Obx(() => Text( + controller.actionType.value.getTitle(context), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + color: Colors.black)))), + Expanded(child: SingleChildScrollView( + physics: const ClampingScrollPhysics(), + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Obx(() => RulesFilterInputField( + hintText: AppLocalizations.of(context).rulesNameHintTextInput, + errorText: controller.errorRuleName.value, + editingController: controller.inputRuleNameController, + onChangeAction: (value) => controller.updateRuleName(context, value),)), + const Padding( + padding: EdgeInsets.symmetric(vertical: 12), + child: Divider( + color: AppColor.colorDividerRuleFilter, + height: 1, + thickness: 0.2), + ), + Text(AppLocalizations.of(context).conditionTitleRulesFilter, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + maxLines: 1, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + color: Colors.black)), + const SizedBox(height: 24), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.colorBackgroundFieldConditionRulesFilter, + borderRadius: BorderRadius.circular(12)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Obx(() { + return RuleFilterButtonField( + value: controller.ruleConditionFieldSelected.value, + tapActionCallback: (value) => + controller.openContextMenuAction( + context, + _bottomSheetRuleConditionFieldActionTiles( + context, + controller.ruleConditionFieldSelected.value)) + ); + }), + Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Obx(() { + return RuleFilterButtonField( + value: controller.ruleConditionComparatorSelected.value, + tapActionCallback: (value) => + controller.openContextMenuAction( + context, + _bottomSheetRuleConditionComparatorActionTiles( + context, + controller.ruleConditionComparatorSelected.value)) + ); + }), + ), + Obx(() => RulesFilterInputField( + hintText: AppLocalizations.of(context).conditionValueHintTextInput, + errorText: controller.errorConditionValue.value, + editingController: controller.inputConditionValueController, + onChangeAction: (value) => + controller.updateConditionValue(context, value))) + ] + ) + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 12), + child: Divider( + color: AppColor.colorDividerRuleFilter, + height: 1), + ), + Text(AppLocalizations.of(context).actionTitleRulesFilter, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + maxLines: 1, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + color: Colors.black)), + const SizedBox(height: 24), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.colorBackgroundFieldConditionRulesFilter, + borderRadius: BorderRadius.circular(12)), + child: Column(children: [ + Obx(() { + return RuleFilterButtonField( + value: controller.emailRuleFilterActionSelected.value, + tapActionCallback: (value) => + controller.openContextMenuAction( + context, + _bottomSheetActionRuleFilterActionTiles( + context, + controller.emailRuleFilterActionSelected.value)) + ); + }), + Container( + alignment: Alignment.centerLeft, + padding: const EdgeInsets.symmetric(vertical: 12), + child: Text( + AppLocalizations.of(context).toMailbox, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + maxLines: 1, + style: const TextStyle( + fontWeight: FontWeight.normal, + fontSize: 16, + color: Colors.black)), + ), + Obx(() => RuleFilterButtonField( + value: controller.mailboxSelected.value, + tapActionCallback: (value) => controller.selectMailbox())) + ]) + ), + ] + ), + ), + )), + Container( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24), + alignment: Alignment.center, + color: Colors.white, + child: Row( + children: [ + Expanded(child: buildTextButton( + AppLocalizations.of(context).cancel, + textStyle: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 17, + color: AppColor.colorTextButton), + backgroundColor: AppColor.emailAddressChipColor, + width: 128, + height: 44, + radius: 10, + onTap: () => controller.closeView(context))), + const SizedBox(width: 12), + Expanded(child: Obx(() => buildTextButton( + controller.actionType.value.getActionName(context), + width: 128, + height: 44, + backgroundColor: AppColor.colorTextButton + .withOpacity(controller.isCreateRuleFilterValid.value + ? 1.0 + : 0.4), + radius: 10, + onTap: () { + if (controller.isCreateRuleFilterValid.value) { + controller.createNewRuleFilter(context); + } + }))), + ] + ), + ) + ]), + Positioned(top: 8, right: 8, + child: buildIconWeb( + icon: SvgPicture.asset( + _imagePaths.icCloseMailbox, + fit: BoxFit.fill), + tooltip: AppLocalizations.of(context).close, + onTap: () => controller.closeView(context))) + ] + ); + } + + List _bottomSheetRuleConditionFieldActionTiles( + BuildContext context, + rule_condition.Field? fieldSelected + ) { + return rule_condition.Field.values + .map((field) => + _buildRuleConditionFieldWidget(context, field, fieldSelected)) + .toList(); + } + + Widget _buildRuleConditionFieldWidget( + BuildContext context, + rule_condition.Field field, + rule_condition.Field? fieldSelected + ) { + return (RuleConditionFieldSheetActionTileBuilder( + field.getTitle(context), + fieldSelected, + field, + iconLeftPadding: const EdgeInsets.only(left: 12, right: 16), + iconRightPadding: const EdgeInsets.only(right: 12), + actionSelected: SvgPicture.asset( + _imagePaths.icFilterSelected, + width: 20, + height: 20, + fit: BoxFit.fill)) + ..onActionClick((field) { + controller.selectRuleConditionField(field); + popBack(); + })) + .build(); + } + + List _bottomSheetRuleConditionComparatorActionTiles( + BuildContext context, + rule_condition.Comparator? comparatorSelected + ) { + return rule_condition.Comparator.values + .map((comparator) => + _buildRuleConditionComparatorWidget(context, comparator, comparatorSelected)) + .toList(); + } + + Widget _buildRuleConditionComparatorWidget( + BuildContext context, + rule_condition.Comparator comparator, + rule_condition.Comparator? comparatorSelected + ) { + return (RuleConditionComparatorSheetActionTileBuilder( + comparator.getTitle(context), + comparatorSelected, + comparator, + iconLeftPadding: const EdgeInsets.only(left: 12, right: 16), + iconRightPadding: const EdgeInsets.only(right: 12), + actionSelected: SvgPicture.asset( + _imagePaths.icFilterSelected, + width: 20, + height: 20, + fit: BoxFit.fill)) + ..onActionClick((comparator) { + controller.selectRuleConditionComparator(comparator); + popBack(); + })) + .build(); + } + + List _bottomSheetActionRuleFilterActionTiles( + BuildContext context, + EmailRuleFilterAction? ruleActionSelected + ) { + return EmailRuleFilterAction.values + .map((ruleAction) => + _buildRuleActionWidget(context, ruleAction, ruleActionSelected)) + .toList(); + } + + Widget _buildRuleActionWidget( + BuildContext context, + EmailRuleFilterAction ruleAction, + EmailRuleFilterAction? ruleActionSelected + ) { + return (RuleActionSheetActionTileBuilder( + ruleAction.getTitle(context), + ruleActionSelected, + ruleAction, + iconLeftPadding: const EdgeInsets.only(left: 12, right: 16), + iconRightPadding: const EdgeInsets.only(right: 12), + actionSelected: SvgPicture.asset( + _imagePaths.icFilterSelected, + width: 20, + height: 20, + fit: BoxFit.fill)) + ..onActionClick((ruleAction) { + controller.selectEmailRuleFilterAction(ruleAction); + popBack(); + })) + .build(); + } +} \ No newline at end of file diff --git a/lib/features/rules_filter_creator/presentation/widgets/rule_action_bottom_sheet_action_tile_builder.dart b/lib/features/rules_filter_creator/presentation/widgets/rule_action_bottom_sheet_action_tile_builder.dart new file mode 100644 index 000000000..b3871d1cc --- /dev/null +++ b/lib/features/rules_filter_creator/presentation/widgets/rule_action_bottom_sheet_action_tile_builder.dart @@ -0,0 +1,67 @@ + +import 'package:core/core.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/email_rule_filter_action.dart'; + +class RuleActionSheetActionTileBuilder + extends CupertinoActionSheetNoIconBuilder { + + final EmailRuleFilterAction ruleAction; + final EmailRuleFilterAction? ruleActionCurrent; + final SvgPicture? actionSelected; + final Color? bgColor; + final EdgeInsets? iconLeftPadding; + final EdgeInsets? iconRightPadding; + + RuleActionSheetActionTileBuilder( + String actionName, + this.ruleActionCurrent, + this.ruleAction, + { + Key? key, + this.actionSelected, + this.bgColor, + this.iconLeftPadding, + this.iconRightPadding, + } + ) : super(actionName, key: key); + + @override + Widget build() { + return Container( + color: bgColor ?? Colors.white, + child: MouseRegion( + cursor: BuildUtils.isWeb + ? MaterialStateMouseCursor.clickable + : MouseCursor.defer, + child: CupertinoActionSheetAction( + key: key, + child: Stack( + children: [ + Center( + child: Text(actionName, + textAlign: TextAlign.center, + style: actionTextStyle()), + ), + if (ruleActionCurrent == ruleAction && actionSelected != null) + Align( + alignment: Alignment.centerRight, + child: Padding( + padding: iconRightPadding ?? + const EdgeInsets.only(right: 12), + child: actionSelected!), + ), + ], + ), + onPressed: () { + if (onCupertinoActionSheetActionClick != null) { + onCupertinoActionSheetActionClick!(ruleAction); + } + }, + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/features/rules_filter_creator/presentation/widgets/rule_condition_comparator_bottom_sheet_action_tile_builder.dart b/lib/features/rules_filter_creator/presentation/widgets/rule_condition_comparator_bottom_sheet_action_tile_builder.dart new file mode 100644 index 000000000..3e871afa6 --- /dev/null +++ b/lib/features/rules_filter_creator/presentation/widgets/rule_condition_comparator_bottom_sheet_action_tile_builder.dart @@ -0,0 +1,67 @@ + +import 'package:core/core.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:rule_filter/rule_filter/rule_condition.dart' as rule_condition; + +class RuleConditionComparatorSheetActionTileBuilder + extends CupertinoActionSheetNoIconBuilder { + + final rule_condition.Comparator comparator; + final rule_condition.Comparator? comparatorCurrent; + final SvgPicture? actionSelected; + final Color? bgColor; + final EdgeInsets? iconLeftPadding; + final EdgeInsets? iconRightPadding; + + RuleConditionComparatorSheetActionTileBuilder( + String actionName, + this.comparatorCurrent, + this.comparator, + { + Key? key, + this.actionSelected, + this.bgColor, + this.iconLeftPadding, + this.iconRightPadding, + } + ) : super(actionName, key: key); + + @override + Widget build() { + return Container( + color: bgColor ?? Colors.white, + child: MouseRegion( + cursor: BuildUtils.isWeb + ? MaterialStateMouseCursor.clickable + : MouseCursor.defer, + child: CupertinoActionSheetAction( + key: key, + child: Stack( + children: [ + Center( + child: Text(actionName, + textAlign: TextAlign.center, + style: actionTextStyle()), + ), + if (comparatorCurrent == comparator && actionSelected != null) + Align( + alignment: Alignment.centerRight, + child: Padding( + padding: iconRightPadding ?? + const EdgeInsets.only(right: 12), + child: actionSelected!), + ), + ], + ), + onPressed: () { + if (onCupertinoActionSheetActionClick != null) { + onCupertinoActionSheetActionClick!(comparator); + } + }, + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/features/rules_filter_creator/presentation/widgets/rule_condition_field_bottom_sheet_action_tile_builder.dart b/lib/features/rules_filter_creator/presentation/widgets/rule_condition_field_bottom_sheet_action_tile_builder.dart new file mode 100644 index 000000000..aef61c31e --- /dev/null +++ b/lib/features/rules_filter_creator/presentation/widgets/rule_condition_field_bottom_sheet_action_tile_builder.dart @@ -0,0 +1,67 @@ + +import 'package:core/core.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:rule_filter/rule_filter/rule_condition.dart' as rule_condition; + +class RuleConditionFieldSheetActionTileBuilder + extends CupertinoActionSheetNoIconBuilder { + + final rule_condition.Field field; + final rule_condition.Field? fieldCurrent; + final SvgPicture? actionSelected; + final Color? bgColor; + final EdgeInsets? iconLeftPadding; + final EdgeInsets? iconRightPadding; + + RuleConditionFieldSheetActionTileBuilder( + String actionName, + this.fieldCurrent, + this.field, + { + Key? key, + this.actionSelected, + this.bgColor, + this.iconLeftPadding, + this.iconRightPadding, + } + ) : super(actionName, key: key); + + @override + Widget build() { + return Container( + color: bgColor ?? Colors.white, + child: MouseRegion( + cursor: BuildUtils.isWeb + ? MaterialStateMouseCursor.clickable + : MouseCursor.defer, + child: CupertinoActionSheetAction( + key: key, + child: Stack( + children: [ + Center( + child: Text(actionName, + textAlign: TextAlign.center, + style: actionTextStyle()), + ), + if (fieldCurrent == field && actionSelected != null) + Align( + alignment: Alignment.centerRight, + child: Padding( + padding: iconRightPadding ?? + const EdgeInsets.only(right: 12), + child: actionSelected!), + ), + ], + ), + onPressed: () { + if (onCupertinoActionSheetActionClick != null) { + onCupertinoActionSheetActionClick!(field); + } + }, + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/features/rules_filter_creator/presentation/widgets/rule_filter_button_field.dart b/lib/features/rules_filter_creator/presentation/widgets/rule_filter_button_field.dart new file mode 100644 index 000000000..9d9e46bbd --- /dev/null +++ b/lib/features/rules_filter_creator/presentation/widgets/rule_filter_button_field.dart @@ -0,0 +1,73 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/style_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; +import 'package:model/model.dart'; +import 'package:rule_filter/rule_filter/rule_condition.dart' as rule_condition; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/extensions/rule_condition_extensions.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/email_rule_filter_action.dart'; + +typedef OnTapActionCallback = Function(T? value); + +class RuleFilterButtonField extends StatelessWidget { + + final T? value; + final OnTapActionCallback? tapActionCallback; + + const RuleFilterButtonField({ + super.key, + this.value, + this.tapActionCallback + }); + + @override + Widget build(BuildContext context) { + final _imagePaths = Get.find(); + + return InkWell( + onTap: () => tapActionCallback?.call(value), + child: Container( + height: 44, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: AppColor.colorInputBorderCreateMailbox, + width: 1), + color: Colors.white), + padding: const EdgeInsets.only(left: 12, right: 10), + child: Row(children: [ + Expanded(child: Text( + _getName(context, value), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + color: Colors.black), + maxLines: 1, + softWrap: CommonTextStyle.defaultSoftWrap, + overflow: CommonTextStyle.defaultTextOverFlow, + )), + SvgPicture.asset(_imagePaths.icDropDown) + ]), + ), + ); + } + + String _getName(BuildContext context, T? value) { + if (value is PresentationMailbox) { + return value.name?.name ?? ''; + } + if (value is rule_condition.Field) { + return value.getTitle(context); + } + if (value is rule_condition.Comparator) { + return value.getTitle(context); + } + if (value is EmailRuleFilterAction) { + return value.getTitle(context); + } + return ''; + } +} \ No newline at end of file diff --git a/lib/features/rules_filter_creator/presentation/widgets/rules_filter_input_decoration_builder.dart b/lib/features/rules_filter_creator/presentation/widgets/rules_filter_input_decoration_builder.dart new file mode 100644 index 000000000..8c757c454 --- /dev/null +++ b/lib/features/rules_filter_creator/presentation/widgets/rules_filter_input_decoration_builder.dart @@ -0,0 +1,53 @@ + +import 'package:core/core.dart'; +import 'package:flutter/material.dart'; + +class RulesFilterInputDecorationBuilder extends InputDecorationBuilder { + + @override + InputDecoration build() { + return InputDecoration( + enabledBorder: enabledBorder ?? const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + borderSide: BorderSide( + width: 1, + color: AppColor.colorInputBorderCreateMailbox)), + focusedBorder: enabledBorder ?? const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + borderSide: BorderSide( + width: 1, + color: AppColor.colorTextButton)), + errorBorder: errorBorder ?? const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + borderSide: BorderSide( + width: 1, + color: AppColor.colorInputBorderErrorVerifyName)), + focusedErrorBorder: errorBorder ?? const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + borderSide: BorderSide( + width: 1, + color: AppColor.colorInputBorderErrorVerifyName)), + prefixText: prefixText, + labelText: labelText, + floatingLabelBehavior: FloatingLabelBehavior.never, + labelStyle: labelStyle ?? const TextStyle( + color: Colors.black, + fontSize: 16), + hintText: hintText, + isDense: true, + hintStyle: hintStyle ?? const TextStyle( + color: AppColor.colorHintInputCreateMailbox, + fontSize: 16), + contentPadding: contentPadding ?? const EdgeInsets.symmetric( + horizontal: 12, + vertical: 12), + errorText: errorText, + errorStyle: errorTextStyle ?? const TextStyle( + color: AppColor.colorInputBorderErrorVerifyName, + fontSize: 13), + filled: true, + fillColor: errorText?.isNotEmpty == true + ? AppColor.colorInputBackgroundErrorVerifyName + : Colors.white); + } +} \ No newline at end of file diff --git a/lib/features/rules_filter_creator/presentation/widgets/rules_filter_input_field_builder.dart b/lib/features/rules_filter_creator/presentation/widgets/rules_filter_input_field_builder.dart new file mode 100644 index 000000000..f16ba08cd --- /dev/null +++ b/lib/features/rules_filter_creator/presentation/widgets/rules_filter_input_field_builder.dart @@ -0,0 +1,43 @@ + +import 'package:core/presentation/views/text/text_field_builder.dart'; +import 'package:core/utils/build_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/widgets/rules_filter_input_decoration_builder.dart'; + +typedef OnChangeFilterInputAction = Function(String? value); + +class RulesFilterInputField extends StatelessWidget { + + final String? errorText; + final String? hintText; + final TextEditingController? editingController; + final bool isMandatory; + final OnChangeFilterInputAction? onChangeAction; + + const RulesFilterInputField({ + Key? key, + this.hintText, + this.errorText, + this.isMandatory = false, + this.editingController, + this.onChangeAction + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return (TextFieldBuilder() + ..onChange((value) => onChangeAction?.call(value)) + ..textInputAction(TextInputAction.next) + ..addController(editingController ?? TextEditingController()) + ..textStyle(const TextStyle(color: Colors.black, fontSize: 16)) + ..keyboardType(TextInputType.text) + ..textDecoration((RulesFilterInputDecorationBuilder() + ..setContentPadding(const EdgeInsets.symmetric( + vertical: BuildUtils.isWeb ? 16 : 12, + horizontal: 12)) + ..setHintText(hintText) + ..setErrorText(errorText)) + .build())) + .build(); + } +} \ No newline at end of file diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 110627d65..742ef6848 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2022-08-09T16:48:03.746319", + "@@last_modified": "2022-08-10T18:05:32.066682", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -1789,5 +1789,95 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "createNewRule": "Create new rule", + "@createNewRule": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "rulesNameHintTextInput": "Enter the rule name", + "@rulesNameHintTextInput": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "conditionValueHintTextInput": "Value", + "@conditionValueHintTextInput": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "conditionTitleRulesFilter": "If all of the following conditions are met:", + "@conditionTitleRulesFilter": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "recipient": "Recipient", + "@recipient": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "contains": "Contains", + "@contains": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "notContains": "Not contains", + "@notContains": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "exactlyEquals": "Exactly equals", + "@exactlyEquals": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "notExactlyEquals": "Not exactly equals", + "@notExactlyEquals": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "actionTitleRulesFilter": "Perform the following actions:", + "@actionTitleRulesFilter": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "toMailbox": "To mailbox:", + "@toMailbox": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "moveMessage": "Move message", + "@moveMessage": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "ruleFilterAddressFromField": "From", + "@ruleFilterAddressFromField": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "ruleFilterAddressToField": "To", + "@ruleFilterAddressToField": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "ruleFilterAddressCcField": "Cc", + "@ruleFilterAddressCcField": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 9e497a083..10dd9a744 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -32,11 +32,11 @@ class AppLocalizations { String get loginInputUrlMessage { return Intl.message('To login and access your message please connect to your JMAP server', name: 'loginInputUrlMessage'); } - + String get loginInputCredentialMessage { return Intl.message('Enter your credentials to sign in', name: 'loginInputCredentialMessage'); } - + String get next { return Intl.message('Next', name: 'next'); } @@ -1840,4 +1840,97 @@ class AppLocalizations { name: 'deleteRule', ); } + + String get createNewRule { + return Intl.message( + 'Create new rule', + name: 'createNewRule'); + } + + String get rulesNameHintTextInput { + return Intl.message( + 'Enter the rule name', + name: 'rulesNameHintTextInput'); + } + + String get conditionValueHintTextInput { + return Intl.message( + 'Value', + name: 'conditionValueHintTextInput'); + } + + String get conditionTitleRulesFilter { + return Intl.message( + 'If all of the following conditions are met:', + name: 'conditionTitleRulesFilter'); + } + + String get recipient { + return Intl.message( + 'Recipient', + name: 'recipient'); + } + + String get contains { + return Intl.message( + 'Contains', + name: 'contains'); + } + + String get notContains { + return Intl.message( + 'Not contains', + name: 'notContains'); + } + + String get exactlyEquals { + return Intl.message( + 'Exactly equals', + name: 'exactlyEquals'); + } + + String get notExactlyEquals { + return Intl.message( + 'Not exactly equals', + name: 'notExactlyEquals'); + } + + String get actionTitleRulesFilter { + return Intl.message( + 'Perform the following actions:', + name: 'actionTitleRulesFilter'); + } + + String get toMailbox { + return Intl.message( + 'To mailbox:', + name: 'toMailbox'); + } + + String get moveMessage { + return Intl.message( + 'Move message', + name: 'moveMessage'); + } + + String get ruleFilterAddressFromField { + return Intl.message( + 'From', + name: 'ruleFilterAddressFromField', + ); + } + + String get ruleFilterAddressToField { + return Intl.message( + 'To', + name: 'ruleFilterAddressToField', + ); + } + + String get ruleFilterAddressCcField { + return Intl.message( + 'Cc', + name: 'ruleFilterAddressCcField', + ); + } } \ No newline at end of file