637 lines
20 KiB
Dart
637 lines
20 KiB
Dart
import 'dart:ui';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:firebase_auth/firebase_auth.dart';
|
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
import 'pages/home_dashboard_page.dart';
|
|
import 'register_page.dart';
|
|
|
|
class LoginPage extends StatefulWidget {
|
|
const LoginPage({super.key});
|
|
|
|
@override
|
|
State<LoginPage> createState() => _LoginPageState();
|
|
}
|
|
|
|
class _LoginPageState extends State<LoginPage> {
|
|
final emailController = TextEditingController();
|
|
final passwordController = TextEditingController();
|
|
|
|
final FirebaseFirestore firestore = FirebaseFirestore.instance;
|
|
|
|
bool allowRegister = false;
|
|
|
|
bool isLoading = false;
|
|
bool obscurePassword = true;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
checkAdminExists();
|
|
}
|
|
|
|
Future<void> checkAdminExists() async {
|
|
try {
|
|
final snapshot = await firestore.collection('users').where('role', isEqualTo: 'admin').limit(1).get();
|
|
setState(() {
|
|
allowRegister = snapshot.docs.isEmpty;
|
|
});
|
|
} catch (e) {
|
|
setState(() {
|
|
allowRegister = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
/// ================= LOGIN =================
|
|
Future login() async {
|
|
try {
|
|
setState(() {
|
|
isLoading = true;
|
|
});
|
|
|
|
await FirebaseAuth.instance.signInWithEmailAndPassword(
|
|
email: emailController.text.trim(),
|
|
password: passwordController.text.trim(),
|
|
);
|
|
|
|
if (mounted) {
|
|
Navigator.pushReplacement(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => const HomeDashboardPage(),
|
|
),
|
|
);
|
|
}
|
|
} on FirebaseAuthException catch (e) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
backgroundColor: Colors.redAccent,
|
|
behavior: SnackBarBehavior.floating,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
content: Text(
|
|
e.message ?? "Login failed",
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
}
|
|
|
|
/// ================= PREMIUM CARD =================
|
|
Widget modernCard({
|
|
required Widget child,
|
|
}) {
|
|
return ClipRRect(
|
|
borderRadius: BorderRadius.circular(38),
|
|
child: BackdropFilter(
|
|
filter: ImageFilter.blur(
|
|
sigmaX: 30,
|
|
sigmaY: 30,
|
|
),
|
|
child: Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(30),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(38),
|
|
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
colors: [
|
|
Colors.white.withOpacity(0.09),
|
|
Colors.white.withOpacity(0.03),
|
|
],
|
|
),
|
|
|
|
border: Border.all(
|
|
color: Colors.white.withOpacity(0.08),
|
|
width: 1.2,
|
|
),
|
|
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.45),
|
|
blurRadius: 40,
|
|
offset: const Offset(0, 25),
|
|
),
|
|
|
|
BoxShadow(
|
|
color: Colors.blueAccent
|
|
.withOpacity(0.08),
|
|
blurRadius: 50,
|
|
spreadRadius: 1,
|
|
),
|
|
],
|
|
),
|
|
child: child,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// ================= MODERN TEXTFIELD =================
|
|
Widget modernField({
|
|
required String hint,
|
|
required TextEditingController controller,
|
|
required IconData icon,
|
|
bool obscure = false,
|
|
Widget? suffixIcon,
|
|
}) {
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(24),
|
|
|
|
color: Colors.white.withOpacity(0.04),
|
|
|
|
border: Border.all(
|
|
color: Colors.white.withOpacity(0.05),
|
|
),
|
|
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.15),
|
|
blurRadius: 15,
|
|
),
|
|
],
|
|
),
|
|
child: TextField(
|
|
controller: controller,
|
|
obscureText: obscure,
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 16,
|
|
),
|
|
decoration: InputDecoration(
|
|
border: InputBorder.none,
|
|
hintText: hint,
|
|
|
|
hintStyle: const TextStyle(
|
|
color: Colors.white38,
|
|
),
|
|
|
|
prefixIcon: Icon(
|
|
icon,
|
|
color: Colors.white54,
|
|
),
|
|
|
|
suffixIcon: suffixIcon,
|
|
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
horizontal: 20,
|
|
vertical: 22,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
|
value: const SystemUiOverlayStyle(
|
|
|
|
/// STATUS BAR
|
|
statusBarColor: Colors.transparent,
|
|
statusBarIconBrightness: Brightness.light,
|
|
statusBarBrightness: Brightness.dark,
|
|
|
|
/// NAVIGATION BAR
|
|
systemNavigationBarColor: Color(0xff030303),
|
|
systemNavigationBarIconBrightness:
|
|
Brightness.light,
|
|
),
|
|
|
|
child: Scaffold(
|
|
backgroundColor: const Color(0xff030303),
|
|
|
|
body: Stack(
|
|
children: [
|
|
|
|
/// ================= BACKGROUND =================
|
|
Container(
|
|
decoration: const BoxDecoration(
|
|
gradient: RadialGradient(
|
|
center: Alignment.topRight,
|
|
radius: 1.5,
|
|
colors: [
|
|
Color(0xff111827),
|
|
Color(0xff050505),
|
|
Colors.black,
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
/// ================= TOP LIGHT =================
|
|
Positioned(
|
|
top: -120,
|
|
right: -50,
|
|
child: Container(
|
|
height: 260,
|
|
width: 260,
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
gradient: RadialGradient(
|
|
colors: [
|
|
Colors.blueAccent
|
|
.withOpacity(0.25),
|
|
Colors.transparent,
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
/// ================= BOTTOM LIGHT =================
|
|
Positioned(
|
|
bottom: -120,
|
|
left: -70,
|
|
child: Container(
|
|
height: 260,
|
|
width: 260,
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
gradient: RadialGradient(
|
|
colors: [
|
|
Colors.cyan.withOpacity(0.18),
|
|
Colors.transparent,
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
/// ================= BLUR =================
|
|
BackdropFilter(
|
|
filter: ImageFilter.blur(
|
|
sigmaX: 90,
|
|
sigmaY: 90,
|
|
),
|
|
child: Container(
|
|
color: Colors.transparent,
|
|
),
|
|
),
|
|
|
|
SafeArea(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 24,
|
|
vertical: 20,
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.start,
|
|
children: [
|
|
|
|
const SizedBox(height: 10),
|
|
|
|
/// ================= HEADER =================
|
|
Row(
|
|
children: [
|
|
|
|
Container(
|
|
padding:
|
|
const EdgeInsets.all(15),
|
|
|
|
decoration: BoxDecoration(
|
|
borderRadius:
|
|
BorderRadius.circular(22),
|
|
|
|
color: Colors.white
|
|
.withOpacity(0.05),
|
|
|
|
border: Border.all(
|
|
color: Colors.white
|
|
.withOpacity(0.06),
|
|
),
|
|
),
|
|
|
|
child: const Icon(
|
|
Icons.lock_rounded,
|
|
color: Colors.white,
|
|
size: 28,
|
|
),
|
|
),
|
|
|
|
const SizedBox(width: 16),
|
|
|
|
const Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.start,
|
|
children: [
|
|
|
|
Text(
|
|
"DOORGUARD",
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 28,
|
|
fontWeight:
|
|
FontWeight.bold,
|
|
letterSpacing: 1,
|
|
),
|
|
),
|
|
|
|
SizedBox(height: 4),
|
|
|
|
Text(
|
|
"Smart Security System",
|
|
style: TextStyle(
|
|
color: Colors.white54,
|
|
fontSize: 13,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 55),
|
|
|
|
/// ================= PREMIUM LOGO =================
|
|
Center(
|
|
child: Container(
|
|
height: 110,
|
|
width: 110,
|
|
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
|
|
gradient:
|
|
const LinearGradient(
|
|
begin: Alignment.topLeft,
|
|
end:
|
|
Alignment.bottomRight,
|
|
colors: [
|
|
Color(0xff3B82F6),
|
|
Color(0xff06B6D4),
|
|
],
|
|
),
|
|
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.blueAccent
|
|
.withOpacity(0.45),
|
|
blurRadius: 35,
|
|
spreadRadius: 5,
|
|
),
|
|
],
|
|
),
|
|
|
|
child: Container(
|
|
margin:
|
|
const EdgeInsets.all(4),
|
|
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
|
|
color:
|
|
const Color(0xff050505),
|
|
|
|
border: Border.all(
|
|
color: Colors.white
|
|
.withOpacity(0.08),
|
|
),
|
|
),
|
|
|
|
child: const Icon(
|
|
Icons.lock_person_rounded,
|
|
color: Colors.white,
|
|
size: 50,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 18),
|
|
|
|
/// ================= TAG =================
|
|
Center(
|
|
child: Container(
|
|
padding:
|
|
const EdgeInsets.symmetric(
|
|
horizontal: 18,
|
|
vertical: 8,
|
|
),
|
|
|
|
decoration: BoxDecoration(
|
|
borderRadius:
|
|
BorderRadius.circular(50),
|
|
|
|
color: Colors.white
|
|
.withOpacity(0.05),
|
|
|
|
border: Border.all(
|
|
color: Colors.white
|
|
.withOpacity(0.06),
|
|
),
|
|
),
|
|
|
|
child: const Text(
|
|
"SMART SECURITY",
|
|
style: TextStyle(
|
|
color: Colors.white70,
|
|
fontSize: 11,
|
|
fontWeight:
|
|
FontWeight.w600,
|
|
letterSpacing: 2,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 40),
|
|
|
|
/// ================= TITLE =================
|
|
const Text(
|
|
"Welcome",
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 46,
|
|
height: 1,
|
|
fontWeight: FontWeight.w800,
|
|
letterSpacing: -1.5,
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 14),
|
|
|
|
const Text(
|
|
"Access and control your smart\nRFID door security system.",
|
|
style: TextStyle(
|
|
color: Colors.white54,
|
|
fontSize: 15,
|
|
height: 1.8,
|
|
letterSpacing: 0.3,
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 35),
|
|
|
|
/// ================= LOGIN CARD =================
|
|
modernCard(
|
|
child: Column(
|
|
children: [
|
|
|
|
modernField(
|
|
hint: "Email Address",
|
|
controller:
|
|
emailController,
|
|
icon:
|
|
Icons.email_outlined,
|
|
),
|
|
|
|
const SizedBox(height: 18),
|
|
|
|
modernField(
|
|
hint: "Password",
|
|
controller:
|
|
passwordController,
|
|
|
|
icon: Icons
|
|
.lock_outline_rounded,
|
|
|
|
obscure: obscurePassword,
|
|
|
|
suffixIcon: IconButton(
|
|
onPressed: () {
|
|
setState(() {
|
|
obscurePassword =
|
|
!obscurePassword;
|
|
});
|
|
},
|
|
icon: Icon(
|
|
obscurePassword
|
|
? Icons
|
|
.visibility_off
|
|
: Icons.visibility,
|
|
color:
|
|
Colors.white54,
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 30),
|
|
|
|
/// ================= LOGIN BUTTON =================
|
|
SizedBox(
|
|
width: double.infinity,
|
|
height: 64,
|
|
|
|
child: ElevatedButton(
|
|
onPressed: isLoading
|
|
? null
|
|
: login,
|
|
|
|
style:
|
|
ElevatedButton
|
|
.styleFrom(
|
|
elevation: 25,
|
|
|
|
shadowColor:
|
|
Colors.blueAccent,
|
|
|
|
backgroundColor:
|
|
const Color(
|
|
0xff2563EB),
|
|
|
|
shape:
|
|
RoundedRectangleBorder(
|
|
borderRadius:
|
|
BorderRadius
|
|
.circular(
|
|
26),
|
|
),
|
|
),
|
|
|
|
child: isLoading
|
|
? const SizedBox(
|
|
height: 24,
|
|
width: 24,
|
|
child:
|
|
CircularProgressIndicator(
|
|
color: Colors
|
|
.white,
|
|
strokeWidth:
|
|
2.5,
|
|
),
|
|
)
|
|
: const Row(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment
|
|
.center,
|
|
|
|
children: [
|
|
|
|
Text(
|
|
"LOGIN",
|
|
style:
|
|
TextStyle(
|
|
color: Colors
|
|
.white,
|
|
fontSize:
|
|
16,
|
|
fontWeight:
|
|
FontWeight
|
|
.w700,
|
|
letterSpacing:
|
|
1,
|
|
),
|
|
),
|
|
|
|
SizedBox(
|
|
width: 10),
|
|
|
|
Icon(
|
|
Icons
|
|
.arrow_forward_rounded,
|
|
color: Colors
|
|
.white,
|
|
size: 22,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 18),
|
|
|
|
if (allowRegister)
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => const RegisterPage(role: 'admin'),
|
|
),
|
|
);
|
|
},
|
|
|
|
child: const Text(
|
|
"Create admin account",
|
|
style: TextStyle(
|
|
color: Colors.white70,
|
|
fontSize: 15,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 35),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
} |