diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index 25c8efa72..92fe2f436 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -245,6 +245,7 @@ extension AppColor on Color { static const blue400 = Color(0xFF80BDFF); static const blue900 = Color(0xFF0F76E7); static const m3Tertiary = Color(0xFF8C9CAF); + static const m3Tertiary60 = Color(0xFFD8E1EB); static const m3Neutral70 = Color(0xFFAEAAAE); static const m3Neutral90 = Color(0xFFE6E1E5); static const grayBackgroundColor = Color(0xFFF3F6F9); diff --git a/core/lib/presentation/utils/theme_utils.dart b/core/lib/presentation/utils/theme_utils.dart index 5788d8c8a..c7dd9ec34 100644 --- a/core/lib/presentation/utils/theme_utils.dart +++ b/core/lib/presentation/utils/theme_utils.dart @@ -287,7 +287,7 @@ class ThemeUtils { static const TextStyle textStyleM3BodyLarge = TextStyle( fontFamily: ConstantsUI.fontApp, fontWeight: FontWeight.w500, - letterSpacing: 00.15, + letterSpacing: -0.15, fontSize: 17, height: 24 / 17, color: AppColor.m3SurfaceBackground, diff --git a/core/lib/presentation/views/checkbox/custom_icon_labeled_checkbox.dart b/core/lib/presentation/views/checkbox/custom_icon_labeled_checkbox.dart index 35eb98ca6..ca827983c 100644 --- a/core/lib/presentation/views/checkbox/custom_icon_labeled_checkbox.dart +++ b/core/lib/presentation/views/checkbox/custom_icon_labeled_checkbox.dart @@ -18,6 +18,7 @@ class CustomIconLabeledCheckbox extends LabeledCheckbox { required this.focusNode, super.value, super.gap = 16.0, + super.padding, }); @override diff --git a/core/lib/presentation/views/checkbox/labeled_checkbox.dart b/core/lib/presentation/views/checkbox/labeled_checkbox.dart index 56df1b223..b3df9c2e5 100644 --- a/core/lib/presentation/views/checkbox/labeled_checkbox.dart +++ b/core/lib/presentation/views/checkbox/labeled_checkbox.dart @@ -11,6 +11,7 @@ class LabeledCheckbox extends StatelessWidget { this.value = false, this.gap = 4.0, this.textStyle, + this.padding, }) : super(key: key); final String label; @@ -18,10 +19,11 @@ class LabeledCheckbox extends StatelessWidget { final OnChangedAction onChanged; final double gap; final TextStyle? textStyle; + final EdgeInsetsGeometry? padding; @override Widget build(BuildContext context) { - return InkWell( + final bodyWidget = InkWell( onTap: () => onChanged(!(value)), child: Row( mainAxisSize: MainAxisSize.min, @@ -32,6 +34,12 @@ class LabeledCheckbox extends StatelessWidget { ], ), ); + + if (padding != null) { + return Padding(padding: padding!, child: bodyWidget); + } else { + return bodyWidget; + } } Widget get buildCheckboxWidget => Checkbox( diff --git a/lib/features/base/widget/default_field/date_drop_down_button.dart b/lib/features/base/widget/default_field/date_drop_down_button.dart new file mode 100644 index 000000000..7432babe7 --- /dev/null +++ b/lib/features/base/widget/default_field/date_drop_down_button.dart @@ -0,0 +1,139 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/theme_utils.dart'; +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:pointer_interceptor/pointer_interceptor.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart'; + +typedef OnReceiveTimeSelected = void Function( + EmailReceiveTimeType receiveTimeType, +); + +class DateDropDownButton extends StatelessWidget { + final ImagePaths imagePaths; + final List receiveTimeTypes; + final EmailReceiveTimeType receiveTimeTypeSelected; + final DateTime? startDate; + final DateTime? endDate; + final OnReceiveTimeSelected onReceiveTimeSelected; + + const DateDropDownButton({ + Key? key, + required this.imagePaths, + required this.receiveTimeTypes, + required this.receiveTimeTypeSelected, + required this.onReceiveTimeSelected, + this.startDate, + this.endDate, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return DropdownButtonHideUnderline( + child: PointerInterceptor( + child: DropdownButton2( + isExpanded: true, + items: receiveTimeTypes + .map((item) => _buildItemMenu(context, item)) + .toList(), + value: receiveTimeTypeSelected, + customButton: Container( + height: 40, + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(10)), + border: Border.all( + color: AppColor.m3Neutral90, + width: 1, + ), + color: Colors.white, + ), + padding: const EdgeInsetsDirectional.only(start: 12, end: 8), + child: Row(children: [ + Expanded( + child: Text( + receiveTimeTypeSelected.getTitle( + context, + startDate: startDate, + endDate: endDate, + ), + style: ThemeUtils.textStyleBodyBody3( + color: AppColor.m3SurfaceBackground, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + SvgPicture.asset(imagePaths.icDropDown) + ]), + ), + onChanged: (value) { + if (value != null) { + onReceiveTimeSelected.call(value); + } + }, + dropdownStyleData: DropdownStyleData( + maxHeight: 332, + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(16)), + color: Colors.white, + border: Border.all(color: AppColor.m3Tertiary60), + boxShadow: const [ + BoxShadow(color: Colors.black12, blurRadius: 24) + ] + ), + padding: const EdgeInsets.all(12), + elevation: 0, + offset: const Offset(0.0, -3.0), + scrollbarTheme: ScrollbarThemeData( + radius: const Radius.circular(40), + thickness: WidgetStateProperty.all(6), + thumbVisibility: WidgetStateProperty.all(true), + ), + ), + menuItemStyleData: const MenuItemStyleData( + height: 44, + padding: EdgeInsets.symmetric(horizontal: 12), + ), + ), + ), + ); + } + + DropdownMenuItem _buildItemMenu( + BuildContext context, + EmailReceiveTimeType receiveTime, + ) { + return DropdownMenuItem( + value: receiveTime, + enabled: receiveTime != receiveTimeTypeSelected, + child: PointerInterceptor( + child: Row( + children: [ + Expanded( + child: Text( + receiveTime.getTitle(context), + style: ThemeUtils.textStyleInter400.copyWith( + fontSize: 15, + height: 20 / 15, + letterSpacing: -0.15, + color: Colors.black, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + if (receiveTime == receiveTimeTypeSelected) + SvgPicture.asset( + imagePaths.icChecked, + width: 20, + height: 20, + fit: BoxFit.fill, + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/base/widget/default_field/default_app_bar_widget.dart b/lib/features/base/widget/default_field/default_app_bar_widget.dart new file mode 100644 index 000000000..931fccfe3 --- /dev/null +++ b/lib/features/base/widget/default_field/default_app_bar_widget.dart @@ -0,0 +1,66 @@ + +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/responsive_utils.dart'; +import 'package:core/presentation/utils/theme_utils.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class DefaultAppBarWidget extends StatelessWidget { + final String title; + final ImagePaths imagePaths; + final ResponsiveUtils responsiveUtils; + final VoidCallback onBackAction; + + const DefaultAppBarWidget({ + Key? key, + required this.title, + required this.imagePaths, + required this.responsiveUtils, + required this.onBackAction, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + height: 64, + padding: _getPadding(context), + color: Colors.white, + child: Stack( + alignment: Alignment.center, + children: [ + Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 50), + child: Text( + title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: ThemeUtils.textStyleM3BodyLarge, + ), + ), + ), + PositionedDirectional( + start: 0, + child: TMailButtonWidget.fromIcon( + icon: imagePaths.icArrowBack, + tooltipMessage: AppLocalizations.of(context).back, + backgroundColor: Colors.transparent, + onTapActionCallback: onBackAction, + ), + ), + ], + ), + ); + } + + EdgeInsetsGeometry _getPadding(BuildContext context) { + if (responsiveUtils.isPortraitMobile(context)) { + return const EdgeInsetsDirectional.symmetric(horizontal: 16); + } else if (responsiveUtils.isLandscapeMobile(context)) { + return const EdgeInsetsDirectional.symmetric(horizontal: 24); + } else { + return const EdgeInsetsDirectional.symmetric(horizontal: 32); + } + } +} diff --git a/lib/features/base/widget/default_field/default_autocomplete_input_field_widget.dart b/lib/features/base/widget/default_field/default_autocomplete_input_field_widget.dart new file mode 100644 index 000000000..a77ac6c06 --- /dev/null +++ b/lib/features/base/widget/default_field/default_autocomplete_input_field_widget.dart @@ -0,0 +1,437 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:collection/collection.dart'; +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/responsive_utils.dart'; +import 'package:core/presentation/utils/theme_utils.dart'; +import 'package:core/utils/platform_info.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; +import 'package:model/extensions/email_address_extension.dart'; +import 'package:model/mailbox/expand_mode.dart'; +import 'package:super_tag_editor/tag_editor.dart'; +import 'package:tmail_ui_user/features/base/widget/default_field/default_autocomplete_tag_item_widget.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/draggable_email_address.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/suggestion_email_address.dart'; +import 'package:tmail_ui_user/features/composer/presentation/widgets/recipient_composer_widget.dart'; +import 'package:tmail_ui_user/features/composer/presentation/widgets/recipient_suggestion_item_widget.dart'; +import 'package:tmail_ui_user/features/base/model/filter_filter.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/main/utils/app_config.dart'; + +typedef OnSuggestionEmailAddress = Future> Function( + String word, { + int? limit, +}); +typedef OnUpdateListEmailAddressAction = void Function( + FilterField field, + List newData, +); +typedef OnDeleteEmailAddressTypeAction = void Function(FilterField field); +typedef OnShowFullListEmailAddressAction = void Function(FilterField field); +typedef OnDeleteTagAction = void Function(EmailAddress emailAddress); + +class DefaultAutocompleteInputFieldWidget extends StatefulWidget { + final FilterField field; + final List listEmailAddress; + final ExpandMode expandMode; + final TextEditingController? controller; + final FocusNode? focusNode; + final GlobalKey? keyTagEditor; + final FocusNode? nextFocusNode; + final EdgeInsetsGeometry? padding; + final int minInputLengthAutocomplete; + final OnSuggestionEmailAddress? onSuggestionEmailAddress; + final OnDeleteTagAction? onDeleteTag; + final OnUpdateListEmailAddressAction? onUpdateListEmailAddressAction; + final OnDeleteEmailAddressTypeAction? onDeleteEmailAddressTypeAction; + final OnShowFullListEmailAddressAction? onShowFullListEmailAddressAction; + final OnRemoveDraggableEmailAddressAction? + onRemoveDraggableEmailAddressAction; + final VoidCallback? onSearchAction; + + const DefaultAutocompleteInputFieldWidget({ + Key? key, + required this.field, + required this.listEmailAddress, + this.expandMode = ExpandMode.EXPAND, + this.minInputLengthAutocomplete = + AppConfig.defaultMinInputLengthAutocomplete, + this.focusNode, + this.keyTagEditor, + this.nextFocusNode, + this.padding, + this.onSuggestionEmailAddress, + this.onDeleteTag, + this.onUpdateListEmailAddressAction, + this.onDeleteEmailAddressTypeAction, + this.onShowFullListEmailAddressAction, + this.onRemoveDraggableEmailAddressAction, + this.controller, + this.onSearchAction, + }) : super(key: key); + + @override + State createState() => + _DefaultAutocompleteInputFieldWidgetState(); +} + +class _DefaultAutocompleteInputFieldWidgetState + extends State { + final _imagePaths = Get.find(); + + bool _lastTagFocused = false; + bool _isDragging = false; + late List _currentListEmailAddress; + Timer? _gapBetweenTagChangedAndFindSuggestion; + + @override + void initState() { + super.initState(); + _currentListEmailAddress = widget.listEmailAddress; + } + + @override + void didUpdateWidget( + covariant DefaultAutocompleteInputFieldWidget oldWidget, + ) { + super.didUpdateWidget(oldWidget); + if (widget.listEmailAddress != oldWidget.listEmailAddress) { + _currentListEmailAddress = widget.listEmailAddress; + } + } + + @override + Widget build(BuildContext context) { + final bodyWidget = LayoutBuilder(builder: ((_, constraints) { + final tagEditor = TagEditor( + key: widget.keyTagEditor, + length: _collapsedListEmailAddress.length, + controller: widget.controller, + focusNodeKeyboard: widget.focusNode, + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.done, + cursorColor: AppColor.primaryColor, + debounceDuration: const Duration(milliseconds: 150), + inputDecoration: InputDecoration( + filled: true, + constraints: const BoxConstraints(maxHeight: 40), + fillColor: Colors.white, + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + borderSide: BorderSide.none, + ), + hintText: widget.field.getHintText(AppLocalizations.of(context)), + hintStyle: ThemeUtils.textStyleBodyBody3( + color: AppColor.m3Tertiary, + ), + isDense: true, + contentPadding: _currentListEmailAddress.isNotEmpty + ? const EdgeInsetsDirectional.symmetric(vertical: 14) + : const EdgeInsetsDirectional.only( + top: 14, + bottom: 14, + start: 12, + end: 8, + ), + ), + padding: _currentListEmailAddress.isNotEmpty + ? const EdgeInsets.symmetric(horizontal: 12) + : EdgeInsets.zero, + borderRadius: 10, + borderSize: 1, + focusedBorderColor: AppColor.primaryColor, + enableBorder: true, + enableBorderColor: + _isDragging ? AppColor.primaryColor : AppColor.m3Neutral90, + minTextFieldWidth: 40.0, + resetTextOnSubmitted: true, + autoScrollToInput: false, + suggestionsBoxElevation: 20, + suggestionsBoxBackgroundColor: Colors.white, + suggestionsBoxRadius: 20, + suggestionsBoxMaxHeight: 350, + suggestionItemHeight: 60, + suggestionBoxWidth: _getSuggestionBoxWidth(constraints.maxWidth), + textStyle: ThemeUtils.textStyleBodyBody3( + color: AppColor.m3SurfaceBackground, + ), + onFocusTagAction: _handleFocusTagAction, + onDeleteTagAction: _handleDeleteLatestTagAction, + onSelectOptionAction: _handleSelectOptionAction, + onSubmitted: _handleSubmitTagAction, + tagBuilder: (context, index) { + final currentEmailAddress = _currentListEmailAddress.elementAt(index); + final isLatestEmail = + currentEmailAddress == _currentListEmailAddress.last; + return DefaultAutocompleteTagItemWidget( + field: widget.field, + currentEmailAddress: currentEmailAddress, + currentListEmailAddress: _currentListEmailAddress, + collapsedListEmailAddress: _collapsedListEmailAddress, + iconClose: _imagePaths.icClose, + isLatestEmail: isLatestEmail, + isCollapsed: _isCollapse, + isLatestTagFocused: _lastTagFocused, + onDeleteTagAction: _handleDeleteTagAction, + onShowFullAction: widget.onShowFullListEmailAddressAction, + ); + }, + onTagChanged: _handleOnTagChangeAction, + findSuggestions: (queryString) => _findSuggestions( + queryString, + limit: AppConfig.defaultLimitAutocomplete, + ), + isLoadMoreOnlyOnce: true, + isLoadMoreReplaceAllOld: false, + loadMoreSuggestions: _findSuggestions, + suggestionBuilder: ( + context, + tagEditorState, + suggestionEmailAddress, + index, + length, + highlight, + suggestionValid, + ) { + return RecipientSuggestionItemWidget( + imagePaths: _imagePaths, + suggestionState: suggestionEmailAddress.state, + emailAddress: suggestionEmailAddress.emailAddress, + suggestionValid: suggestionValid, + highlight: highlight, + onSelectedAction: (emailAddress) { + _setStateSafety(() => _currentListEmailAddress.add(emailAddress)); + _updateListEmailAddressAction(); + tagEditorState.resetTextField(); + tagEditorState.closeSuggestionBox(); + }, + ); + }, + onHandleKeyEventAction: + widget.nextFocusNode == null ? null : _onKeyEvent, + ); + + if (PlatformInfo.isWeb) { + return DragTarget( + onAcceptWithDetails: (draggableEmailAddress) => _onAcceptWithDetails( + draggableEmailAddress.data, + ), + onLeave: (_) { + if (_isDragging) { + _setStateSafety(() => _isDragging = false); + } + }, + onMove: (_) { + if (!_isDragging) { + _setStateSafety(() => _isDragging = true); + } + }, + builder: (_, __, ___) => tagEditor, + ); + } else { + return tagEditor; + } + })); + + if (widget.padding != null) { + return Padding(padding: widget.padding!, child: bodyWidget); + } else { + return bodyWidget; + } + } + + void _onKeyEvent(KeyEvent event) { + if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.tab) { + widget.nextFocusNode?.requestFocus(); + } + } + + bool get _isCollapse => + _currentListEmailAddress.length > 1 && + widget.expandMode == ExpandMode.COLLAPSE; + + List get _collapsedListEmailAddress => _isCollapse + ? _currentListEmailAddress.sublist(0, 1) + : _currentListEmailAddress; + + FutureOr> _findSuggestions( + String query, { + int? limit, + }) async { + if (_gapBetweenTagChangedAndFindSuggestion?.isActive ?? false) { + return []; + } + + final processedQuery = query.trim(); + if (processedQuery.isEmpty) { + return []; + } + + final displayedSuggestion = + List.empty(growable: true); + if (processedQuery.length >= widget.minInputLengthAutocomplete && + widget.onSuggestionEmailAddress != null) { + final listEmailAddress = await widget.onSuggestionEmailAddress!( + processedQuery, + limit: limit, + ); + final listSuggestionEmailAddress = + listEmailAddress.map((emailAddress) => _toSuggestionEmailAddress( + emailAddress, + _currentListEmailAddress, + )); + displayedSuggestion.addAll(listSuggestionEmailAddress); + } + + displayedSuggestion.addAll(_matchedSuggestionEmailAddress( + processedQuery, + _currentListEmailAddress, + )); + + final currentTextOnTextField = widget.controller?.text ?? ''; + if (currentTextOnTextField.isEmpty) { + return []; + } + + return displayedSuggestion.toSet().toList(); + } + + bool _isDuplicated(String inputEmail) { + if (inputEmail.isEmpty) { + return false; + } + return _currentListEmailAddress + .map((emailAddress) => emailAddress.email) + .whereNotNull() + .contains(inputEmail); + } + + SuggestionEmailAddress _toSuggestionEmailAddress( + EmailAddress item, + List addedEmailAddresses, + ) { + if (addedEmailAddresses.contains(item)) { + return SuggestionEmailAddress( + item, + state: SuggestionEmailState.duplicated, + ); + } else { + return SuggestionEmailAddress(item); + } + } + + Iterable _matchedSuggestionEmailAddress( + String query, + List addedEmailAddress, + ) { + return addedEmailAddress + .where((addedMail) => addedMail.emailAddress.contains(query)) + .map((emailAddress) => SuggestionEmailAddress( + emailAddress, + state: SuggestionEmailState.duplicated, + )); + } + + void _updateListEmailAddressAction() { + widget.onUpdateListEmailAddressAction?.call( + widget.field, + _currentListEmailAddress, + ); + } + + void _handleFocusTagAction(bool focused) { + _setStateSafety(() => _lastTagFocused = focused); + } + + void _handleDeleteLatestTagAction() { + if (_currentListEmailAddress.isNotEmpty) { + _setStateSafety(_currentListEmailAddress.removeLast); + _updateListEmailAddressAction(); + } + } + + void _handleDeleteTagAction(EmailAddress emailAddress) { + if (_currentListEmailAddress.isNotEmpty) { + _setStateSafety(() => _currentListEmailAddress.remove(emailAddress)); + _updateListEmailAddressAction(); + } + } + + void _handleSelectOptionAction( + SuggestionEmailAddress suggestionEmailAddress, + ) { + if (!_isDuplicated(suggestionEmailAddress.emailAddress.emailAddress)) { + _setStateSafety( + () => _currentListEmailAddress.add(suggestionEmailAddress.emailAddress), + ); + _updateListEmailAddressAction(); + } + } + + void _handleSubmitTagAction(String value) { + final textTrim = value.trim(); + if (textTrim.isEmpty) { + widget.onSearchAction?.call(); + return; + } + + if (!_isDuplicated(textTrim)) { + _setStateSafety( + () => _currentListEmailAddress.add(EmailAddress(null, textTrim)), + ); + _updateListEmailAddressAction(); + } + } + + void _handleOnTagChangeAction(String value) { + final textTrim = value.trim(); + if (!_isDuplicated(textTrim)) { + _setStateSafety( + () => _currentListEmailAddress.add(EmailAddress(null, textTrim)), + ); + _updateListEmailAddressAction(); + } + _gapBetweenTagChangedAndFindSuggestion = Timer( + const Duration(seconds: 1), + () {}, + ); + } + + double? _getSuggestionBoxWidth(double maxWidth) { + if (maxWidth < ResponsiveUtils.minTabletWidth) { + final newWidth = min(maxWidth, 300.0); + return newWidth; + } else { + return null; + } + } + + void _onAcceptWithDetails(DraggableEmailAddress draggableEmailAddress) { + if (draggableEmailAddress.filterField != widget.field) { + if (!_currentListEmailAddress + .contains(draggableEmailAddress.emailAddress)) { + _setStateSafety(() { + _currentListEmailAddress.add(draggableEmailAddress.emailAddress); + _isDragging = false; + }); + _updateListEmailAddressAction(); + } else if (_isDragging) { + _setStateSafety(() => _isDragging = false); + } + widget.onRemoveDraggableEmailAddressAction?.call(draggableEmailAddress); + } else if (_isDragging) { + _setStateSafety(() => _isDragging = false); + } + } + + void _setStateSafety(VoidCallback onAction) { + if (mounted) { + setState(onAction); + } + } +} diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/autocomplete_tag_item_widget_web.dart b/lib/features/base/widget/default_field/default_autocomplete_tag_item_widget.dart similarity index 88% rename from lib/features/mailbox_dashboard/presentation/widgets/advanced_search/autocomplete_tag_item_widget_web.dart rename to lib/features/base/widget/default_field/default_autocomplete_tag_item_widget.dart index 9fba96de2..a3789b134 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/autocomplete_tag_item_widget_web.dart +++ b/lib/features/base/widget/default_field/default_autocomplete_tag_item_widget.dart @@ -1,38 +1,38 @@ - -import 'package:core/core.dart'; +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/utils/style_utils.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:core/utils/direction_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; import 'package:model/extensions/email_address_extension.dart'; +import 'package:tmail_ui_user/features/base/widget/default_field/default_autocomplete_input_field_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/draggable_email_address.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/draggable_recipient_tag_widget.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/advanced_search_filter.dart'; +import 'package:tmail_ui_user/features/base/model/filter_filter.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/autocomplete_tag_item_web_style.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/avatar_tag_item_widget.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/text_field_autocomplete_email_address_web.dart'; -class AutoCompleteTagItemWidgetWeb extends StatelessWidget { +class DefaultAutocompleteTagItemWidget extends StatelessWidget { final bool isCollapsed; final bool isLatestTagFocused; final bool isLatestEmail; - final AdvancedSearchFilterField field; + final FilterField field; final EmailAddress currentEmailAddress; + final String iconClose; final List currentListEmailAddress; final List collapsedListEmailAddress; final OnDeleteTagAction? onDeleteTagAction; final OnShowFullListEmailAddressAction? onShowFullAction; - final _imagePaths = Get.find(); - - AutoCompleteTagItemWidgetWeb({ + const DefaultAutocompleteTagItemWidget({ Key? key, required this.field, required this.currentEmailAddress, required this.currentListEmailAddress, required this.collapsedListEmailAddress, + required this.iconClose, this.isCollapsed = false, this.isLatestTagFocused = false, this.isLatestEmail = false, @@ -53,7 +53,7 @@ class AutoCompleteTagItemWidgetWeb extends StatelessWidget { child: Draggable( data: DraggableEmailAddress( emailAddress: currentEmailAddress, - prefix: field.getPrefixEmailAddress() + filterField: field, ), feedback: DraggableRecipientTagWidget( emailAddress: currentEmailAddress, @@ -82,7 +82,7 @@ class AutoCompleteTagItemWidgetWeb extends StatelessWidget { softWrap: CommonTextStyle.defaultSoftWrap, ), deleteIcon: SvgPicture.asset( - _imagePaths.icClose, + iconClose, fit: BoxFit.fill, ), padding: EdgeInsets.zero, diff --git a/lib/features/base/widget/default_field/default_close_button_widget.dart b/lib/features/base/widget/default_field/default_close_button_widget.dart new file mode 100644 index 000000000..8adb0f7e8 --- /dev/null +++ b/lib/features/base/widget/default_field/default_close_button_widget.dart @@ -0,0 +1,32 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; +import 'package:flutter/material.dart'; + +class DefaultCloseButtonWidget extends StatelessWidget { + final String iconClose; + final VoidCallback onTapActionCallback; + + const DefaultCloseButtonWidget({ + super.key, + required this.iconClose, + required this.onTapActionCallback, + }); + + @override + Widget build(BuildContext context) { + return PositionedDirectional( + top: 4, + end: 4, + child: TMailButtonWidget.fromIcon( + icon: iconClose, + iconSize: 24, + iconColor: AppColor.m3Tertiary, + padding: const EdgeInsets.all(10), + borderRadius: 24, + backgroundColor: Colors.transparent, + onTapActionCallback: onTapActionCallback, + ), + ); + } +} diff --git a/lib/features/base/widget/default_field/default_date_drop_down_field_widget.dart b/lib/features/base/widget/default_field/default_date_drop_down_field_widget.dart new file mode 100644 index 000000000..b9bd27020 --- /dev/null +++ b/lib/features/base/widget/default_field/default_date_drop_down_field_widget.dart @@ -0,0 +1,76 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart'; +import 'package:tmail_ui_user/features/base/widget/default_field/date_drop_down_button.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +typedef OnOpenDatPicker = void Function(); + +class DefaultDateDropDownFieldWidget extends StatelessWidget { + final ImagePaths imagePaths; + final List receiveTimeTypes; + final EmailReceiveTimeType receiveTimeTypeSelected; + final OnReceiveTimeSelected onReceiveTimeSelected; + final OnOpenDatPicker onOpenDatPicker; + final DateTime? startDate; + final DateTime? endDate; + final EdgeInsetsGeometry? padding; + + const DefaultDateDropDownFieldWidget({ + super.key, + required this.imagePaths, + required this.receiveTimeTypes, + required this.receiveTimeTypeSelected, + required this.onReceiveTimeSelected, + required this.onOpenDatPicker, + this.startDate, + this.endDate, + this.padding, + }); + + @override + Widget build(BuildContext context) { + final bodyWidget = Row( + children: [ + Expanded( + child: DateDropDownButton( + imagePaths: imagePaths, + receiveTimeTypes: receiveTimeTypes, + startDate: startDate, + endDate: endDate, + receiveTimeTypeSelected: receiveTimeTypeSelected, + onReceiveTimeSelected: onReceiveTimeSelected, + ), + ), + const SizedBox(width: 8), + Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(10)), + border: Border.all( + color: AppColor.m3Neutral90, + width: 1, + ), + ), + height: 40, + width: 40, + child: TMailButtonWidget.fromIcon( + icon: imagePaths.icCalendarSB, + iconSize: 22, + iconColor: AppColor.steelGray400, + backgroundColor: Colors.transparent, + tooltipMessage: AppLocalizations.of(context).selectDate, + onTapActionCallback: onOpenDatPicker, + ), + ), + ], + ); + + if (padding != null) { + return Padding(padding: padding!, child: bodyWidget); + } else { + return bodyWidget; + } + } +} diff --git a/lib/features/base/widget/default_field/default_input_field_widget.dart b/lib/features/base/widget/default_field/default_input_field_widget.dart new file mode 100644 index 000000000..f83207039 --- /dev/null +++ b/lib/features/base/widget/default_field/default_input_field_widget.dart @@ -0,0 +1,56 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/utils/theme_utils.dart'; +import 'package:core/presentation/views/text/text_field_builder.dart'; +import 'package:flutter/material.dart'; + +typedef OnTextSubmitted = void Function(String text); +typedef OnTextChange = void Function(String text); + +class DefaultInputFieldWidget extends StatelessWidget { + final TextEditingController textEditingController; + final String? hintText; + final OnTextChange? onTextChange; + final OnTextSubmitted? onTextSubmitted; + + const DefaultInputFieldWidget({ + super.key, + required this.textEditingController, + this.hintText, + this.onTextChange, + this.onTextSubmitted, + }); + + @override + Widget build(BuildContext context) { + return TextFieldBuilder( + controller: textEditingController, + maxLines: 1, + textInputAction: TextInputAction.next, + textStyle: ThemeUtils.textStyleBodyBody3( + color: AppColor.m3SurfaceBackground, + ), + onTextSubmitted: onTextSubmitted, + onTextChange: onTextChange, + decoration: InputDecoration( + constraints: const BoxConstraints(maxHeight: 40), + filled: true, + fillColor: Colors.white, + contentPadding: const EdgeInsetsDirectional.only(start: 12, end: 8), + enabledBorder: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + borderSide: BorderSide(width: 1, color: AppColor.m3Neutral90), + ), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + borderSide: BorderSide(width: 1, color: AppColor.m3Neutral90), + ), + focusedBorder: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + borderSide: BorderSide(width: 1, color: AppColor.primaryColor), + ), + hintText: hintText, + hintStyle: ThemeUtils.textStyleBodyBody3(color: AppColor.m3Tertiary), + ), + ); + } +} diff --git a/lib/features/base/widget/default_field/default_input_field_with_tab_key_widget.dart b/lib/features/base/widget/default_field/default_input_field_with_tab_key_widget.dart new file mode 100644 index 000000000..bb37cbaea --- /dev/null +++ b/lib/features/base/widget/default_field/default_input_field_with_tab_key_widget.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:tmail_ui_user/features/base/widget/default_field/default_input_field_widget.dart'; + +typedef OnTextSubmitted = void Function(String text); +typedef OnTextChange = void Function(String text); + +class DefaultInputFieldWithTabKeyWidget extends StatelessWidget { + final TextEditingController textEditingController; + final FocusNode currentFocusNode; + final FocusNode? nextFocusNode; + final String? hintText; + final OnTextChange? onTextChange; + final OnTextSubmitted? onTextSubmitted; + final EdgeInsetsGeometry? padding; + + const DefaultInputFieldWithTabKeyWidget({ + super.key, + required this.textEditingController, + required this.currentFocusNode, + this.nextFocusNode, + this.hintText, + this.onTextChange, + this.onTextSubmitted, + this.padding, + }); + + @override + Widget build(BuildContext context) { + final bodyWidget = KeyboardListener( + focusNode: currentFocusNode, + onKeyEvent: nextFocusNode == null ? null : _onKeyEvent, + child: DefaultInputFieldWidget( + textEditingController: textEditingController, + hintText: hintText, + onTextChange: onTextChange, + onTextSubmitted: onTextSubmitted, + ), + ); + + if (padding != null) { + return Padding(padding: padding!, child: bodyWidget); + } else { + return bodyWidget; + } + } + + void _onKeyEvent(KeyEvent event) { + if (event is KeyDownEvent && + event.logicalKey == LogicalKeyboardKey.tab) { + nextFocusNode?.requestFocus(); + } + } +} diff --git a/lib/features/base/widget/default_field/default_label_field_widget.dart b/lib/features/base/widget/default_field/default_label_field_widget.dart new file mode 100644 index 000000000..28c0fe638 --- /dev/null +++ b/lib/features/base/widget/default_field/default_label_field_widget.dart @@ -0,0 +1,35 @@ + +import 'package:core/presentation/utils/theme_utils.dart'; +import 'package:flutter/material.dart'; + +class DefaultLabelFieldWidget extends StatelessWidget { + final String label; + final EdgeInsetsGeometry? padding; + final double? width; + + const DefaultLabelFieldWidget({ + super.key, + required this.label, + this.padding, + this.width, + }); + + @override + Widget build(BuildContext context) { + final bodyWidget = Container( + height: 38, + width: width, + alignment: AlignmentDirectional.centerStart, + child: Text( + label, + style: ThemeUtils.textStyleBodyBody3(color: Colors.black), + ), + ); + + if (padding != null) { + return Padding(padding: padding!, child: bodyWidget); + } else { + return bodyWidget; + } + } +} diff --git a/lib/features/email_recovery/presentation/email_recovery_controller.dart b/lib/features/email_recovery/presentation/email_recovery_controller.dart index b93a04d64..25ebfaadd 100644 --- a/lib/features/email_recovery/presentation/email_recovery_controller.dart +++ b/lib/features/email_recovery/presentation/email_recovery_controller.dart @@ -13,6 +13,7 @@ import 'package:permission_handler/permission_handler.dart'; import 'package:super_tag_editor/tag_editor.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; import 'package:tmail_ui_user/features/base/mixin/date_range_picker_mixin.dart'; +import 'package:tmail_ui_user/features/base/model/filter_filter.dart'; import 'package:tmail_ui_user/features/composer/domain/model/contact_suggestion_source.dart'; import 'package:tmail_ui_user/features/composer/domain/state/get_autocomplete_state.dart'; import 'package:tmail_ui_user/features/composer/domain/state/get_device_contact_suggestions_state.dart'; @@ -21,9 +22,8 @@ import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete import 'package:tmail_ui_user/features/composer/domain/usecases/get_device_contact_suggestions_interactor.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/controller/input_field_focus_manager.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/model/email_recovery_arguments.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/model/email_recovery_field.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/model/email_recovery_time_type.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/model/session_extension.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/extensions/datetime_extension.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/main/utils/app_config.dart'; @@ -33,8 +33,8 @@ class EmailRecoveryController extends BaseController with DateRangePickerMixin { GetAutoCompleteInteractor? _getAutoCompleteInteractor; GetDeviceContactSuggestionsInteractor? _getDeviceContactSuggestionsInteractor; - final deletionDateFieldSelected = EmailRecoveryTimeType.last7Days.obs; - final receptionDateFieldSelected = EmailRecoveryTimeType.allTime.obs; + final deletionDateFieldSelected = EmailReceiveTimeType.last7Days.obs; + final receptionDateFieldSelected = EmailReceiveTimeType.allTime.obs; final startDeletionDate = Rxn(); final endDeletionDate = Rxn(); final startReceptionDate = Rxn(); @@ -47,6 +47,8 @@ class EmailRecoveryController extends BaseController with DateRangePickerMixin { List listRecipients = []; List listSenders = []; + List deletionDateTimeTypes = []; + List receptionDateTimeTypes = []; TextEditingController subjectFieldInputController = TextEditingController(); TextEditingController recipientsFieldInputController = TextEditingController(); @@ -59,8 +61,7 @@ class EmailRecoveryController extends BaseController with DateRangePickerMixin { EmailRecoveryArguments? arguments; AccountId? _accountId; Session? _session; - - EmailRecoveryController(); + int minInputLengthAutocomplete = AppConfig.defaultMinInputLengthAutocomplete; @override void onInit() { @@ -76,6 +77,8 @@ class EmailRecoveryController extends BaseController with DateRangePickerMixin { if (arguments != null) { _accountId = arguments!.accountId; _session = arguments!.session; + _setUpTimeTypesForFields(arguments!.session); + _setUpMinInputLengthAutocomplete(); } if (PlatformInfo.isMobile) { Future.delayed( @@ -87,13 +90,29 @@ class EmailRecoveryController extends BaseController with DateRangePickerMixin { super.onReady(); } - int get minInputLengthAutocomplete { + void _setUpTimeTypesForFields(Session session) { + deletionDateTimeTypes = FilterField + .deletionDate + .getSupportedTimeTypes(session.getRestorationHorizonAsDateTime()); + + receptionDateTimeTypes = FilterField + .receptionDate + .getSupportedTimeTypes(session.getRestorationHorizonAsDateTime()); + + deletionDateFieldSelected.value = deletionDateTimeTypes.firstOrNull + ?? EmailReceiveTimeType.last7Days; + deletionDateFieldSelected.value = deletionDateTimeTypes.firstOrNull + ?? EmailReceiveTimeType.allTime; + } + + void _setUpMinInputLengthAutocomplete() { if (_session == null || _accountId == null) { - return AppConfig.defaultMinInputLengthAutocomplete; + minInputLengthAutocomplete = AppConfig.defaultMinInputLengthAutocomplete; } - return getMinInputLengthAutocomplete( + minInputLengthAutocomplete = getMinInputLengthAutocomplete( session: _session!, - accountId: _accountId!); + accountId: _accountId!, + ); } void _checkContactPermission() async { @@ -156,8 +175,8 @@ class EmailRecoveryController extends BaseController with DateRangePickerMixin { endDeletionDate.value, onCallbackAction: (startDate, endDate) { _updateDateRangeTime( - EmailRecoveryField.deletionDate, - EmailRecoveryTimeType.customRange, + FilterField.deletionDate, + EmailReceiveTimeType.customRange, startDate: startDate, endDate: endDate, ); @@ -172,8 +191,8 @@ class EmailRecoveryController extends BaseController with DateRangePickerMixin { endReceptionDate.value, onCallbackAction: (startDate, endDate) { _updateDateRangeTime( - EmailRecoveryField.receptionDate, - EmailRecoveryTimeType.customRange, + FilterField.receptionDate, + EmailReceiveTimeType.customRange, startDate: startDate, endDate: endDate, ); @@ -182,14 +201,14 @@ class EmailRecoveryController extends BaseController with DateRangePickerMixin { } void _updateDateRangeTime( - EmailRecoveryField field, - EmailRecoveryTimeType recoveryTimeType, + FilterField field, + EmailReceiveTimeType recoveryTimeType, { DateTime? startDate, DateTime? endDate, } ) { - if (field == EmailRecoveryField.deletionDate) { + if (field == FilterField.deletionDate) { deletionDateFieldSelected.value = recoveryTimeType; startDeletionDate.value = startDate; endDeletionDate.value = endDate; @@ -200,12 +219,12 @@ class EmailRecoveryController extends BaseController with DateRangePickerMixin { } } - void onDeletionDateTypeSelected(BuildContext context, EmailRecoveryTimeType type) { - if (type == EmailRecoveryTimeType.customRange) { + void onDeletionDateTypeSelected(BuildContext context, EmailReceiveTimeType type) { + if (type == EmailReceiveTimeType.customRange) { onSelectDeletionDateRange(context); } else { _updateDateRangeTime( - EmailRecoveryField.deletionDate, + FilterField.deletionDate, type, startDate: type.toLatestUTCDate()?.value, endDate: DateTime.now(), @@ -213,12 +232,12 @@ class EmailRecoveryController extends BaseController with DateRangePickerMixin { } } - void onReceptionDateTypeSelected(BuildContext context, EmailRecoveryTimeType type) { - if (type == EmailRecoveryTimeType.customRange) { + void onReceptionDateTypeSelected(BuildContext context, EmailReceiveTimeType type) { + if (type == EmailReceiveTimeType.customRange) { onSelectReceptionDateRange(context); } else { _updateDateRangeTime( - EmailRecoveryField.receptionDate, + FilterField.receptionDate, type, ); } @@ -278,12 +297,12 @@ class EmailRecoveryController extends BaseController with DateRangePickerMixin { } } - void showFullEmailAddress(EmailRecoveryField field) { + void showFullEmailAddress(FilterField field) { switch (field) { - case EmailRecoveryField.recipients: + case FilterField.recipients: recipientsExpandMode.value = ExpandMode.EXPAND; break; - case EmailRecoveryField.sender: + case FilterField.sender: senderExpandMode.value = ExpandMode.EXPAND; break; default: @@ -292,15 +311,15 @@ class EmailRecoveryController extends BaseController with DateRangePickerMixin { } void updateListEmailAddress( - EmailRecoveryField field, + FilterField field, List listEmailAddress, ) { switch (field) { - case EmailRecoveryField.recipients: + case FilterField.recipients: listRecipients = List.from(listEmailAddress); log('EmailRecoveryController::updateListEmailAddress(): listRecipients: ${listRecipients.first}'); break; - case EmailRecoveryField.sender: + case FilterField.sender: listSenders = List.from(listEmailAddress); log('EmailRecoveryController::updateListEmailAddress(): listSenders: ${listSenders.first}'); break; @@ -316,7 +335,7 @@ class EmailRecoveryController extends BaseController with DateRangePickerMixin { UTCDate? deletedAfter; UTCDate? receivedBefore; UTCDate? receivedAfter; - if (deletionDateFieldSelected.value == EmailRecoveryTimeType.customRange) { + if (deletionDateFieldSelected.value == EmailReceiveTimeType.customRange) { deletedBefore = endDeletionDate.value?.toUTCDate(); deletedAfter = startDeletionDate.value?.toUTCDate(); receivedBefore = endReceptionDate.value?.toUTCDate(); @@ -358,7 +377,7 @@ class EmailRecoveryController extends BaseController with DateRangePickerMixin { DateTime getRestorationHorizonAsDateTime() => arguments!.session.getRestorationHorizonAsDateTime(); @override - void dispose() { + void onClose() { focusManager.subjectFieldFocusNode.removeListener(_onSubjectFieldFocusChanged); focusManager.recipientsFieldFocusNode.removeListener(_onRecipientsFieldFocusChanged); focusManager.senderFieldFocusNode.removeListener(_onSenderFieldFocusChanged); @@ -366,6 +385,10 @@ class EmailRecoveryController extends BaseController with DateRangePickerMixin { recipientsFieldInputController.dispose(); senderFieldInputController.dispose(); focusManager.dispose(); - super.dispose(); + listSenders.clear(); + listRecipients.clear(); + deletionDateTimeTypes.clear(); + receptionDateTimeTypes.clear(); + super.onClose(); } } \ No newline at end of file diff --git a/lib/features/email_recovery/presentation/email_recovery_view.dart b/lib/features/email_recovery/presentation/email_recovery_view.dart index 73c64388e..db15e6a62 100644 --- a/lib/features/email_recovery/presentation/email_recovery_view.dart +++ b/lib/features/email_recovery/presentation/email_recovery_view.dart @@ -1,13 +1,10 @@ import 'package:core/presentation/views/responsive/responsive_widget.dart'; -import 'package:core/utils/platform_info.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/email_recovery_controller.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/styles/email_recovery_view_styles.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_desktop_builder.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_mobile_builder.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_tablet_builder.dart'; class EmailRecoveryView extends GetWidget { @override @@ -20,66 +17,8 @@ class EmailRecoveryView extends GetWidget { return PointerInterceptor( child: ResponsiveWidget( responsiveUtils: controller.responsiveUtils, - mobile: Scaffold( - backgroundColor: PlatformInfo.isWeb ? Colors.black.withAlpha(24) : Colors.black38, - body: GestureDetector( - onTap: () => FocusScope.of(context).unfocus(), - child: SafeArea( - bottom: false, - left: false, - right: false, - child: Container( - decoration: EmailRecoveryViewStyles.backgroundDecorationMobile, - margin: EmailRecoveryViewStyles.backgroundMarginMobile, - child: ClipRRect( - borderRadius: EmailRecoveryViewStyles.clipRRectBorderRadiusMobile, - child: SafeArea( - child: EmailRecoveryFormMobileBuilder(), - ), - ), - ), - ), - ), - ), - tablet: Scaffold( - backgroundColor: Colors.black.withAlpha(24), - body: GestureDetector( - onTap: () => FocusScope.of(context).unfocus(), - child: Align( - alignment: Alignment.bottomCenter, - child: Container( - decoration: EmailRecoveryViewStyles.backgroundDecorationMobile, - width: double.infinity, - height: controller.responsiveUtils.getSizeScreenHeight(context) * 0.7, - child: ClipRRect( - borderRadius: EmailRecoveryViewStyles.clipRRectBorderRadiusMobile, - child: EmailRecoveryFormTabletBuilder(), - ), - ), - ), - ), - ), - desktop: Scaffold( - backgroundColor: Colors.black.withAlpha(24), - body: GestureDetector( - onTap:() => FocusScope.of(context).unfocus(), - child: Center( - child: Card( - color: Colors.transparent, - shape: EmailRecoveryViewStyles.shapeBorderDesktop, - child: Container( - decoration: EmailRecoveryViewStyles.backgroundDecorationDesktop, - width: controller.responsiveUtils.getSizeScreenWidth(context) * 0.38, - height: controller.responsiveUtils.getSizeScreenHeight(context) * 0.6, - child: ClipRRect( - borderRadius: EmailRecoveryViewStyles.clipRRectBorderRadiusDesktop, - child: EmailRecoveryFormDesktopBuilder(), - ), - ), - ), - ), - ), - ), + mobile: EmailRecoveryFormMobileBuilder(controller), + tablet: EmailRecoveryFormDesktopBuilder(controller), ), ); } diff --git a/lib/features/email_recovery/presentation/extension/handle_drag_email_tag_between_field_extension.dart b/lib/features/email_recovery/presentation/extension/handle_drag_email_tag_between_field_extension.dart new file mode 100644 index 000000000..7f4460f34 --- /dev/null +++ b/lib/features/email_recovery/presentation/extension/handle_drag_email_tag_between_field_extension.dart @@ -0,0 +1,24 @@ + +import 'package:model/mailbox/expand_mode.dart'; +import 'package:tmail_ui_user/features/base/model/filter_filter.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/draggable_email_address.dart'; +import 'package:tmail_ui_user/features/email_recovery/presentation/email_recovery_controller.dart'; + +extension HandleDragEmailTagBetweenFieldExtension on EmailRecoveryController { + void removeDraggableEmailAddress(DraggableEmailAddress draggableEmailAddress) { + switch(draggableEmailAddress.filterField) { + case FilterField.recipients: + listRecipients.remove(draggableEmailAddress.emailAddress); + recipientsExpandMode.value = ExpandMode.EXPAND; + recipientsExpandMode.refresh(); + break; + case FilterField.sender: + listSenders.remove(draggableEmailAddress.emailAddress); + senderExpandMode.value = ExpandMode.EXPAND; + senderExpandMode.refresh(); + break; + default: + break; + } + } +} \ No newline at end of file diff --git a/lib/features/email_recovery/presentation/styles/email_recovery_view_styles.dart b/lib/features/email_recovery/presentation/styles/email_recovery_view_styles.dart deleted file mode 100644 index 64a056933..000000000 --- a/lib/features/email_recovery/presentation/styles/email_recovery_view_styles.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:core/utils/platform_info.dart'; -import 'package:flutter/material.dart'; - -class EmailRecoveryViewStyles { - static const BoxDecoration backgroundDecorationMobile = BoxDecoration( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - ), - color: Colors.white, - ); - static const BoxDecoration backgroundDecorationDesktop = BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(16)), - color: Colors.white, - ); - - static EdgeInsetsGeometry backgroundMarginMobile = EdgeInsets.only(top: PlatformInfo.isWeb ? 70 : 0); - - static const BorderRadiusGeometry clipRRectBorderRadiusMobile = BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - ); - static const BorderRadiusGeometry clipRRectBorderRadiusDesktop = BorderRadius.all(Radius.circular(16)); - - static const ShapeBorder shapeBorderDesktop = RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(16)), - ); -} \ No newline at end of file diff --git a/lib/features/email_recovery/presentation/widgets/check_box_has_attachment_widget.dart b/lib/features/email_recovery/presentation/widgets/check_box_has_attachment_widget.dart deleted file mode 100644 index a730eb433..000000000 --- a/lib/features/email_recovery/presentation/widgets/check_box_has_attachment_widget.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/presentation/views/checkbox/default_labeled_checkbox.dart'; -import 'package:flutter/material.dart'; -import 'package:tmail_ui_user/features/base/isolate/background_isolate_binary_messenger/background_isolate_binary_messenger_mobile.dart'; -import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; - -typedef OnChangedHasAttachment = void Function(bool? value); - -class CheckBoxHasAttachmentWidget extends StatelessWidget { - final bool hasAttachmentValue; - final FocusNode currentFocusNode; - final FocusNode? nextFocusNode; - final OnChangedHasAttachment onChanged; - - const CheckBoxHasAttachmentWidget({ - super.key, - required this.hasAttachmentValue, - required this.currentFocusNode, - required this.onChanged, - this.nextFocusNode, - }); - - @override - Widget build(BuildContext context) { - return KeyboardListener( - focusNode: FocusNode(), - onKeyEvent: (event) { - if (event.logicalKey == LogicalKeyboardKey.tab) { - nextFocusNode?.requestFocus(); - } - }, - child: DefaultLabeledCheckbox( - label: AppLocalizations.of(context).hasAttachment, - focusNode: currentFocusNode, - value: hasAttachmentValue, - activeColor: AppColor.primaryColor, - onChanged: onChanged, - ) - ); - } -} \ No newline at end of file diff --git a/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_dropdown_button.dart b/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_dropdown_button.dart deleted file mode 100644 index 32cc96f31..000000000 --- a/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_dropdown_button.dart +++ /dev/null @@ -1,139 +0,0 @@ -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:dropdown_button2/dropdown_button2.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:pointer_interceptor/pointer_interceptor.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/model/email_recovery_time_type.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/styles/date_selection_field/date_selection_dropdown_button_styles.dart'; - -typedef OnRecoveryTimeSelected = void Function(EmailRecoveryTimeType recoveryTime); - -class DateSelectionDropDownButton extends StatelessWidget { - - final ImagePaths imagePaths; - final List items; - final DateTime? startDate; - final DateTime? endDate; - final EmailRecoveryTimeType? recoveryTimeSelected; - final OnRecoveryTimeSelected? onRecoveryTimeSelected; - - const DateSelectionDropDownButton({ - super.key, - required this.imagePaths, - required this.items, - this.startDate, - this.endDate, - this.recoveryTimeSelected, - this.onRecoveryTimeSelected, - }); - - @override - Widget build(BuildContext context) { - return DropdownButtonHideUnderline( - child: PointerInterceptor( - child: DropdownButton2( - isExpanded: true, - items: items - .map((item) => _buildItemMenu(context, item)) - .toList(), - value: recoveryTimeSelected, - customButton: Container( - height: DateSelectionDropdownButtonStyles.height, - decoration: BoxDecoration( - borderRadius: DateSelectionDropdownButtonStyles.borderRadius, - border: Border.all( - color: AppColor.colorInputBorderCreateMailbox, - width: DateSelectionDropdownButtonStyles.borderWidth, - ), - color: AppColor.colorInputBackgroundCreateMailbox - ), - padding: DateSelectionDropdownButtonStyles.padding, - child: Row( - children: [ - Expanded( - child: Text( - recoveryTimeSelected?.getTitle(context, startDate: startDate, endDate: endDate) ?? '', - style: DateSelectionDropdownButtonStyles.selectedValueTexStyle, - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - ) - ), - SvgPicture.asset(imagePaths.icDropDown) - ] - ), - ), - onChanged: (value) { - if (value != null) { - onRecoveryTimeSelected?.call(value); - } - }, - buttonStyleData: ButtonStyleData( - height: DateSelectionDropdownButtonStyles.height, - padding: DateSelectionDropdownButtonStyles.padding, - decoration: BoxDecoration( - borderRadius: DateSelectionDropdownButtonStyles.borderRadius, - border: Border.all( - color: AppColor.colorInputBorderCreateMailbox, - width: DateSelectionDropdownButtonStyles.borderWidth, - ), - color: AppColor.colorInputBackgroundCreateMailbox - ) - ), - dropdownStyleData: DropdownStyleData( - maxHeight: DateSelectionDropdownButtonStyles.dropdownMaxHeight, - decoration: DateSelectionDropdownButtonStyles.dropdownDecoration, - elevation: DateSelectionDropdownButtonStyles.dropdownElevation, - offset: DateSelectionDropdownButtonStyles.dropdownOffset, - scrollbarTheme: ScrollbarThemeData( - radius: DateSelectionDropdownButtonStyles.scrollbarRadius, - thickness: WidgetStateProperty.all(DateSelectionDropdownButtonStyles.scrollbarThickness), - thumbVisibility: WidgetStateProperty.all(true), - ), - ), - iconStyleData: IconStyleData( - icon: SvgPicture.asset(imagePaths.icDropDown), - ), - menuItemStyleData: DateSelectionDropdownButtonStyles.menuItemStyleData - ), - ), - ); - } - - DropdownMenuItem _buildItemMenu( - BuildContext context, - EmailRecoveryTimeType recoveryTime - ) { - return DropdownMenuItem( - value: recoveryTime, - child: PointerInterceptor( - child: Container( - color: Colors.transparent, - height: DateSelectionDropdownButtonStyles.height, - child: Row( - children: [ - Expanded( - child: Text( - recoveryTime.getTitle(context), - style: DateSelectionDropdownButtonStyles.selectedValueTexStyle, - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - ) - ), - if (recoveryTime == recoveryTimeSelected) - SvgPicture.asset( - imagePaths.icChecked, - width: DateSelectionDropdownButtonStyles.checkedIconSize, - height: DateSelectionDropdownButtonStyles.checkedIconSize, - fit: BoxFit.fill - ) - ] - ), - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_field_mobile_widget.dart b/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_field_mobile_widget.dart deleted file mode 100644 index dae80f145..000000000 --- a/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_field_mobile_widget.dart +++ /dev/null @@ -1,85 +0,0 @@ -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'; -import 'package:tmail_ui_user/features/email_recovery/presentation/model/email_recovery_field.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/model/email_recovery_time_type.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/styles/date_selection_field/date_selection_field_styles.dart'; - -class DateSelectionFieldMobileWidget extends StatelessWidget { - final EmailRecoveryField field; - final EmailRecoveryTimeType recoveryTimeSelected; - final ImagePaths imagePaths; - final VoidCallback? onTap; - final DateTime? startDate; - final DateTime? endDate; - - const DateSelectionFieldMobileWidget({ - super.key, - required this.field, - required this.recoveryTimeSelected, - required this.imagePaths, - this.onTap, - this.startDate, - this.endDate, - }); - - @override - Widget build(BuildContext context) { - return Padding( - padding: DateSelectionFieldStyles.paddingMobile, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - field.getTitle(context), - style: field.getTitleTextStyle(), - ), - const SizedBox(height: DateSelectionFieldStyles.titleSpaceMobile), - Container( - width: double.infinity, - height: DateSelectionFieldStyles.height, - decoration: const ShapeDecoration( - color: AppColor.loginTextFieldBackgroundColor, - shape: RoundedRectangleBorder( - side: BorderSide( - width: DateSelectionFieldStyles.borderWidthMobile, - color: AppColor.colorInputBorderCreateMailbox - ), - borderRadius: DateSelectionFieldStyles.borderRadius - ) - ), - child: Material( - color: Colors.transparent, - child: InkWell( - radius: DateSelectionFieldStyles.radius, - onTap: onTap, - child: Padding( - padding: DateSelectionFieldStyles.inputFieldMobilePadding, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - recoveryTimeSelected.getTitle( - context, - startDate: startDate, - endDate: endDate - ), - style: field.getHintTextStyle(), - ), - SvgPicture.asset( - imagePaths.icCalendar, - width: DateSelectionFieldStyles.icCalendarSize, - height: DateSelectionFieldStyles.icCalendarSize, - ) - ], - ), - ), - ), - ), - ) - ], - ), - ); - } -} \ No newline at end of file diff --git a/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_field_web_widget.dart b/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_field_web_widget.dart deleted file mode 100644 index 17ce46961..000000000 --- a/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_field_web_widget.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:core/presentation/resources/image_paths.dart'; -import 'package:core/presentation/utils/responsive_utils.dart'; -import 'package:core/presentation/views/button/icon_button_web.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/model/email_recovery_field.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/model/email_recovery_time_type.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/styles/date_selection_field/date_selection_field_styles.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/date_selection_field/date_selection_dropdown_button.dart'; -import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; - -class DateSelectionFieldWebWidget extends StatelessWidget { - final EmailRecoveryField field; - final ResponsiveUtils responsiveUtils; - final ImagePaths imagePaths; - final DateTime? startDate; - final DateTime? endDate; - final EmailRecoveryTimeType? recoveryTimeSelected; - final OnRecoveryTimeSelected? onRecoveryTimeSelected; - final VoidCallback? onTapCalendar; - final DateTime restorationHorizon; - - const DateSelectionFieldWebWidget({ - super.key, - required this.field, - required this.responsiveUtils, - required this.imagePaths, - this.startDate, - this.endDate, - this.recoveryTimeSelected, - this.onRecoveryTimeSelected, - this.onTapCalendar, - required this.restorationHorizon - }); - - @override - Widget build(BuildContext context) { - return Padding( - padding: DateSelectionFieldStyles.padding, - child: SizedBox( - height: DateSelectionFieldStyles.height, - child: Row( - children: [ - SizedBox( - width: DateSelectionFieldStyles.titleWidth, - child: Text( - field.getTitle(context), - style: field.getTitleTextStyle(), - ), - ), - const SizedBox(width: DateSelectionFieldStyles.titleSpace), - Expanded( - child: DateSelectionDropDownButton( - imagePaths: imagePaths, - startDate: startDate, - endDate: endDate, - recoveryTimeSelected: recoveryTimeSelected, - onRecoveryTimeSelected: onRecoveryTimeSelected, - items: field.getSupportedTimeTypes(restorationHorizon), - ), - ), - const SizedBox(width: DateSelectionFieldStyles.icCalenderSpace), - buildIconWeb( - icon: SvgPicture.asset( - imagePaths.icCalendarSB, - width: DateSelectionFieldStyles.icCalendarSize, - height: DateSelectionFieldStyles.icCalendarSize, - fit: BoxFit.fill, - ), - tooltip: AppLocalizations.of(context).selectDate, - iconPadding: EdgeInsets.zero, - onTap: onTapCalendar - ) - ], - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_desktop_builder.dart b/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_desktop_builder.dart index 0e7567da3..2e4fba4ab 100644 --- a/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_desktop_builder.dart +++ b/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_desktop_builder.dart @@ -1,153 +1,268 @@ -import 'package:core/presentation/views/button/icon_button_web.dart'; +import 'dart:math'; + +import 'package:core/presentation/utils/theme_utils.dart'; +import 'package:core/presentation/views/checkbox/custom_icon_labeled_checkbox.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/base/model/filter_filter.dart'; +import 'package:tmail_ui_user/features/base/widget/default_field/default_autocomplete_input_field_widget.dart'; +import 'package:tmail_ui_user/features/base/widget/default_field/default_close_button_widget.dart'; +import 'package:tmail_ui_user/features/base/widget/default_field/default_date_drop_down_field_widget.dart'; +import 'package:tmail_ui_user/features/base/widget/default_field/default_input_field_with_tab_key_widget.dart'; +import 'package:tmail_ui_user/features/base/widget/default_field/default_label_field_widget.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/email_recovery_controller.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/model/email_recovery_field.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/styles/email_recovery_form_styles.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/check_box_has_attachment_widget.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/date_selection_field/date_selection_field_web_widget.dart'; +import 'package:tmail_ui_user/features/email_recovery/presentation/extension/handle_drag_email_tag_between_field_extension.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/limits_banner.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/list_button_widget.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/text_input_field/text_input_field_suggestion_widget.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/text_input_field/text_input_field_widget.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; -class EmailRecoveryFormDesktopBuilder extends GetWidget { - @override - final controller = Get.find(); +class EmailRecoveryFormDesktopBuilder extends StatelessWidget { - EmailRecoveryFormDesktopBuilder({super.key}); + final EmailRecoveryController controller; + + const EmailRecoveryFormDesktopBuilder(this.controller, {super.key}); @override Widget build(BuildContext context) { - return Stack( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: EmailRecoveryFormStyles.padding, - alignment: Alignment.center, - child: Text( - AppLocalizations.of(context).recoverDeletedMessages, - style: EmailRecoveryFormStyles.titleTextStyle, + final appLocalizations = AppLocalizations.of(context); + + return Scaffold( + backgroundColor: Colors.black.withOpacity(0.20), + body: GestureDetector( + onTap: FocusScope.of(context).unfocus, + child: Center( + child: Card( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), + ), + child: Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(16)), + color: Colors.white, ), - ), - Expanded( - child: SingleChildScrollView( - physics: const ClampingScrollPhysics(), - child: Padding( - padding: EmailRecoveryFormStyles.inputAreaPadding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LimitsBanner( - bannerContent: AppLocalizations.of(context).recoverDeletedMessagesBannerContent(controller.getRestorationHorizonAsString()), - ), - const SizedBox(height: 16.0), - Obx(() => DateSelectionFieldWebWidget( - field: EmailRecoveryField.deletionDate, - imagePaths: controller.imagePaths, - responsiveUtils: controller.responsiveUtils, - startDate: controller.startDeletionDate.value, - endDate: controller.endDeletionDate.value, - recoveryTimeSelected: controller.deletionDateFieldSelected.value, - onTapCalendar: () => controller.onSelectDeletionDateRange(context), - onRecoveryTimeSelected: (type) => controller.onDeletionDateTypeSelected(context, type), - restorationHorizon: controller.getRestorationHorizonAsDateTime(), - )), - Obx(() => DateSelectionFieldWebWidget( - field: EmailRecoveryField.receptionDate, - imagePaths: controller.imagePaths, - responsiveUtils: controller.responsiveUtils, - startDate: controller.startReceptionDate.value, - endDate: controller.endReceptionDate.value, - recoveryTimeSelected: controller.receptionDateFieldSelected.value, - onTapCalendar: () => controller.onSelectReceptionDateRange(context), - onRecoveryTimeSelected: (type) => controller.onReceptionDateTypeSelected(context, type), - restorationHorizon: controller.getRestorationHorizonAsDateTime(), - )), - TextInputFieldWidget( - field: EmailRecoveryField.subject, - textEditingController: controller.subjectFieldInputController, - currentFocusNode: controller.focusManager.subjectFieldFocusNode, - nextFocusNode: controller.focusManager.recipientsFieldFocusNode, - responsiveUtils: controller.responsiveUtils, - imagePaths: controller.imagePaths, - ), - Obx(() => TextInputFieldSuggestionWidget( - keyTagEditor: controller.recipientsFieldKey, - field: EmailRecoveryField.recipients, - listEmailAddress: controller.listRecipients, - responsiveUtils: controller.responsiveUtils, - expandMode: controller.recipientsExpandMode.value, - minInputLengthAutocomplete: controller.minInputLengthAutocomplete, - textEditingController: controller.recipientsFieldInputController, - focusNode: controller.focusManager.recipientsFieldFocusNode, - nextFocusNode: controller.listSenders.length != 1 - ? controller.focusManager.senderFieldFocusNode - : controller.focusManager.attachmentCheckboxFocusNode, - onShowFullListEmailAddressAction: controller.showFullEmailAddress, - onUpdateListEmailAddressAction: controller.updateListEmailAddress, - onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, - )), - Obx(() => TextInputFieldSuggestionWidget( - keyTagEditor: controller.senderFieldKey, - field: EmailRecoveryField.sender, - listEmailAddress: controller.listSenders, - responsiveUtils: controller.responsiveUtils, - expandMode: controller.senderExpandMode.value, - minInputLengthAutocomplete: controller.minInputLengthAutocomplete, - textEditingController: controller.senderFieldInputController, - focusNode: controller.focusManager.senderFieldFocusNode, - nextFocusNode: controller.focusManager.attachmentCheckboxFocusNode, - onShowFullListEmailAddressAction: controller.showFullEmailAddress, - onUpdateListEmailAddressAction: controller.updateListEmailAddress, - onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, - )), - ], - ), - ), - ) - ), - Padding( - padding: EmailRecoveryFormStyles.bottomAreaPadding, - child: Row( + width: min( + controller.responsiveUtils.getSizeScreenWidth(context), + 576, + ), + clipBehavior: Clip.antiAlias, + child: Stack( children: [ - Obx(() => CheckBoxHasAttachmentWidget( - hasAttachmentValue: controller.hasAttachment.value, - currentFocusNode: controller.focusManager.attachmentCheckboxFocusNode, - onChanged: (value) => controller.onChangeHasAttachment(value), - )), - SizedBox(width: controller.responsiveUtils.getDeviceWidth(context) * 0.04), - Expanded( - child: ListButtonWidget( - onCancel: () => controller.closeView(context), - onRestore: () => controller.onRestore(context), - responsiveUtils: controller.responsiveUtils, + FocusTraversalGroup( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Center( + child: Padding( + padding: const EdgeInsetsDirectional.symmetric( + horizontal: 16, + vertical: 24 + ), + child: Text( + appLocalizations.recoverDeletedMessages, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: ThemeUtils.textStyleM3HeadlineSmall, + ), + ), + ), + Flexible( + child: SingleChildScrollView( + physics: const ClampingScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + LimitsBanner( + bannerContent: appLocalizations + .dialogRecoverDeletedMessagesDescription( + controller.getRestorationHorizonAsString(), + ), + padding: const EdgeInsetsDirectional.symmetric( + horizontal: 32, + ), + isCenter: true, + ), + const SizedBox(height: 24), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Row( + children: [ + DefaultLabelFieldWidget( + label: FilterField.deletionDate.getTitle(appLocalizations), + width: 112, + ), + const SizedBox(width: 12), + Expanded( + child: Obx(() => DefaultDateDropDownFieldWidget( + imagePaths: controller.imagePaths, + startDate: controller.startDeletionDate.value, + endDate: controller.endDeletionDate.value, + onReceiveTimeSelected: (receiveTimeType) { + controller.onDeletionDateTypeSelected( + context, + receiveTimeType, + ); + }, + onOpenDatPicker: () => + controller.onSelectDeletionDateRange(context), + receiveTimeTypes: controller.deletionDateTimeTypes, + receiveTimeTypeSelected: + controller.deletionDateFieldSelected.value, + )), + ), + ], + ), + ), + const SizedBox(height: 12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Row( + children: [ + DefaultLabelFieldWidget( + label: FilterField.receptionDate.getTitle(appLocalizations), + width: 112, + ), + const SizedBox(width: 12), + Expanded( + child: Obx(() => DefaultDateDropDownFieldWidget( + imagePaths: controller.imagePaths, + startDate: controller.startReceptionDate.value, + endDate: controller.endReceptionDate.value, + onReceiveTimeSelected: (receiveTimeType) { + controller.onReceptionDateTypeSelected( + context, + receiveTimeType, + ); + }, + onOpenDatPicker: () => + controller.onSelectReceptionDateRange(context), + receiveTimeTypes: controller.receptionDateTimeTypes, + receiveTimeTypeSelected: + controller.receptionDateFieldSelected.value, + )), + ), + ], + ), + ), + const SizedBox(height: 12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Row( + children: [ + DefaultLabelFieldWidget( + label: FilterField.subject.getTitle(appLocalizations), + width: 112, + ), + const SizedBox(width: 12), + Expanded( + child: DefaultInputFieldWithTabKeyWidget( + textEditingController: controller.subjectFieldInputController, + currentFocusNode: controller.focusManager.subjectFieldFocusNode, + nextFocusNode: controller.focusManager.recipientsFieldFocusNode, + hintText: FilterField.recoverSubject.getHintText(appLocalizations), + ), + ), + ], + ), + ), + const SizedBox(height: 12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Row( + children: [ + DefaultLabelFieldWidget( + label: FilterField.recipients.getTitle(appLocalizations), + width: 112, + ), + const SizedBox(width: 12), + Expanded( + child: Obx(() => DefaultAutocompleteInputFieldWidget( + field: FilterField.recipients, + listEmailAddress: controller.listRecipients, + expandMode: controller.recipientsExpandMode.value, + minInputLengthAutocomplete: controller.minInputLengthAutocomplete, + controller: controller.recipientsFieldInputController, + focusNode: controller.focusManager.recipientsFieldFocusNode, + nextFocusNode: controller.focusManager.senderFieldFocusNode, + keyTagEditor: controller.recipientsFieldKey, + onShowFullListEmailAddressAction: controller.showFullEmailAddress, + onUpdateListEmailAddressAction: controller.updateListEmailAddress, + onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, + )), + ), + ], + ), + ), + const SizedBox(height: 12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Row( + children: [ + DefaultLabelFieldWidget( + label: FilterField.sender.getTitle(appLocalizations), + width: 112, + ), + const SizedBox(width: 12), + Expanded( + child: Obx(() => DefaultAutocompleteInputFieldWidget( + field: FilterField.sender, + listEmailAddress: controller.listSenders, + expandMode: controller.senderExpandMode.value, + minInputLengthAutocomplete: controller.minInputLengthAutocomplete, + controller: controller.senderFieldInputController, + focusNode: controller.focusManager.senderFieldFocusNode, + nextFocusNode: controller.focusManager.attachmentCheckboxFocusNode, + keyTagEditor: controller.senderFieldKey, + onShowFullListEmailAddressAction: controller.showFullEmailAddress, + onUpdateListEmailAddressAction: controller.updateListEmailAddress, + onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, + )), + ), + ], + ), + ), + const SizedBox(height: 12), + Obx(() => Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: CustomIconLabeledCheckbox( + label: AppLocalizations.of(context).hasAttachment, + svgIconPath: controller.imagePaths.icCheckboxUnselected, + selectedSvgIconPath: controller.imagePaths.icCheckboxSelected, + focusNode: controller.focusManager.attachmentCheckboxFocusNode, + value: controller.hasAttachment.value, + onChanged: controller.onChangeHasAttachment, + ), + )), + ], + ), + ), + ), + ListButtonWidget( + onCancel: () => controller.closeView(context), + onRestore: () => controller.onRestore(context), + nextFocusNode: controller.focusManager.subjectFieldFocusNode, + isMobile: false, + padding: const EdgeInsetsDirectional.symmetric( + horizontal: 37, + vertical: 24, + ), + ) + ], ), - ) + ), + DefaultCloseButtonWidget( + iconClose: controller.imagePaths.icCloseDialog, + onTapActionCallback: () => controller.closeView(context), + ), ], ), - ) - ], - ), - Positioned( - top: EmailRecoveryFormStyles.iconClosePositionTop, - right: EmailRecoveryFormStyles.iconClosePositionRight, - child: buildIconWeb( - icon: SvgPicture.asset( - controller.imagePaths.icClose, - fit: BoxFit.fill, - width: 24, - height: 24, ), - tooltip: AppLocalizations.of(context).close, - onTap: () => controller.closeView(context), ), - ) - ], + ), + ), ); } } \ No newline at end of file diff --git a/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_mobile_builder.dart b/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_mobile_builder.dart index 318a312a5..0fcc4e79d 100644 --- a/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_mobile_builder.dart +++ b/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_mobile_builder.dart @@ -1,138 +1,186 @@ -import 'package:core/presentation/views/button/icon_button_web.dart'; +import 'package:core/presentation/views/checkbox/custom_icon_labeled_checkbox.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/base/model/filter_filter.dart'; +import 'package:tmail_ui_user/features/base/widget/default_field/default_app_bar_widget.dart'; +import 'package:tmail_ui_user/features/base/widget/default_field/default_autocomplete_input_field_widget.dart'; +import 'package:tmail_ui_user/features/base/widget/default_field/default_date_drop_down_field_widget.dart'; +import 'package:tmail_ui_user/features/base/widget/default_field/default_input_field_with_tab_key_widget.dart'; +import 'package:tmail_ui_user/features/base/widget/default_field/default_label_field_widget.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/email_recovery_controller.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/model/email_recovery_field.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/styles/email_recovery_form_styles.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/check_box_has_attachment_widget.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/date_selection_field/date_selection_field_mobile_widget.dart'; +import 'package:tmail_ui_user/features/email_recovery/presentation/extension/handle_drag_email_tag_between_field_extension.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/limits_banner.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/list_button_widget.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/text_input_field/text_input_field_suggestion_widget.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/text_input_field/text_input_field_widget.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; -class EmailRecoveryFormMobileBuilder extends GetWidget { - @override - final controller = Get.find(); - - EmailRecoveryFormMobileBuilder({super.key}); +class EmailRecoveryFormMobileBuilder extends StatelessWidget { + + final EmailRecoveryController controller; + + const EmailRecoveryFormMobileBuilder(this.controller, {super.key}); @override Widget build(BuildContext context) { - return Stack( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: EmailRecoveryFormStyles.padding, - alignment: Alignment.center, - child: Text( - AppLocalizations.of(context).recoverDeletedMessages, - style: EmailRecoveryFormStyles.titleTextStyle, - ), - ), - Expanded( - child: SingleChildScrollView( - physics: const ClampingScrollPhysics(), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LimitsBanner( - bannerContent: AppLocalizations.of(context).recoverDeletedMessagesBannerContent(controller.getRestorationHorizonAsString()), - ), - const SizedBox(height: 16.0), - Obx(() => DateSelectionFieldMobileWidget( - field: EmailRecoveryField.deletionDate, - recoveryTimeSelected: controller.deletionDateFieldSelected.value, - imagePaths: controller.imagePaths, - onTap: () => controller.onSelectDeletionDateRange(context), - startDate: controller.startDeletionDate.value, - endDate: controller.endDeletionDate.value, - )), - Obx(() => DateSelectionFieldMobileWidget( - field: EmailRecoveryField.receptionDate, - recoveryTimeSelected: controller.receptionDateFieldSelected.value, - imagePaths: controller.imagePaths, - onTap: () => controller.onSelectReceptionDateRange(context), - startDate: controller.startReceptionDate.value, - endDate: controller.endReceptionDate.value, - )), - TextInputFieldWidget( - field: EmailRecoveryField.subject, - responsiveUtils: controller.responsiveUtils, - imagePaths: controller.imagePaths, - textEditingController: controller.subjectFieldInputController, - currentFocusNode: controller.focusManager.subjectFieldFocusNode, - nextFocusNode: controller.focusManager.recipientsFieldFocusNode, - ), - Obx(() => TextInputFieldSuggestionWidget( - keyTagEditor: controller.recipientsFieldKey, - field: EmailRecoveryField.recipients, - listEmailAddress: controller.listRecipients, - responsiveUtils: controller.responsiveUtils, - focusNode: controller.focusManager.recipientsFieldFocusNode, - nextFocusNode: controller.focusManager.senderFieldFocusNode, - expandMode: controller.recipientsExpandMode.value, - minInputLengthAutocomplete: controller.minInputLengthAutocomplete, - textEditingController: controller.recipientsFieldInputController, - onShowFullListEmailAddressAction: controller.showFullEmailAddress, - onUpdateListEmailAddressAction: controller.updateListEmailAddress, - onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, - )), - Obx(() => TextInputFieldSuggestionWidget( - keyTagEditor: controller.senderFieldKey, - field: EmailRecoveryField.sender, - listEmailAddress: controller.listSenders, - responsiveUtils: controller.responsiveUtils, - focusNode: controller.focusManager.senderFieldFocusNode, - nextFocusNode: controller.focusManager.attachmentCheckboxFocusNode, - expandMode: controller.senderExpandMode.value, - minInputLengthAutocomplete: controller.minInputLengthAutocomplete, - textEditingController: controller.senderFieldInputController, - onShowFullListEmailAddressAction: controller.showFullEmailAddress, - onUpdateListEmailAddressAction: controller.updateListEmailAddress, - onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, - )), - Obx(() => CheckBoxHasAttachmentWidget( - hasAttachmentValue: controller.hasAttachment.value, - currentFocusNode: controller.focusManager.attachmentCheckboxFocusNode, - onChanged: (value) => controller.onChangeHasAttachment(value), - )) - ], + final appLocalizations = AppLocalizations.of(context); + return Scaffold( + backgroundColor: Colors.white, + body: GestureDetector( + onTap: FocusScope.of(context).unfocus, + child: SafeArea( + child: ColoredBox( + color: Colors.white, + child: FocusTraversalGroup( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DefaultAppBarWidget( + title: appLocalizations.recoverDeletedMessages, + imagePaths: controller.imagePaths, + responsiveUtils: controller.responsiveUtils, + onBackAction: () => controller.closeView(context), ), - ), + Expanded( + child: SingleChildScrollView( + physics: const ClampingScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LimitsBanner( + bannerContent: appLocalizations + .dialogRecoverDeletedMessagesDescription( + controller.getRestorationHorizonAsString(), + ), + padding: const EdgeInsetsDirectional.symmetric( + horizontal: 32, + vertical: 4, + ), + ), + const SizedBox(height: 15.0), + DefaultLabelFieldWidget( + label: FilterField.deletionDate.getTitle(appLocalizations), + padding: const EdgeInsets.symmetric(horizontal: 17), + ), + Obx(() => DefaultDateDropDownFieldWidget( + imagePaths: controller.imagePaths, + startDate: controller.startDeletionDate.value, + endDate: controller.endDeletionDate.value, + onReceiveTimeSelected: (receiveTimeType) { + controller.onDeletionDateTypeSelected( + context, + receiveTimeType, + ); + }, + onOpenDatPicker: () => + controller.onSelectDeletionDateRange(context), + receiveTimeTypes: controller.deletionDateTimeTypes, + receiveTimeTypeSelected: + controller.deletionDateFieldSelected.value, + padding: const EdgeInsets.symmetric(horizontal: 17), + )), + const SizedBox(height: 15.0), + DefaultLabelFieldWidget( + label: FilterField.receptionDate.getTitle(appLocalizations), + padding: const EdgeInsets.symmetric(horizontal: 17), + ), + Obx(() => DefaultDateDropDownFieldWidget( + imagePaths: controller.imagePaths, + startDate: controller.startReceptionDate.value, + endDate: controller.endReceptionDate.value, + onReceiveTimeSelected: (receiveTimeType) { + controller.onReceptionDateTypeSelected( + context, + receiveTimeType, + ); + }, + onOpenDatPicker: () => + controller.onSelectReceptionDateRange(context), + receiveTimeTypes: controller.receptionDateTimeTypes, + receiveTimeTypeSelected: + controller.receptionDateFieldSelected.value, + padding: const EdgeInsets.symmetric(horizontal: 17), + )), + const SizedBox(height: 15.0), + DefaultLabelFieldWidget( + label: FilterField.subject.getTitle(appLocalizations), + padding: const EdgeInsets.symmetric(horizontal: 17), + ), + DefaultInputFieldWithTabKeyWidget( + textEditingController: controller.subjectFieldInputController, + currentFocusNode: controller.focusManager.subjectFieldFocusNode, + nextFocusNode: controller.focusManager.recipientsFieldFocusNode, + hintText: FilterField.recoverSubject.getHintText(appLocalizations), + padding: const EdgeInsets.symmetric(horizontal: 17), + ), + const SizedBox(height: 15.0), + DefaultLabelFieldWidget( + label: FilterField.recipients.getTitle(appLocalizations), + padding: const EdgeInsets.symmetric(horizontal: 17), + ), + Obx(() => DefaultAutocompleteInputFieldWidget( + field: FilterField.recipients, + listEmailAddress: controller.listRecipients, + expandMode: controller.recipientsExpandMode.value, + minInputLengthAutocomplete: controller.minInputLengthAutocomplete, + controller: controller.recipientsFieldInputController, + focusNode: controller.focusManager.recipientsFieldFocusNode, + nextFocusNode: controller.focusManager.senderFieldFocusNode, + keyTagEditor: controller.recipientsFieldKey, + onShowFullListEmailAddressAction: controller.showFullEmailAddress, + onUpdateListEmailAddressAction: controller.updateListEmailAddress, + onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, + padding: const EdgeInsets.symmetric(horizontal: 17), + )), + const SizedBox(height: 15.0), + DefaultLabelFieldWidget( + label: FilterField.sender.getTitle(appLocalizations), + padding: const EdgeInsets.symmetric(horizontal: 17), + ), + Obx(() => DefaultAutocompleteInputFieldWidget( + field: FilterField.sender, + listEmailAddress: controller.listSenders, + expandMode: controller.senderExpandMode.value, + minInputLengthAutocomplete: controller.minInputLengthAutocomplete, + controller: controller.senderFieldInputController, + focusNode: controller.focusManager.senderFieldFocusNode, + nextFocusNode: controller.focusManager.attachmentCheckboxFocusNode, + keyTagEditor: controller.senderFieldKey, + onShowFullListEmailAddressAction: controller.showFullEmailAddress, + onUpdateListEmailAddressAction: controller.updateListEmailAddress, + onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, + onRemoveDraggableEmailAddressAction: controller.removeDraggableEmailAddress, + padding: const EdgeInsets.symmetric(horizontal: 17), + )), + const SizedBox(height: 15.0), + Obx(() => CustomIconLabeledCheckbox( + label: AppLocalizations.of(context).hasAttachment, + svgIconPath: controller.imagePaths.icCheckboxUnselected, + selectedSvgIconPath: controller.imagePaths.icCheckboxSelected, + focusNode: controller.focusManager.attachmentCheckboxFocusNode, + value: controller.hasAttachment.value, + padding: const EdgeInsets.symmetric(horizontal: 17), + onChanged: controller.onChangeHasAttachment, + )), + ], + ), + ), + ), + ListButtonWidget( + onCancel: () => controller.closeView(context), + onRestore: () => controller.onRestore(context), + nextFocusNode: controller.focusManager.subjectFieldFocusNode, + isMobile: true, + padding: const EdgeInsetsDirectional.symmetric( + horizontal: 16, + vertical: 44, + ), + ) + ], ), ), - Padding( - padding: EmailRecoveryFormStyles.bottomAreaPadding, - child: ListButtonWidget( - onCancel: () => controller.closeView(context), - onRestore: () => controller.onRestore(context), - responsiveUtils: controller.responsiveUtils, - ), - ) - ], - ), - Positioned( - top: EmailRecoveryFormStyles.iconClosePositionTop, - left: EmailRecoveryFormStyles.iconClosePositionRight, - child: buildIconWeb( - icon: SvgPicture.asset( - controller.imagePaths.icClose, - height: 24, - width: 24, - fit: BoxFit.fill, - ), - tooltip: AppLocalizations.of(context).close, - onTap: () => controller.closeView(context), ), - ) - ], + ), + ), ); } } \ No newline at end of file diff --git a/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_tablet_builder.dart b/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_tablet_builder.dart deleted file mode 100644 index 5d300845c..000000000 --- a/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_tablet_builder.dart +++ /dev/null @@ -1,149 +0,0 @@ -import 'package:core/presentation/views/button/icon_button_web.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:get/get.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/email_recovery_controller.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/model/email_recovery_field.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/styles/email_recovery_form_styles.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/check_box_has_attachment_widget.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/date_selection_field/date_selection_field_web_widget.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/limits_banner.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/list_button_widget.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/text_input_field/text_input_field_suggestion_widget.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/text_input_field/text_input_field_widget.dart'; -import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; - -class EmailRecoveryFormTabletBuilder extends GetWidget { - @override - final controller = Get.find(); - - EmailRecoveryFormTabletBuilder({super.key}); - - @override - Widget build(BuildContext context) { - return Stack( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: EmailRecoveryFormStyles.padding, - alignment: Alignment.center, - child: Text( - AppLocalizations.of(context).recoverDeletedMessages, - style: EmailRecoveryFormStyles.titleTextStyle, - ), - ), - Expanded( - child: SingleChildScrollView( - physics: const ClampingScrollPhysics(), - child: Padding( - padding: EmailRecoveryFormStyles.inputAreaPadding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LimitsBanner( - bannerContent: AppLocalizations.of(context).recoverDeletedMessagesBannerContent(controller.getRestorationHorizonAsString()), - ), - const SizedBox(height: 16.0), - Obx(() => DateSelectionFieldWebWidget( - field: EmailRecoveryField.deletionDate, - imagePaths: controller.imagePaths, - responsiveUtils: controller.responsiveUtils, - startDate: controller.startDeletionDate.value, - endDate: controller.endDeletionDate.value, - recoveryTimeSelected: controller.deletionDateFieldSelected.value, - onTapCalendar: () => controller.onSelectDeletionDateRange(context), - onRecoveryTimeSelected: (type) => controller.onDeletionDateTypeSelected(context, type), - restorationHorizon: controller.getRestorationHorizonAsDateTime(), - )), - Obx(() => DateSelectionFieldWebWidget( - field: EmailRecoveryField.receptionDate, - imagePaths: controller.imagePaths, - responsiveUtils: controller.responsiveUtils, - startDate: controller.startReceptionDate.value, - endDate: controller.endReceptionDate.value, - recoveryTimeSelected: controller.receptionDateFieldSelected.value, - onTapCalendar: () => controller.onSelectReceptionDateRange(context), - onRecoveryTimeSelected: (type) => controller.onReceptionDateTypeSelected(context, type), - restorationHorizon: controller.getRestorationHorizonAsDateTime(), - )), - TextInputFieldWidget( - field: EmailRecoveryField.subject, - textEditingController: controller.subjectFieldInputController, - currentFocusNode: controller.focusManager.subjectFieldFocusNode, - nextFocusNode: controller.focusManager.recipientsFieldFocusNode, - responsiveUtils: controller.responsiveUtils, - imagePaths: controller.imagePaths, - ), - Obx(() => TextInputFieldSuggestionWidget( - keyTagEditor: controller.recipientsFieldKey, - field: EmailRecoveryField.recipients, - listEmailAddress: controller.listRecipients, - responsiveUtils: controller.responsiveUtils, - expandMode: controller.recipientsExpandMode.value, - minInputLengthAutocomplete: controller.minInputLengthAutocomplete, - textEditingController: controller.recipientsFieldInputController, - focusNode: controller.focusManager.recipientsFieldFocusNode, - nextFocusNode: controller.focusManager.senderFieldFocusNode, - onShowFullListEmailAddressAction: controller.showFullEmailAddress, - onUpdateListEmailAddressAction: controller.updateListEmailAddress, - onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, - )), - Obx(() => TextInputFieldSuggestionWidget( - keyTagEditor: controller.senderFieldKey, - field: EmailRecoveryField.sender, - listEmailAddress: controller.listSenders, - responsiveUtils: controller.responsiveUtils, - expandMode: controller.senderExpandMode.value, - minInputLengthAutocomplete: controller.minInputLengthAutocomplete, - textEditingController: controller.senderFieldInputController, - focusNode: controller.focusManager.senderFieldFocusNode, - nextFocusNode: controller.focusManager.attachmentCheckboxFocusNode, - onShowFullListEmailAddressAction: controller.showFullEmailAddress, - onUpdateListEmailAddressAction: controller.updateListEmailAddress, - onSuggestionEmailAddress: controller.getAutoCompleteSuggestion, - )), - ], - ), - ), - ) - ), - Padding( - padding: EmailRecoveryFormStyles.bottomAreaPadding, - child: Row( - children: [ - Obx(() => CheckBoxHasAttachmentWidget( - hasAttachmentValue: controller.hasAttachment.value, - currentFocusNode: controller.focusManager.attachmentCheckboxFocusNode, - onChanged: (value) => controller.onChangeHasAttachment(value), - )), - SizedBox(width: controller.responsiveUtils.getDeviceWidth(context) * 0.04), - Expanded( - child: ListButtonWidget( - onCancel: () => controller.closeView(context), - onRestore: () => controller.onRestore(context), - responsiveUtils: controller.responsiveUtils, - ), - ) - ], - ), - ) - ], - ), - Positioned( - top: EmailRecoveryFormStyles.iconClosePositionTop, - right: EmailRecoveryFormStyles.iconClosePositionRight, - child: buildIconWeb( - icon: SvgPicture.asset( - controller.imagePaths.icClose, - fit: BoxFit.fill, - ), - tooltip: AppLocalizations.of(context).close, - onTap: () => controller.closeView(context), - ), - ) - ], - ); - } -} \ No newline at end of file diff --git a/lib/features/email_recovery/presentation/widgets/limits_banner.dart b/lib/features/email_recovery/presentation/widgets/limits_banner.dart index f00d9ec2d..07e249672 100644 --- a/lib/features/email_recovery/presentation/widgets/limits_banner.dart +++ b/lib/features/email_recovery/presentation/widgets/limits_banner.dart @@ -3,32 +3,28 @@ import 'package:flutter/material.dart'; class LimitsBanner extends StatelessWidget { final String bannerContent; - + final EdgeInsetsGeometry? padding; + final bool isCenter; + const LimitsBanner({ super.key, required this.bannerContent, + this.isCenter = false, + this.padding, }); @override Widget build(BuildContext context) { - return Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0), - decoration: BoxDecoration( - color: Colors.yellow[400], - borderRadius: const BorderRadius.all(Radius.circular(8.0)), - ), - child: Center( - child: Text( - bannerContent, - style: ThemeUtils.defaultTextStyleInterFont.copyWith( - fontSize: 16, - color: Colors.black, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - ), + final bodyWidget = Text( + bannerContent, + textAlign: isCenter ? TextAlign.center : TextAlign.start, + style: ThemeUtils.textStyleM3BodyMedium1, ); + + if (padding != null) { + return Padding(padding: padding!, child: bodyWidget); + } else { + return bodyWidget; + } } } \ No newline at end of file diff --git a/lib/features/email_recovery/presentation/widgets/list_button_widget.dart b/lib/features/email_recovery/presentation/widgets/list_button_widget.dart index f41535183..070d47f74 100644 --- a/lib/features/email_recovery/presentation/widgets/list_button_widget.dart +++ b/lib/features/email_recovery/presentation/widgets/list_button_widget.dart @@ -1,47 +1,85 @@ import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/presentation/utils/responsive_utils.dart'; +import 'package:core/presentation/views/dialog/confirm_dialog_button.dart'; import 'package:flutter/material.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/styles/list_button_widget_styles.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/text_action_button_widget.dart'; +import 'package:flutter/services.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; class ListButtonWidget extends StatelessWidget { final VoidCallback onCancel; final VoidCallback onRestore; - final ResponsiveUtils responsiveUtils; + final bool isMobile; + final FocusNode? nextFocusNode; + final EdgeInsetsGeometry? padding; const ListButtonWidget({ super.key, required this.onCancel, required this.onRestore, - required this.responsiveUtils, + this.isMobile = false, + this.nextFocusNode, + this.padding, }); @override Widget build(BuildContext context) { - return Row( - children: [ - Expanded( - child: TextActionButtonWidget( - colorButton: AppColor.colorBgSearchBar, - colorText: AppColor.primaryColor, - text: AppLocalizations.of(context).cancel, - onAction: onCancel - ), - ), - SizedBox(width: responsiveUtils.isMobile(context) - ? ListButtonWidgetStyles.mobileSpace - : ListButtonWidgetStyles.space - ), - Expanded( - child: TextActionButtonWidget( - colorButton: AppColor.primaryColor, - colorText: Colors.white, - text: AppLocalizations.of(context).restore, - onAction: onRestore - ) - ) - ], + Widget cancelButton = Container( + constraints: !isMobile ? const BoxConstraints(minWidth: 67) : null, + height: 48, + child: ConfirmDialogButton( + label: AppLocalizations.of(context).cancel, + onTapAction: onCancel, + ), ); + + Widget restoreButton = Container( + constraints: !isMobile ? const BoxConstraints(minWidth: 117) : null, + height: 48, + child: ConfirmDialogButton( + label: AppLocalizations.of(context).restore, + backgroundColor: AppColor.primaryMain, + textColor: Colors.white, + onTapAction: onRestore, + ), + ); + + if (nextFocusNode != null) { + restoreButton = KeyboardListener( + focusNode: FocusNode(), + onKeyEvent: (event) { + if (event is KeyDownEvent && + event.logicalKey == LogicalKeyboardKey.tab) { + nextFocusNode?.requestFocus(); + } + }, + child: restoreButton, + ); + } + + Widget bodyWidget; + + if (!isMobile) { + bodyWidget = Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Flexible(child: cancelButton), + const SizedBox(width: 8), + Flexible(child: restoreButton), + ], + ); + } else { + bodyWidget = Row( + children: [ + Expanded(child: cancelButton), + const SizedBox(width: 8), + Expanded(child: restoreButton), + ], + ); + } + + if (padding != null) { + return Padding(padding: padding!, child: bodyWidget); + } else { + return bodyWidget; + } } -} \ No newline at end of file +} diff --git a/lib/features/email_recovery/presentation/widgets/text_action_button_widget.dart b/lib/features/email_recovery/presentation/widgets/text_action_button_widget.dart index 26da3ce48..e69de29bb 100644 --- a/lib/features/email_recovery/presentation/widgets/text_action_button_widget.dart +++ b/lib/features/email_recovery/presentation/widgets/text_action_button_widget.dart @@ -1,38 +0,0 @@ -import 'package:core/presentation/utils/theme_utils.dart'; -import 'package:core/presentation/views/button/icon_button_web.dart'; -import 'package:flutter/material.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/styles/text_action_button_widget_styles.dart'; - -class TextActionButtonWidget extends StatelessWidget { - final Color colorButton; - final Color colorText; - final String text; - final VoidCallback onAction; - final double? minWidth; - - const TextActionButtonWidget({ - super.key, - required this.colorButton, - required this.colorText, - required this.text, - required this.onAction, - this.minWidth, - }); - - @override - Widget build(BuildContext context) { - return buildButtonWrapText( - text, - radius: TextActionButtonWidgetStyles.radius, - height: TextActionButtonWidgetStyles.height, - minWidth: minWidth, - textStyle: ThemeUtils.defaultTextStyleInterFont.copyWith( - fontSize: TextActionButtonWidgetStyles.fontSize, - color: colorText, - fontWeight: FontWeight.w500 - ), - bgColor: colorButton, - onTap: onAction - ); - } -} \ No newline at end of file diff --git a/lib/features/email_recovery/presentation/widgets/text_input_field/avatar_suggestion_item_widget.dart b/lib/features/email_recovery/presentation/widgets/text_input_field/avatar_suggestion_item_widget.dart deleted file mode 100644 index 6e68a6384..000000000 --- a/lib/features/email_recovery/presentation/widgets/text_input_field/avatar_suggestion_item_widget.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/presentation/extensions/string_extension.dart'; -import 'package:flutter/material.dart'; -import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; -import 'package:model/model.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/styles/text_input_field/avatar_suggestion_item_widget_styles.dart'; - -class AvatarSuggestionItemWidget extends StatelessWidget { - final EmailAddress emailAddress; - - const AvatarSuggestionItemWidget({super.key, required this.emailAddress}); - - @override - Widget build(BuildContext context) { - return Container( - width: AvatarSuggestionItemWidgetStyles.width, - height: AvatarSuggestionItemWidgetStyles.height, - alignment: Alignment.center, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: AppColor.avatarColor, - border: Border.all( - color: AppColor.colorShadowBgContentEmail, - width: AvatarSuggestionItemWidgetStyles.borderWidth - ) - ), - child: Text( - emailAddress.asString().isNotEmpty - ? emailAddress.asString().firstLetterToUpperCase - : '', - style: AvatarSuggestionItemWidgetStyles.textStyle, - ), - ); - } -} \ No newline at end of file diff --git a/lib/features/email_recovery/presentation/widgets/text_input_field/avatar_tag_item_widget.dart b/lib/features/email_recovery/presentation/widgets/text_input_field/avatar_tag_item_widget.dart deleted file mode 100644 index 2ee8908c2..000000000 --- a/lib/features/email_recovery/presentation/widgets/text_input_field/avatar_tag_item_widget.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:flutter/material.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/styles/text_input_field/avatar_tag_item_widget_styles.dart'; - -class AvatarTagItemWidget extends StatelessWidget { - final String tagName; - - const AvatarTagItemWidget({super.key, required this.tagName}); - - @override - Widget build(BuildContext context) { - return Container( - width: AvatarTagItemWidgetStyles.size, - height: AvatarTagItemWidgetStyles.size, - alignment: Alignment.center, - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors:AppColor.mapGradientColor[_generateIndex(tagName)] - ), - border: Border.all( - color: AppColor.colorShadowBgContentEmail, - width: AvatarTagItemWidgetStyles.borderWidth - ), - ), - child: Text( - tagName.isNotEmpty ? tagName[0].toUpperCase() : '', - style: AvatarTagItemWidgetStyles.textStyle - ), - ); - } - - int _generateIndex(String tag) { - if (tag.isNotEmpty) { - final codeUnits = tag.codeUnits; - if (codeUnits.isNotEmpty) { - final sumCodeUnits = codeUnits.sum; - final index = sumCodeUnits % AppColor.mapGradientColor.length; - return index; - } - } - return 0; - } -} \ No newline at end of file diff --git a/lib/features/email_recovery/presentation/widgets/text_input_field/suggestion_tag_item_widget.dart b/lib/features/email_recovery/presentation/widgets/text_input_field/suggestion_tag_item_widget.dart deleted file mode 100644 index 58b804158..000000000 --- a/lib/features/email_recovery/presentation/widgets/text_input_field/suggestion_tag_item_widget.dart +++ /dev/null @@ -1,122 +0,0 @@ -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:core/presentation/views/button/tmail_button_widget.dart'; -import 'package:core/utils/direction_utils.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:get/get.dart'; -import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; -import 'package:model/model.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/model/email_recovery_field.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/styles/text_input_field/suggestion_tag_item_widget_styles.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/text_input_field/avatar_tag_item_widget.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/text_input_field/text_input_field_suggestion_widget.dart'; - -class SuggestionTagItemWidget extends StatelessWidget { - final bool isCollapsed; - final bool isLatestTagFocused; - final bool isLatestEmail; - final EmailRecoveryField field; - final EmailAddress currentEmailAddress; - final List currentListEmailAddress; - final List collapsedListEmailAddress; - final OnDeleteTagAction? onDeleteTagAction; - final OnShowFullListEmailAddressAction? onShowFullAction; - - final _imagePaths = Get.find(); - - SuggestionTagItemWidget({ - super.key, - required this.field, - required this.currentEmailAddress, - required this.currentListEmailAddress, - required this.collapsedListEmailAddress, - this.isCollapsed = false, - this.isLatestTagFocused = false, - this.isLatestEmail = false, - this.onDeleteTagAction, - this.onShowFullAction, - }); - - @override - Widget build(BuildContext context) { - return Stack( - alignment: AlignmentDirectional.centerEnd, - children: [ - Padding( - padding: EdgeInsetsDirectional.only( - top: SuggestionTagItemWidgetStyles.paddingTop, - end: isCollapsed - ? SuggestionTagItemWidgetStyles.paddingEndCollapsed - : SuggestionTagItemWidgetStyles.paddingEndExpanded, - ), - child: TextFieldTapRegion( - child: InkWell( - onTap: () => isCollapsed - ? onShowFullAction?.call(field) - : null, - child: MouseRegion( - cursor: SystemMouseCursors.grab, - child: Chip( - labelPadding: EdgeInsetsDirectional.symmetric( - horizontal: SuggestionTagItemWidgetStyles.labelPaddingHorizontal, - vertical: DirectionUtils.isDirectionRTLByHasAnyRtl(currentEmailAddress.asString()) - ? 0 - : SuggestionTagItemWidgetStyles.labelPaddingVertical, - ), - label: Text( - currentEmailAddress.asString(), - maxLines: 1, - overflow: CommonTextStyle.defaultTextOverFlow, - softWrap: CommonTextStyle.defaultSoftWrap, - ), - deleteIcon: SvgPicture.asset( - _imagePaths.icClose, - fit: BoxFit.fill, - ), - labelStyle: SuggestionTagItemWidgetStyles.labelStyle, - backgroundColor: _getTagBackgroundColor(), - shape: RoundedRectangleBorder( - borderRadius: SuggestionTagItemWidgetStyles.borderRadius, - side: _getTagBorderSide(), - ), - avatar: currentEmailAddress.emailAddress.isNotEmpty - ? AvatarTagItemWidget(tagName: currentEmailAddress.emailAddress) - : null, - onDeleted: () => onDeleteTagAction?.call(currentEmailAddress), - ), - ), - ), - ), - ), - if (isCollapsed) - TMailButtonWidget.fromText( - margin: SuggestionTagItemWidgetStyles.collapsedTextMargin, - text: '+${currentListEmailAddress.length - collapsedListEmailAddress.length}', - onTapActionCallback: () => onShowFullAction?.call(field), - borderRadius: SuggestionTagItemWidgetStyles.collapsedTextBorderRadius, - textStyle: SuggestionTagItemWidgetStyles.labelStyle, - padding: SuggestionTagItemWidgetStyles.collapsedTextPadding, - backgroundColor: AppColor.colorEmailAddressTag, - ) - ], - ); - } - - Color _getTagBackgroundColor() { - if (isLatestTagFocused && isLatestEmail) { - return AppColor.colorItemRecipientSelected; - } else { - return AppColor.colorEmailAddressTag; - } - } - - BorderSide _getTagBorderSide() { - if (isLatestTagFocused && isLatestEmail) { - return SuggestionTagItemWidgetStyles.latestTagBorderSide; - } else { - return BorderSide.none; - } - } -} \ No newline at end of file diff --git a/lib/features/email_recovery/presentation/widgets/text_input_field/text_input_field_suggestion_widget.dart b/lib/features/email_recovery/presentation/widgets/text_input_field/text_input_field_suggestion_widget.dart deleted file mode 100644 index 315b2b016..000000000 --- a/lib/features/email_recovery/presentation/widgets/text_input_field/text_input_field_suggestion_widget.dart +++ /dev/null @@ -1,380 +0,0 @@ -import 'dart:async'; -import 'dart:math'; -import 'package:core/presentation/resources/image_paths.dart'; -import 'package:core/utils/app_logger.dart'; -import 'package:collection/collection.dart'; -import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/presentation/utils/responsive_utils.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:get/get.dart'; -import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; -import 'package:model/mailbox/expand_mode.dart'; -import 'package:model/extensions/email_address_extension.dart'; -import 'package:super_tag_editor/tag_editor.dart'; -import 'package:tmail_ui_user/features/composer/presentation/model/suggestion_email_address.dart'; -import 'package:tmail_ui_user/features/composer/presentation/widgets/recipient_suggestion_item_widget.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/model/email_recovery_field.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/styles/text_input_field/text_input_suggestion_field_widget_styles.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/text_input_field/suggestion_tag_item_widget.dart'; -import 'package:tmail_ui_user/main/utils/app_config.dart'; - -typedef OnSuggestionEmailAddress = Future> Function(String word, {int? limit}); -typedef OnUpdateListEmailAddressAction = void Function(EmailRecoveryField field, List newDate); -typedef OnDeleteEmailAddressTypeAction = void Function(EmailRecoveryField field, EmailAddress emailAddress); -typedef OnShowFullListEmailAddressAction = void Function(EmailRecoveryField field); -typedef OnDeleteTagAction = void Function(EmailAddress emailAddress); - -class TextInputFieldSuggestionWidget extends StatefulWidget { - final EmailRecoveryField field; - final List listEmailAddress; - final ExpandMode expandMode; - final ResponsiveUtils responsiveUtils; - final FocusNode? focusNode; - final GlobalKey? keyTagEditor; - final FocusNode? nextFocusNode; - final EdgeInsetsGeometry? padding; - final EdgeInsetsGeometry? margin; - final OnSuggestionEmailAddress? onSuggestionEmailAddress; - final OnUpdateListEmailAddressAction? onUpdateListEmailAddressAction; - final OnDeleteEmailAddressTypeAction? onDeleteEmailAddressTypeAction; - final OnShowFullListEmailAddressAction? onShowFullListEmailAddressAction; - final OnDeleteTagAction? onDeleteTagAction; - final TextEditingController? textEditingController; - final VoidCallback? onCreateAction; - final int minInputLengthAutocomplete; - - const TextInputFieldSuggestionWidget({ - super.key, - required this.field, - required this.listEmailAddress, - required this.responsiveUtils, - this.expandMode = ExpandMode.EXPAND, - this.minInputLengthAutocomplete = AppConfig.defaultMinInputLengthAutocomplete, - this.focusNode, - this.keyTagEditor, - this.nextFocusNode, - this.padding, - this.margin, - this.onSuggestionEmailAddress, - this.onUpdateListEmailAddressAction, - this.onDeleteEmailAddressTypeAction, - this.onShowFullListEmailAddressAction, - this.onDeleteTagAction, - this.textEditingController, - this.onCreateAction, - }); - - @override - State createState() => _TextInputFieldSuggestionWidgetState(); -} - -class _TextInputFieldSuggestionWidgetState extends State { - final _imagePaths = Get.find(); - - bool _lastTagFocused = false; - late List _currentListEmailAddress; - Timer? _gapBetweenTagChangedAndFindSuggestion; - - @override - void initState() { - super.initState(); - _currentListEmailAddress = widget.listEmailAddress; - } - - @override - void didUpdateWidget(covariant TextInputFieldSuggestionWidget oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.listEmailAddress != oldWidget.listEmailAddress) { - _currentListEmailAddress = widget.listEmailAddress; - } - } - - @override - Widget build(BuildContext context) { - return Padding( - padding: TextInputSuggestionFieldWidgetStyles.padding, - child: LayoutBuilder( - builder: (context, constraints) { - final title = SizedBox( - width: TextInputSuggestionFieldWidgetStyles.titleWidth, - child: Text( - widget.field.getTitle(context), - style: widget.field.getTitleTextStyle(), - ), - ); - final inputField = StatefulBuilder( - builder: (context, setState) { - return TagEditor( - key: widget.keyTagEditor, - length: _collapsedListEmailAddress.length, - controller: widget.textEditingController, - focusNodeKeyboard: widget.focusNode, - keyboardType: TextInputType.emailAddress, - textInputAction: TextInputAction.done, - debounceDuration: const Duration(milliseconds: 150), - enabled: widget.field == EmailRecoveryField.sender && _currentListEmailAddress.length == 1 - ? false - : true, - inputDecoration: InputDecoration( - filled: true, - fillColor: Colors.white, - border: TextInputSuggestionFieldWidgetStyles.border, - hintText: widget.field.getHintText(context), - hintStyle: widget.field.getHintTextStyle(), - isDense: true, - contentPadding: _currentListEmailAddress.isNotEmpty - ? TextInputSuggestionFieldWidgetStyles.contentPadding - : TextInputSuggestionFieldWidgetStyles.emptyListContentPadding, - ), - padding: _currentListEmailAddress.isNotEmpty - ? TextInputSuggestionFieldWidgetStyles.inputFieldPadding - : EdgeInsets.zero, - borderRadius: TextInputSuggestionFieldWidgetStyles.borderRadius, - borderSize: TextInputSuggestionFieldWidgetStyles.borderSize, - focusedBorderColor: AppColor.primaryColor, - enableBorder: true, - enableBorderColor: AppColor.colorInputBorderCreateMailbox, - minTextFieldWidth: TextInputSuggestionFieldWidgetStyles.minTextFieldWidth, - resetTextOnSubmitted: true, - autoScrollToInput: false, - suggestionsBoxElevation: TextInputSuggestionFieldWidgetStyles.suggestionsBoxElevation, - suggestionsBoxBackgroundColor: Colors.white, - suggestionsBoxRadius: TextInputSuggestionFieldWidgetStyles.suggestionsBoxRadius, - suggestionsBoxMaxHeight: TextInputSuggestionFieldWidgetStyles.suggestionsBoxMaxHeight, - suggestionItemHeight: TextInputSuggestionFieldWidgetStyles.suggestionBoxItemHeight, - suggestionBoxWidth: _getSuggestionBoxWidth(constraints.maxWidth), - textStyle: TextInputSuggestionFieldWidgetStyles.inputFieldTextStyle, - onFocusTagAction: (focused) => _handleFocusTagAction.call(focused, setState), - onDeleteTagAction: () => _handleDeleteLatestTagAction.call(setState), - onSelectOptionAction: (item) => _handleSelectOptionAction.call(item, setState), - onSubmitted: (value) => _handleSubmitTagAction.call(value, setState), - tagBuilder: (context, index) { - final currentEmailAddress = _currentListEmailAddress.elementAt(index); - final isLatestEmail = currentEmailAddress == _currentListEmailAddress.last; - return SuggestionTagItemWidget( - field: widget.field, - currentEmailAddress: currentEmailAddress, - currentListEmailAddress: _currentListEmailAddress, - collapsedListEmailAddress: _collapsedListEmailAddress, - isLatestEmail: isLatestEmail, - isCollapsed: _isCollapse, - isLatestTagFocused: _lastTagFocused, - onDeleteTagAction: (emailAddress) => _handleDeleteTagAction.call(emailAddress, setState), - onShowFullAction: widget.onShowFullListEmailAddressAction, - ); - }, - onTagChanged: (tag) => _handleOnTagChangeAction.call(tag, setState), - findSuggestions: (queryString) => _findSuggestions( - queryString, - limit: AppConfig.defaultLimitAutocomplete, - ), - isLoadMoreOnlyOnce: true, - isLoadMoreReplaceAllOld: false, - loadMoreSuggestions: _findSuggestions, - suggestionBuilder: (context, tagEditorState, suggestionEmailAddress, index, length, highlight, suggestionValid) { - return RecipientSuggestionItemWidget( - imagePaths: _imagePaths, - suggestionState: suggestionEmailAddress.state, - emailAddress: suggestionEmailAddress.emailAddress, - suggestionValid: suggestionValid, - highlight: highlight, - onSelectedAction: (emailAddress) { - setState(() => _currentListEmailAddress.add(emailAddress)); - _updateListEmailAddressAction(); - tagEditorState.resetTextField(); - tagEditorState.closeSuggestionBox(); - }, - ); - }, - onHandleKeyEventAction: (event) { - if (event is KeyDownEvent) { - switch (event.logicalKey) { - case LogicalKeyboardKey.tab: - widget.nextFocusNode?.requestFocus(); - break; - default: - break; - } - } - }, - ); - }, - ); - - if (widget.responsiveUtils.isMobile(context)) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - title, - const SizedBox(height: TextInputSuggestionFieldWidgetStyles.spaceMobile), - inputField, - ] - ); - } else { - return Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - title, - const SizedBox(width: TextInputSuggestionFieldWidgetStyles.space), - Expanded( - child: inputField, - ) - ], - ); - } - }, - ), - ); - } - - bool get _isCollapse => _currentListEmailAddress.length > 1 && widget.expandMode == ExpandMode.COLLAPSE; - - List get _collapsedListEmailAddress => _isCollapse - ? _currentListEmailAddress.sublist(0, 1) - : _currentListEmailAddress; - - double? _getSuggestionBoxWidth(double maxWidth) { - if (maxWidth < ResponsiveUtils.minTabletWidth) { - final newWidth = min(maxWidth, 300.0); - return newWidth; - } else { - return null; - } - } - - void _handleFocusTagAction(bool focused, StateSetter stateSetter) { - if (mounted) { - stateSetter(() => _lastTagFocused = focused); - } - } - - void _handleDeleteLatestTagAction(StateSetter stateSetter) { - if (_currentListEmailAddress.isNotEmpty) { - stateSetter(_currentListEmailAddress.removeLast); - _updateListEmailAddressAction(); - } - } - - void _updateListEmailAddressAction() { - widget.onUpdateListEmailAddressAction?.call( - widget.field, - _currentListEmailAddress - ); - } - - void _handleSelectOptionAction( - SuggestionEmailAddress suggestionEmailAddress, - StateSetter stateSetter - ) { - if (!_isDuplicated(suggestionEmailAddress.emailAddress.emailAddress)) { - stateSetter(() => _currentListEmailAddress.add(suggestionEmailAddress.emailAddress)); - _updateListEmailAddressAction(); - } - } - - bool _isDuplicated(String inputEmail) { - if (inputEmail.isEmpty) { - return false; - } - return _currentListEmailAddress - .map((emailAddress) => emailAddress.email) - .whereNotNull() - .contains(inputEmail); - } - - void _handleSubmitTagAction( - String value, - StateSetter stateSetter - ) { - final textTrim = value.trim(); - if (textTrim.isEmpty) { - widget.onCreateAction?.call(); - return; - } - - if (!_isDuplicated(textTrim)) { - stateSetter(() => _currentListEmailAddress.add(EmailAddress(null, textTrim))); - _updateListEmailAddressAction(); - } - } - - void _handleDeleteTagAction(EmailAddress emailAddress, StateSetter stateSetter) { - if (_currentListEmailAddress.isNotEmpty) { - stateSetter(() => _currentListEmailAddress.remove(emailAddress)); - _updateListEmailAddressAction(); - } - } - - void _handleOnTagChangeAction( - String value, - StateSetter stateSetter - ) { - final textTrim = value.trim(); - if (!_isDuplicated(textTrim)) { - stateSetter(() => _currentListEmailAddress.add(EmailAddress(null, textTrim))); - _updateListEmailAddressAction(); - } - _gapBetweenTagChangedAndFindSuggestion = Timer( - const Duration(seconds: 1), - _handleGapBetweenTagChangedAndFindSuggestion - ); - } - - void _handleGapBetweenTagChangedAndFindSuggestion() { - log('_TextFieldAutocompleteEmailAddressWebState::_handleGapBetweenTagChangedAndFindSuggestion:Timeout'); - } - - FutureOr> _findSuggestions(String query, {int? limit}) async { - if (_gapBetweenTagChangedAndFindSuggestion?.isActive ?? false) { - return []; - } - - final processedQuery = query.trim(); - if (processedQuery.isEmpty) { - return []; - } - - final tmailSuggestion = List.empty(growable: true); - if ( - processedQuery.length >= widget.minInputLengthAutocomplete - && widget.onSuggestionEmailAddress != null - ) { - final listEmailAddress = await widget.onSuggestionEmailAddress!( - processedQuery, - limit: limit, - ); - log('_TextFieldAutocompleteEmailAddressWebState::_findSuggestions: listEmailAddress: $listEmailAddress'); - final listSuggestionEmailAddress = listEmailAddress.map((emailAddress) => _toSuggestionEmailAddress(emailAddress, _currentListEmailAddress)); - tmailSuggestion.addAll(listSuggestionEmailAddress); - } - - tmailSuggestion.addAll(_matchedSuggestionEmailAddress(processedQuery, _currentListEmailAddress)); - - final currentTextOnTextField = widget.textEditingController?.text ?? ''; - if (currentTextOnTextField.isEmpty) { - return []; - } - - return tmailSuggestion.toSet().toList(); - } - - SuggestionEmailAddress _toSuggestionEmailAddress(EmailAddress item, List addedEmailAddresses) { - if (addedEmailAddresses.contains(item)) { - return SuggestionEmailAddress(item, state: SuggestionEmailState.duplicated); - } else { - return SuggestionEmailAddress(item); - } - } - - Iterable _matchedSuggestionEmailAddress(String query, List addedEmailAddress) { - return addedEmailAddress - .where((addedMail) => addedMail.emailAddress.contains(query)) - .map((emailAddress) => SuggestionEmailAddress( - emailAddress, - state: SuggestionEmailState.duplicated - )); - } -} \ No newline at end of file diff --git a/lib/features/email_recovery/presentation/widgets/text_input_field/text_input_field_widget.dart b/lib/features/email_recovery/presentation/widgets/text_input_field/text_input_field_widget.dart deleted file mode 100644 index b1be54cd0..000000000 --- a/lib/features/email_recovery/presentation/widgets/text_input_field/text_input_field_widget.dart +++ /dev/null @@ -1,102 +0,0 @@ -import 'package:core/presentation/resources/image_paths.dart'; -import 'package:core/presentation/utils/responsive_utils.dart'; -import 'package:core/presentation/views/text/text_field_builder.dart'; -import 'package:flutter/material.dart'; -import 'package:tmail_ui_user/features/base/isolate/background_isolate_binary_messenger/background_isolate_binary_messenger_mobile.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/model/email_recovery_field.dart'; -import 'package:tmail_ui_user/features/email_recovery/presentation/styles/text_input_field/text_input_field_widget_styles.dart'; - -typedef OnTextSubmitted = void Function(String text)?; - -class TextInputFieldWidget extends StatelessWidget { - final EmailRecoveryField field; - final ResponsiveUtils responsiveUtils; - final ImagePaths imagePaths; - final TextEditingController textEditingController; - final VoidCallback? onTap; - final MouseCursor? mouseCursor; - final FocusNode? currentFocusNode; - final FocusNode? nextFocusNode; - final OnTextSubmitted? onTextSubmitted; - - const TextInputFieldWidget({ - super.key, - required this.field, - required this.responsiveUtils, - required this.imagePaths, - required this.textEditingController, - this.onTap, - this.mouseCursor, - this.currentFocusNode, - this.nextFocusNode, - this.onTextSubmitted, - }); - - @override - Widget build(BuildContext context) { - final title = SizedBox( - width: responsiveUtils.isMobile(context) - ? null - : TextInputFieldWidgetStyles.titleWidth, - child: Text( - field.getTitle(context), - style: field.getTitleTextStyle(), - ), - ); - final inputField = TextFieldBuilder( - controller: textEditingController, - readOnly: false, - mouseCursor: mouseCursor, - maxLines: 1, - textInputAction: TextInputAction.next, - textStyle: TextInputFieldWidgetStyles.inputtedTextStyle, - onTap: onTap, - onTextSubmitted: onTextSubmitted, - decoration: InputDecoration( - filled: true, - fillColor: Colors.white, - contentPadding: TextInputFieldWidgetStyles.contentPadding, - enabledBorder: TextInputFieldWidgetStyles.enableBorder, - border: TextInputFieldWidgetStyles.border, - focusedBorder: TextInputFieldWidgetStyles.focusedBorder, - hintText: field.getHintText(context), - hintStyle: field.getHintTextStyle(), - ), - ); - - return KeyboardListener( - focusNode: currentFocusNode ?? FocusNode(), - onKeyEvent: (event) { - if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.tab) { - nextFocusNode?.requestFocus(); - } - }, - child: Padding( - padding: TextInputFieldWidgetStyles.padding, - child: responsiveUtils.isMobile(context) - ? Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - title, - const SizedBox(height: TextInputFieldWidgetStyles.spaceMobile), - inputField, - ] - ) - : SizedBox( - height: 44, - child: Row( - children: [ - title, - const SizedBox(width: TextInputFieldWidgetStyles.space), - Expanded( - child: inputField, - ) - ], - ), - ), - ) - ); - } -} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/date_drop_down_button.dart b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/date_drop_down_button.dart index 856a6515a..e69de29bb 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/date_drop_down_button.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/advanced_search/date_drop_down_button.dart @@ -1,140 +0,0 @@ -import 'package:core/presentation/resources/image_paths.dart'; -import 'package:core/presentation/utils/style_utils.dart'; -import 'package:core/presentation/utils/theme_utils.dart'; -import 'package:dropdown_button2/dropdown_button2.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:pointer_interceptor/pointer_interceptor.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/advanced_search_input_form_style.dart'; - -class DateDropDownButton extends StatelessWidget { - - final ImagePaths imagePaths; - final DateTime? startDate; - final DateTime? endDate; - final EmailReceiveTimeType? receiveTimeSelected; - final Function(EmailReceiveTimeType)? onReceiveTimeSelected; - - const DateDropDownButton( - this.imagePaths, { - Key? key, - this.startDate, - this.endDate, - this.receiveTimeSelected, - this.onReceiveTimeSelected, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return DropdownButtonHideUnderline( - child: PointerInterceptor( - child: DropdownButton2( - isExpanded: true, - items: EmailReceiveTimeType.values - .map((item) => _buildItemMenu(context, item)) - .toList(), - value: receiveTimeSelected, - customButton: Container( - height: AdvancedSearchInputFormStyle.inputFieldHeight, - decoration: BoxDecoration( - borderRadius: const BorderRadius.all( - Radius.circular(AdvancedSearchInputFormStyle.inputFieldBorderRadius), - ), - border: Border.all( - color: AdvancedSearchInputFormStyle.inputFieldBorderColor, - width: AdvancedSearchInputFormStyle.inputFieldBorderWidth, - ), - color: AdvancedSearchInputFormStyle.inputFieldBackgroundColor, - ), - padding: const EdgeInsetsDirectional.only(start: 12, end: 8), - child: Row(children: [ - Expanded(child: Text( - receiveTimeSelected?.getTitle(context, startDate: startDate, endDate: endDate) ?? '', - style: AdvancedSearchInputFormStyle.inputTextStyle, - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - )), - SvgPicture.asset(imagePaths.icDropDown) - ]), - ), - onChanged: (value) { - if (value != null) { - onReceiveTimeSelected?.call(value); - } - }, - buttonStyleData: ButtonStyleData( - height: AdvancedSearchInputFormStyle.inputFieldHeight, - padding: const EdgeInsetsDirectional.only(start: 12, end: 8), - decoration: BoxDecoration( - borderRadius: const BorderRadius.all( - Radius.circular(AdvancedSearchInputFormStyle.inputFieldBorderRadius), - ), - border: Border.all( - color: AdvancedSearchInputFormStyle.inputFieldBorderColor, - width: AdvancedSearchInputFormStyle.inputFieldBorderWidth, - ), - color: AdvancedSearchInputFormStyle.inputFieldBackgroundColor, - ) - ), - dropdownStyleData: DropdownStyleData( - maxHeight: 200, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: Colors.white), - elevation: 4, - offset: const Offset(0.0, -8.0), - scrollbarTheme: ScrollbarThemeData( - radius: const Radius.circular(40), - thickness: WidgetStateProperty.all(6), - thumbVisibility: WidgetStateProperty.all(true), - ), - ), - iconStyleData: IconStyleData( - icon: SvgPicture.asset(imagePaths.icDropDown), - ), - menuItemStyleData: const MenuItemStyleData( - height: 44, - padding: EdgeInsets.symmetric(horizontal: 12), - ) - ), - ), - ); - } - - DropdownMenuItem _buildItemMenu( - BuildContext context, - EmailReceiveTimeType receiveTime - ) { - return DropdownMenuItem( - value: receiveTime, - child: PointerInterceptor( - child: Container( - color: Colors.transparent, - height: 44, - child: Row(children: [ - Expanded(child: Text( - receiveTime.getTitle(context), - style: ThemeUtils.defaultTextStyleInterFont.copyWith( - fontSize: 16, - fontWeight: FontWeight.normal, - color: Colors.black - ), - maxLines: 1, - softWrap: CommonTextStyle.defaultSoftWrap, - overflow: CommonTextStyle.defaultTextOverFlow, - )), - if (receiveTime == receiveTimeSelected) - SvgPicture.asset( - imagePaths.icChecked, - width: 20, - height: 20, - fit: BoxFit.fill - ) - ]), - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index b84750a36..8226a6fe8 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -4619,5 +4619,27 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "enterSubjectKeywords": "Enter subject keywords...", + "@enterSubjectKeywords": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "addAnEmailAddress": "Add an email address", + "@addAnEmailAddress": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "dialogRecoverDeletedMessagesDescription": "You can recover messages deleted within the last {period} days. Recovered emails will be restored to your recovered folder.", + "@dialogRecoverDeletedMessagesDescription": { + "type": "text", + "placeholders_order": [ + "period" + ], + "placeholders": { + "period": {} + } } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 5835723a8..0ecaa8801 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4869,4 +4869,26 @@ class AppLocalizations { name: 'createARule', ); } + + String get enterSubjectKeywords { + return Intl.message( + 'Enter subject keywords...', + name: 'enterSubjectKeywords', + ); + } + + String get addAnEmailAddress { + return Intl.message( + 'Add an email address', + name: 'addAnEmailAddress', + ); + } + + String dialogRecoverDeletedMessagesDescription(String period) { + return Intl.message( + 'You can recover messages deleted within the last $period days. Recovered emails will be restored to your recovered folder.', + name: 'dialogRecoverDeletedMessagesDescription', + args: [period], + ); + } }