diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index 1deeff0441..3ba17921ea 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -206,6 +206,7 @@ extension AppColor on Color { static const colorClosePopupDialogButton = Color(0xFFAEB7C2); static const colorEmptyPopupDialogButton = Color(0xFFFFC107); static const colorCancelPopupDialogButton = Color(0xFFEBEDF0); + static const colorRemoveRuleFilterConditionButton = Color(0xFFE6E8EC); static const mapGradientColor = [ [Color(0xFF21D4FD), Color(0xFFB721FF)], diff --git a/lib/features/rules_filter_creator/presentation/model/rule_filter_condition_type.dart b/lib/features/rules_filter_creator/presentation/model/rule_filter_condition_type.dart new file mode 100644 index 0000000000..a8984f187b --- /dev/null +++ b/lib/features/rules_filter_creator/presentation/model/rule_filter_condition_type.dart @@ -0,0 +1,5 @@ +enum RuleFilterConditionScreenType { + mobile, + tablet, + desktop +} \ No newline at end of file diff --git a/lib/features/rules_filter_creator/presentation/model/rules_filter_input_field_arguments.dart b/lib/features/rules_filter_creator/presentation/model/rules_filter_input_field_arguments.dart new file mode 100644 index 0000000000..eefbb82d37 --- /dev/null +++ b/lib/features/rules_filter_creator/presentation/model/rules_filter_input_field_arguments.dart @@ -0,0 +1,21 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; + +class RulesFilterInputFieldArguments with EquatableMixin { + final FocusNode focusNode; + final String errorText; + final TextEditingController controller; + + RulesFilterInputFieldArguments({ + required this.focusNode, + required this.errorText, + required this.controller, + }); + + @override + List get props => [ + focusNode, + errorText, + controller, + ]; +} 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 index 5d584a8ef8..5f70f24817 100644 --- a/lib/features/rules_filter_creator/presentation/rules_filter_creator_controller.dart +++ b/lib/features/rules_filter_creator/presentation/rules_filter_creator_controller.dart @@ -4,6 +4,7 @@ import 'package:core/presentation/utils/app_toast.dart'; import 'package:core/presentation/utils/keyboard_utils.dart'; import 'package:core/utils/app_logger.dart'; import 'package:core/utils/platform_info.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; @@ -14,6 +15,7 @@ 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/rule_condition.dart'; import 'package:rule_filter/rule_filter/tmail_rule.dart'; import 'package:tmail_ui_user/features/base/base_mailbox_controller.dart'; import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart'; @@ -38,6 +40,8 @@ import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart'; import 'package:tmail_ui_user/main/routes/dialog_router.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/rules_filter_input_field_arguments.dart'; + class RulesFilterCreatorController extends BaseMailboxController { @@ -48,21 +52,17 @@ class RulesFilterCreatorController extends BaseMailboxController { GetAllRulesInteractor? _getAllRulesInteractor; final errorRuleName = Rxn(); - final errorRuleConditionValue = Rxn(); final errorRuleActionValue = Rxn(); - final ruleConditionFieldSelected = Rxn(); - final ruleConditionComparatorSelected = Rxn(); final emailRuleFilterActionSelected = Rxn(); final mailboxSelected = Rxn(); final actionType = CreatorActionType.create.obs; + final listRuleCondition = RxList(); final TextEditingController inputRuleNameController = TextEditingController(); - final TextEditingController inputConditionValueController = TextEditingController(); final FocusNode inputRuleNameFocusNode = FocusNode(); - final FocusNode inputRuleConditionFocusNode = FocusNode(); + final listRuleConditionValueArguments = RxList(); String? _newRuleName; - String? _newRuleConditionValue; RulesFilterCreatorArguments? arguments; AccountId? _accountId; @@ -111,9 +111,11 @@ class RulesFilterCreatorController extends BaseMailboxController { void onClose() { log('RulesFilterCreatorController::onClose():'); inputRuleNameFocusNode.dispose(); - inputRuleConditionFocusNode.dispose(); inputRuleNameController.dispose(); - inputConditionValueController.dispose(); + for (var ruleConditionValueArguments in listRuleConditionValueArguments) { + ruleConditionValueArguments.focusNode.dispose(); + ruleConditionValueArguments.controller.dispose(); + } super.onClose(); } @@ -143,12 +145,31 @@ class RulesFilterCreatorController extends BaseMailboxController { void _setUpDefaultValueRuleFilter() { switch(actionType.value) { case CreatorActionType.create: - ruleConditionFieldSelected.value = rule_condition.Field.from; - ruleConditionComparatorSelected.value = rule_condition.Comparator.contains; + RuleCondition newRuleCondition = RuleCondition( + field: rule_condition.Field.from, + comparator: rule_condition.Comparator.contains, + value: '' + ); + listRuleCondition.add(newRuleCondition); + RulesFilterInputFieldArguments newRuleConditionValueArguments = RulesFilterInputFieldArguments( + focusNode: FocusNode(), + errorText: '', + controller: TextEditingController(), + ); + listRuleConditionValueArguments.add(newRuleConditionValueArguments); emailRuleFilterActionSelected.value = EmailRuleFilterAction.moveMessage; if (_emailAddress != null) { - _newRuleConditionValue = _emailAddress?.email; - _setValueInputField(inputConditionValueController, _newRuleConditionValue ?? ''); + RuleCondition firstRuleCondition = RuleCondition( + field: rule_condition.Field.from, + comparator: rule_condition.Comparator.contains, + value: _emailAddress!.email!, + ); + listRuleCondition[0] = firstRuleCondition; + listRuleCondition.refresh(); + _setValueInputField( + listRuleConditionValueArguments[0].controller, + listRuleCondition[0].value + ); } if (_mailboxDestination != null) { mailboxSelected.value = _mailboxDestination; @@ -156,11 +177,23 @@ class RulesFilterCreatorController extends BaseMailboxController { break; case CreatorActionType.edit: if (_currentTMailRule != null) { - ruleConditionFieldSelected.value = _currentTMailRule!.condition.field; - ruleConditionComparatorSelected.value = _currentTMailRule!.condition.comparator; + RuleCondition currentRule = RuleCondition( + field: _currentTMailRule!.condition.field, + comparator: _currentTMailRule!.condition.comparator, + value: _currentTMailRule!.condition.value + ); + listRuleCondition.add(currentRule); + RulesFilterInputFieldArguments newRuleConditionValueArguments = RulesFilterInputFieldArguments( + focusNode: FocusNode(), + errorText: '', + controller: TextEditingController(), + ); + listRuleConditionValueArguments.add(newRuleConditionValueArguments); emailRuleFilterActionSelected.value = EmailRuleFilterAction.moveMessage; - _newRuleConditionValue = _currentTMailRule!.condition.value; - _setValueInputField(inputConditionValueController, _newRuleConditionValue ?? ''); + _setValueInputField( + listRuleConditionValueArguments[0].controller, + listRuleCondition[0].value + ); _newRuleName = _currentTMailRule!.name; _setValueInputField(inputRuleNameController, _newRuleName ?? ''); _getAllMailboxAction(); @@ -195,9 +228,26 @@ class RulesFilterCreatorController extends BaseMailboxController { errorRuleName.value = _getErrorStringByInputValue(context, _newRuleName); } - void updateConditionValue(BuildContext context, String? value) { - _newRuleConditionValue = value; - errorRuleConditionValue.value = _getErrorStringByInputValue(context, _newRuleConditionValue); + void updateConditionValue(BuildContext context, String? value, int ruleConditionIndex) { + RuleCondition newRuleCondition = RuleCondition( + field: listRuleCondition[ruleConditionIndex].field, + comparator: listRuleCondition[ruleConditionIndex].comparator, + value: value!, + ); + listRuleCondition[ruleConditionIndex] = newRuleCondition; + listRuleCondition.refresh(); + String? errorString = _getErrorStringByInputValue(context, listRuleCondition[ruleConditionIndex].value); + RulesFilterInputFieldArguments newRuleConditionValueArguments = RulesFilterInputFieldArguments( + focusNode: listRuleConditionValueArguments[ruleConditionIndex].focusNode, + errorText: errorString ?? '', + controller: listRuleConditionValueArguments[ruleConditionIndex].controller, + ); + if (listRuleConditionValueArguments.length > ruleConditionIndex) { + listRuleConditionValueArguments[ruleConditionIndex] = newRuleConditionValueArguments; + } else { + listRuleConditionValueArguments.add(newRuleConditionValueArguments); + } + listRuleConditionValueArguments.refresh(); } String? _getErrorStringByInputValue(BuildContext context, String? inputValue) { @@ -213,12 +263,28 @@ class RulesFilterCreatorController extends BaseMailboxController { ); } - void selectRuleConditionField(rule_condition.Field? newField) { - ruleConditionFieldSelected.value = newField; + void selectRuleConditionField(rule_condition.Field? newField, int? ruleConditionIndex) { + if (newField != null && ruleConditionIndex != null) { + RuleCondition newRuleCondition = RuleCondition( + field: newField, + comparator: listRuleCondition[ruleConditionIndex].comparator, + value: listRuleCondition[ruleConditionIndex].value, + ); + listRuleCondition[ruleConditionIndex] = newRuleCondition; + listRuleCondition.refresh(); + } } - void selectRuleConditionComparator(rule_condition.Comparator? newComparator) { - ruleConditionComparatorSelected.value = newComparator; + void selectRuleConditionComparator(rule_condition.Comparator? newComparator, int? ruleConditionIndex) { + if (newComparator != null && ruleConditionIndex != null) { + RuleCondition newRuleCondition = RuleCondition( + field: listRuleCondition[ruleConditionIndex].field, + comparator: newComparator, + value: listRuleCondition[ruleConditionIndex].value, + ); + listRuleCondition[ruleConditionIndex] = newRuleCondition; + listRuleCondition.refresh(); + } } void selectEmailRuleFilterAction(EmailRuleFilterAction? newAction) { @@ -255,10 +321,20 @@ class RulesFilterCreatorController extends BaseMailboxController { return; } - final errorCondition = _getErrorStringByInputValue(context, _newRuleConditionValue); - if (errorCondition?.isNotEmpty == true) { - errorRuleConditionValue.value = errorCondition; - inputRuleConditionFocusNode.requestFocus(); + if (listRuleCondition.isNotEmpty) { + for (var ruleCondition in listRuleCondition) { + String? errorString = _getErrorStringByInputValue(context, ruleCondition.value); + if (errorString != null) { + int ruleConditionIndex = listRuleCondition.indexOf(ruleCondition); + RulesFilterInputFieldArguments newRuleConditionValueArguments = RulesFilterInputFieldArguments( + focusNode: listRuleConditionValueArguments[ruleConditionIndex].focusNode, + errorText: errorString, + controller: listRuleConditionValueArguments[ruleConditionIndex].controller, + ); + listRuleConditionValueArguments[ruleConditionIndex] = newRuleConditionValueArguments; + listRuleConditionValueArguments[listRuleCondition.indexOf(ruleCondition)].focusNode.requestFocus(); + } + } return; } @@ -273,9 +349,7 @@ class RulesFilterCreatorController extends BaseMailboxController { return; } - if (ruleConditionFieldSelected.value == null || - ruleConditionComparatorSelected.value == null || - emailRuleFilterActionSelected.value == null) { + if (listRuleCondition.isEmpty || emailRuleFilterActionSelected.value == null) { if (currentOverlayContext != null && currentContext != null) { _appToast.showToastErrorMessage( currentOverlayContext!, @@ -284,7 +358,11 @@ class RulesFilterCreatorController extends BaseMailboxController { return; } - final newTMailRule = TMailRule( + late EquatableMixin ruleFilterRequest; + + if (actionType.value == CreatorActionType.create) { + for (var ruleCondition in listRuleCondition) { + final newTMailRule = TMailRule( id: _currentTMailRule?.id, name: _newRuleName!, action: RuleAction( @@ -293,15 +371,28 @@ class RulesFilterCreatorController extends BaseMailboxController { ) ), condition: rule_condition.RuleCondition( - field: ruleConditionFieldSelected.value!, - comparator: ruleConditionComparatorSelected.value!, - value: _newRuleConditionValue! + field: ruleCondition.field, + comparator: ruleCondition.comparator, + value: ruleCondition.value )); - - final ruleFilterRequest = - actionType.value == CreatorActionType.create - ? CreateNewEmailRuleFilterRequest(_listEmailRule ?? [], newTMailRule) - : EditEmailRuleFilterRequest(_listEmailRule?.withIds ?? [], newTMailRule); + ruleFilterRequest = CreateNewEmailRuleFilterRequest(_listEmailRule ?? [], newTMailRule); + } + } else { + final newTMailRule = TMailRule( + id: _currentTMailRule?.id, + name: _newRuleName!, + action: RuleAction( + appendIn: RuleAppendIn( + mailboxIds: [mailboxSelected.value!.id] + ) + ), + condition: rule_condition.RuleCondition( + field: listRuleCondition[0].field, + comparator: listRuleCondition[0].comparator, + value: listRuleCondition[0].value + )); + ruleFilterRequest = EditEmailRuleFilterRequest(_listEmailRule?.withIds ?? [], newTMailRule); + } popBack(result: ruleFilterRequest); } @@ -309,4 +400,23 @@ class RulesFilterCreatorController extends BaseMailboxController { KeyboardUtils.hideKeyboard(context); popBack(); } + + void tapAddCondition() { + RuleCondition newRuleCondition = RuleCondition( + field: rule_condition.Field.from, + comparator: rule_condition.Comparator.contains, + value: '' + ); + listRuleCondition.add(newRuleCondition); + listRuleConditionValueArguments.add(RulesFilterInputFieldArguments( + focusNode: FocusNode(), + errorText: '', + controller: TextEditingController(), + )); + } + + void tapRemoveCondition(int ruleConditionIndex) { + listRuleCondition.removeAt(ruleConditionIndex); + listRuleConditionValueArguments.removeAt(ruleConditionIndex); + } } \ 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 index 373578814f..dc33991aa8 100644 --- a/lib/features/rules_filter_creator/presentation/rules_filter_creator_view.dart +++ b/lib/features/rules_filter_creator/presentation/rules_filter_creator_view.dart @@ -13,10 +13,13 @@ import 'package:tmail_ui_user/features/rules_filter_creator/presentation/widgets 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/rule_filter_condition_widget.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'; +import 'model/rule_filter_condition_type.dart'; + class RuleFilterCreatorView extends GetWidget { final _imagePaths = Get.find(); @@ -143,39 +146,31 @@ class RuleFilterCreatorView extends GetWidget { fontSize: 16, color: Colors.black)), const SizedBox(height: 24), + _buildListRuleFilterConditionList(context, RuleFilterConditionScreenType.desktop), 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.errorRuleConditionValue.value, - editingController: controller.inputConditionValueController, - focusNode: controller.inputRuleConditionFocusNode, - onChangeAction: (value) => - controller.updateConditionValue(context, value)))) - ] - ) + padding: const EdgeInsets.only(top: 8), + child: InkWell( + onTap: controller.tapAddCondition, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SvgPicture.asset( + _imagePaths.icAddNewFolder, + fit: BoxFit.fill, + ), + const SizedBox(width: 15,), + Text( + AppLocalizations.of(context).addCondition, + maxLines: 1, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 17, + color: AppColor.primaryColor + ) + ) + ], + ), + ), ), const SizedBox(height: 24), Text(AppLocalizations.of(context).actionTitleRulesFilter, @@ -302,39 +297,31 @@ class RuleFilterCreatorView extends GetWidget { fontSize: 16, color: Colors.black)), const SizedBox(height: 24), + _buildListRuleFilterConditionList(context, RuleFilterConditionScreenType.tablet), Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: AppColor.colorBackgroundFieldConditionRulesFilter, - borderRadius: BorderRadius.circular(12)), + padding: const EdgeInsets.only(top: 8), + child: InkWell( + onTap: controller.tapAddCondition, 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.errorRuleConditionValue.value, - editingController: controller.inputConditionValueController, - focusNode: controller.inputRuleConditionFocusNode, - onChangeAction: (value) => - controller.updateConditionValue(context, value)))) - ] - ) + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SvgPicture.asset( + _imagePaths.icAddNewFolder, + fit: BoxFit.fill, + ), + const SizedBox(width: 15,), + Text( + AppLocalizations.of(context).addCondition, + maxLines: 1, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 17, + color: AppColor.primaryColor + ) + ) + ], + ), + ), ), const SizedBox(height: 24), Text(AppLocalizations.of(context).actionTitleRulesFilter, @@ -467,52 +454,32 @@ class RuleFilterCreatorView extends GetWidget { fontSize: 16, color: Colors.black)), const SizedBox(height: 24), + _buildListRuleFilterConditionList(context, RuleFilterConditionScreenType.mobile), 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) { - KeyboardUtils.hideKeyboard(context); - 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) { - KeyboardUtils.hideKeyboard(context); - controller.openContextMenuAction( - context, - _bottomSheetRuleConditionComparatorActionTiles( - context, - controller.ruleConditionComparatorSelected.value)); - } - ); - }), - ), - Obx(() => RulesFilterInputField( - hintText: AppLocalizations.of(context).conditionValueHintTextInput, - errorText: controller.errorRuleConditionValue.value, - editingController: controller.inputConditionValueController, - focusNode: controller.inputRuleConditionFocusNode, - onChangeAction: (value) => - controller.updateConditionValue(context, value))) - ] - ) + padding: const EdgeInsets.only(top: 12), + child: InkWell( + onTap: controller.tapAddCondition, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + _imagePaths.icAddNewFolder, + fit: BoxFit.fill, + ), + const SizedBox(width: 15,), + Text( + AppLocalizations.of(context).addCondition, + maxLines: 1, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 17, + color: AppColor.primaryColor + ) + ) + ], + ), + ), ), const Padding( padding: EdgeInsets.symmetric(vertical: 12), @@ -614,20 +581,79 @@ class RuleFilterCreatorView extends GetWidget { ); } + Widget _buildListRuleFilterConditionList( + BuildContext context, + RuleFilterConditionScreenType ruleFilterConditionScreenType + ) { + return Obx(() { + return ListView.separated( + shrinkWrap: true, + itemCount: controller.listRuleCondition.length, + itemBuilder: (context, index) { + return RuleFilterConditionWidget( + key: ValueKey(controller.listRuleConditionValueArguments[index].focusNode), + ruleFilterConditionScreenType: ruleFilterConditionScreenType, + ruleCondition: controller.listRuleCondition[index], + imagePaths: _imagePaths, + conditionValueErrorText: controller.listRuleConditionValueArguments[index].errorText, + conditionValueFocusNode: controller.listRuleConditionValueArguments[index].focusNode, + conditionValueEditingController: controller.listRuleConditionValueArguments[index].controller, + tapRuleConditionFieldCallback: (value) => { + if (ruleFilterConditionScreenType == RuleFilterConditionScreenType.mobile) { + controller.openContextMenuAction( + context, + _bottomSheetRuleConditionFieldActionTiles( + context, + controller.listRuleCondition[index].field, + index, + ) + ), + } else { + controller.selectRuleConditionField(value, index) + } + }, + tapRuleConditionComparatorCallback: (value) => { + if (ruleFilterConditionScreenType == RuleFilterConditionScreenType.mobile) { + controller.openContextMenuAction( + context, + _bottomSheetRuleConditionComparatorActionTiles( + context, + controller.listRuleCondition[index].comparator, + index, + ) + ), + } else { + controller.selectRuleConditionComparator(value, index), + } + }, + conditionValueOnChangeAction: (value) => + controller.updateConditionValue(context, value, index), + tapRemoveRuleFilterConditionCallback: () => controller.tapRemoveCondition(index), + ); + }, + separatorBuilder: (context, index) { + return const SizedBox(height: 12,); + }, + ); + }); + } + List _bottomSheetRuleConditionFieldActionTiles( BuildContext context, - rule_condition.Field? fieldSelected + rule_condition.Field? fieldSelected, + int? ruleConditionIndex, ) { return rule_condition.Field.values .map((field) => - _buildRuleConditionFieldWidget(context, field, fieldSelected)) + _buildRuleConditionFieldWidget(context, field, fieldSelected, ruleConditionIndex)) .toList(); } Widget _buildRuleConditionFieldWidget( BuildContext context, rule_condition.Field field, - rule_condition.Field? fieldSelected + rule_condition.Field? fieldSelected, + int? ruleConditionIndex, ) { return (RuleConditionFieldSheetActionTileBuilder( field.getTitle(context), @@ -641,7 +667,7 @@ class RuleFilterCreatorView extends GetWidget { height: 20, fit: BoxFit.fill)) ..onActionClick((field) { - controller.selectRuleConditionField(field); + controller.selectRuleConditionField(field, ruleConditionIndex); popBack(); })) .build(); @@ -649,18 +675,20 @@ class RuleFilterCreatorView extends GetWidget { List _bottomSheetRuleConditionComparatorActionTiles( BuildContext context, - rule_condition.Comparator? comparatorSelected + rule_condition.Comparator? comparatorSelected, + int? ruleConditionIndex, ) { return rule_condition.Comparator.values .map((comparator) => - _buildRuleConditionComparatorWidget(context, comparator, comparatorSelected)) + _buildRuleConditionComparatorWidget(context, comparator, comparatorSelected, ruleConditionIndex)) .toList(); } Widget _buildRuleConditionComparatorWidget( BuildContext context, rule_condition.Comparator comparator, - rule_condition.Comparator? comparatorSelected + rule_condition.Comparator? comparatorSelected, + int? ruleConditionIndex, ) { return (RuleConditionComparatorSheetActionTileBuilder( comparator.getTitle(context), @@ -674,7 +702,7 @@ class RuleFilterCreatorView extends GetWidget { height: 20, fit: BoxFit.fill)) ..onActionClick((comparator) { - controller.selectRuleConditionComparator(comparator); + controller.selectRuleConditionComparator(comparator, ruleConditionIndex); popBack(); })) .build(); diff --git a/lib/features/rules_filter_creator/presentation/widgets/rule_filter_condition_remove_button_builder.dart b/lib/features/rules_filter_creator/presentation/widgets/rule_filter_condition_remove_button_builder.dart new file mode 100644 index 0000000000..24220254b2 --- /dev/null +++ b/lib/features/rules_filter_creator/presentation/widgets/rule_filter_condition_remove_button_builder.dart @@ -0,0 +1,31 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class RuleFilterConditionRemoveButton extends StatelessWidget { + final Function()? tapRemoveRuleFilterConditionCallback; + final ImagePaths? imagePath; + + const RuleFilterConditionRemoveButton({ + Key? key, + this.tapRemoveRuleFilterConditionCallback, + this.imagePath, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: tapRemoveRuleFilterConditionCallback, + child: CircleAvatar( + backgroundColor: AppColor.colorRemoveRuleFilterConditionButton, + radius: 22, + child: SvgPicture.asset( + imagePath!.icMinimize, + fit: BoxFit.fill, + colorFilter: AppColor.colorDeletePermanentlyButton.asFilter(), + ), + ) + ); + } +} \ No newline at end of file diff --git a/lib/features/rules_filter_creator/presentation/widgets/rule_filter_condition_row_builder.dart b/lib/features/rules_filter_creator/presentation/widgets/rule_filter_condition_row_builder.dart new file mode 100644 index 0000000000..a26cd3811a --- /dev/null +++ b/lib/features/rules_filter_creator/presentation/widgets/rule_filter_condition_row_builder.dart @@ -0,0 +1,123 @@ +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/keyboard_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:rule_filter/rule_filter/rule_condition.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/model/rule_filter_condition_type.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/rule_filter_condition_remove_button_builder.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'; + +class RuleFilterConditionRow extends StatelessWidget { + final RuleFilterConditionScreenType? ruleFilterConditionScreenType; + final RuleCondition ruleCondition; + final Function(Field?)? tapRuleConditionFieldCallback; + final Function(Comparator?)? tapRuleConditionComparatorCallback; + final String? conditionValueErrorText; + final TextEditingController? conditionValueEditingController; + final FocusNode? conditionValueFocusNode; + final OnChangeFilterInputAction? conditionValueOnChangeAction; + final Function()? tapRemoveRuleFilterConditionCallback; + final ImagePaths? imagePaths; + + const RuleFilterConditionRow({ + Key? key, + required this.ruleFilterConditionScreenType, + required this.ruleCondition, + this.tapRuleConditionFieldCallback, + this.tapRuleConditionComparatorCallback, + this.conditionValueErrorText, + this.conditionValueEditingController, + this.conditionValueFocusNode, + this.conditionValueOnChangeAction, + this.tapRemoveRuleFilterConditionCallback, + this.imagePaths, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + switch (ruleFilterConditionScreenType) { + case RuleFilterConditionScreenType.mobile: + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RuleFilterButtonField( + value: ruleCondition.field, + tapActionCallback: (value) { + KeyboardUtils.hideKeyboard(context); + tapRuleConditionFieldCallback!(ruleCondition.field); + }, + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: RuleFilterButtonField( + value: ruleCondition.comparator, + tapActionCallback: (value) { + KeyboardUtils.hideKeyboard(context); + tapRuleConditionComparatorCallback!(ruleCondition.comparator); + }, + ) + ), + RulesFilterInputField( + hintText: AppLocalizations.of(context).conditionValueHintTextInput, + errorText: conditionValueErrorText, + focusNode: conditionValueFocusNode, + onChangeAction: conditionValueOnChangeAction, + editingController: conditionValueEditingController, + ), + ], + ); + case RuleFilterConditionScreenType.tablet: + case RuleFilterConditionScreenType.desktop: + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: DropDownButtonWidget( + items: rule_condition.Field.values, + itemSelected: ruleCondition.field, + dropdownMaxHeight: 250, + onChanged: (newField) => { + tapRuleConditionFieldCallback!(newField) + }, + supportSelectionIcon: true, + ) + ), + Container( + width: 220, + padding: const EdgeInsets.symmetric(horizontal: 12), + child: DropDownButtonWidget( + items: rule_condition.Comparator.values, + itemSelected: ruleCondition.comparator, + onChanged: (newComparator) => { + tapRuleConditionComparatorCallback!(newComparator) + }, + supportSelectionIcon: true, + ) + ), + Expanded( + child: RulesFilterInputField( + hintText: AppLocalizations.of(context).conditionValueHintTextInput, + errorText: conditionValueErrorText, + focusNode: conditionValueFocusNode, + onChangeAction: conditionValueOnChangeAction, + editingController: conditionValueEditingController, + ) + ), + Container( + padding: const EdgeInsets.only(left: 12), + alignment: Alignment.center, + child: RuleFilterConditionRemoveButton( + tapRemoveRuleFilterConditionCallback: tapRemoveRuleFilterConditionCallback, + imagePath: imagePaths, + ) + ), + ], + ); + default: + return const SizedBox.shrink(); + } + } +} diff --git a/lib/features/rules_filter_creator/presentation/widgets/rule_filter_condition_widget.dart b/lib/features/rules_filter_creator/presentation/widgets/rule_filter_condition_widget.dart new file mode 100644 index 0000000000..460f929f24 --- /dev/null +++ b/lib/features/rules_filter_creator/presentation/widgets/rule_filter_condition_widget.dart @@ -0,0 +1,98 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/rule_filter_condition_type.dart'; +import 'package:rule_filter/rule_filter/rule_condition.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/widgets/rule_filter_condition_row_builder.dart'; +import 'package:tmail_ui_user/features/rules_filter_creator/presentation/widgets/rules_filter_input_field_builder.dart'; +import 'package:core/presentation/resources/image_paths.dart'; + +class RuleFilterConditionWidget extends StatelessWidget { + final RuleFilterConditionScreenType? ruleFilterConditionScreenType; + final RuleCondition ruleCondition; + final Function(Field?)? tapRuleConditionFieldCallback; + final Function(Comparator?)? tapRuleConditionComparatorCallback; + final String? conditionValueErrorText; + final TextEditingController? conditionValueEditingController; + final FocusNode? conditionValueFocusNode; + final OnChangeFilterInputAction? conditionValueOnChangeAction; + final ImagePaths? imagePaths; + final Function()? tapRemoveRuleFilterConditionCallback; + + const RuleFilterConditionWidget({ + super.key, + this.ruleFilterConditionScreenType, + required this.ruleCondition, + this.tapRuleConditionFieldCallback, + this.tapRuleConditionComparatorCallback, + this.conditionValueErrorText, + this.conditionValueEditingController, + this.conditionValueFocusNode, + this.conditionValueOnChangeAction, + this.imagePaths, + this.tapRemoveRuleFilterConditionCallback, + }); + + @override + Widget build(BuildContext context) { + return Slidable( + enabled: ruleFilterConditionScreenType == RuleFilterConditionScreenType.mobile ? true : false, + endActionPane: ActionPane( + extentRatio: 0.1, + motion: const BehindMotion(), + children: [ + CustomSlidableAction( + padding: const EdgeInsets.only(right: 12), + borderRadius: const BorderRadius.only(topRight: Radius.circular(12), bottomRight: Radius.circular(12)), + onPressed: (_) => tapRemoveRuleFilterConditionCallback!(), + backgroundColor: AppColor.colorBackgroundFieldConditionRulesFilter, + child: CircleAvatar( + backgroundColor: AppColor.colorRemoveRuleFilterConditionButton, + radius: 110, + child: SvgPicture.asset( + imagePaths!.icMinimize, + fit: BoxFit.fill, + colorFilter: AppColor.colorDeletePermanentlyButton.asFilter(), + ), + ) + ) + ] + ), + child: Builder(builder: (context) { + SlidableController? slideController = Slidable.of(context); + return ValueListenableBuilder( + valueListenable: slideController?.direction ?? ValueNotifier(0), + builder: (context, value, _) { + var borderRadius = value != -1 ? + BorderRadius.circular(12) : + const BorderRadius.only( + bottomLeft: Radius.circular(12), + topLeft: Radius.circular(12) + ); + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.colorBackgroundFieldConditionRulesFilter, + borderRadius: borderRadius, + ), + child: RuleFilterConditionRow( + ruleFilterConditionScreenType: ruleFilterConditionScreenType, + ruleCondition: ruleCondition, + tapRuleConditionFieldCallback: tapRuleConditionFieldCallback, + tapRuleConditionComparatorCallback: tapRuleConditionComparatorCallback, + conditionValueErrorText: conditionValueErrorText, + conditionValueEditingController: conditionValueEditingController, + conditionValueFocusNode: conditionValueFocusNode, + conditionValueOnChangeAction: conditionValueOnChangeAction, + tapRemoveRuleFilterConditionCallback: tapRemoveRuleFilterConditionCallback, + imagePaths: imagePaths, + ) + ); + } + ); + }) + ); + + } +} \ 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 index 8c757c4545..cbea00dce7 100644 --- 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 @@ -41,7 +41,7 @@ class RulesFilterInputDecorationBuilder extends InputDecorationBuilder { contentPadding: contentPadding ?? const EdgeInsets.symmetric( horizontal: 12, vertical: 12), - errorText: errorText, + errorText: errorText != '' ? errorText : null, errorStyle: errorTextStyle ?? const TextStyle( color: AppColor.colorInputBorderErrorVerifyName, fontSize: 13), diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index b2c4fb9310..2175ee6773 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2023-08-30T16:14:12.029258", + "@@last_modified": "2023-08-31T14:39:56.761446", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -3211,5 +3211,11 @@ "placeholders": { "folder": {} } + }, + "addCondition": "Add condition", + "@addCondition": { + "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 d3fe0532e0..0d81d81ea2 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -3310,4 +3310,11 @@ class AppLocalizations { args: [folder] ); } + + String get addCondition { + return Intl.message( + 'Add condition', + name: 'addCondition', + ); + } } \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 166983d376..0e85d09633 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -788,6 +788,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.3" + flutter_slidable: + dependency: "direct main" + description: + name: flutter_slidable + sha256: cc4231579e3eae41ae166660df717f4bad1359c87f4a4322ad8ba1befeb3d2be + url: "https://pub.dev" + source: hosted + version: "3.0.0" flutter_staggered_grid_view: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index d0ead2e9dd..9eb234041d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -204,6 +204,8 @@ dependencies: super_tag_editor: 0.2.0 + flutter_slidable: 3.0.0 + dev_dependencies: flutter_test: sdk: flutter