Convert LoginTextInputBuilder functional to StatefulWidget
(cherry picked from commit b7c41f18d6e57e17e6e9f46e2b18905fca1ea85a)
This commit is contained in:
@@ -115,65 +115,59 @@ abstract class BaseLoginView extends GetWidget<LoginController> {
|
|||||||
|
|
||||||
Widget buildInputCredentialForm(BuildContext context) {
|
Widget buildInputCredentialForm(BuildContext context) {
|
||||||
return AutofillGroup(
|
return AutofillGroup(
|
||||||
child: Column(
|
child: Padding(
|
||||||
children: [
|
padding: const EdgeInsetsDirectional.symmetric(horizontal: 24),
|
||||||
buildUserNameInput(context),
|
child: Column(
|
||||||
buildPasswordInput(context)
|
children: [
|
||||||
],
|
buildUserNameInput(context),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
buildPasswordInput(context),
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildUserNameInput(BuildContext context) {
|
Widget buildUserNameInput(BuildContext context) {
|
||||||
return Padding(
|
return TypeAheadFormFieldBuilder<RecentLoginUsername>(
|
||||||
padding: const EdgeInsets.only(bottom: 24, right: 24, left: 24),
|
key: const Key('login_username_input'),
|
||||||
child: TypeAheadFormFieldBuilder<RecentLoginUsername>(
|
controller: controller.usernameInputController,
|
||||||
key: const Key('login_username_input'),
|
onTextChange: controller.setUserNameText,
|
||||||
controller: controller.usernameInputController,
|
textInputAction: TextInputAction.next,
|
||||||
onTextChange: controller.setUserNameText,
|
autocorrect: false,
|
||||||
textInputAction: TextInputAction.next,
|
autofillHints: const [AutofillHints.email],
|
||||||
autocorrect: false,
|
keyboardType: TextInputType.emailAddress,
|
||||||
autofillHints: const [AutofillHints.email],
|
decoration: (LoginInputDecorationBuilder()
|
||||||
keyboardType: TextInputType.emailAddress,
|
..setLabelText(AppLocalizations.of(context).email)
|
||||||
decoration: (LoginInputDecorationBuilder()
|
..setHintText(AppLocalizations.of(context).email))
|
||||||
..setLabelText(AppLocalizations.of(context).email)
|
.build(),
|
||||||
..setHintText(AppLocalizations.of(context).email))
|
debounceDuration: const Duration(milliseconds: 300),
|
||||||
.build(),
|
suggestionsCallback: controller.getAllRecentLoginUsernameAction,
|
||||||
debounceDuration: const Duration(milliseconds: 300),
|
itemBuilder: (context, loginUsername) => RecentItemTileWidget(loginUsername, imagePath: imagePaths),
|
||||||
suggestionsCallback: controller.getAllRecentLoginUsernameAction,
|
onSuggestionSelected: (recentUsername) {
|
||||||
itemBuilder: (context, loginUsername) => RecentItemTileWidget(loginUsername, imagePath: imagePaths),
|
controller.setUsername(recentUsername.username);
|
||||||
onSuggestionSelected: (recentUsername) {
|
controller.passFocusNode.requestFocus();
|
||||||
controller.setUsername(recentUsername.username);
|
},
|
||||||
controller.passFocusNode.requestFocus();
|
suggestionsBoxDecoration: const SuggestionsBoxDecoration(borderRadius: BorderRadius.all(Radius.circular(14))),
|
||||||
},
|
noItemsFoundBuilder: (context) => const SizedBox(),
|
||||||
suggestionsBoxDecoration: const SuggestionsBoxDecoration(borderRadius: BorderRadius.all(Radius.circular(14))),
|
hideOnEmpty: true,
|
||||||
noItemsFoundBuilder: (context) => const SizedBox(),
|
hideOnError: true,
|
||||||
hideOnEmpty: true,
|
hideOnLoading: true,
|
||||||
hideOnError: true,
|
|
||||||
hideOnLoading: true,
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildPasswordInput(BuildContext context) {
|
Widget buildPasswordInput(BuildContext context) {
|
||||||
return Padding(
|
return LoginTextInputBuilder(
|
||||||
padding: const EdgeInsets.only(bottom: 40, right: 24, left: 24),
|
key: const Key('login_password_input'),
|
||||||
child: Container(
|
controller: controller.passwordInputController,
|
||||||
child: (LoginTextInputBuilder(
|
autofillHints: const [AutofillHints.password],
|
||||||
context,
|
textInputAction: TextInputAction.done,
|
||||||
imagePaths,
|
hintText: AppLocalizations.of(context).password,
|
||||||
autocorrect: false,
|
focusNode: controller.passFocusNode,
|
||||||
autofillHints: [AutofillHints.password]
|
onTextChange: controller.setPasswordText,
|
||||||
)
|
onSubmitted: (value) => controller.handleLoginPressed(),
|
||||||
..setOnSubmitted((value) => controller.handleLoginPressed())
|
);
|
||||||
..passwordInput(true)
|
|
||||||
..key(const Key('login_password_input'))
|
|
||||||
..obscureText(true)
|
|
||||||
..onChange(controller.setPasswordText)
|
|
||||||
..textInputAction(TextInputAction.done)
|
|
||||||
..hintText(AppLocalizations.of(context).password)
|
|
||||||
..setFocusNode(controller.passFocusNode))
|
|
||||||
.build()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildLoadingCircularProgress() {
|
Widget buildLoadingCircularProgress() {
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ class LoginController extends ReloadableController {
|
|||||||
|
|
||||||
final TextEditingController urlInputController = TextEditingController();
|
final TextEditingController urlInputController = TextEditingController();
|
||||||
final TextEditingController usernameInputController = TextEditingController();
|
final TextEditingController usernameInputController = TextEditingController();
|
||||||
|
final TextEditingController passwordInputController = TextEditingController();
|
||||||
final FocusNode passFocusNode = FocusNode();
|
final FocusNode passFocusNode = FocusNode();
|
||||||
|
|
||||||
LoginController(
|
LoginController(
|
||||||
@@ -393,6 +394,7 @@ class LoginController extends ReloadableController {
|
|||||||
passFocusNode.dispose();
|
passFocusNode.dispose();
|
||||||
urlInputController.dispose();
|
urlInputController.dispose();
|
||||||
usernameInputController.dispose();
|
usernameInputController.dispose();
|
||||||
|
passwordInputController.dispose();
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ class LoginInputDecorationBuilder extends InputDecorationBuilder {
|
|||||||
labelStyle: labelStyle ?? const TextStyle(color: AppColor.textFieldLabelColor, fontSize: 16),
|
labelStyle: labelStyle ?? const TextStyle(color: AppColor.textFieldLabelColor, fontSize: 16),
|
||||||
hintText: hintText,
|
hintText: hintText,
|
||||||
hintStyle: hintStyle ?? const TextStyle(color: AppColor.textFieldHintColor, fontSize: 16),
|
hintStyle: hintStyle ?? const TextStyle(color: AppColor.textFieldHintColor, fontSize: 16),
|
||||||
contentPadding: contentPadding ?? const EdgeInsets.only(left: 25, top: 15, bottom: 15, right: 25),
|
contentPadding: contentPadding ?? const EdgeInsetsDirectional.only(start: 25, top: 15, bottom: 15, end: 25),
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: AppColor.textFieldBorderColor);
|
fillColor: AppColor.textFieldBorderColor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,176 +1,145 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
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/views/button/tmail_button_widget.dart';
|
||||||
import 'package:core/presentation/views/text/text_form_field_builder.dart';
|
import 'package:core/presentation/views/text/text_form_field_builder.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:tmail_ui_user/features/login/presentation/widgets/login_input_decoration_builder.dart';
|
||||||
|
|
||||||
import 'login_input_decoration_builder.dart';
|
|
||||||
|
|
||||||
typedef SetErrorString = String? Function(String);
|
|
||||||
typedef OnSubmitted = void Function(String);
|
typedef OnSubmitted = void Function(String);
|
||||||
|
|
||||||
class LoginTextInputBuilder {
|
class LoginTextInputBuilder extends StatefulWidget {
|
||||||
Key? _key;
|
final String? hintText;
|
||||||
String? _title;
|
final String? prefixText;
|
||||||
String? _hintText;
|
final TextInputAction? textInputAction;
|
||||||
String? _prefixText;
|
final TextEditingController? controller;
|
||||||
SetErrorString? _setErrorString;
|
final FocusNode? focusNode;
|
||||||
String? _errorText;
|
|
||||||
Timer? _debounce;
|
|
||||||
bool? _obscureText;
|
|
||||||
ValueChanged<String>? _onTextChange;
|
|
||||||
TextInputAction? _textInputAction;
|
|
||||||
TextEditingController? _textEditingController;
|
|
||||||
bool? _passwordInput;
|
|
||||||
OnSubmitted? _onSubmitted;
|
|
||||||
FocusNode? _focusNode;
|
|
||||||
final List<String>? autofillHints;
|
final List<String>? autofillHints;
|
||||||
final bool autocorrect;
|
final bool obscureText;
|
||||||
final BuildContext context;
|
final bool passwordInput;
|
||||||
final ImagePaths imagePaths;
|
final ValueChanged<String>? onTextChange;
|
||||||
|
final OnSubmitted? onSubmitted;
|
||||||
|
|
||||||
LoginTextInputBuilder(
|
const LoginTextInputBuilder({
|
||||||
this.context,
|
super.key,
|
||||||
this.imagePaths,
|
this.hintText,
|
||||||
{
|
this.prefixText,
|
||||||
this.autocorrect = true,
|
this.textInputAction,
|
||||||
this.autofillHints
|
this.focusNode,
|
||||||
|
this.controller,
|
||||||
|
this.autofillHints,
|
||||||
|
this.onSubmitted,
|
||||||
|
this.onTextChange,
|
||||||
|
this.passwordInput = true,
|
||||||
|
this.obscureText = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LoginTextInputBuilder> createState() => _LoginTextInputBuilderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginTextInputBuilderState extends State<LoginTextInputBuilder> {
|
||||||
|
|
||||||
|
final imagePaths = Get.find<ImagePaths>();
|
||||||
|
|
||||||
|
late TextEditingController _controller;
|
||||||
|
late bool _obscureText;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_obscureText = widget.obscureText;
|
||||||
|
if (widget.controller != null) {
|
||||||
|
_controller = widget.controller!;
|
||||||
|
} else {
|
||||||
|
_controller = TextEditingController();
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
void key(Key key) {
|
|
||||||
_key = key;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void title(String? title) {
|
@override
|
||||||
_title = title;
|
Widget build(BuildContext context) {
|
||||||
}
|
return Stack(
|
||||||
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
void hintText(String? hintText) {
|
children: [
|
||||||
_hintText = hintText;
|
TextFormFieldBuilder(
|
||||||
}
|
onTextSubmitted: widget.onSubmitted,
|
||||||
|
onTextChange: widget.onTextChange,
|
||||||
void labelText(String? labelText) {
|
obscureText: _obscureText,
|
||||||
}
|
textInputAction: widget.textInputAction,
|
||||||
|
autofillHints: widget.autofillHints,
|
||||||
void prefixText(String? prefixText) {
|
controller: _controller,
|
||||||
_prefixText = prefixText;
|
textStyle: const TextStyle(
|
||||||
}
|
color: AppColor.loginTextFieldHintColor,
|
||||||
|
fontSize: 16,
|
||||||
void textInputAction(TextInputAction inputAction) {
|
fontWeight: FontWeight.normal
|
||||||
_textInputAction = inputAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
void obscureText(bool obscureText) {
|
|
||||||
_obscureText = obscureText;
|
|
||||||
}
|
|
||||||
|
|
||||||
void onChange(ValueChanged<String> onChange) {
|
|
||||||
_onTextChange = onChange;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setErrorString(SetErrorString setErrorString) {
|
|
||||||
_setErrorString = setErrorString;
|
|
||||||
}
|
|
||||||
|
|
||||||
void passwordInput(bool? passwordInput) {
|
|
||||||
_passwordInput = passwordInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setTextEditingController(TextEditingController textEditingController) {
|
|
||||||
_textEditingController = textEditingController;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onTextChanged(String name, StateSetter setState) {
|
|
||||||
if (_onTextChange != null) {
|
|
||||||
_onTextChange!(name);
|
|
||||||
}
|
|
||||||
if (_debounce?.isActive ?? false) _debounce?.cancel();
|
|
||||||
_debounce = Timer(const Duration(milliseconds: 500), () {
|
|
||||||
setState(() {
|
|
||||||
_errorText = (_setErrorString != null) ? _setErrorString!(name) : '';
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void errorText(String? errorText) {
|
|
||||||
_errorText = errorText;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onObscureTextChanged(bool? value, StateSetter setState) {
|
|
||||||
setState(() {
|
|
||||||
_obscureText = value == true ? false : true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void setOnSubmitted(OnSubmitted onSubmitted) {
|
|
||||||
_onSubmitted = onSubmitted;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setFocusNode(FocusNode? focusNode) {
|
|
||||||
_focusNode = focusNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget build() {
|
|
||||||
return StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
|
|
||||||
return Wrap(
|
|
||||||
key: _key,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(_title ?? '', style: const TextStyle(color: AppColor.loginTextFieldHintColor, fontSize: 14, fontWeight: FontWeight.normal)),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8),
|
|
||||||
child: Stack(
|
|
||||||
alignment: AlignmentDirectional.centerEnd,
|
|
||||||
children: [
|
|
||||||
TextFormFieldBuilder(
|
|
||||||
onTextSubmitted: _onSubmitted,
|
|
||||||
onTextChange: (value) => _onTextChanged(value, setState),
|
|
||||||
obscureText: _obscureText ?? false,
|
|
||||||
textInputAction: _textInputAction,
|
|
||||||
autofillHints: autofillHints,
|
|
||||||
autocorrect: autocorrect,
|
|
||||||
controller: _textEditingController,
|
|
||||||
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))
|
|
||||||
..setFocusBorder(const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
|
||||||
borderSide: BorderSide(width: 1, color: AppColor.loginTextFieldFocusedBorder)))
|
|
||||||
..setEnabledBorder(const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
|
||||||
borderSide: BorderSide(width: 1, color: AppColor.loginTextFieldBorderColor)))
|
|
||||||
..setErrorBorder(const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
|
||||||
borderSide: BorderSide(width: 1, color: AppColor.loginTextFieldErrorBorder))))
|
|
||||||
.build(),
|
|
||||||
),
|
|
||||||
if (_passwordInput == true)
|
|
||||||
Transform(
|
|
||||||
transform: Matrix4.translationValues(
|
|
||||||
0.0,
|
|
||||||
(_errorText != null && _errorText!.isNotEmpty) ? -10.0 : 0.0,
|
|
||||||
0.0),
|
|
||||||
child: IconButton(
|
|
||||||
onPressed: () => _onObscureTextChanged(_obscureText, setState),
|
|
||||||
icon: SvgPicture.asset(
|
|
||||||
_obscureText == true ? imagePaths.icEye : imagePaths.icEyeOff,
|
|
||||||
width: 18,
|
|
||||||
height: 18,
|
|
||||||
fit: BoxFit.fill)))
|
|
||||||
]
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
],
|
focusNode: widget.focusNode,
|
||||||
);
|
decoration: (LoginInputDecorationBuilder()
|
||||||
});
|
..setHintText(widget.hintText)
|
||||||
|
..setPrefixText(widget.prefixText)
|
||||||
|
..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
|
||||||
|
))
|
||||||
|
..setFocusBorder(const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
width: 1,
|
||||||
|
color: AppColor.loginTextFieldFocusedBorder
|
||||||
|
)
|
||||||
|
))
|
||||||
|
..setEnabledBorder(const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
width: 1,
|
||||||
|
color: AppColor.loginTextFieldBorderColor
|
||||||
|
)
|
||||||
|
))
|
||||||
|
..setErrorBorder(const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
width: 1,
|
||||||
|
color: AppColor.loginTextFieldErrorBorder
|
||||||
|
)
|
||||||
|
))
|
||||||
|
).build(),
|
||||||
|
),
|
||||||
|
if (widget.passwordInput)
|
||||||
|
TMailButtonWidget.fromIcon(
|
||||||
|
icon: widget.obscureText ? imagePaths.icEye : imagePaths.icEyeOff,
|
||||||
|
iconSize: 18,
|
||||||
|
margin: const EdgeInsetsDirectional.only(end: 4),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
onTapActionCallback: () {
|
||||||
|
setState(() => _obscureText = !_obscureText);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
if (widget.controller == null) {
|
||||||
|
_controller.dispose();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user