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) {
|
||||
return AutofillGroup(
|
||||
child: Column(
|
||||
children: [
|
||||
buildUserNameInput(context),
|
||||
buildPasswordInput(context)
|
||||
],
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(horizontal: 24),
|
||||
child: Column(
|
||||
children: [
|
||||
buildUserNameInput(context),
|
||||
const SizedBox(height: 24),
|
||||
buildPasswordInput(context),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildUserNameInput(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24, right: 24, left: 24),
|
||||
child: TypeAheadFormFieldBuilder<RecentLoginUsername>(
|
||||
key: const Key('login_username_input'),
|
||||
controller: controller.usernameInputController,
|
||||
onTextChange: controller.setUserNameText,
|
||||
textInputAction: TextInputAction.next,
|
||||
autocorrect: false,
|
||||
autofillHints: const [AutofillHints.email],
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: (LoginInputDecorationBuilder()
|
||||
..setLabelText(AppLocalizations.of(context).email)
|
||||
..setHintText(AppLocalizations.of(context).email))
|
||||
.build(),
|
||||
debounceDuration: const Duration(milliseconds: 300),
|
||||
suggestionsCallback: controller.getAllRecentLoginUsernameAction,
|
||||
itemBuilder: (context, loginUsername) => RecentItemTileWidget(loginUsername, imagePath: imagePaths),
|
||||
onSuggestionSelected: (recentUsername) {
|
||||
controller.setUsername(recentUsername.username);
|
||||
controller.passFocusNode.requestFocus();
|
||||
},
|
||||
suggestionsBoxDecoration: const SuggestionsBoxDecoration(borderRadius: BorderRadius.all(Radius.circular(14))),
|
||||
noItemsFoundBuilder: (context) => const SizedBox(),
|
||||
hideOnEmpty: true,
|
||||
hideOnError: true,
|
||||
hideOnLoading: true,
|
||||
)
|
||||
return TypeAheadFormFieldBuilder<RecentLoginUsername>(
|
||||
key: const Key('login_username_input'),
|
||||
controller: controller.usernameInputController,
|
||||
onTextChange: controller.setUserNameText,
|
||||
textInputAction: TextInputAction.next,
|
||||
autocorrect: false,
|
||||
autofillHints: const [AutofillHints.email],
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: (LoginInputDecorationBuilder()
|
||||
..setLabelText(AppLocalizations.of(context).email)
|
||||
..setHintText(AppLocalizations.of(context).email))
|
||||
.build(),
|
||||
debounceDuration: const Duration(milliseconds: 300),
|
||||
suggestionsCallback: controller.getAllRecentLoginUsernameAction,
|
||||
itemBuilder: (context, loginUsername) => RecentItemTileWidget(loginUsername, imagePath: imagePaths),
|
||||
onSuggestionSelected: (recentUsername) {
|
||||
controller.setUsername(recentUsername.username);
|
||||
controller.passFocusNode.requestFocus();
|
||||
},
|
||||
suggestionsBoxDecoration: const SuggestionsBoxDecoration(borderRadius: BorderRadius.all(Radius.circular(14))),
|
||||
noItemsFoundBuilder: (context) => const SizedBox(),
|
||||
hideOnEmpty: true,
|
||||
hideOnError: true,
|
||||
hideOnLoading: true,
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildPasswordInput(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 40, right: 24, left: 24),
|
||||
child: Container(
|
||||
child: (LoginTextInputBuilder(
|
||||
context,
|
||||
imagePaths,
|
||||
autocorrect: false,
|
||||
autofillHints: [AutofillHints.password]
|
||||
)
|
||||
..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()));
|
||||
return LoginTextInputBuilder(
|
||||
key: const Key('login_password_input'),
|
||||
controller: controller.passwordInputController,
|
||||
autofillHints: const [AutofillHints.password],
|
||||
textInputAction: TextInputAction.done,
|
||||
hintText: AppLocalizations.of(context).password,
|
||||
focusNode: controller.passFocusNode,
|
||||
onTextChange: controller.setPasswordText,
|
||||
onSubmitted: (value) => controller.handleLoginPressed(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildLoadingCircularProgress() {
|
||||
|
||||
@@ -73,6 +73,7 @@ class LoginController extends ReloadableController {
|
||||
|
||||
final TextEditingController urlInputController = TextEditingController();
|
||||
final TextEditingController usernameInputController = TextEditingController();
|
||||
final TextEditingController passwordInputController = TextEditingController();
|
||||
final FocusNode passFocusNode = FocusNode();
|
||||
|
||||
LoginController(
|
||||
@@ -393,6 +394,7 @@ class LoginController extends ReloadableController {
|
||||
passFocusNode.dispose();
|
||||
urlInputController.dispose();
|
||||
usernameInputController.dispose();
|
||||
passwordInputController.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ class LoginInputDecorationBuilder extends InputDecorationBuilder {
|
||||
labelStyle: labelStyle ?? const TextStyle(color: AppColor.textFieldLabelColor, fontSize: 16),
|
||||
hintText: hintText,
|
||||
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,
|
||||
fillColor: AppColor.textFieldBorderColor);
|
||||
}
|
||||
|
||||
@@ -1,176 +1,145 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:core/presentation/extensions/color_extension.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: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);
|
||||
|
||||
class LoginTextInputBuilder {
|
||||
Key? _key;
|
||||
String? _title;
|
||||
String? _hintText;
|
||||
String? _prefixText;
|
||||
SetErrorString? _setErrorString;
|
||||
String? _errorText;
|
||||
Timer? _debounce;
|
||||
bool? _obscureText;
|
||||
ValueChanged<String>? _onTextChange;
|
||||
TextInputAction? _textInputAction;
|
||||
TextEditingController? _textEditingController;
|
||||
bool? _passwordInput;
|
||||
OnSubmitted? _onSubmitted;
|
||||
FocusNode? _focusNode;
|
||||
class LoginTextInputBuilder extends StatefulWidget {
|
||||
final String? hintText;
|
||||
final String? prefixText;
|
||||
final TextInputAction? textInputAction;
|
||||
final TextEditingController? controller;
|
||||
final FocusNode? focusNode;
|
||||
final List<String>? autofillHints;
|
||||
final bool autocorrect;
|
||||
final BuildContext context;
|
||||
final ImagePaths imagePaths;
|
||||
final bool obscureText;
|
||||
final bool passwordInput;
|
||||
final ValueChanged<String>? onTextChange;
|
||||
final OnSubmitted? onSubmitted;
|
||||
|
||||
LoginTextInputBuilder(
|
||||
this.context,
|
||||
this.imagePaths,
|
||||
{
|
||||
this.autocorrect = true,
|
||||
this.autofillHints
|
||||
const LoginTextInputBuilder({
|
||||
super.key,
|
||||
this.hintText,
|
||||
this.prefixText,
|
||||
this.textInputAction,
|
||||
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) {
|
||||
_title = title;
|
||||
}
|
||||
|
||||
void hintText(String? hintText) {
|
||||
_hintText = hintText;
|
||||
}
|
||||
|
||||
void labelText(String? labelText) {
|
||||
}
|
||||
|
||||
void prefixText(String? prefixText) {
|
||||
_prefixText = prefixText;
|
||||
}
|
||||
|
||||
void textInputAction(TextInputAction inputAction) {
|
||||
_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)))
|
||||
]
|
||||
)
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
alignment: AlignmentDirectional.centerEnd,
|
||||
children: [
|
||||
TextFormFieldBuilder(
|
||||
onTextSubmitted: widget.onSubmitted,
|
||||
onTextChange: widget.onTextChange,
|
||||
obscureText: _obscureText,
|
||||
textInputAction: widget.textInputAction,
|
||||
autofillHints: widget.autofillHints,
|
||||
controller: _controller,
|
||||
textStyle: const TextStyle(
|
||||
color: AppColor.loginTextFieldHintColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
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