TF-2180 Fix suggestion list being hidden once user use device with small screen

(cherry picked from commit 924490be86d36a0cb206d867ced790ae49454673)
This commit is contained in:
dab246
2023-09-29 15:36:36 +07:00
committed by Dat H. Pham
parent 37b829ba0d
commit 20fd8ebdee
3 changed files with 228 additions and 204 deletions
@@ -6,10 +6,19 @@ class RecipientComposerWidgetStyle {
static const double deleteRecipientFieldIconSize = 20; static const double deleteRecipientFieldIconSize = 20;
static const double space = 8; static const double space = 8;
static const double enableBorderRadius = 10; 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 borderColor = AppColor.colorLineComposer;
static const Color deleteRecipientFieldIconColor = AppColor.colorCollapseMailbox; static const Color deleteRecipientFieldIconColor = AppColor.colorCollapseMailbox;
static const Color enableBorderColor = AppColor.primaryColor; static const Color enableBorderColor = AppColor.primaryColor;
static const Color suggestionsBoxBackgroundColor = Colors.white;
static const EdgeInsetsGeometry deleteRecipientFieldIconPadding = EdgeInsetsDirectional.all(3); static const EdgeInsetsGeometry deleteRecipientFieldIconPadding = EdgeInsetsDirectional.all(3);
static const EdgeInsetsGeometry prefixButtonPadding = EdgeInsetsDirectional.symmetric(vertical: 3, horizontal: 5); static const EdgeInsetsGeometry prefixButtonPadding = EdgeInsetsDirectional.symmetric(vertical: 3, horizontal: 5);
@@ -1,8 +1,10 @@
import 'dart:async'; import 'dart:async';
import 'dart:math';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:core/presentation/resources/image_paths.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/presentation/views/button/tmail_button_widget.dart';
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart'; import 'package:core/utils/platform_info.dart';
@@ -111,216 +113,220 @@ class _RecipientComposerWidgetState extends State<RecipientComposerWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return LayoutBuilder(builder: (context, constraints) {
decoration: const BoxDecoration( return Container(
border: Border( decoration: const BoxDecoration(
bottom: BorderSide( border: Border(
color: RecipientComposerWidgetStyle.borderColor, bottom: BorderSide(
width: 1 color: RecipientComposerWidgetStyle.borderColor,
width: 1
)
) )
) ),
), padding: widget.padding,
padding: widget.padding, margin: widget.margin,
margin: widget.margin, child: Row(
child: Row( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ Padding(
Padding( padding: RecipientComposerWidgetStyle.labelMargin,
padding: RecipientComposerWidgetStyle.labelMargin, child: Text(
child: Text( '${widget.prefix.asName(context)}:',
'${widget.prefix.asName(context)}:', style: RecipientComposerWidgetStyle.labelTextStyle
style: RecipientComposerWidgetStyle.labelTextStyle ),
), ),
), const SizedBox(width: RecipientComposerWidgetStyle.space),
const SizedBox(width: RecipientComposerWidgetStyle.space), Expanded(
Expanded( child: FocusScope(
child: FocusScope( child: Focus(
child: Focus( onFocusChange: (focus) => widget.onFocusEmailAddressChangeAction?.call(widget.prefix, focus),
onFocusChange: (focus) => widget.onFocusEmailAddressChangeAction?.call(widget.prefix, focus), onKey: (focusNode, event) {
onKey: (focusNode, event) { if (event is RawKeyDownEvent && event.logicalKey == LogicalKeyboardKey.tab) {
if (event is RawKeyDownEvent && event.logicalKey == LogicalKeyboardKey.tab) { widget.nextFocusNode?.requestFocus();
widget.nextFocusNode?.requestFocus(); widget.onFocusNextAddressAction?.call();
widget.onFocusNextAddressAction?.call(); return KeyEventResult.handled;
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
},
child: StatefulBuilder(
builder: (context, stateSetter) {
if (PlatformInfo.isWeb) {
return DragTarget<DraggableEmailAddress>(
builder: (context, candidateData, rejectedData) {
return TagEditor<SuggestionEmailAddress>(
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<SuggestionEmailAddress>(
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();
},
);
},
);
} }
return KeyEventResult.ignored;
}, },
child: StatefulBuilder(
builder: (context, stateSetter) {
if (PlatformInfo.isWeb) {
return DragTarget<DraggableEmailAddress>(
builder: (context, candidateData, rejectedData) {
return TagEditor<SuggestionEmailAddress>(
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<SuggestionEmailAddress>(
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) const SizedBox(width: RecipientComposerWidgetStyle.space),
TMailButtonWidget.fromText( if (widget.prefix == PrefixEmailAddress.to && widget.ccState == PrefixRecipientState.disabled)
text: AppLocalizations.of(context).bcc_email_address_prefix, TMailButtonWidget.fromText(
textStyle: RecipientComposerWidgetStyle.prefixButtonTextStyle, text: AppLocalizations.of(context).cc_email_address_prefix,
backgroundColor: Colors.transparent, textStyle: RecipientComposerWidgetStyle.prefixButtonTextStyle,
padding: RecipientComposerWidgetStyle.prefixButtonPadding, backgroundColor: Colors.transparent,
margin: RecipientComposerWidgetStyle.recipientMargin, padding: RecipientComposerWidgetStyle.prefixButtonPadding,
onTapActionCallback: () => widget.onAddEmailAddressTypeAction?.call(PrefixEmailAddress.bcc), margin: RecipientComposerWidgetStyle.recipientMargin,
), onTapActionCallback: () => widget.onAddEmailAddressTypeAction?.call(PrefixEmailAddress.cc),
if (widget.prefix != PrefixEmailAddress.to) ),
TMailButtonWidget.fromIcon( if (widget.prefix == PrefixEmailAddress.to && widget.bccState == PrefixRecipientState.disabled)
icon: _imagePaths.icClose, TMailButtonWidget.fromText(
backgroundColor: Colors.transparent, text: AppLocalizations.of(context).bcc_email_address_prefix,
iconColor: RecipientComposerWidgetStyle.deleteRecipientFieldIconColor, textStyle: RecipientComposerWidgetStyle.prefixButtonTextStyle,
iconSize: RecipientComposerWidgetStyle.deleteRecipientFieldIconSize, backgroundColor: Colors.transparent,
padding: RecipientComposerWidgetStyle.deleteRecipientFieldIconPadding, padding: RecipientComposerWidgetStyle.prefixButtonPadding,
margin: RecipientComposerWidgetStyle.recipientMargin, margin: RecipientComposerWidgetStyle.recipientMargin,
onTapActionCallback: () => widget.onDeleteEmailAddressTypeAction?.call(widget.prefix), 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; bool get _isCollapse => _currentListEmailAddress.length > 1 && widget.expandMode == ExpandMode.COLLAPSE;
@@ -473,4 +479,13 @@ class _RecipientComposerWidgetState extends State<RecipientComposerWidget> {
} }
} }
} }
double? _getSuggestionBoxWidth(double maxWidth) {
if (maxWidth < ResponsiveUtils.minTabletWidth) {
final newWidth = min(maxWidth, RecipientComposerWidgetStyle.suggestionBoxWidth);
return newWidth;
} else {
return null;
}
}
} }
+1 -1
View File
@@ -1529,7 +1529,7 @@ packages:
description: description:
path: "." path: "."
ref: master ref: master
resolved-ref: "22fcc31f816ed62ffa1ef38c2103333b7f001345" resolved-ref: "6e066b29659c6d3a2f20686219bb131164ab2e51"
url: "https://github.com/dab246/super_tag_editor.git" url: "https://github.com/dab246/super_tag_editor.git"
source: git source: git
version: "0.2.0" version: "0.2.0"