Created
May 8, 2025 17:56
-
-
Save kururu-abdo/f7d6bae9e417eebc810a9d1d5de18041 to your computer and use it in GitHub Desktop.
Login Page Witn Flutter using CustomPainter
This file contains hidden or 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'; | |
void main() { | |
runApp(const MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
const MyApp({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Login App', | |
theme: ThemeData(primarySwatch: Colors.blue, fontFamily: 'Inter'), | |
home: const LoginPage(), | |
); | |
} | |
} | |
class LoginPage extends StatefulWidget { | |
const LoginPage({super.key}); | |
@override | |
_LoginPageState createState() => _LoginPageState(); | |
} | |
class _LoginPageState extends State<LoginPage> { | |
final _formKey = GlobalKey<FormState>(); | |
final _emailController = TextEditingController(); | |
final _passwordController = TextEditingController(); | |
final _usernameController = TextEditingController(); // Added for Sign Up | |
bool _isLogin = true; // Track whether it's login or sign up | |
bool _obscurePassword = true; | |
@override | |
void dispose() { | |
_emailController.dispose(); | |
_passwordController.dispose(); | |
_usernameController.dispose(); // Dispose the new controller | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
// Set the system UI overlay style | |
SystemChrome.setSystemUIOverlayStyle( | |
const SystemUiOverlayStyle( | |
statusBarColor: Colors.transparent, | |
statusBarIconBrightness: Brightness.dark, | |
systemNavigationBarColor: Colors.transparent, | |
systemNavigationBarIconBrightness: Brightness.dark, | |
), | |
); | |
final Size size = MediaQuery.of(context).size; | |
return Scaffold( | |
body: Stack( | |
children: [ | |
// Background with Custom Painter | |
CustomPaint( | |
painter: BackgroundPainter(), | |
size: Size(size.width, size.height), | |
), | |
// Main Content Area | |
Padding( | |
padding: const EdgeInsets.only(top: 80.0), // Increased top padding | |
child: SingleChildScrollView( | |
child: Form( | |
key: _formKey, | |
child: Column( | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: [ | |
// Welcome Text | |
Text( | |
'WELCOME', | |
style: TextStyle( | |
fontSize: 30, | |
fontWeight: FontWeight.bold, | |
color: Colors.black, // Consistent text color | |
), | |
), | |
const SizedBox(height: 30), | |
// Input Fields | |
if (_isLogin) ...[ | |
_buildEmailField(), | |
const SizedBox(height: 20), | |
_buildPasswordField(), | |
] else ...[ | |
_buildUsernameField(), | |
const SizedBox(height: 20), | |
_buildEmailField(), | |
const SizedBox(height: 20), | |
_buildPasswordField(), | |
], | |
const SizedBox(height: 30), | |
// Main Action Button | |
_buildActionButton(), | |
const SizedBox(height: 20), | |
// Forgot Password? | |
if (_isLogin) _buildForgotPassword(), | |
const SizedBox(height: 20), | |
// Switch to Sign Up/Login | |
_buildSwitchButton(), | |
], | |
), | |
), | |
), | |
), | |
], | |
), | |
); | |
} | |
// Email Input Field | |
Widget _buildEmailField() { | |
return SizedBox( | |
width: 300, | |
child: TextFormField( | |
controller: _emailController, | |
keyboardType: TextInputType.emailAddress, | |
decoration: InputDecoration( | |
labelText: 'Email Address', | |
labelStyle: const TextStyle(color: Colors.black), | |
prefixIcon: const Icon(Icons.email_outlined, color: Colors.black), | |
enabledBorder: OutlineInputBorder( | |
borderRadius: BorderRadius.circular(10), | |
borderSide: const BorderSide(color: Colors.black), | |
), | |
focusedBorder: OutlineInputBorder( | |
borderRadius: BorderRadius.circular(10), | |
borderSide: const BorderSide(color: Colors.blue), | |
), | |
errorBorder: OutlineInputBorder( | |
borderRadius: BorderRadius.circular(10), | |
borderSide: const BorderSide(color: Colors.red), | |
), | |
focusedErrorBorder: OutlineInputBorder( | |
borderRadius: BorderRadius.circular(10), | |
borderSide: const BorderSide(color: Colors.red), | |
), | |
errorStyle: const TextStyle(color: Colors.red), | |
), | |
validator: (value) { | |
if (value == null || value.isEmpty) { | |
return 'Please enter your email address'; | |
} | |
if (!RegExp( | |
r'^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$', | |
).hasMatch(value)) { | |
return 'Invalid email format'; | |
} | |
return null; | |
}, | |
style: const TextStyle(color: Colors.black), | |
), | |
); | |
} | |
// Password Input Field | |
Widget _buildPasswordField() { | |
return SizedBox( | |
width: 300, | |
child: TextFormField( | |
controller: _passwordController, | |
obscureText: _obscurePassword, | |
decoration: InputDecoration( | |
labelText: 'Password', | |
labelStyle: const TextStyle(color: Colors.black), | |
prefixIcon: const Icon(Icons.lock_outline, color: Colors.black), | |
suffixIcon: IconButton( | |
icon: Icon( | |
_obscurePassword | |
? Icons.visibility_off_outlined | |
: Icons.visibility_outlined, | |
color: Colors.black, // Consistent icon color | |
), | |
onPressed: () { | |
setState(() { | |
_obscurePassword = !_obscurePassword; | |
}); | |
}, | |
), | |
enabledBorder: OutlineInputBorder( | |
borderRadius: BorderRadius.circular(10), | |
borderSide: const BorderSide(color: Colors.black), | |
), | |
focusedBorder: OutlineInputBorder( | |
borderRadius: BorderRadius.circular(10), | |
borderSide: const BorderSide(color: Colors.blue), | |
), | |
errorBorder: OutlineInputBorder( | |
borderRadius: BorderRadius.circular(10), | |
borderSide: const BorderSide(color: Colors.red), | |
), | |
focusedErrorBorder: OutlineInputBorder( | |
borderRadius: BorderRadius.circular(10), | |
borderSide: const BorderSide(color: Colors.red), | |
), | |
errorStyle: const TextStyle(color: Colors.red), | |
), | |
validator: (value) { | |
if (value == null || value.isEmpty) { | |
return 'Please enter your password'; | |
} | |
if (value.length < 6) { | |
return 'Password must be at least 6 characters long'; | |
} | |
return null; | |
}, | |
style: const TextStyle(color: Colors.black), | |
), | |
); | |
} | |
// Username Input Field (for Sign Up) | |
Widget _buildUsernameField() { | |
return SizedBox( | |
width: 300, | |
child: TextFormField( | |
controller: _usernameController, | |
keyboardType: TextInputType.text, | |
decoration: InputDecoration( | |
labelText: 'Username', | |
labelStyle: const TextStyle(color: Colors.black), | |
prefixIcon: const Icon(Icons.person_outline, color: Colors.black), | |
enabledBorder: OutlineInputBorder( | |
borderRadius: BorderRadius.circular(10), | |
borderSide: const BorderSide(color: Colors.black), | |
), | |
focusedBorder: OutlineInputBorder( | |
borderRadius: BorderRadius.circular(10), | |
borderSide: const BorderSide(color: Colors.blue), | |
), | |
errorBorder: OutlineInputBorder( | |
borderRadius: BorderRadius.circular(10), | |
borderSide: const BorderSide(color: Colors.red), | |
), | |
focusedErrorBorder: OutlineInputBorder( | |
borderRadius: BorderRadius.circular(10), | |
borderSide: const BorderSide(color: Colors.red), | |
), | |
errorStyle: const TextStyle(color: Colors.red), | |
), | |
validator: (value) { | |
if (value == null || value.isEmpty) { | |
return 'Please enter your username'; | |
} | |
if (value.length < 3) { | |
return 'Username must be at least 3 characters long'; | |
} | |
return null; | |
}, | |
style: const TextStyle(color: Colors.black), | |
), | |
); | |
} | |
// Main Action Button (Login/Sign Up) | |
Widget _buildActionButton() { | |
return SizedBox( | |
width: 300, | |
child: ElevatedButton( | |
onPressed: () { | |
if (_formKey.currentState!.validate()) { | |
// Simulate login/sign up | |
if (_isLogin) { | |
// Perform login | |
print( | |
'Logging in with Email: ${_emailController.text}, Password: ${_passwordController.text}', | |
); | |
_showSuccessDialog('Login Successful!'); | |
} else { | |
// Perform sign up | |
print( | |
'Signing up with Username: ${_usernameController.text}, Email: ${_emailController.text}, Password: ${_passwordController.text}', | |
); | |
_showSuccessDialog('Sign Up Successful!'); | |
} | |
} | |
}, | |
style: ElevatedButton.styleFrom( | |
padding: const EdgeInsets.symmetric(vertical: 15), | |
backgroundColor: Colors.blue, | |
shape: RoundedRectangleBorder( | |
borderRadius: BorderRadius.circular(10), | |
), | |
), | |
child: Text( | |
_isLogin ? 'LOGIN' : 'SIGN UP', | |
style: const TextStyle( | |
fontSize: 18, | |
fontWeight: FontWeight.bold, | |
color: Colors.white, | |
), | |
), | |
), | |
); | |
} | |
// Forgot Password Text | |
Widget _buildForgotPassword() { | |
return GestureDetector( | |
onTap: () { | |
// Navigate to forgot password screen | |
_showForgotPasswordDialog(); | |
}, | |
child: const Text( | |
'Forgot your Password?', | |
style: TextStyle(fontSize: 14, color: Colors.black), | |
), | |
); | |
} | |
// Switch to Sign Up/Login Button | |
Widget _buildSwitchButton() { | |
return Row( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: [ | |
Text( | |
_isLogin ? 'Don\'t have an account? ' : 'Already have an account? ', | |
style: const TextStyle(fontSize: 14, color: Colors.black), | |
), | |
GestureDetector( | |
onTap: () { | |
setState(() { | |
_isLogin = !_isLogin; | |
}); | |
}, | |
child: Text( | |
_isLogin ? 'Sign Up' : 'Login', | |
style: const TextStyle( | |
fontSize: 14, | |
color: Colors.blue, | |
fontWeight: FontWeight.bold, | |
), | |
), | |
), | |
], | |
); | |
} | |
// Show success dialog | |
void _showSuccessDialog(String message) { | |
showDialog( | |
context: context, | |
builder: | |
(context) => AlertDialog( | |
title: const Text('Success'), | |
content: Text(message), | |
actions: [ | |
TextButton( | |
onPressed: () { | |
Navigator.of(context).pop(); | |
}, | |
child: const Text('OK'), | |
), | |
], | |
), | |
); | |
} | |
// Show Forgot Password Dialog | |
void _showForgotPasswordDialog() { | |
showDialog( | |
context: context, | |
builder: (context) { | |
final emailController = TextEditingController(); // Local controller | |
return AlertDialog( | |
title: const Text('Forgot Password?'), | |
content: Column( | |
mainAxisSize: MainAxisSize.min, | |
children: [ | |
const Text( | |
'Enter your email address to receive a password reset link:', | |
), | |
const SizedBox(height: 20), | |
TextField( | |
controller: emailController, | |
keyboardType: TextInputType.emailAddress, | |
decoration: const InputDecoration( | |
labelText: 'Email Address', | |
prefixIcon: Icon(Icons.email_outlined), | |
border: OutlineInputBorder(), | |
), | |
), | |
], | |
), | |
actions: [ | |
TextButton( | |
onPressed: () { | |
Navigator.of(context).pop(); // Close the dialog | |
}, | |
child: const Text('Cancel'), | |
), | |
TextButton( | |
onPressed: () { | |
// Simulate sending reset link | |
final email = emailController.text; | |
if (email.isNotEmpty && | |
RegExp( | |
r'^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$', | |
).hasMatch(email)) { | |
print('Sending reset link to: $email'); | |
Navigator.of(context).pop(); | |
_showResetLinkSentDialog(); // Show confirmation | |
} else { | |
//show error | |
_showErrorDialog( | |
"Invalid Email Address. Please check and try again.", | |
); | |
} | |
}, | |
child: const Text('Reset Password'), | |
), | |
], | |
); | |
}, | |
); | |
} | |
void _showResetLinkSentDialog() { | |
showDialog( | |
context: context, | |
builder: | |
(context) => AlertDialog( | |
title: const Text('Password Reset'), | |
content: const Text( | |
"We've sent a password reset link to your email address. Check that email for the link.", | |
), | |
actions: [ | |
TextButton( | |
onPressed: () { | |
Navigator.of(context).pop(); | |
}, | |
child: const Text('OK'), | |
), | |
], | |
), | |
); | |
} | |
void _showErrorDialog(String message) { | |
showDialog( | |
context: context, | |
builder: | |
(context) => AlertDialog( | |
title: const Text('Error'), | |
content: Text(message), | |
actions: [ | |
TextButton( | |
onPressed: () { | |
Navigator.of(context).pop(); | |
}, | |
child: const Text('OK'), | |
), | |
], | |
), | |
); | |
} | |
} | |
// Custom Painter for Background | |
class BackgroundPainter extends CustomPainter { | |
@override | |
void paint(Canvas canvas, Size size) { | |
final paint = Paint(); | |
// First gradient | |
final gradient1 = LinearGradient( | |
begin: Alignment.topCenter, | |
end: Alignment.bottomCenter, | |
colors: [const Color(0xFFF49464), const Color(0xFFF9C19E)], | |
); | |
// Second gradient | |
final gradient2 = LinearGradient( | |
begin: Alignment.bottomCenter, | |
end: Alignment.topCenter, | |
colors: [const Color(0xFF91D8F5), const Color(0xFF72B8D4)], | |
); | |
// Create a path for the curved shape | |
final path = Path(); | |
path.lineTo(0, size.height * 0.35); // Start of the curve | |
path.quadraticBezierTo( | |
size.width / 2, | |
size.height * 0.5, | |
size.width, | |
size.height * 0.35, | |
); | |
path.lineTo(size.width, 0); | |
path.lineTo(0, 0); | |
path.close(); | |
// Draw the first gradient filled shape | |
paint.shader = gradient1.createShader( | |
Rect.fromLTWH(0, 0, size.width, size.height), | |
); | |
canvas.drawPath(path, paint); | |
// Draw the second gradient filled shape | |
final path2 = Path(); | |
path2.moveTo(0, size.height); | |
path2.lineTo(size.width, size.height); | |
path2.lineTo(size.width, size.height * 0.65); | |
path2.quadraticBezierTo( | |
size.width / 2, | |
size.height * 0.55, | |
0, | |
size.height * 0.65, | |
); | |
path2.lineTo(0, size.height); | |
path2.close(); | |
paint.shader = gradient2.createShader( | |
Rect.fromLTWH(0, size.height * 0.3, size.width, size.height * 0.7), | |
); | |
canvas.drawPath(path2, paint); | |
} | |
@override | |
bool shouldRepaint(CustomPainter oldDelegate) { | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment