TF-1884 Support RTL mode for TextFormField

(cherry picked from commit 7c12f65a037bb5a1b6cef91938af1ec1dac20a9b)
This commit is contained in:
dab246
2023-06-14 12:29:22 +07:00
committed by Dat H. Pham
parent df19b979a2
commit 9064d95483
7 changed files with 138 additions and 200 deletions
+1
View File
@@ -46,6 +46,7 @@ export 'utils/platform_info.dart';
// Views
export 'presentation/views/text/slogan_builder.dart';
export 'presentation/views/text/text_field_builder.dart';
export 'presentation/views/text/text_form_field_builder.dart';
export 'presentation/views/text/input_decoration_builder.dart';
export 'presentation/views/text/rich_text_builder.dart';
export 'presentation/views/responsive/responsive_widget.dart';
@@ -102,10 +102,10 @@ class EditTextDialogBuilder {
textAlign: TextAlign.center),
Padding(
padding: const EdgeInsets.only(top: 20),
child: TextFormField(
child: TextFormFieldBuilder(
keyboardType: TextInputType.visiblePassword,
onChanged: (value) => _onTextChanged(value, setState),
autofocus: true,
onTextChange: (value) => _onTextChanged(value, setState),
autoFocus: true,
controller: _textController,
decoration: InputDecoration(
errorText: _error,
@@ -2,6 +2,7 @@
import 'dart:async';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/views/text/text_form_field_builder.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -126,10 +127,10 @@ class EditTextModalSheetBuilder {
textAlign: TextAlign.center),
Padding(
padding: const EdgeInsets.only(top: 20),
child: TextFormField(
child: TextFormFieldBuilder(
keyboardType: TextInputType.visiblePassword,
onChanged: (value) => _onTextChanged(value, setState),
autofocus: true,
onTextChange: (value) => _onTextChanged(value, setState),
autoFocus: true,
controller: _textController,
decoration: InputDecoration(
errorText: _error,
@@ -7,7 +7,7 @@ abstract class InputDecorationBuilder {
TextStyle? labelStyle;
String? hintText;
TextStyle? hintStyle;
EdgeInsets? contentPadding;
EdgeInsetsGeometry? contentPadding;
OutlineInputBorder? enabledBorder;
OutlineInputBorder? errorBorder;
OutlineInputBorder? focusBorder;
@@ -43,7 +43,7 @@ abstract class InputDecorationBuilder {
hintStyle = newHintStyle;
}
void setContentPadding(EdgeInsets? newContentPadding) {
void setContentPadding(EdgeInsetsGeometry? newContentPadding) {
contentPadding = newContentPadding;
}
@@ -0,0 +1,120 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:flutter/material.dart';
class TextFormFieldBuilder extends StatefulWidget {
final ValueChanged<String>? onTextChange;
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;
final List<String>? autofillHints;
const TextFormFieldBuilder({
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 = 1,
this.minLines,
this.controller,
this.keyboardType,
this.focusNode,
this.fromValue,
this.keyboardAppearance,
this.mouseCursor,
this.autofillHints,
this.onTap,
this.onTextChange,
this.onTextSubmitted,
});
@override
State<TextFormFieldBuilder> createState() => _TextFieldFormBuilderState();
}
class _TextFieldFormBuilderState extends State<TextFormFieldBuilder> {
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();
}
@override
Widget build(BuildContext context) {
return TextFormField(
key: widget.key,
controller: _controller,
cursorColor: widget.cursorColor,
autocorrect: widget.autocorrect,
textInputAction: widget.textInputAction,
decoration: widget.decoration,
maxLines: widget.maxLines,
minLines: widget.minLines,
keyboardAppearance: widget.keyboardAppearance,
style: widget.textStyle,
obscureText: widget.obscureText,
keyboardType: widget.keyboardType,
autofocus: widget.autoFocus,
focusNode: widget.focusNode,
textDirection: _textDirection,
readOnly: widget.readOnly,
mouseCursor: widget.mouseCursor,
autofillHints: widget.autofillHints,
onChanged: (value) {
widget.onTextChange?.call(value);
if (value.isNotEmpty) {
final directionByText = DirectionUtils.getDirectionByEndsText(value);
if (directionByText != _textDirection) {
setState(() {
_textDirection = directionByText;
});
}
}
},
onFieldSubmitted: widget.onTextSubmitted,
onTap: widget.onTap,
);
}
@override
void dispose() {
if (widget.fromValue == null && widget.controller == null) {
_controller.dispose();
}
super.dispose();
}
}
@@ -1,186 +0,0 @@
import 'package:core/presentation/extensions/color_extension.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:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:model/upload/file_info.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/image_source.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/inline_image.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
typedef InsertImageActionCallback = Function(InlineImage image);
class InsertImageDialogBuilder {
final _inputFileController = TextEditingController();
final _inputUrlController = TextEditingController();
final InsertImageActionCallback? insertActionCallback;
final BuildContext _context;
FileInfo? fileSelected;
String? validateFailed;
InsertImageDialogBuilder(this._context, {
this.insertActionCallback
});
Future show() async {
await showDialog(
context: _context,
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
builder: (BuildContext context) {
return PointerInterceptor(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return AlertDialog(
title: Text(AppLocalizations.of(context).insertImage,
textAlign: TextAlign.center,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
color: Colors.black)),
titleTextStyle: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
color: Colors.black),
titlePadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16),
contentPadding: const EdgeInsets.symmetric(vertical: 0, horizontal: 16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
scrollable: true,
elevation: 10,
content: Container(
color: Colors.white,
width: 300,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(AppLocalizations.of(context).selectFromFile,
style: const TextStyle(
fontSize: 14,
color: Colors.black,
fontWeight: FontWeight.w500)),
const SizedBox(height: 10),
TextFormField(
controller: _inputFileController,
readOnly: true,
decoration: InputDecoration(
prefixIcon: buildButtonWrapText(
AppLocalizations.of(context).chooseImage,
radius: 5,
height: 30,
padding: const EdgeInsets.only(right: 8),
textStyle: const TextStyle(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.normal),
bgColor: AppColor.colorShadowComposer,
onTap: () => _selectFromFile(setState)),
suffixIcon: fileSelected != null
? IconButton(
splashRadius: 10,
icon: const Icon(Icons.close),
onPressed: () {
setState(() {
fileSelected = null;
_inputFileController.text = '';
});
})
: const SizedBox.shrink(),
errorText: validateFailed,
errorMaxLines: 2,
border: InputBorder.none,
)),
const SizedBox(height: 20),
Text(AppLocalizations.of(context).urlLink,
style: const TextStyle(
fontSize: 14,
color: Colors.black,
fontWeight: FontWeight.w500)),
const SizedBox(height: 10),
TextFieldBuilder(
controller: _inputUrlController,
textInputAction: TextInputAction.done,
decoration: InputDecoration(
border: const OutlineInputBorder(),
hintText: AppLocalizations.of(context).urlLink,
errorText: validateFailed,
errorMaxLines: 2,
),
)
]),
),
actionsPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
actions: [
buildButtonWrapText(
AppLocalizations.of(context).cancel,
radius: 5,
textStyle: const TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.normal),
bgColor: AppColor.colorShadowComposer,
onTap: () => popBack()),
buildButtonWrapText(
AppLocalizations.of(context).insert,
radius: 5,
textStyle: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w500),
onTap: () => _insertImageAction(context, setState))
],
);
}),
);
});
}
void _selectFromFile(StateSetter setState) async {
final filePickerResult = await FilePicker.platform.pickFiles(
type: FileType.image,
withReadStream: true);
final platformFile = filePickerResult?.files.single;
if (platformFile != null) {
fileSelected = FileInfo(
platformFile.name,
BuildUtils.isWeb ? '' : platformFile.path ?? '',
platformFile.size,
readStream: platformFile.readStream);
setState(() {
_inputFileController.text = fileSelected!.fileName;
});
}
}
void _insertImageAction(BuildContext context, StateSetter setState) {
final inputFile = _inputFileController.text;
final inputUrl = _inputUrlController.text;
if (inputFile.isEmpty && inputUrl.isEmpty) {
setState(() {
validateFailed = AppLocalizations.of(context).insertImageErrorFileEmpty;
});
} else if (inputFile.isNotEmpty && inputUrl.isNotEmpty) {
setState(() {
validateFailed = AppLocalizations.of(context).insertImageErrorDuplicate;
});
} else if (inputFile.isNotEmpty && fileSelected != null) {
if (insertActionCallback != null) {
insertActionCallback!.call(InlineImage(ImageSource.local, fileInfo: fileSelected));
}
popBack();
} else {
if (insertActionCallback != null) {
insertActionCallback!.call(InlineImage(ImageSource.network, link: inputUrl));
}
popBack();
}
}
}
@@ -1,6 +1,8 @@
import 'dart:async';
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/text/text_form_field_builder.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
@@ -122,21 +124,21 @@ class LoginTextInputBuilder {
child: Stack(
alignment: AlignmentDirectional.centerEnd,
children: [
TextFormField(
onFieldSubmitted: _onSubmitted,
onChanged: (value) => _onTextChanged(value, setState),
TextFormFieldBuilder(
onTextSubmitted: _onSubmitted,
onTextChange: (value) => _onTextChanged(value, setState),
obscureText: _obscureText ?? false,
textInputAction: _textInputAction,
autofillHints: autofillHints,
autocorrect: autocorrect,
controller: _textEditingController,
cursorColor: AppColor.primaryColor,
style: const TextStyle(color: AppColor.loginTextFieldHintColor, fontSize: 16, fontWeight: FontWeight.normal),
textStyle: const TextStyle(color: AppColor.loginTextFieldHintColor, fontSize: 16, fontWeight: FontWeight.normal),
focusNode: _focusNode,
decoration: (LoginInputDecorationBuilder()
..setHintText(_hintText)
..setPrefixText(_prefixText)
..setErrorText(_errorText)
..setContentPadding(const EdgeInsetsDirectional.only(start: 25, top: 15, bottom: 15, end: 40))
..setHintStyle(const TextStyle(color: AppColor.loginTextFieldHintColor, fontSize: 16, fontWeight: FontWeight.normal))
..setPrefixStyle(const TextStyle(color: AppColor.loginTextFieldHintColor, fontSize: 16, fontWeight: FontWeight.normal))
..setErrorTextStyle(const TextStyle(color: AppColor.loginTextFieldErrorBorder, fontSize: 13, fontWeight: FontWeight.normal))