diff --git a/lib/features/composer/presentation/styles/recipient_composer_widget_style.dart b/lib/features/composer/presentation/styles/recipient_composer_widget_style.dart index c926bd5dd..84910faca 100644 --- a/lib/features/composer/presentation/styles/recipient_composer_widget_style.dart +++ b/lib/features/composer/presentation/styles/recipient_composer_widget_style.dart @@ -6,10 +6,19 @@ class RecipientComposerWidgetStyle { static const double deleteRecipientFieldIconSize = 20; static const double space = 8; static const double enableBorderRadius = 10; + static const double suggestionsBoxElevation = 20.0; + static const double suggestionsBoxRadius = 20; + static const double suggestionsBoxMaxHeight = 350; + static const double suggestionBoxWidth = 300; + static const double minTextFieldWidth = 20; + static const double tagSpacing = 8; + + static const Duration suggestionDebounceDuration = Duration(milliseconds: 150); static const Color borderColor = AppColor.colorLineComposer; static const Color deleteRecipientFieldIconColor = AppColor.colorCollapseMailbox; static const Color enableBorderColor = AppColor.primaryColor; + static const Color suggestionsBoxBackgroundColor = Colors.white; static const EdgeInsetsGeometry deleteRecipientFieldIconPadding = EdgeInsetsDirectional.all(3); static const EdgeInsetsGeometry prefixButtonPadding = EdgeInsetsDirectional.symmetric(vertical: 3, horizontal: 5); diff --git a/lib/features/composer/presentation/widgets/recipient_composer_widget.dart b/lib/features/composer/presentation/widgets/recipient_composer_widget.dart index 91f347bad..958ea64f4 100644 --- a/lib/features/composer/presentation/widgets/recipient_composer_widget.dart +++ b/lib/features/composer/presentation/widgets/recipient_composer_widget.dart @@ -1,8 +1,10 @@ import 'dart:async'; +import 'dart:math'; import 'package:collection/collection.dart'; import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:core/utils/app_logger.dart'; import 'package:core/utils/platform_info.dart'; @@ -111,216 +113,220 @@ class _RecipientComposerWidgetState extends State { @override Widget build(BuildContext context) { - return Container( - decoration: const BoxDecoration( - border: Border( - bottom: BorderSide( - color: RecipientComposerWidgetStyle.borderColor, - width: 1 + return LayoutBuilder(builder: (context, constraints) { + return Container( + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: RecipientComposerWidgetStyle.borderColor, + width: 1 + ) ) - ) - ), - padding: widget.padding, - margin: widget.margin, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: RecipientComposerWidgetStyle.labelMargin, - child: Text( - '${widget.prefix.asName(context)}:', - style: RecipientComposerWidgetStyle.labelTextStyle + ), + padding: widget.padding, + margin: widget.margin, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: RecipientComposerWidgetStyle.labelMargin, + child: Text( + '${widget.prefix.asName(context)}:', + style: RecipientComposerWidgetStyle.labelTextStyle + ), ), - ), - const SizedBox(width: RecipientComposerWidgetStyle.space), - Expanded( - child: FocusScope( - child: Focus( - onFocusChange: (focus) => widget.onFocusEmailAddressChangeAction?.call(widget.prefix, focus), - onKey: (focusNode, event) { - if (event is RawKeyDownEvent && event.logicalKey == LogicalKeyboardKey.tab) { - widget.nextFocusNode?.requestFocus(); - widget.onFocusNextAddressAction?.call(); - return KeyEventResult.handled; - } - return KeyEventResult.ignored; - }, - child: StatefulBuilder( - builder: (context, stateSetter) { - if (PlatformInfo.isWeb) { - return DragTarget( - builder: (context, candidateData, rejectedData) { - return TagEditor( - key: widget.keyTagEditor, - length: _collapsedListEmailAddress.length, - controller: widget.controller, - focusNode: widget.focusNode, - enableBorder: _isDragging, - borderRadius: RecipientComposerWidgetStyle.enableBorderRadius, - enableBorderColor: RecipientComposerWidgetStyle.enableBorderColor, - autoDisposeFocusNode: widget.autoDisposeFocusNode, - keyboardType: TextInputType.emailAddress, - textInputAction: TextInputAction.done, - debounceDuration: const Duration(milliseconds: 150), - hasAddButton: false, - tagSpacing: 8, - autofocus: widget.prefix != PrefixEmailAddress.to && _currentListEmailAddress.isEmpty, - minTextFieldWidth: 20, - resetTextOnSubmitted: true, - suggestionsBoxElevation: 20.0, - suggestionsBoxBackgroundColor: Colors.white, - suggestionsBoxRadius: 20, - suggestionsBoxMaxHeight: 350, - textStyle: RecipientComposerWidgetStyle.inputTextStyle, - onFocusTagAction: (focused) => _handleFocusTagAction.call(focused, stateSetter), - onDeleteTagAction: () => _handleDeleteLatestTagAction.call(stateSetter), - onSelectOptionAction: (item) => _handleSelectOptionAction.call(item, stateSetter), - onSubmitted: (value) => _handleSubmitTagAction.call(value, stateSetter), - inputDecoration: const InputDecoration(border: InputBorder.none), - tagBuilder: (context, index) { - final currentEmailAddress = _currentListEmailAddress[index]; - final isLatestEmail = currentEmailAddress == _currentListEmailAddress.last; - - return RecipientTagItemWidget( - prefix: widget.prefix, - currentEmailAddress: currentEmailAddress, - currentListEmailAddress: _currentListEmailAddress, - collapsedListEmailAddress: _collapsedListEmailAddress, - isLatestEmail: isLatestEmail, - isCollapsed: _isCollapse, - isLatestTagFocused: _lastTagFocused, - onDeleteTagAction: (emailAddress) => _handleDeleteTagAction.call(emailAddress, stateSetter), - onShowFullAction: widget.onShowFullListEmailAddressAction, - ); - }, - onTagChanged: (value) => _handleOnTagChangeAction.call(value, stateSetter), - findSuggestions: _findSuggestions, - useDefaultHighlight: false, - suggestionBuilder: (context, tagEditorState, suggestionEmailAddress, index, length, highlight, suggestionValid) { - return RecipientSuggestionItemWidget( - suggestionState: suggestionEmailAddress.state, - emailAddress: suggestionEmailAddress.emailAddress, - suggestionValid: suggestionValid, - highlight: highlight, - onSelectedAction: (emailAddress) { - stateSetter(() => _currentListEmailAddress.add(emailAddress)); - _updateListEmailAddressAction(); - tagEditorState.resetTextField(); - tagEditorState.closeSuggestionBox(); - }, - ); - }, - ); - }, - onAccept: (draggableEmailAddress) => _handleAcceptDraggableEmailAddressAction(draggableEmailAddress, stateSetter), - onLeave: (draggableEmailAddress) { - if (_isDragging) { - stateSetter(() => _isDragging = false); - } - }, - onMove: (details) { - if (!_isDragging) { - stateSetter(() => _isDragging = true); - } - }, - ); - } else { - return TagEditor( - key: widget.keyTagEditor, - length: _collapsedListEmailAddress.length, - controller: widget.controller, - focusNode: widget.focusNode, - autoDisposeFocusNode: widget.autoDisposeFocusNode, - keyboardType: TextInputType.emailAddress, - textInputAction: TextInputAction.done, - debounceDuration: const Duration(milliseconds: 150), - hasAddButton: false, - tagSpacing: 8, - autofocus: widget.prefix != PrefixEmailAddress.to && _currentListEmailAddress.isEmpty, - minTextFieldWidth: 20, - resetTextOnSubmitted: true, - suggestionsBoxElevation: 20.0, - suggestionsBoxBackgroundColor: Colors.white, - suggestionsBoxRadius: 20, - suggestionsBoxMaxHeight: 350, - textStyle: RecipientComposerWidgetStyle.inputTextStyle, - onFocusTagAction: (focused) => _handleFocusTagAction.call(focused, stateSetter), - onDeleteTagAction: () => _handleDeleteLatestTagAction.call(stateSetter), - onSelectOptionAction: (item) => _handleSelectOptionAction.call(item, stateSetter), - onSubmitted: (value) => _handleSubmitTagAction.call(value, stateSetter), - inputDecoration: const InputDecoration(border: InputBorder.none), - tagBuilder: (context, index) { - final currentEmailAddress = _currentListEmailAddress[index]; - final isLatestEmail = currentEmailAddress == _currentListEmailAddress.last; - - return RecipientTagItemWidget( - prefix: widget.prefix, - currentEmailAddress: currentEmailAddress, - currentListEmailAddress: _currentListEmailAddress, - collapsedListEmailAddress: _collapsedListEmailAddress, - isLatestEmail: isLatestEmail, - isCollapsed: _isCollapse, - isLatestTagFocused: _lastTagFocused, - onDeleteTagAction: (emailAddress) => _handleDeleteTagAction.call(emailAddress, stateSetter), - onShowFullAction: widget.onShowFullListEmailAddressAction, - ); - }, - onTagChanged: (value) => _handleOnTagChangeAction.call(value, stateSetter), - findSuggestions: _findSuggestions, - useDefaultHighlight: false, - suggestionBuilder: (context, tagEditorState, suggestionEmailAddress, index, length, highlight, suggestionValid) { - return RecipientSuggestionItemWidget( - suggestionState: suggestionEmailAddress.state, - emailAddress: suggestionEmailAddress.emailAddress, - suggestionValid: suggestionValid, - highlight: highlight, - onSelectedAction: (emailAddress) { - stateSetter(() => _currentListEmailAddress.add(emailAddress)); - _updateListEmailAddressAction(); - tagEditorState.resetTextField(); - tagEditorState.closeSuggestionBox(); - }, - ); - }, - ); + const SizedBox(width: RecipientComposerWidgetStyle.space), + Expanded( + child: FocusScope( + child: Focus( + onFocusChange: (focus) => widget.onFocusEmailAddressChangeAction?.call(widget.prefix, focus), + onKey: (focusNode, event) { + if (event is RawKeyDownEvent && event.logicalKey == LogicalKeyboardKey.tab) { + widget.nextFocusNode?.requestFocus(); + widget.onFocusNextAddressAction?.call(); + return KeyEventResult.handled; } + return KeyEventResult.ignored; }, + child: StatefulBuilder( + builder: (context, stateSetter) { + if (PlatformInfo.isWeb) { + return DragTarget( + builder: (context, candidateData, rejectedData) { + return TagEditor( + key: widget.keyTagEditor, + length: _collapsedListEmailAddress.length, + controller: widget.controller, + focusNode: widget.focusNode, + enableBorder: _isDragging, + borderRadius: RecipientComposerWidgetStyle.enableBorderRadius, + enableBorderColor: RecipientComposerWidgetStyle.enableBorderColor, + autoDisposeFocusNode: widget.autoDisposeFocusNode, + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.done, + debounceDuration: RecipientComposerWidgetStyle.suggestionDebounceDuration, + hasAddButton: false, + tagSpacing: RecipientComposerWidgetStyle.tagSpacing, + autofocus: widget.prefix != PrefixEmailAddress.to && _currentListEmailAddress.isEmpty, + minTextFieldWidth: RecipientComposerWidgetStyle.minTextFieldWidth, + resetTextOnSubmitted: true, + suggestionsBoxElevation: RecipientComposerWidgetStyle.suggestionsBoxElevation, + suggestionsBoxBackgroundColor: RecipientComposerWidgetStyle.suggestionsBoxBackgroundColor, + suggestionsBoxRadius: RecipientComposerWidgetStyle.suggestionsBoxRadius, + suggestionsBoxMaxHeight: RecipientComposerWidgetStyle.suggestionsBoxMaxHeight, + suggestionBoxWidth: _getSuggestionBoxWidth(constraints.maxWidth), + textStyle: RecipientComposerWidgetStyle.inputTextStyle, + onFocusTagAction: (focused) => _handleFocusTagAction.call(focused, stateSetter), + onDeleteTagAction: () => _handleDeleteLatestTagAction.call(stateSetter), + onSelectOptionAction: (item) => _handleSelectOptionAction.call(item, stateSetter), + onSubmitted: (value) => _handleSubmitTagAction.call(value, stateSetter), + inputDecoration: const InputDecoration(border: InputBorder.none), + tagBuilder: (context, index) { + final currentEmailAddress = _currentListEmailAddress[index]; + final isLatestEmail = currentEmailAddress == _currentListEmailAddress.last; + + return RecipientTagItemWidget( + prefix: widget.prefix, + currentEmailAddress: currentEmailAddress, + currentListEmailAddress: _currentListEmailAddress, + collapsedListEmailAddress: _collapsedListEmailAddress, + isLatestEmail: isLatestEmail, + isCollapsed: _isCollapse, + isLatestTagFocused: _lastTagFocused, + onDeleteTagAction: (emailAddress) => _handleDeleteTagAction.call(emailAddress, stateSetter), + onShowFullAction: widget.onShowFullListEmailAddressAction, + ); + }, + onTagChanged: (value) => _handleOnTagChangeAction.call(value, stateSetter), + findSuggestions: _findSuggestions, + useDefaultHighlight: false, + suggestionBuilder: (context, tagEditorState, suggestionEmailAddress, index, length, highlight, suggestionValid) { + return RecipientSuggestionItemWidget( + suggestionState: suggestionEmailAddress.state, + emailAddress: suggestionEmailAddress.emailAddress, + suggestionValid: suggestionValid, + highlight: highlight, + onSelectedAction: (emailAddress) { + stateSetter(() => _currentListEmailAddress.add(emailAddress)); + _updateListEmailAddressAction(); + tagEditorState.resetTextField(); + tagEditorState.closeSuggestionBox(); + }, + ); + }, + ); + }, + onAccept: (draggableEmailAddress) => _handleAcceptDraggableEmailAddressAction(draggableEmailAddress, stateSetter), + onLeave: (draggableEmailAddress) { + if (_isDragging) { + stateSetter(() => _isDragging = false); + } + }, + onMove: (details) { + if (!_isDragging) { + stateSetter(() => _isDragging = true); + } + }, + ); + } else { + return TagEditor( + key: widget.keyTagEditor, + length: _collapsedListEmailAddress.length, + controller: widget.controller, + focusNode: widget.focusNode, + autoDisposeFocusNode: widget.autoDisposeFocusNode, + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.done, + debounceDuration: RecipientComposerWidgetStyle.suggestionDebounceDuration, + hasAddButton: false, + tagSpacing: RecipientComposerWidgetStyle.tagSpacing, + autofocus: widget.prefix != PrefixEmailAddress.to && _currentListEmailAddress.isEmpty, + minTextFieldWidth: RecipientComposerWidgetStyle.minTextFieldWidth, + resetTextOnSubmitted: true, + suggestionsBoxElevation: RecipientComposerWidgetStyle.suggestionsBoxElevation, + suggestionsBoxBackgroundColor: RecipientComposerWidgetStyle.suggestionsBoxBackgroundColor, + suggestionsBoxRadius: RecipientComposerWidgetStyle.suggestionsBoxRadius, + suggestionsBoxMaxHeight: RecipientComposerWidgetStyle.suggestionsBoxMaxHeight, + suggestionBoxWidth: _getSuggestionBoxWidth(constraints.maxWidth), + textStyle: RecipientComposerWidgetStyle.inputTextStyle, + onFocusTagAction: (focused) => _handleFocusTagAction.call(focused, stateSetter), + onDeleteTagAction: () => _handleDeleteLatestTagAction.call(stateSetter), + onSelectOptionAction: (item) => _handleSelectOptionAction.call(item, stateSetter), + onSubmitted: (value) => _handleSubmitTagAction.call(value, stateSetter), + inputDecoration: const InputDecoration(border: InputBorder.none), + tagBuilder: (context, index) { + final currentEmailAddress = _currentListEmailAddress[index]; + final isLatestEmail = currentEmailAddress == _currentListEmailAddress.last; + + return RecipientTagItemWidget( + prefix: widget.prefix, + currentEmailAddress: currentEmailAddress, + currentListEmailAddress: _currentListEmailAddress, + collapsedListEmailAddress: _collapsedListEmailAddress, + isLatestEmail: isLatestEmail, + isCollapsed: _isCollapse, + isLatestTagFocused: _lastTagFocused, + onDeleteTagAction: (emailAddress) => _handleDeleteTagAction.call(emailAddress, stateSetter), + onShowFullAction: widget.onShowFullListEmailAddressAction, + ); + }, + onTagChanged: (value) => _handleOnTagChangeAction.call(value, stateSetter), + findSuggestions: _findSuggestions, + useDefaultHighlight: false, + suggestionBuilder: (context, tagEditorState, suggestionEmailAddress, index, length, highlight, suggestionValid) { + return RecipientSuggestionItemWidget( + suggestionState: suggestionEmailAddress.state, + emailAddress: suggestionEmailAddress.emailAddress, + suggestionValid: suggestionValid, + highlight: highlight, + onSelectedAction: (emailAddress) { + stateSetter(() => _currentListEmailAddress.add(emailAddress)); + _updateListEmailAddressAction(); + tagEditorState.resetTextField(); + tagEditorState.closeSuggestionBox(); + }, + ); + }, + ); + } + }, + ) ) ) - ) - ), - const SizedBox(width: RecipientComposerWidgetStyle.space), - if (widget.prefix == PrefixEmailAddress.to && widget.ccState == PrefixRecipientState.disabled) - TMailButtonWidget.fromText( - text: AppLocalizations.of(context).cc_email_address_prefix, - textStyle: RecipientComposerWidgetStyle.prefixButtonTextStyle, - backgroundColor: Colors.transparent, - padding: RecipientComposerWidgetStyle.prefixButtonPadding, - margin: RecipientComposerWidgetStyle.recipientMargin, - onTapActionCallback: () => widget.onAddEmailAddressTypeAction?.call(PrefixEmailAddress.cc), ), - if (widget.prefix == PrefixEmailAddress.to && widget.bccState == PrefixRecipientState.disabled) - TMailButtonWidget.fromText( - text: AppLocalizations.of(context).bcc_email_address_prefix, - textStyle: RecipientComposerWidgetStyle.prefixButtonTextStyle, - backgroundColor: Colors.transparent, - padding: RecipientComposerWidgetStyle.prefixButtonPadding, - margin: RecipientComposerWidgetStyle.recipientMargin, - onTapActionCallback: () => widget.onAddEmailAddressTypeAction?.call(PrefixEmailAddress.bcc), - ), - if (widget.prefix != PrefixEmailAddress.to) - TMailButtonWidget.fromIcon( - icon: _imagePaths.icClose, - backgroundColor: Colors.transparent, - iconColor: RecipientComposerWidgetStyle.deleteRecipientFieldIconColor, - iconSize: RecipientComposerWidgetStyle.deleteRecipientFieldIconSize, - padding: RecipientComposerWidgetStyle.deleteRecipientFieldIconPadding, - margin: RecipientComposerWidgetStyle.recipientMargin, - onTapActionCallback: () => widget.onDeleteEmailAddressTypeAction?.call(widget.prefix), - ) - ] - ), - ); + const SizedBox(width: RecipientComposerWidgetStyle.space), + if (widget.prefix == PrefixEmailAddress.to && widget.ccState == PrefixRecipientState.disabled) + TMailButtonWidget.fromText( + text: AppLocalizations.of(context).cc_email_address_prefix, + textStyle: RecipientComposerWidgetStyle.prefixButtonTextStyle, + backgroundColor: Colors.transparent, + padding: RecipientComposerWidgetStyle.prefixButtonPadding, + margin: RecipientComposerWidgetStyle.recipientMargin, + onTapActionCallback: () => widget.onAddEmailAddressTypeAction?.call(PrefixEmailAddress.cc), + ), + if (widget.prefix == PrefixEmailAddress.to && widget.bccState == PrefixRecipientState.disabled) + TMailButtonWidget.fromText( + text: AppLocalizations.of(context).bcc_email_address_prefix, + textStyle: RecipientComposerWidgetStyle.prefixButtonTextStyle, + backgroundColor: Colors.transparent, + padding: RecipientComposerWidgetStyle.prefixButtonPadding, + margin: RecipientComposerWidgetStyle.recipientMargin, + onTapActionCallback: () => widget.onAddEmailAddressTypeAction?.call(PrefixEmailAddress.bcc), + ), + if (widget.prefix != PrefixEmailAddress.to) + TMailButtonWidget.fromIcon( + icon: _imagePaths.icClose, + backgroundColor: Colors.transparent, + iconColor: RecipientComposerWidgetStyle.deleteRecipientFieldIconColor, + iconSize: RecipientComposerWidgetStyle.deleteRecipientFieldIconSize, + padding: RecipientComposerWidgetStyle.deleteRecipientFieldIconPadding, + margin: RecipientComposerWidgetStyle.recipientMargin, + onTapActionCallback: () => widget.onDeleteEmailAddressTypeAction?.call(widget.prefix), + ) + ] + ), + ); + }); } bool get _isCollapse => _currentListEmailAddress.length > 1 && widget.expandMode == ExpandMode.COLLAPSE; @@ -473,4 +479,13 @@ class _RecipientComposerWidgetState extends State { } } } + + double? _getSuggestionBoxWidth(double maxWidth) { + if (maxWidth < ResponsiveUtils.minTabletWidth) { + final newWidth = min(maxWidth, RecipientComposerWidgetStyle.suggestionBoxWidth); + return newWidth; + } else { + return null; + } + } } \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 4ed30885d..e37829e50 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1529,7 +1529,7 @@ packages: description: path: "." ref: master - resolved-ref: "22fcc31f816ed62ffa1ef38c2103333b7f001345" + resolved-ref: "6e066b29659c6d3a2f20686219bb131164ab2e51" url: "https://github.com/dab246/super_tag_editor.git" source: git version: "0.2.0"