Apply new design for recover deleted emails

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-07-11 01:34:17 +07:00
committed by Dat H. Pham
parent da29ff01e3
commit 42339a3003
35 changed files with 1542 additions and 1795 deletions
@@ -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);
+1 -1
View File
@@ -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,
@@ -18,6 +18,7 @@ class CustomIconLabeledCheckbox extends LabeledCheckbox {
required this.focusNode,
super.value,
super.gap = 16.0,
super.padding,
});
@override
@@ -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(
@@ -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<EmailReceiveTimeType> 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<EmailReceiveTimeType>(
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<double>(6),
thumbVisibility: WidgetStateProperty.all<bool>(true),
),
),
menuItemStyleData: const MenuItemStyleData(
height: 44,
padding: EdgeInsets.symmetric(horizontal: 12),
),
),
),
);
}
DropdownMenuItem<EmailReceiveTimeType> _buildItemMenu(
BuildContext context,
EmailReceiveTimeType receiveTime,
) {
return DropdownMenuItem<EmailReceiveTimeType>(
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,
),
],
),
),
);
}
}
@@ -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);
}
}
}
@@ -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<List<EmailAddress>> Function(
String word, {
int? limit,
});
typedef OnUpdateListEmailAddressAction = void Function(
FilterField field,
List<EmailAddress> 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<EmailAddress> 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<DefaultAutocompleteInputFieldWidget> createState() =>
_DefaultAutocompleteInputFieldWidgetState();
}
class _DefaultAutocompleteInputFieldWidgetState
extends State<DefaultAutocompleteInputFieldWidget> {
final _imagePaths = Get.find<ImagePaths>();
bool _lastTagFocused = false;
bool _isDragging = false;
late List<EmailAddress> _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<SuggestionEmailAddress>(
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<DraggableEmailAddress>(
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<EmailAddress> get _collapsedListEmailAddress => _isCollapse
? _currentListEmailAddress.sublist(0, 1)
: _currentListEmailAddress;
FutureOr<List<SuggestionEmailAddress>> _findSuggestions(
String query, {
int? limit,
}) async {
if (_gapBetweenTagChangedAndFindSuggestion?.isActive ?? false) {
return [];
}
final processedQuery = query.trim();
if (processedQuery.isEmpty) {
return [];
}
final displayedSuggestion =
List<SuggestionEmailAddress>.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<EmailAddress> addedEmailAddresses,
) {
if (addedEmailAddresses.contains(item)) {
return SuggestionEmailAddress(
item,
state: SuggestionEmailState.duplicated,
);
} else {
return SuggestionEmailAddress(item);
}
}
Iterable<SuggestionEmailAddress> _matchedSuggestionEmailAddress(
String query,
List<EmailAddress> 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);
}
}
}
@@ -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<EmailAddress> currentListEmailAddress;
final List<EmailAddress> collapsedListEmailAddress;
final OnDeleteTagAction? onDeleteTagAction;
final OnShowFullListEmailAddressAction? onShowFullAction;
final _imagePaths = Get.find<ImagePaths>();
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<DraggableEmailAddress>(
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,
@@ -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,
),
);
}
}
@@ -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<EmailReceiveTimeType> 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;
}
}
}
@@ -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),
),
);
}
}
@@ -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();
}
}
}
@@ -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;
}
}
}
@@ -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<DateTime>();
final endDeletionDate = Rxn<DateTime>();
final startReceptionDate = Rxn<DateTime>();
@@ -47,6 +47,8 @@ class EmailRecoveryController extends BaseController with DateRangePickerMixin {
List<EmailAddress> listRecipients = <EmailAddress>[];
List<EmailAddress> listSenders = <EmailAddress>[];
List<EmailReceiveTimeType> deletionDateTimeTypes = <EmailReceiveTimeType>[];
List<EmailReceiveTimeType> receptionDateTimeTypes = <EmailReceiveTimeType>[];
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<EmailAddress> 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();
}
}
@@ -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<EmailRecoveryController> {
@override
@@ -20,66 +17,8 @@ class EmailRecoveryView extends GetWidget<EmailRecoveryController> {
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),
),
);
}
@@ -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;
}
}
}
@@ -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)),
);
}
@@ -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,
)
);
}
}
@@ -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<EmailRecoveryTimeType> 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<EmailRecoveryTimeType>(
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<double>(DateSelectionDropdownButtonStyles.scrollbarThickness),
thumbVisibility: WidgetStateProperty.all<bool>(true),
),
),
iconStyleData: IconStyleData(
icon: SvgPicture.asset(imagePaths.icDropDown),
),
menuItemStyleData: DateSelectionDropdownButtonStyles.menuItemStyleData
),
),
);
}
DropdownMenuItem<EmailRecoveryTimeType> _buildItemMenu(
BuildContext context,
EmailRecoveryTimeType recoveryTime
) {
return DropdownMenuItem<EmailRecoveryTimeType>(
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
)
]
),
),
),
);
}
}
@@ -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,
)
],
),
),
),
),
)
],
),
);
}
}
@@ -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
)
],
),
),
);
}
}
@@ -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<EmailRecoveryController> {
@override
final controller = Get.find<EmailRecoveryController>();
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),
),
)
],
),
),
);
}
}
@@ -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<EmailRecoveryController> {
@override
final controller = Get.find<EmailRecoveryController>();
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),
),
)
],
),
),
);
}
}
@@ -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<EmailRecoveryController> {
@override
final controller = Get.find<EmailRecoveryController>();
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),
),
)
],
);
}
}
@@ -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;
}
}
}
@@ -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;
}
}
}
}
@@ -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
);
}
}
@@ -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,
),
);
}
}
@@ -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;
}
}
@@ -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<EmailAddress> currentListEmailAddress;
final List<EmailAddress> collapsedListEmailAddress;
final OnDeleteTagAction? onDeleteTagAction;
final OnShowFullListEmailAddressAction? onShowFullAction;
final _imagePaths = Get.find<ImagePaths>();
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;
}
}
}
@@ -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<List<EmailAddress>> Function(String word, {int? limit});
typedef OnUpdateListEmailAddressAction = void Function(EmailRecoveryField field, List<EmailAddress> 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<EmailAddress> 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<TextInputFieldSuggestionWidget> createState() => _TextInputFieldSuggestionWidgetState();
}
class _TextInputFieldSuggestionWidgetState extends State<TextInputFieldSuggestionWidget> {
final _imagePaths = Get.find<ImagePaths>();
bool _lastTagFocused = false;
late List<EmailAddress> _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<SuggestionEmailAddress>(
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<EmailAddress> 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<List<SuggestionEmailAddress>> _findSuggestions(String query, {int? limit}) async {
if (_gapBetweenTagChangedAndFindSuggestion?.isActive ?? false) {
return [];
}
final processedQuery = query.trim();
if (processedQuery.isEmpty) {
return [];
}
final tmailSuggestion = List<SuggestionEmailAddress>.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<EmailAddress> addedEmailAddresses) {
if (addedEmailAddresses.contains(item)) {
return SuggestionEmailAddress(item, state: SuggestionEmailState.duplicated);
} else {
return SuggestionEmailAddress(item);
}
}
Iterable<SuggestionEmailAddress> _matchedSuggestionEmailAddress(String query, List<EmailAddress> addedEmailAddress) {
return addedEmailAddress
.where((addedMail) => addedMail.emailAddress.contains(query))
.map((emailAddress) => SuggestionEmailAddress(
emailAddress,
state: SuggestionEmailState.duplicated
));
}
}
@@ -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,
)
],
),
),
)
);
}
}
@@ -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<EmailReceiveTimeType>(
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<double>(6),
thumbVisibility: WidgetStateProperty.all<bool>(true),
),
),
iconStyleData: IconStyleData(
icon: SvgPicture.asset(imagePaths.icDropDown),
),
menuItemStyleData: const MenuItemStyleData(
height: 44,
padding: EdgeInsets.symmetric(horizontal: 12),
)
),
),
);
}
DropdownMenuItem<EmailReceiveTimeType> _buildItemMenu(
BuildContext context,
EmailReceiveTimeType receiveTime
) {
return DropdownMenuItem<EmailReceiveTimeType>(
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
)
]),
),
),
);
}
}
+22
View File
@@ -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": {}
}
}
}
@@ -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],
);
}
}