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
@@ -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);
}
}
}
@@ -0,0 +1,134 @@
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: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/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';
class DefaultAutocompleteTagItemWidget extends StatelessWidget {
final bool isCollapsed;
final bool isLatestTagFocused;
final bool isLatestEmail;
final FilterField field;
final EmailAddress currentEmailAddress;
final String iconClose;
final List<EmailAddress> currentListEmailAddress;
final List<EmailAddress> collapsedListEmailAddress;
final OnDeleteTagAction? onDeleteTagAction;
final OnShowFullListEmailAddressAction? onShowFullAction;
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,
this.onDeleteTagAction,
this.onShowFullAction,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Stack(
alignment: AlignmentDirectional.centerEnd,
children: [
Padding(
padding: EdgeInsetsDirectional.only(
top: AutoCompleteTagItemWebStyle.paddingTop,
end: isCollapsed ? AutoCompleteTagItemWebStyle.paddingEnd : 0,
),
child: Draggable<DraggableEmailAddress>(
data: DraggableEmailAddress(
emailAddress: currentEmailAddress,
filterField: field,
),
feedback: DraggableRecipientTagWidget(
emailAddress: currentEmailAddress,
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
),
childWhenDragging: DraggableRecipientTagWidget(
emailAddress: currentEmailAddress,
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
),
child: TextFieldTapRegion(
child: InkWell(
onTap: () => isCollapsed
? onShowFullAction?.call(field)
: null,
child: MouseRegion(
cursor: SystemMouseCursors.grab,
child: Chip(
labelPadding: EdgeInsetsDirectional.symmetric(
horizontal: AutoCompleteTagItemWebStyle.labelPaddingHorizontal,
vertical: DirectionUtils.isDirectionRTLByHasAnyRtl(currentEmailAddress.asString()) ? 0 : 2
),
label: Text(
currentEmailAddress.asString(),
maxLines: 1,
overflow: CommonTextStyle.defaultTextOverFlow,
softWrap: CommonTextStyle.defaultSoftWrap,
),
deleteIcon: SvgPicture.asset(
iconClose,
fit: BoxFit.fill,
),
padding: EdgeInsets.zero,
labelStyle: AutoCompleteTagItemWebStyle.labelTextStyle,
backgroundColor: _getTagBackgroundColor(),
shape: RoundedRectangleBorder(
borderRadius: AutoCompleteTagItemWebStyle.shapeBorderRadius,
side: _getTagBorderSide(),
),
avatar: currentEmailAddress.emailAddress.isNotEmpty
? AvatarTagItemWidget(tagName: currentEmailAddress.emailAddress)
: null,
onDeleted: () => onDeleteTagAction?.call(currentEmailAddress),
),
),
),
),
),
),
if (isCollapsed)
TMailButtonWidget.fromText(
margin: AutoCompleteTagItemWebStyle.marginCollapsed,
text: '+${currentListEmailAddress.length - collapsedListEmailAddress.length}',
onTapActionCallback: () => onShowFullAction?.call(field),
borderRadius: 10,
textStyle: AutoCompleteTagItemWebStyle.collapsedTextStyle,
padding: AutoCompleteTagItemWebStyle.collapsedPadding,
backgroundColor: AutoCompleteTagItemWebStyle.collapsedBackgroundColor,
)
],
);
}
Color _getTagBackgroundColor() {
if (isLatestTagFocused && isLatestEmail) {
return AppColor.colorItemRecipientSelected;
} else {
return AppColor.colorEmailAddressTag;
}
}
BorderSide _getTagBorderSide() {
if (isLatestTagFocused && isLatestEmail) {
return const BorderSide(width: 1, color: AppColor.primaryColor);
} else {
return const BorderSide(width: 0, color: AppColor.colorEmailAddressTag);
}
}
}
@@ -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;
}
}
}