Last active
March 6, 2023 14:12
-
-
Save isaacadariku/7af36e598973e02a1dcda0130b8369f0 to your computer and use it in GitHub Desktop.
Animated Validation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
View on DartPad