Skip to content

Instantly share code, notes, and snippets.

@kururu-abdo
Created May 8, 2025 17:56
Show Gist options
  • Save kururu-abdo/f7d6bae9e417eebc810a9d1d5de18041 to your computer and use it in GitHub Desktop.
Save kururu-abdo/f7d6bae9e417eebc810a9d1d5de18041 to your computer and use it in GitHub Desktop.
Login Page Witn Flutter using CustomPainter
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