TF-1884 Support RTL mode for TextField

(cherry picked from commit 3f8445141bea9dbf276a6a336114e4e2b549936f)
This commit is contained in:
dab246
2023-06-14 11:47:13 +07:00
committed by Dat H. Pham
parent 0cc2a2f0cc
commit df19b979a2
28 changed files with 500 additions and 485 deletions
-1
View File
@@ -47,7 +47,6 @@ export 'utils/platform_info.dart';
export 'presentation/views/text/slogan_builder.dart'; export 'presentation/views/text/slogan_builder.dart';
export 'presentation/views/text/text_field_builder.dart'; export 'presentation/views/text/text_field_builder.dart';
export 'presentation/views/text/input_decoration_builder.dart'; export 'presentation/views/text/input_decoration_builder.dart';
export 'presentation/views/text/text_builder.dart';
export 'presentation/views/text/rich_text_builder.dart'; export 'presentation/views/text/rich_text_builder.dart';
export 'presentation/views/responsive/responsive_widget.dart'; export 'presentation/views/responsive/responsive_widget.dart';
export 'presentation/views/list/tree_view.dart'; export 'presentation/views/list/tree_view.dart';
@@ -5,6 +5,7 @@ import 'dart:io';
import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -576,6 +577,7 @@ class _TypeAheadFieldQuickSearchState<T, R> extends State<TypeAheadFieldQuickSea
FocusNode? _focusNode; FocusNode? _focusNode;
TextEditingController? _textEditingController; TextEditingController? _textEditingController;
_SuggestionsBox? _suggestionsBox; _SuggestionsBox? _suggestionsBox;
late TextDirection _textDirection;
TextEditingController? get _effectiveController => TextEditingController? get _effectiveController =>
widget.textFieldConfiguration.controller ?? _textEditingController; widget.textFieldConfiguration.controller ?? _textEditingController;
@@ -622,6 +624,8 @@ class _TypeAheadFieldQuickSearchState<T, R> extends State<TypeAheadFieldQuickSea
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
_textDirection = widget.textFieldConfiguration.textDirection ?? TextDirection.ltr;
if (widget.textFieldConfiguration.controller == null) { if (widget.textFieldConfiguration.controller == null) {
_textEditingController = TextEditingController(); _textEditingController = TextEditingController();
} }
@@ -840,7 +844,17 @@ class _TypeAheadFieldQuickSearchState<T, R> extends State<TypeAheadFieldQuickSea
maxLength: widget.textFieldConfiguration.maxLength, maxLength: widget.textFieldConfiguration.maxLength,
maxLengthEnforcement: widget.textFieldConfiguration.maxLengthEnforcement, maxLengthEnforcement: widget.textFieldConfiguration.maxLengthEnforcement,
obscureText: widget.textFieldConfiguration.obscureText, obscureText: widget.textFieldConfiguration.obscureText,
onChanged: widget.textFieldConfiguration.onChanged, onChanged: (input) {
widget.textFieldConfiguration.onChanged?.call(input);
if (input.isNotEmpty) {
final directionByText = DirectionUtils.getDirectionByEndsText(input);
if (directionByText != _textDirection) {
setState(() {
_textDirection = directionByText;
});
}
}
},
onSubmitted: widget.textFieldConfiguration.onSubmitted, onSubmitted: widget.textFieldConfiguration.onSubmitted,
onEditingComplete: widget.textFieldConfiguration.onEditingComplete, onEditingComplete: widget.textFieldConfiguration.onEditingComplete,
onTap: widget.textFieldConfiguration.onTap, onTap: widget.textFieldConfiguration.onTap,
@@ -851,9 +865,8 @@ class _TypeAheadFieldQuickSearchState<T, R> extends State<TypeAheadFieldQuickSea
cursorWidth: widget.textFieldConfiguration.cursorWidth, cursorWidth: widget.textFieldConfiguration.cursorWidth,
cursorRadius: widget.textFieldConfiguration.cursorRadius, cursorRadius: widget.textFieldConfiguration.cursorRadius,
cursorColor: widget.textFieldConfiguration.cursorColor, cursorColor: widget.textFieldConfiguration.cursorColor,
textDirection: widget.textFieldConfiguration.textDirection, textDirection: _textDirection,
enableInteractiveSelection: enableInteractiveSelection: widget.textFieldConfiguration.enableInteractiveSelection,
widget.textFieldConfiguration.enableInteractiveSelection,
readOnly: widget.hideKeyboard, readOnly: widget.hideKeyboard,
), ),
), ),
@@ -1,40 +0,0 @@
import 'package:core/presentation/utils/style_utils.dart';
import 'package:flutter/material.dart';
class TextBuilder {
String? _text;
TextStyle? _textStyle;
TextAlign? _textAlign;
Key? _key;
TextBuilder key(Key key) {
_key = key;
return this;
}
TextBuilder text(String text) {
_text = text;
return this;
}
TextBuilder textStyle(TextStyle textStyle) {
_textStyle = textStyle;
return this;
}
TextBuilder textAlign(TextAlign textAlign) {
_textAlign = textAlign;
return this;
}
Text build() {
return Text(_text ?? '', key: _key ?? const Key('TextBuilder'), style: _textStyle ?? CommonTextStyle.textStyleNormal, textAlign: _textAlign ?? TextAlign.center);
}
}
class CenterTextBuilder extends TextBuilder {
@override
Text build() {
return Text(_text ?? '', key: _key ?? const Key('TextBuilder'), style: _textStyle, textAlign: TextAlign.center);
}
}
@@ -1,100 +1,117 @@
import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class TextFieldBuilder { class TextFieldBuilder extends StatefulWidget {
Key? _key;
ValueChanged<String>? _onTextChange;
ValueChanged<String>? _onTextSubmitted;
TextStyle? _textStyle;
TextInputAction? _textInputAction;
InputDecoration? _inputDecoration;
bool? _obscureText;
int? _maxLines = 1;
int? _minLines;
TextEditingController? _textController;
TextInputType? _keyboardType;
Color? _cursorColor;
bool? _autoFocus;
FocusNode? _focusNode;
void key(Key key) { final ValueChanged<String>? onTextChange;
_key = key; final ValueChanged<String>? onTextSubmitted;
final VoidCallback? onTap;
final TextStyle? textStyle;
final TextInputAction? textInputAction;
final InputDecoration? decoration;
final bool obscureText;
final int? maxLines;
final int? minLines;
final TextEditingController? controller;
final TextInputType? keyboardType;
final Color cursorColor;
final bool autoFocus;
final FocusNode? focusNode;
final String? fromValue;
final Brightness? keyboardAppearance;
final bool autocorrect;
final TextDirection textDirection;
final bool readOnly;
final MouseCursor? mouseCursor;
const TextFieldBuilder({
super.key,
this.cursorColor = AppColor.primaryColor,
this.autocorrect = false,
this.obscureText = false,
this.autoFocus = false,
this.readOnly = false,
this.textStyle = const TextStyle(color: AppColor.textFieldTextColor),
this.textDirection = TextDirection.ltr,
this.textInputAction,
this.decoration,
this.maxLines,
this.minLines,
this.controller,
this.keyboardType,
this.focusNode,
this.fromValue,
this.keyboardAppearance,
this.mouseCursor,
this.onTap,
this.onTextChange,
this.onTextSubmitted,
});
@override
State<TextFieldBuilder> createState() => _TextFieldBuilderState();
}
class _TextFieldBuilderState extends State<TextFieldBuilder> {
late TextEditingController _controller;
late TextDirection _textDirection;
@override
void initState() {
if (widget.fromValue != null) {
_controller = TextEditingController.fromValue(TextEditingValue(text: widget.fromValue!));
} else if (widget.controller != null) {
_controller = widget.controller!;
} else {
_controller = TextEditingController();
}
_textDirection = widget.textDirection;
super.initState();
} }
void onChange(ValueChanged<String> onChange) { @override
_onTextChange = onChange; Widget build(BuildContext context) {
}
void onSubmitted(ValueChanged<String> onSubmitted) {
_onTextSubmitted = onSubmitted;
}
void textStyle(TextStyle style) {
_textStyle = style;
}
void textInputAction(TextInputAction inputAction) {
_textInputAction = inputAction;
}
void textDecoration(InputDecoration inputDecoration) {
_inputDecoration = inputDecoration;
}
void obscureText(bool obscureText) {
_obscureText = obscureText;
}
void setText(String value) {
_textController = TextEditingController.fromValue(TextEditingValue(text: value));
}
void addController(TextEditingController? textEditingController) {
_textController = textEditingController;
}
void maxLines(int? value) {
_maxLines = value;
}
void minLines(int? value) {
_minLines = value;
}
void keyboardType(TextInputType? value) {
_keyboardType = value;
}
void cursorColor(Color? color) {
_cursorColor = color;
}
void autoFocus(bool autoFocus) {
_autoFocus = autoFocus;
}
void addFocusNode(FocusNode? focusNode) {
_focusNode = focusNode;
}
TextField build() {
return TextField( return TextField(
key: _key ?? const Key('TextFieldBuilder'), key: widget.key,
onChanged: _onTextChange, controller: _controller,
cursorColor: _cursorColor ?? AppColor.primaryColor, cursorColor: widget.cursorColor,
controller: _textController, autocorrect: widget.autocorrect,
autocorrect: false, textInputAction: widget.textInputAction,
textInputAction: _textInputAction, decoration: widget.decoration,
decoration: _inputDecoration, maxLines: widget.maxLines,
maxLines: _maxLines, minLines: widget.minLines,
minLines: _minLines, keyboardAppearance: widget.keyboardAppearance,
keyboardAppearance: Brightness.light, style: widget.textStyle,
style: _textStyle ?? const TextStyle(color: AppColor.textFieldTextColor), obscureText: widget.obscureText,
obscureText: _obscureText ?? false, keyboardType: widget.keyboardType,
keyboardType: _keyboardType, autofocus: widget.autoFocus,
onSubmitted: _onTextSubmitted, focusNode: widget.focusNode,
autofocus: _autoFocus ?? false, textDirection: _textDirection,
focusNode: _focusNode, readOnly: widget.readOnly,
mouseCursor: widget.mouseCursor,
onChanged: (value) {
widget.onTextChange?.call(value);
if (value.isNotEmpty) {
final directionByText = DirectionUtils.getDirectionByEndsText(value);
if (directionByText != _textDirection) {
setState(() {
_textDirection = directionByText;
});
}
}
},
onSubmitted: widget.onTextSubmitted,
onTap: widget.onTap,
); );
} }
@override
void dispose() {
if (widget.fromValue == null && widget.controller == null) {
_controller.dispose();
}
super.dispose();
}
} }
+14
View File
@@ -0,0 +1,14 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart' as intl;
abstract class DirectionUtils {
static bool isDirectionRTLByLanguage(BuildContext context) => intl.Bidi.isRtlLanguage(Localizations.localeOf(context).languageCode);
static bool isDirectionRTLByEndsText(String text) => intl.Bidi.endsWithRtl(text);
static TextDirection getDirectionByLanguage(BuildContext context) => isDirectionRTLByLanguage(context) ? TextDirection.rtl : TextDirection.ltr;
static TextDirection getDirectionByEndsText(String text) => isDirectionRTLByEndsText(text) ? TextDirection.rtl : TextDirection.ltr;
}
+1 -1
View File
@@ -3,7 +3,7 @@ import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
abstract class PlatformInfo { abstract class PlatformInfo {
static bool get isWeb => kIsWeb; static const bool isWeb = kIsWeb;
static bool get isLinux => !kIsWeb && Platform.isLinux; static bool get isLinux => !kIsWeb && Platform.isLinux;
static bool get isWindows => !kIsWeb && Platform.isWindows; static bool get isWindows => !kIsWeb && Platform.isWindows;
static bool get isMacOS => !kIsWeb && Platform.isMacOS; static bool get isMacOS => !kIsWeb && Platform.isMacOS;
+8
View File
@@ -296,6 +296,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.0.2"
intl:
dependency: "direct main"
description:
name: intl
sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
url: "https://pub.dev"
source: hosted
version: "0.17.0"
js: js:
dependency: transitive dependency: transitive
description: description:
+3
View File
@@ -70,6 +70,9 @@ dependencies:
# flutter_test from sdk depends on collection 1.17.0 # flutter_test from sdk depends on collection 1.17.0
collection: 1.17.0 collection: 1.17.0
# flutter_localizations depends on intl 0.17.0
intl: 0.17.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
@@ -1,9 +1,10 @@
import 'package:core/core.dart'; import 'package:core/core.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tmail_ui_user/features/base/widget/text_input_decoration_builder.dart'; import 'package:tmail_ui_user/features/base/widget/text_input_decoration_builder.dart';
typedef OnChangeInputAction = Function(String? value); typedef OnChangeInputAction = Function(String value);
class TextInputFieldBuilder extends StatelessWidget { class TextInputFieldBuilder extends StatelessWidget {
@@ -46,24 +47,23 @@ class TextInputFieldBuilder extends StatelessWidget {
color: AppColor.colorContentEmail)), color: AppColor.colorContentEmail)),
const SizedBox(height: 8) const SizedBox(height: 8)
], ],
(TextFieldBuilder() TextFieldBuilder(
..onChange((value) => onChangeInputAction?.call(value)) onTextChange: onChangeInputAction,
..textInputAction(TextInputAction.next) textInputAction: TextInputAction.next,
..addController(editingController ?? TextEditingController()) controller: editingController,
..addFocusNode(focusNode) focusNode: focusNode,
..textStyle(const TextStyle(color: Colors.black, fontSize: 16)) textStyle: const TextStyle(color: Colors.black, fontSize: 16),
..keyboardType(inputType ?? TextInputType.text) keyboardType: inputType ?? TextInputType.text,
..minLines(minLines) textDirection: DirectionUtils.getDirectionByLanguage(context),
..maxLines(maxLines) minLines: minLines,
..textDecoration((TextInputDecorationBuilder() maxLines: maxLines,
..setContentPadding(const EdgeInsets.symmetric( decoration: (TextInputDecorationBuilder()
vertical: BuildUtils.isWeb ? 16 : 12, ..setContentPadding(const EdgeInsets.symmetric(vertical: PlatformInfo.isWeb ? 16 : 12, horizontal: 12))
horizontal: 12)) ..setHintText(hint)
..setHintText(hint) ..setFillColor(backgroundColor)
..setFillColor(backgroundColor) ..setErrorText(error))
..setErrorText(error)) .build(),
.build())) )
.build()
]); ]);
} }
} }
@@ -13,6 +13,7 @@ import 'package:core/presentation/views/list/sliver_grid_delegate_fixed_height.d
import 'package:core/presentation/views/responsive/responsive_widget.dart'; import 'package:core/presentation/views/responsive/responsive_widget.dart';
import 'package:core/presentation/views/text/text_field_builder.dart'; import 'package:core/presentation/views/text/text_field_builder.dart';
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:filesize/filesize.dart'; import 'package:filesize/filesize.dart';
@@ -490,16 +491,17 @@ class ComposerView extends GetWidget<ComposerController>
) )
) )
), ),
Expanded(child: (TextFieldBuilder() Expanded(child: TextFieldBuilder(
..key(const Key('subject_email_input')) key: const Key('subject_email_input'),
..cursorColor(AppColor.colorTextButton) cursorColor: AppColor.colorTextButton,
..maxLines(responsiveUtils.isMobile(context) ? null : 1) maxLines: responsiveUtils.isMobile(context) ? null : 1,
..addFocusNode(controller.subjectEmailInputFocusNode) focusNode: controller.subjectEmailInputFocusNode,
..onChange((value) => controller.setSubjectEmail(value)) textDirection: DirectionUtils.getDirectionByLanguage(context),
..textStyle(const TextStyle(color: Colors.black, fontSize: 15, fontWeight: FontWeight.normal)) onTextChange: controller.setSubjectEmail,
..textDecoration(const InputDecoration(contentPadding: EdgeInsets.zero, border: InputBorder.none)) textStyle: const TextStyle(color: Colors.black, fontSize: 15, fontWeight: FontWeight.normal),
..addController(controller.subjectEmailInputController)) decoration: const InputDecoration(contentPadding: EdgeInsets.zero, border: InputBorder.none),
.build()) controller: controller.subjectEmailInputController,
))
] ]
); );
} }
@@ -11,6 +11,7 @@ import 'package:core/presentation/views/image/avatar_builder.dart';
import 'package:core/presentation/views/responsive/responsive_widget.dart'; import 'package:core/presentation/views/responsive/responsive_widget.dart';
import 'package:core/presentation/views/text/text_field_builder.dart'; import 'package:core/presentation/views/text/text_field_builder.dart';
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:filesize/filesize.dart'; import 'package:filesize/filesize.dart';
@@ -690,16 +691,16 @@ class ComposerView extends GetWidget<ComposerController>
) )
) )
), ),
Expanded(child: (TextFieldBuilder() Expanded(child: TextFieldBuilder(
..key(const Key('subject_email_input')) key: const Key('subject_email_input'),
..cursorColor(AppColor.colorTextButton) cursorColor: AppColor.colorTextButton,
..addFocusNode(controller.subjectEmailInputFocusNode) focusNode: controller.subjectEmailInputFocusNode,
..onChange((value) => controller.setSubjectEmail(value)) onTextChange: controller.setSubjectEmail,
..textStyle(const TextStyle(color: Colors.black, fontSize: 15, fontWeight: FontWeight.normal)) textDirection: DirectionUtils.getDirectionByLanguage(context),
..textDecoration(const InputDecoration(contentPadding: EdgeInsets.zero, border: InputBorder.none)) textStyle: const TextStyle(color: Colors.black, fontSize: 15, fontWeight: FontWeight.normal),
..addController(controller.subjectEmailInputController)) decoration: const InputDecoration(contentPadding: EdgeInsets.zero, border: InputBorder.none),
.build() controller: controller.subjectEmailInputController,
) ))
] ]
); );
} }
@@ -1,6 +1,7 @@
import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/views/button/icon_button_web.dart'; import 'package:core/presentation/views/button/icon_button_web.dart';
import 'package:core/presentation/views/text/text_field_builder.dart';
import 'package:core/utils/build_utils.dart'; import 'package:core/utils/build_utils.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -101,7 +102,7 @@ class InsertImageDialogBuilder {
color: Colors.black, color: Colors.black,
fontWeight: FontWeight.w500)), fontWeight: FontWeight.w500)),
const SizedBox(height: 10), const SizedBox(height: 10),
TextField( TextFieldBuilder(
controller: _inputUrlController, controller: _inputUrlController,
textInputAction: TextInputAction.done, textInputAction: TextInputAction.done,
decoration: InputDecoration( decoration: InputDecoration(
@@ -110,7 +111,7 @@ class InsertImageDialogBuilder {
errorText: validateFailed, errorText: validateFailed,
errorMaxLines: 2, errorMaxLines: 2,
), ),
), )
]), ]),
), ),
actionsPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8), actionsPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
@@ -91,6 +91,10 @@ class ContactController extends BaseController {
_deBouncerTime.value = text; _deBouncerTime.value = text;
} }
void onSearchTextAction(String text) {
_deBouncerTime.value = text;
}
void clearAllTextInputSearchForm() { void clearAllTextInputSearchForm() {
textInputSearchController?.clear(); textInputSearchController?.clear();
searchQuery.value = SearchQuery.initial(); searchQuery.value = SearchQuery.initial();
@@ -78,29 +78,29 @@ class ContactView extends GetWidget<ContactController> {
onCloseContactView: () => controller.closeContactView(context)) onCloseContactView: () => controller.closeContactView(context))
), ),
const Divider(color: AppColor.colorDividerComposer, height: 1), const Divider(color: AppColor.colorDividerComposer, height: 1),
(SearchAppBarWidget( SearchAppBarWidget(
_imagePaths, imagePaths: _imagePaths,
controller.searchQuery.value, searchQuery: controller.searchQuery.value,
controller.textInputSearchFocus, searchFocusNode: controller.textInputSearchFocus,
controller.textInputSearchController, searchInputController :controller.textInputSearchController,
hasBackButton: false, hasBackButton: false,
hasSearchButton: true) hasSearchButton: true,
..addPadding(EdgeInsets.zero) padding: EdgeInsets.zero,
..setHeightSearchBar(44) heightSearchBar: 44,
..setMargin(ContactUtils.getPaddingSearchInputForm(context, _responsiveUtils)) margin: ContactUtils.getPaddingSearchInputForm(context, _responsiveUtils),
..addDecoration(BoxDecoration( decoration: const BoxDecoration(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.all(Radius.circular(10)),
color: AppColor.colorBgSearchBar)) color: AppColor.colorBgSearchBar),
..addIconClearText(SvgPicture.asset( iconClearText: SvgPicture.asset(
_imagePaths.icClearTextSearch, _imagePaths.icClearTextSearch,
width: 18, width: 18,
height: 18, height: 18,
fit: BoxFit.fill)) fit: BoxFit.fill),
..setHintText(AppLocalizations.of(context).hintSearchInputContact) hintText: AppLocalizations.of(context).hintSearchInputContact,
..addOnClearTextSearchAction(() => controller.clearAllTextInputSearchForm()) onClearTextSearchAction: controller.clearAllTextInputSearchForm,
..addOnTextChangeSearchAction(controller.onTextSearchChange) onTextChangeSearchAction: controller.onTextSearchChange,
..addOnSearchTextAction((query) => {})) onSearchTextAction: controller.onSearchTextAction,
.build(), ),
Expanded(child: Obx(() { Expanded(child: Obx(() {
if (controller.listContactSearched.isNotEmpty) { if (controller.listContactSearched.isNotEmpty) {
return Container( return Container(
@@ -8,6 +8,7 @@ import 'package:core/presentation/views/list/tree_view.dart';
import 'package:core/presentation/views/search/search_bar_view.dart'; import 'package:core/presentation/views/search/search_bar_view.dart';
import 'package:core/presentation/views/text/text_field_builder.dart'; import 'package:core/presentation/views/text/text_field_builder.dart';
import 'package:core/utils/build_utils.dart'; import 'package:core/utils/build_utils.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -171,23 +172,24 @@ class DestinationPickerView extends GetWidget<DestinationPickerController>
Widget _buildCreateMailboxNameInput(BuildContext context) { Widget _buildCreateMailboxNameInput(BuildContext context) {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Obx(() => (TextFieldBuilder() child: Obx(() => TextFieldBuilder(
..key(const Key('create_mailbox_name_input')) key: const Key('create_mailbox_name_input'),
..onChange((value) => controller.setNewNameMailbox(value)) onTextChange: controller.setNewNameMailbox,
..keyboardType(TextInputType.visiblePassword) keyboardType: TextInputType.visiblePassword,
..cursorColor(AppColor.colorTextButton) cursorColor: AppColor.colorTextButton,
..addController(controller.nameInputController) controller: controller.nameInputController,
..maxLines(1) textDirection: DirectionUtils.getDirectionByLanguage(context),
..textStyle(const TextStyle( maxLines: 1,
color: AppColor.colorNameEmail, textStyle: const TextStyle(
fontSize: 16, color: AppColor.colorNameEmail,
overflow: CommonTextStyle.defaultTextOverFlow)) fontSize: 16,
..addFocusNode(controller.nameInputFocusNode) overflow: CommonTextStyle.defaultTextOverFlow),
..textDecoration((CreateMailboxNameInputDecorationBuilder() focusNode: controller.nameInputFocusNode,
..setHintText(AppLocalizations.of(context).hint_input_create_new_mailbox) decoration: (CreateMailboxNameInputDecorationBuilder()
..setErrorText(controller.getErrorInputNameString(context))) ..setHintText(AppLocalizations.of(context).hint_input_create_new_mailbox)
.build())) ..setErrorText(controller.getErrorInputNameString(context)))
.build()) .build(),
))
); );
} }
@@ -554,25 +556,30 @@ class DestinationPickerView extends GetWidget<DestinationPickerController>
colorFilter: AppColor.colorTextButton.asFilter(), colorFilter: AppColor.colorTextButton.asFilter(),
fit: BoxFit.fill), fit: BoxFit.fill),
onTap: () => controller.disableSearch(context))), onTap: () => controller.disableSearch(context))),
Expanded(child: (SearchAppBarWidget( Expanded(child: SearchAppBarWidget(
_imagePaths, imagePaths: _imagePaths,
controller.searchQuery.value, searchQuery: controller.searchQuery.value,
controller.searchFocus, searchFocusNode: controller.searchFocus,
controller.searchInputController, searchInputController: controller.searchInputController,
hasBackButton: false, hasBackButton: false,
hasSearchButton: true) hasSearchButton: true,
..addPadding(EdgeInsets.zero) padding: EdgeInsets.zero,
..setMargin(EdgeInsets.only( margin: EdgeInsets.only(
right: AppUtils.isDirectionRTL(context) ? 0 : 16, right: DirectionUtils.isDirectionRTLByLanguage(context) ? 0 : 16,
left: AppUtils.isDirectionRTL(context) ? 16 : 0, left: DirectionUtils.isDirectionRTLByLanguage(context) ? 16 : 0),
)) decoration: const BoxDecoration(
..addDecoration(BoxDecoration(borderRadius: BorderRadius.circular(12), color: AppColor.colorBgSearchBar)) borderRadius: BorderRadius.all(Radius.circular(12)),
..addIconClearText(SvgPicture.asset(_imagePaths.icClearTextSearch, width: 18, height: 18, fit: BoxFit.fill)) color: AppColor.colorBgSearchBar),
..setHintText(AppLocalizations.of(context).hint_search_mailboxes) iconClearText: SvgPicture.asset(
..addOnClearTextSearchAction(() => controller.clearSearchText()) _imagePaths.icClearTextSearch,
..addOnTextChangeSearchAction((query) => controller.searchMailbox(query)) width: 18,
..addOnSearchTextAction((query) => controller.searchMailbox(query))) height: 18,
.build()) fit: BoxFit.fill),
hintText: AppLocalizations.of(context).hint_search_mailboxes,
onClearTextSearchAction: controller.clearSearchText,
onTextChangeSearchAction: controller.searchMailbox,
onSearchTextAction: controller.searchMailbox,
))
] ]
) )
); );
@@ -1,7 +1,8 @@
import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/views/text/text_field_builder.dart'; import 'package:core/presentation/views/text/text_field_builder.dart';
import 'package:core/utils/build_utils.dart'; import 'package:core/utils/direction_utils.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_input_decoration_builder.dart'; import 'package:tmail_ui_user/features/identity_creator/presentation/widgets/identity_input_decoration_builder.dart';
@@ -41,21 +42,20 @@ class IdentityInputFieldBuilder extends StatelessWidget {
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: AppColor.colorContentEmail)), color: AppColor.colorContentEmail)),
const SizedBox(height: 8), const SizedBox(height: 8),
(TextFieldBuilder() TextFieldBuilder(
..onChange((value) => onChangeInputNameAction?.call(value)) onTextChange: onChangeInputNameAction,
..textInputAction(TextInputAction.next) textInputAction: TextInputAction.next,
..addController(editingController ?? TextEditingController()) autoFocus: true,
..autoFocus(true) textDirection: DirectionUtils.getDirectionByLanguage(context),
..addFocusNode(focusNode) controller: editingController,
..textStyle(const TextStyle(color: Colors.black, fontSize: 16)) focusNode: focusNode,
..keyboardType(inputType ?? TextInputType.text) textStyle: const TextStyle(color: Colors.black, fontSize: 16),
..textDecoration((IdentityInputDecorationBuilder() keyboardType: inputType ?? TextInputType.text,
..setContentPadding(const EdgeInsets.symmetric( decoration: (IdentityInputDecorationBuilder()
vertical: BuildUtils.isWeb ? 16 : 12, ..setContentPadding(const EdgeInsets.symmetric(vertical: PlatformInfo.isWeb ? 16 : 12,horizontal: 12))
horizontal: 12)) ..setErrorText(_error))
..setErrorText(_error)) .build(),
.build())) )
.build()
]); ]);
} }
} }
@@ -1,7 +1,6 @@
import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/extensions/color_extension.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/utils/responsive_utils.dart';
import 'package:core/presentation/views/text/text_builder.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -30,9 +29,8 @@ abstract class BaseLoginView extends GetWidget<LoginController> {
padding: const EdgeInsets.only(top: 11, bottom: 36, left: 58, right: 58), padding: const EdgeInsets.only(top: 11, bottom: 36, left: 58, right: 58),
child: SizedBox( child: SizedBox(
width: responsiveUtils.getWidthLoginTextField(context), width: responsiveUtils.getWidthLoginTextField(context),
child: CenterTextBuilder() child: Text(
.key(const Key('login_message')) loginState.viewState.fold(
.text(loginState.viewState.fold(
(failure) { (failure) {
if (failure is LoginMissUrlAction) { if (failure is LoginMissUrlAction) {
return AppLocalizations.of(context).requiredUrl; return AppLocalizations.of(context).requiredUrl;
@@ -43,7 +41,7 @@ abstract class BaseLoginView extends GetWidget<LoginController> {
} else if (failure is LoginSSONotAvailableAction) { } else if (failure is LoginSSONotAvailableAction) {
return AppLocalizations.of(context).ssoNotAvailable; return AppLocalizations.of(context).ssoNotAvailable;
} else if (failure is GetOIDCConfigurationFailure } else if (failure is GetOIDCConfigurationFailure
|| failure is LoginCanNotVerifySSOConfigurationAction) { || failure is LoginCanNotVerifySSOConfigurationAction) {
return AppLocalizations.of(context).canNotVerifySSOConfiguration; return AppLocalizations.of(context).canNotVerifySSOConfiguration;
} else if (failure is GetTokenOIDCFailure || failure is LoginCanNotGetTokenAction) { } else if (failure is GetTokenOIDCFailure || failure is LoginCanNotGetTokenAction) {
return AppLocalizations.of(context).canNotGetToken; return AppLocalizations.of(context).canNotGetToken;
@@ -58,15 +56,16 @@ abstract class BaseLoginView extends GetWidget<LoginController> {
return AppLocalizations.of(context).loginInputSSOMessage; return AppLocalizations.of(context).loginInputSSOMessage;
} }
return AppLocalizations.of(context).loginInputUrlMessage; return AppLocalizations.of(context).loginInputUrlMessage;
})) }),
.textStyle(TextStyle( key: const Key('login_message'),
fontSize: 15, textAlign: TextAlign.center,
fontWeight: FontWeight.w400, style: TextStyle(
color: loginState.viewState.fold( fontSize: 15,
(failure) => AppColor.textFieldErrorBorderColor, fontWeight: FontWeight.w400,
(success) => AppColor.colorNameEmail))) color: loginState.viewState.fold(
.build() (failure) => AppColor.textFieldErrorBorderColor,
) (success) => AppColor.colorNameEmail)),
))
); );
} }
@@ -4,6 +4,7 @@ import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:core/presentation/utils/style_utils.dart'; import 'package:core/presentation/utils/style_utils.dart';
import 'package:core/presentation/views/text/text_field_builder.dart'; import 'package:core/presentation/views/text/text_field_builder.dart';
import 'package:core/utils/build_utils.dart'; import 'package:core/utils/build_utils.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -112,23 +113,24 @@ class MailboxCreatorView extends GetWidget<MailboxCreatorController> {
Widget _buildCreateMailboxNameInput(BuildContext context) { Widget _buildCreateMailboxNameInput(BuildContext context) {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Obx(() => (TextFieldBuilder() child: Obx(() => TextFieldBuilder(
..key(const Key('create_mailbox_name_input')) key: const Key('create_mailbox_name_input'),
..onChange((value) => controller.setNewNameMailbox(value)) onTextChange: controller.setNewNameMailbox,
..keyboardType(TextInputType.visiblePassword) keyboardType: TextInputType.visiblePassword,
..cursorColor(AppColor.colorTextButton) cursorColor: AppColor.colorTextButton,
..addController(controller.nameInputController) controller: controller.nameInputController,
..maxLines(1) textDirection: DirectionUtils.getDirectionByLanguage(context),
..textStyle(const TextStyle( maxLines: 1,
color: AppColor.colorNameEmail, textStyle: const TextStyle(
fontSize: 16, color: AppColor.colorNameEmail,
overflow: CommonTextStyle.defaultTextOverFlow)) fontSize: 16,
..addFocusNode(controller.nameInputFocusNode) overflow: CommonTextStyle.defaultTextOverFlow),
..textDecoration((CreateMailboxNameInputDecorationBuilder() focusNode: controller.nameInputFocusNode,
..setHintText(AppLocalizations.of(context).hint_input_create_new_mailbox) decoration: (CreateMailboxNameInputDecorationBuilder()
..setErrorText(controller.getErrorInputNameString(context))) ..setHintText(AppLocalizations.of(context).hint_input_create_new_mailbox)
.build())) ..setErrorText(controller.getErrorInputNameString(context)))
.build()) .build(),
))
); );
} }
@@ -356,13 +356,13 @@ class AdvancedSearchInputForm extends GetWidget<AdvancedFilterController>
nextFocusNode?.requestFocus(); nextFocusNode?.requestFocus();
} }
}, },
child: TextField( child: TextFieldBuilder(
controller: textEditingController, controller: textEditingController,
readOnly: isSelectFormList, readOnly: isSelectFormList,
mouseCursor: mouseCursor, mouseCursor: mouseCursor,
textInputAction: isSelectFormList ? TextInputAction.done : TextInputAction.next, textInputAction: isSelectFormList ? TextInputAction.done : TextInputAction.next,
onTap: onTap, onTap: onTap,
onSubmitted: (value) { onTextSubmitted: (value) {
if (isSelectFormList) { if (isSelectFormList) {
onTap?.call(); onTap?.call();
} else { } else {
@@ -1,5 +1,11 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:core/core.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/style_utils.dart';
import 'package:core/presentation/views/text/text_field_builder.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@@ -66,8 +72,8 @@ class _TextFieldAutoCompleteEmailAddressState
optionsViewBuilder: (context, onSelected, listEmailAddress) { optionsViewBuilder: (context, onSelected, listEmailAddress) {
return Container( return Container(
margin: const EdgeInsets.only( margin: const EdgeInsets.only(
top: BuildUtils.isWeb ? 5 : 8, top: PlatformInfo.isWeb ? 5 : 8,
bottom: 16), bottom: 16),
height: _getHeightSuggestionBox(listEmailAddress.length, 65), height: _getHeightSuggestionBox(listEmailAddress.length, 65),
width: maxWidthSuggestionBox, width: maxWidthSuggestionBox,
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
@@ -134,7 +140,7 @@ class _TextFieldAutoCompleteEmailAddressState
widget.nextFocusNode?.requestFocus(); widget.nextFocusNode?.requestFocus();
} }
}, },
child: TextField( child: TextFieldBuilder(
controller: tec, controller: tec,
focusNode: fn, focusNode: fn,
textInputAction: TextInputAction.next, textInputAction: TextInputAction.next,
@@ -180,12 +186,12 @@ class _TextFieldAutoCompleteEmailAddressState
) )
: null, : null,
), ),
onChanged: (value) { onTextChange: (value) {
if (value.trim().isNotEmpty) { if (value.trim().isNotEmpty) {
onChanged?.call(value); onChanged?.call(value);
} }
}, },
onSubmitted: (tag) { onTextSubmitted: (tag) {
if (tag.trim().isNotEmpty) { if (tag.trim().isNotEmpty) {
onSubmitted?.call(tag); onSubmitted?.call(tag);
fn.requestFocus(); fn.requestFocus();
@@ -334,7 +340,7 @@ class _TextFieldAutoCompleteEmailAddressState
double _getHeightSuggestionBox(int countItem, double heightItem) { double _getHeightSuggestionBox(int countItem, double heightItem) {
final maxHeightList = countItem * heightItem; final maxHeightList = countItem * heightItem;
if (BuildUtils.isWeb) { if (PlatformInfo.isWeb) {
return maxHeightList > 250 ? 250 : maxHeightList; return maxHeightList > 250 ? 250 : maxHeightList;
} else { } else {
if (_responsiveUtils.isLandscapeMobile(context)) { if (_responsiveUtils.isLandscapeMobile(context)) {
@@ -346,7 +352,7 @@ class _TextFieldAutoCompleteEmailAddressState
} }
double get maxWidthSuggestionBox { double get maxWidthSuggestionBox {
if (BuildUtils.isWeb) { if (PlatformInfo.isWeb) {
if (_responsiveUtils.isTabletLarge(context)) { if (_responsiveUtils.isTabletLarge(context)) {
return 300; return 300;
} else { } else {
@@ -4,6 +4,7 @@ import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/style_utils.dart'; import 'package:core/presentation/utils/style_utils.dart';
import 'package:core/presentation/views/button/icon_button_web.dart'; import 'package:core/presentation/views/button/icon_button_web.dart';
import 'package:core/presentation/views/quick_search/quick_search_input_form.dart'; import 'package:core/presentation/views/quick_search/quick_search_input_form.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_portal/flutter_portal.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
@@ -186,6 +187,7 @@ class SearchInputFormWidget extends StatelessWidget with AppLoaderMixin {
controller: controller.searchInputController, controller: controller.searchInputController,
focusNode: controller.searchFocus, focusNode: controller.searchFocus,
textInputAction: TextInputAction.done, textInputAction: TextInputAction.done,
textDirection: DirectionUtils.getDirectionByLanguage(context),
onSubmitted: (keyword) => _invokeSearchEmailAction(context, keyword.trim()), onSubmitted: (keyword) => _invokeSearchEmailAction(context, keyword.trim()),
decoration: InputDecoration( decoration: InputDecoration(
border: InputBorder.none, border: InputBorder.none,
@@ -1,6 +1,7 @@
import 'package:core/core.dart'; import 'package:core/core.dart';
import 'package:core/presentation/utils/html_transformer/html_utils.dart'; import 'package:core/presentation/utils/html_transformer/html_utils.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -409,22 +410,21 @@ class VacationView extends GetWidget<VacationController> with RichTextButtonMixi
} }
Widget _buildMessagePlainTextEditor(BuildContext context) { Widget _buildMessagePlainTextEditor(BuildContext context) {
return (TextFieldBuilder() return TextFieldBuilder(
..onChange((value) => controller.updateMessageBody(context, value)) onTextChange: (value) => controller.updateMessageBody(context, value),
..keyboardType(TextInputType.multiline) keyboardType: TextInputType.multiline,
..addController(controller.messageTextController) controller: controller.messageTextController,
..textStyle(const TextStyle(color: Colors.black, fontSize: 16)) textStyle: const TextStyle(color: Colors.black, fontSize: 16),
..minLines(10) textDirection: DirectionUtils.getDirectionByLanguage(context),
..maxLines(null) minLines: 10,
..textDecoration((TextInputDecorationBuilder() maxLines: null,
decoration: (TextInputDecorationBuilder()
..setContentPadding(const EdgeInsets.all(16)) ..setContentPadding(const EdgeInsets.all(16))
..setHintText(AppLocalizations.of(context).hintMessageBodyVacation) ..setHintText(AppLocalizations.of(context).hintMessageBodyVacation)
..setFillColor(Colors.white) ..setFillColor(Colors.white)
..setErrorText(controller.isVacationDeactivated ..setErrorText(controller.isVacationDeactivated ? null : controller.errorMessageBody.value))
? null .build(),
: controller.errorMessageBody.value)) );
.build()))
.build();
} }
Widget _buildMessageHtmlTextEditor(BuildContext context) { Widget _buildMessageHtmlTextEditor(BuildContext context) {
@@ -1,6 +1,7 @@
import 'package:core/presentation/views/text/text_field_builder.dart'; import 'package:core/presentation/views/text/text_field_builder.dart';
import 'package:core/utils/build_utils.dart'; import 'package:core/utils/direction_utils.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tmail_ui_user/features/rules_filter_creator/presentation/widgets/rules_filter_input_decoration_builder.dart'; import 'package:tmail_ui_user/features/rules_filter_creator/presentation/widgets/rules_filter_input_decoration_builder.dart';
@@ -25,20 +26,19 @@ class RulesFilterInputField extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return (TextFieldBuilder() return TextFieldBuilder(
..onChange((value) => onChangeAction?.call(value)) onTextChange: onChangeAction,
..textInputAction(TextInputAction.next) textInputAction: TextInputAction.next,
..addController(editingController ?? TextEditingController()) controller: editingController,
..textStyle(const TextStyle(color: Colors.black, fontSize: 16)) textStyle: const TextStyle(color: Colors.black, fontSize: 16),
..keyboardType(TextInputType.text) textDirection: DirectionUtils.getDirectionByLanguage(context),
..addFocusNode(focusNode) keyboardType: TextInputType.text,
..textDecoration((RulesFilterInputDecorationBuilder() focusNode: focusNode,
..setContentPadding(const EdgeInsets.symmetric( decoration: (RulesFilterInputDecorationBuilder()
vertical: BuildUtils.isWeb ? 16 : 12, ..setContentPadding(const EdgeInsets.symmetric(vertical: PlatformInfo.isWeb ? 16 : 12, horizontal: 12))
horizontal: 12)) ..setHintText(hintText)
..setHintText(hintText) ..setErrorText(errorText))
..setErrorText(errorText)) .build(),
.build())) );
.build();
} }
} }
@@ -642,6 +642,14 @@ class SearchEmailController extends BaseController
_deBouncerTime.value = text; _deBouncerTime.value = text;
} }
void onTextSearchSubmitted(BuildContext context, String text) {
final query = text.trim();
if (query.isNotEmpty) {
saveRecentSearch(RecentSearch.now(query));
submitSearchAction(context, query);
}
}
void setTextInputSearchForm(String value) { void setTextInputSearchForm(String value) {
textInputSearchController.text = value; textInputSearchController.text = value;
} }
@@ -7,6 +7,7 @@ import 'package:core/presentation/views/background/background_widget_builder.dar
import 'package:core/presentation/views/button/icon_button_web.dart'; import 'package:core/presentation/views/button/icon_button_web.dart';
import 'package:core/presentation/views/text/text_field_builder.dart'; import 'package:core/presentation/views/text/text_field_builder.dart';
import 'package:core/utils/build_utils.dart'; import 'package:core/utils/build_utils.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -117,29 +118,21 @@ class SearchEmailView extends GetWidget<SearchEmailController>
tooltip: AppLocalizations.of(context).back, tooltip: AppLocalizations.of(context).back,
onTap: () => controller.closeSearchView(context) onTap: () => controller.closeSearchView(context)
), ),
Expanded(child: (TextFieldBuilder() Expanded(child: TextFieldBuilder(
..onChange(controller.onTextSearchChange) onTextChange: controller.onTextSearchChange,
..textInputAction(TextInputAction.search) textInputAction: TextInputAction.search,
..addController(controller.textInputSearchController) controller: controller.textInputSearchController,
..addFocusNode(controller.textInputSearchFocus) focusNode: controller.textInputSearchFocus,
..textStyle(const TextStyle(color: Colors.black, fontSize: 16)) textDirection: DirectionUtils.getDirectionByLanguage(context),
..keyboardType(TextInputType.text) textStyle: const TextStyle(color: Colors.black, fontSize: 16),
..onSubmitted((value) { keyboardType: TextInputType.text,
final query = value.trim(); onTextSubmitted: (text) => controller.onTextSearchSubmitted(context, text),
if (query.isNotEmpty) { decoration: InputDecoration(
controller.saveRecentSearch(RecentSearch.now(query)); contentPadding: const EdgeInsets.all(12),
controller.submitSearchAction(context, query); hintText: AppLocalizations.of(context).search_emails,
} hintStyle: const TextStyle(color: AppColor.loginTextFieldHintColor, fontSize: 16),
}) border: InputBorder.none),
..maxLines(1) )),
..textDecoration(InputDecoration(
contentPadding: const EdgeInsets.all(12),
hintText: AppLocalizations.of(context).search_emails,
hintStyle: const TextStyle(
color: AppColor.loginTextFieldHintColor,
fontSize: 16),
border: InputBorder.none)))
.build()),
Obx(() { Obx(() {
if (controller.currentSearchText.isNotEmpty) { if (controller.currentSearchText.isNotEmpty) {
return return
@@ -207,6 +207,13 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa
_deBouncerTime.value = text; _deBouncerTime.value = text;
} }
void onTextSearchSubmitted(BuildContext context, String text) {
final query = text.trim();
if (query.isNotEmpty) {
submitSearchAction(context, query);
}
}
void setTextInputSearchForm(String value) { void setTextInputSearchForm(String value) {
textInputSearchController.text = value; textInputSearchController.text = value;
} }
@@ -3,6 +3,7 @@ import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/views/button/icon_button_web.dart'; import 'package:core/presentation/views/button/icon_button_web.dart';
import 'package:core/presentation/views/text/text_field_builder.dart'; import 'package:core/presentation/views/text/text_field_builder.dart';
import 'package:core/utils/build_utils.dart'; import 'package:core/utils/build_utils.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:focused_menu_custom/modals.dart'; import 'package:focused_menu_custom/modals.dart';
@@ -138,33 +139,28 @@ class SearchMailboxView extends GetWidget<SearchMailboxController>
} }
Widget _buildTextFieldSearchInput(BuildContext context) { Widget _buildTextFieldSearchInput(BuildContext context) {
return (TextFieldBuilder() return TextFieldBuilder(
..onChange(controller.onTextSearchChange) onTextChange: controller.onTextSearchChange,
..textInputAction(TextInputAction.search) textInputAction: TextInputAction.search,
..autoFocus(true) autoFocus: true,
..addController(controller.textInputSearchController) controller: controller.textInputSearchController,
..textStyle(const TextStyle( textDirection: DirectionUtils.getDirectionByLanguage(context),
color: Colors.black, textStyle: const TextStyle(
color: Colors.black,
fontSize: 15,
fontWeight: FontWeight.normal),
keyboardType: TextInputType.text,
onTextSubmitted: (text) => controller.onTextSearchSubmitted(context, text),
decoration: InputDecoration(
contentPadding: EdgeInsets.zero,
hintText: AppLocalizations.of(context).searchForMailboxes,
hintStyle: const TextStyle(
color: AppColor.loginTextFieldHintColor,
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.normal)) fontWeight: FontWeight.normal),
..keyboardType(TextInputType.text) border: InputBorder.none
..onSubmitted((value) { ),
final query = value.trim(); );
if (query.isNotEmpty) {
controller.submitSearchAction(context, query);
}
})
..maxLines(1)
..textDecoration(InputDecoration(
contentPadding: EdgeInsets.zero,
hintText: AppLocalizations.of(context).searchForMailboxes,
hintStyle: const TextStyle(
color: AppColor.loginTextFieldHintColor,
fontSize: 15,
fontWeight: FontWeight.normal),
border: InputBorder.none
)
)).build();
} }
Widget _buildMailboxListView(BuildContext context) { Widget _buildMailboxListView(BuildContext context) {
@@ -1,5 +1,9 @@
import 'package:core/core.dart'; import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/views/button/icon_button_web.dart';
import 'package:core/presentation/views/text/text_field_builder.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart';
@@ -9,93 +13,61 @@ typedef OnClearTextSearchAction = Function();
typedef OnTextChangeSearchAction = Function(String); typedef OnTextChangeSearchAction = Function(String);
typedef OnSearchTextAction = Function(String); typedef OnSearchTextAction = Function(String);
class SearchAppBarWidget { class SearchAppBarWidget extends StatelessWidget {
OnCancelSearchPressed? _onCancelSearchPressed;
OnTextChangeSearchAction? _onTextChangeSearchAction;
OnClearTextSearchAction? _onClearTextSearchAction;
OnSearchTextAction? _onSearchTextAction;
final ImagePaths _imagePaths; final ImagePaths imagePaths;
final SearchQuery? _searchQuery; final SearchQuery? searchQuery;
final TextEditingController? _searchInputController; final TextEditingController? searchInputController;
final FocusNode? _searchFocusNode; final FocusNode? searchFocusNode;
final List<String>? suggestionSearch; final List<String>? suggestionSearch;
final bool hasBackButton; final bool hasBackButton;
final bool hasSearchButton; final bool hasSearchButton;
final double? heightSearchBar;
final Decoration? decoration;
final EdgeInsets? padding;
final EdgeInsets? margin;
final String? hintText;
final Widget? iconClearText;
final OnCancelSearchPressed? onCancelSearchPressed;
final OnTextChangeSearchAction? onTextChangeSearchAction;
final OnClearTextSearchAction? onClearTextSearchAction;
final OnSearchTextAction? onSearchTextAction;
double? _heightSearchBar; const SearchAppBarWidget({
Decoration? _decoration; super.key,
EdgeInsets? _padding; required this.imagePaths,
EdgeInsets? _margin; required this.hasBackButton,
String? _hintText; required this.hasSearchButton,
Widget? _iconClearText; this.searchQuery,
this.searchInputController,
this.searchFocusNode,
this.suggestionSearch,
this.heightSearchBar,
this.decoration,
this.padding,
this.margin,
this.hintText,
this.iconClearText,
this.onCancelSearchPressed,
this.onTextChangeSearchAction,
this.onClearTextSearchAction,
this.onSearchTextAction
});
SearchAppBarWidget( @override
this._imagePaths, Widget build(BuildContext context) {
this._searchQuery,
this._searchFocusNode,
this._searchInputController,
{
this.hasBackButton = true,
this.suggestionSearch,
this.hasSearchButton = false,
}
);
void addOnCancelSearchPressed(OnCancelSearchPressed onCancelSearchPressed) {
_onCancelSearchPressed = onCancelSearchPressed;
}
void addOnTextChangeSearchAction(OnTextChangeSearchAction onTextChangeSearchAction) {
_onTextChangeSearchAction = onTextChangeSearchAction;
}
void addOnClearTextSearchAction(OnClearTextSearchAction onClearTextSearchAction) {
_onClearTextSearchAction = onClearTextSearchAction;
}
void addOnSearchTextAction(OnSearchTextAction onSearchTextAction) {
_onSearchTextAction = onSearchTextAction;
}
void setHeightSearchBar(double heightSearchBar) {
_heightSearchBar = heightSearchBar;
}
void addDecoration(Decoration decoration) {
_decoration = decoration;
}
void addPadding(EdgeInsets padding) {
_padding = padding;
}
void setMargin(EdgeInsets margin) {
_margin = margin;
}
void setHintText(String text) {
_hintText = text;
}
void addIconClearText(Widget? icon) {
_iconClearText = icon;
}
Widget build() {
return Container( return Container(
key: const Key('search_app_bar_widget'), key: key ?? const Key('search_app_bar_widget'),
height: _heightSearchBar, height: heightSearchBar,
decoration: _decoration, decoration: decoration,
padding: _padding ?? EdgeInsets.zero, padding: padding ?? EdgeInsets.zero,
margin: _margin, margin: margin,
child: Row( child: Row(
children: [ children: [
if (hasBackButton) _buildBackButton(), if (hasBackButton) _buildBackButton(),
if (hasSearchButton) _buildSearchButton(), if (hasSearchButton) _buildSearchButton(),
Expanded(child: _buildSearchInputForm()), Expanded(child: _buildSearchInputForm(context)),
if (suggestionSearch?.isNotEmpty == true if (suggestionSearch?.isNotEmpty == true || (searchQuery != null && searchQuery!.value.isNotEmpty))
|| (_searchQuery != null && _searchQuery!.value.isNotEmpty))
_buildClearTextSearchButton(), _buildClearTextSearchButton(),
] ]
) )
@@ -104,47 +76,48 @@ class SearchAppBarWidget {
Widget _buildBackButton() { Widget _buildBackButton() {
return buildIconWeb( return buildIconWeb(
icon: SvgPicture.asset( icon: SvgPicture.asset(
_imagePaths.icBack, imagePaths.icBack,
colorFilter: AppColor.colorTextButton.asFilter(), colorFilter: AppColor.colorTextButton.asFilter(),
fit: BoxFit.fill), fit: BoxFit.fill),
onTap: () { onTap: () {
_searchInputController?.clear(); searchInputController?.clear();
if (_onCancelSearchPressed != null) { if (onCancelSearchPressed != null) {
_onCancelSearchPressed!(); onCancelSearchPressed!();
} }
}); });
} }
Widget _buildClearTextSearchButton() { Widget _buildClearTextSearchButton() {
return buildIconWeb( return buildIconWeb(
icon: _iconClearText ?? SvgPicture.asset(_imagePaths.icComposerClose, width: 18, height: 18, fit: BoxFit.fill), icon: iconClearText ?? SvgPicture.asset(imagePaths.icComposerClose, width: 18, height: 18, fit: BoxFit.fill),
onTap: () { onTap: () {
_searchInputController?.clear(); searchInputController?.clear();
_onClearTextSearchAction?.call(); onClearTextSearchAction?.call();
}); });
} }
Widget _buildSearchInputForm() { Widget _buildSearchInputForm(BuildContext context) {
return (TextFieldBuilder() return TextFieldBuilder(
..key(const Key('search_input_form')) key: const Key('search_input_form'),
..textInputAction(TextInputAction.done) textInputAction: TextInputAction.done,
..onChange((value) => _onTextChangeSearchAction?.call(value)) onTextChange: onTextChangeSearchAction,
..onSubmitted((value) => _onSearchTextAction?.call(value)) onTextSubmitted: onSearchTextAction,
..cursorColor(AppColor.colorTextButton) cursorColor: AppColor.colorTextButton,
..autoFocus(true) textDirection: DirectionUtils.getDirectionByLanguage(context),
..addFocusNode(_searchFocusNode) autoFocus: true,
..textStyle(const TextStyle(color: AppColor.colorNameEmail, fontSize: 17)) focusNode: searchFocusNode,
..textDecoration(InputDecoration( textStyle: const TextStyle(color: AppColor.colorNameEmail, fontSize: 17),
border: InputBorder.none, decoration: InputDecoration(
focusedBorder: InputBorder.none, border: InputBorder.none,
enabledBorder: InputBorder.none, focusedBorder: InputBorder.none,
contentPadding: EdgeInsets.zero, enabledBorder: InputBorder.none,
hintText: _hintText, contentPadding: EdgeInsets.zero,
hintStyle: const TextStyle(color: AppColor.colorHintSearchBar, fontSize: 17.0), hintText: hintText,
labelStyle: const TextStyle(color: AppColor.colorHintSearchBar, fontSize: 17.0))) hintStyle: const TextStyle(color: AppColor.colorHintSearchBar, fontSize: 17.0),
..addController(_searchInputController)) labelStyle: const TextStyle(color: AppColor.colorHintSearchBar, fontSize: 17.0)),
.build(); controller: searchInputController,
);
} }
Widget _buildSearchButton() { Widget _buildSearchButton() {
@@ -154,10 +127,10 @@ class SearchAppBarWidget {
minSize: 40, minSize: 40,
iconPadding: EdgeInsets.zero, iconPadding: EdgeInsets.zero,
icon: SvgPicture.asset( icon: SvgPicture.asset(
_imagePaths.icSearchBar, imagePaths.icSearchBar,
fit: BoxFit.fill fit: BoxFit.fill
), ),
onTap: () => _onSearchTextAction?.call(_searchInputController?.text ?? '') onTap: () => onSearchTextAction?.call(searchInputController?.text ?? '')
), ),
); );
} }