Apply new design for recover deleted emails
Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user