Skip to content

Instantly share code, notes, and snippets.

@isaacadariku
Last active March 6, 2023 14:12
Show Gist options
  • Save isaacadariku/7af36e598973e02a1dcda0130b8369f0 to your computer and use it in GitHub Desktop.
Save isaacadariku/7af36e598973e02a1dcda0130b8369f0 to your computer and use it in GitHub Desktop.
Animated Validation
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
const kGap = SizedBox(height: 40);
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Animated Login',
home: AnimatedLogin(),
);
}
}
class AnimatedLogin extends StatefulWidget {
const AnimatedLogin({super.key});
@override
State<AnimatedLogin> createState() => _AnimatedLoginState();
}
class _AnimatedLoginState extends State<AnimatedLogin> {
final _formKey = GlobalKey<FormState>();
final _nameFieldKey = GlobalKey();
final _emailFieldKey = GlobalKey();
final _passwordFieldKey = GlobalKey();
ValidationModel _userName = ValidationModel();
ValidationModel _email = ValidationModel();
ValidationModel _password = ValidationModel();
bool get isNameValid => _userName.value.isNotEmpty && _userName.error == null;
bool get isEmailValid => _email.value.isNotEmpty && _email.error == null;
bool get isPasswordValid =>
_password.value.isNotEmpty && _password.error == null;
bool get isValidated => isNameValid && isEmailValid && isPasswordValid;
void _onUserNameChanged(String value) {
setState(() {
_userName = userNameValidator(value);
});
}
void _onEmailChanged(String value) {
setState(() {
_email = emailValidator(value);
});
}
void _onPasswordChanged(String value) {
setState(() {
_password = passwordValidator(value);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 50),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
kGap,
AppTextFormField(
key: _nameFieldKey,
labelText: 'Username',
errorText: _userName.error,
onChanged: _onUserNameChanged,
keyboardType: TextInputType.name,
textInputAction: TextInputAction.next,
autofillHints: const [AutofillHints.givenName],
),
kGap,
AppTextFormField(
key: _emailFieldKey,
labelText: 'your@email.com',
errorText: _email.error,
onChanged: _onEmailChanged,
keyboardType: TextInputType.emailAddress,
inputFormatters: [FilteringTextInputFormatter.deny(' ')],
textInputAction: TextInputAction.next,
autofillHints: const [AutofillHints.email],
),
kGap,
AppTextFormField(
key: _passwordFieldKey,
labelText: 'Password (8+ characters)',
errorText: _password.error,
onChanged: _onPasswordChanged,
obscureText: true,
autofillHints: const [AutofillHints.password],
),
kGap,
AppButton(isValidated: isValidated),
kGap,
],
),
),
),
),
);
}
}
class AppButton extends StatefulWidget {
const AppButton({super.key, this.isValidated = false});
final bool isValidated;
@override
State<AppButton> createState() => _AppButtonState();
}
class _AppButtonState extends State<AppButton> {
Alignment alignment = Alignment.center;
void _triggerLocationChange() {
if (!widget.isValidated) {
if (alignment == Alignment.topLeft) {
setState(() {
alignment = Alignment.topRight;
});
} else if (alignment == Alignment.topRight) {
setState(() {
alignment = Alignment.topLeft;
});
} else if (alignment == Alignment.center) {
setState(() {
alignment = Alignment.topLeft;
});
}
}
}
void _onPressed() {
// Using this check to trigger location change for mobile platforms
!widget.isValidated
? _triggerLocationChange()
: ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Welcome! You are Validated!'),
),
);
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 30),
child: AnimatedAlign(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
alignment: widget.isValidated ? Alignment.center : alignment,
child: ElevatedButton(
onHover: (isHovering) {
if (isHovering) {
_triggerLocationChange();
}
},
style: ElevatedButton.styleFrom(
enabledMouseCursor: widget.isValidated
? SystemMouseCursors.click
: SystemMouseCursors.basic,
backgroundColor: widget.isValidated ? Colors.green : Colors.blue,
fixedSize:
widget.isValidated ? const Size(300, 50) : const Size(120, 50),
),
onPressed: _onPressed,
child: const Text('Submit'),
),
),
);
}
}
class AppTextFormField extends StatefulWidget {
const AppTextFormField({
super.key,
required this.labelText,
this.onChanged,
this.keyboardType,
this.textInputAction,
this.autofillHints,
this.inputFormatters = const [],
this.controller,
this.errorText,
this.obscureText = false,
});
final String labelText;
final ValueChanged<String>? onChanged;
final TextInputType? keyboardType;
final TextInputAction? textInputAction;
final List<String>? autofillHints;
final List<TextInputFormatter> inputFormatters;
final TextEditingController? controller;
final String? errorText;
final bool obscureText;
@override
State<AppTextFormField> createState() => _AppTextFormFieldState();
}
class _AppTextFormFieldState extends State<AppTextFormField> {
late final TextEditingController textController =
widget.controller ?? TextEditingController();
bool get _hasError => widget.errorText != null;
@override
Widget build(BuildContext context) {
return TextFormField(
controller: textController,
obscureText: widget.obscureText,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(width: 0.0),
),
focusedBorder: _hasError
? const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.redAccent,
),
)
: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.greenAccent,
),
),
labelText: widget.labelText,
labelStyle: const TextStyle(color: Colors.grey),
errorText: widget.errorText,
),
onChanged: widget.onChanged,
keyboardType: widget.keyboardType,
textInputAction: widget.textInputAction,
autofillHints: widget.autofillHints,
inputFormatters: widget.inputFormatters,
);
}
}
extension ExtString on String? {
bool get isValidEmail {
final emailRegExp = RegExp(r'^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\.[a-zA-Z]+');
return this != null && emailRegExp.hasMatch(this!);
}
bool get isValidUserName {
final nameRegExp = RegExp(r"[a-zA-Z]+|\s");
return this != null && nameRegExp.hasMatch(this!);
}
bool get isValidPassword {
final passwordRegExp =
RegExp(r'^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$');
return this != null && passwordRegExp.hasMatch(this!);
}
}
class ValidationModel {
String value;
String? error;
ValidationModel({this.value = '', this.error});
}
ValidationModel emailValidator(String value) {
if (value.trim().isValidEmail) {
return ValidationModel(value: value);
} else {
return ValidationModel(error: 'Please enter a valid email address');
}
}
ValidationModel passwordValidator(String value) {
if (value.isValidPassword) {
return ValidationModel(value: value);
} else {
return ValidationModel(
error:
'Password should contain uppercase, lowercase,\n numeric digit and a special character.');
}
}
ValidationModel userNameValidator(String value) {
if (value.trim().isValidUserName) {
return ValidationModel(value: value);
} else {
return ValidationModel(error: 'Enter valid user name');
}
}
@isaacadariku
Copy link
Author

View on DartPad

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment