Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
f0b7f33b8d |
BIN
assets/gas.jpeg
Normal file
BIN
assets/gas.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
BIN
assets/wi-fi.gif
Normal file
BIN
assets/wi-fi.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
@ -15,9 +15,12 @@ class MyApp extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const MaterialApp(
|
return MaterialApp(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
home: SplashScreen(), // Start with SplashScreen
|
theme:ThemeData(
|
||||||
|
fontFamily: 'Poppins',
|
||||||
|
),
|
||||||
|
home: const SplashScreen(), // Start with SplashScreen
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,53 +1,209 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:login_page/data/bg_data.dart'; // Import your bgList
|
import 'package:login_page/screens/login_screen.dart'; // Make sure this import path is correct
|
||||||
|
|
||||||
class HomePage extends StatelessWidget {
|
class HomePage extends StatelessWidget {
|
||||||
final String email;
|
final String email;
|
||||||
|
|
||||||
const HomePage({super.key, required this.email});
|
const HomePage({super.key, required this.email});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
automaticallyImplyLeading: true,
|
title: Text("Dashboard"),
|
||||||
title: Text('Hello $email'),
|
automaticallyImplyLeading: false,
|
||||||
centerTitle: true,
|
actions: [
|
||||||
actions: [
|
// Logout Icon Button
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.logout),
|
icon: Icon(Icons.logout, color: Colors.black),
|
||||||
tooltip: 'Logout',
|
onPressed: () {
|
||||||
onPressed: () {
|
Navigator.of(context).pushAndRemoveUntil(
|
||||||
Navigator.of(context).pop(); // Go back to login screen
|
MaterialPageRoute(builder: (context) => LoginScreen()),
|
||||||
},
|
(route) => false,
|
||||||
),
|
);
|
||||||
],
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(12.0),
|
child: Padding(
|
||||||
child: ListView.builder(
|
padding: EdgeInsets.all(20),
|
||||||
itemCount: bgList.length,
|
child: Column(
|
||||||
itemBuilder: (context, index) {
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
return Container(
|
children: [
|
||||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
// Welcome Section Container with Greeting Inside
|
||||||
height: 180,
|
Container(
|
||||||
decoration: BoxDecoration(
|
padding: EdgeInsets.all(15),
|
||||||
borderRadius: BorderRadius.circular(16),
|
decoration: BoxDecoration(
|
||||||
boxShadow: [
|
border: Border.all(color: Colors.grey.shade300),
|
||||||
BoxShadow(
|
borderRadius: BorderRadius.circular(8),
|
||||||
color: Colors.black26,
|
),
|
||||||
blurRadius: 8,
|
child: Column(
|
||||||
offset: Offset(0, 4),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
),
|
children: [
|
||||||
],
|
// Greeting + Waving Hand Icon
|
||||||
image: DecorationImage(
|
Row(
|
||||||
image: AssetImage(bgList[index]),
|
children: [
|
||||||
fit: BoxFit.cover,
|
Text(
|
||||||
|
"Good Morning, Thereza",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 5),
|
||||||
|
Icon(Icons.waving_hand, size: 24, color: Colors.blue),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
|
||||||
|
// Prepaid Info
|
||||||
|
Text("Prepaid: 0794606921"),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
Text("Credit (Ksh): 0"),
|
||||||
|
Text("Net Points: 0"),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
},
|
SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Full Width Wholesome Image - No Background
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.symmetric(vertical: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/gas.jpeg',
|
||||||
|
width: double.infinity,
|
||||||
|
height: 250, // Fallback height for most devices
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Active Subscription Title
|
||||||
|
Text(
|
||||||
|
"Your active subscriptions",
|
||||||
|
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 10),
|
||||||
|
|
||||||
|
// Active Subscription Card
|
||||||
|
Card(
|
||||||
|
elevation: 3,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(15),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"KKWZBZVZ",
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
Text("Sh850= 30Days UnlimiNET - 30 Days"),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
Text("Used: 3.96 GB"),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
Text("Expires: 14/06/2025 17:55"),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
onPressed: () {},
|
||||||
|
child: Text("RECONNECT"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Enter Voucher Code
|
||||||
|
TextField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Enter Voucher Code",
|
||||||
|
suffixIcon: ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
onPressed: () {},
|
||||||
|
child: Text("CONNECT"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Offers Title
|
||||||
|
Text(
|
||||||
|
"Offers",
|
||||||
|
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 10),
|
||||||
|
|
||||||
|
// Offers List
|
||||||
|
_buildOfferCard("Daily FREE 20 Minutes UnlimiNET", "FREE"),
|
||||||
|
_buildOfferCard("Sh5= 30Minutes UnlimiNET", "Sh5"),
|
||||||
|
_buildOfferCard("Sh9= 1Hour UnlimiNET", "Sh9"),
|
||||||
|
_buildOfferCard("Sh13= 2Hours UnlimiNET", "Sh13"),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Reusable widget for each offer card
|
||||||
|
Widget _buildOfferCard(String name, String price) {
|
||||||
|
return Card(
|
||||||
|
margin: EdgeInsets.symmetric(vertical: 5),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(10),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(name, style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
Text("1 Device"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
price == "FREE"
|
||||||
|
? OutlinedButton(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
foregroundColor: Colors.red,
|
||||||
|
side: BorderSide(color: Colors.red),
|
||||||
|
),
|
||||||
|
onPressed: () {},
|
||||||
|
child: Text("FREE"),
|
||||||
|
)
|
||||||
|
: OutlinedButton(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
foregroundColor: Colors.red,
|
||||||
|
side: BorderSide(color: Colors.red),
|
||||||
|
),
|
||||||
|
onPressed: () {},
|
||||||
|
child: Text("BUY"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import '../utils/text_utils.dart';
|
import '../utils/text_utils.dart';
|
||||||
import 'home.dart';
|
import 'verify.dart';
|
||||||
|
|
||||||
class LoginScreen extends StatefulWidget {
|
class LoginScreen extends StatefulWidget {
|
||||||
const LoginScreen({super.key});
|
const LoginScreen({super.key});
|
||||||
@ -9,431 +11,99 @@ class LoginScreen extends StatefulWidget {
|
|||||||
State<LoginScreen> createState() => _LoginScreenState();
|
State<LoginScreen> createState() => _LoginScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
// State class for LoginScreen, with animation support for flipping forms
|
|
||||||
class _LoginScreenState extends State<LoginScreen>
|
class _LoginScreenState extends State<LoginScreen>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
bool isDarkMode = false; // Tracks dark mode state
|
bool isDarkMode = false;
|
||||||
bool isRemembered = false; // Tracks "Remember Me" checkbox
|
bool _phoneHasFocus = false;
|
||||||
bool isLogin = true; // Tracks if login or register form is shown
|
bool _phoneContainsText = false;
|
||||||
|
|
||||||
// Controllers for email and password input fields
|
final TextEditingController _phoneController = TextEditingController();
|
||||||
final TextEditingController _emailController = TextEditingController();
|
|
||||||
final TextEditingController _passwordController = TextEditingController();
|
|
||||||
|
|
||||||
// Hardcoded credentials, updated on registration or password reset
|
// Auto-generated verification code
|
||||||
String _correctEmail = 'Amazons@tech.com';
|
String? _generatedCode;
|
||||||
String _correctPassword = '12345';
|
|
||||||
|
|
||||||
// Animation controller and animation for flipping between forms
|
// Timer variables
|
||||||
late AnimationController _animationController;
|
late Timer _timer;
|
||||||
late Animation<double> _animation;
|
int _countdown = 60;
|
||||||
|
bool _isCounting = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
// Initialize animation controller for flip effect
|
|
||||||
_animationController = AnimationController(
|
|
||||||
vsync: this,
|
|
||||||
duration: const Duration(milliseconds: 600),
|
|
||||||
);
|
|
||||||
// Tween for flip animation
|
|
||||||
_animation = Tween<double>(begin: 0, end: 1).animate(
|
|
||||||
CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
// Dispose controllers to free resources
|
_phoneController.dispose();
|
||||||
_animationController.dispose();
|
if (_isCounting) _timer.cancel();
|
||||||
_emailController.dispose();
|
|
||||||
_passwordController.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login logic: checks credentials and navigates to HomePage if correct
|
/// Validates phone number input
|
||||||
void _login() {
|
String? _validatePhone(String value) {
|
||||||
if (_emailController.text == _correctEmail &&
|
if (value.isEmpty) return 'Phone number cannot be empty';
|
||||||
_passwordController.text == _correctPassword) {
|
if (!RegExp(r'^\d{10}$').hasMatch(value)) {
|
||||||
Navigator.push(
|
return 'Enter a valid 10-digit phone number';
|
||||||
context,
|
}
|
||||||
MaterialPageRoute(
|
return null;
|
||||||
builder: (context) => HomePage(email: _emailController.text), // Go to HomePage
|
}
|
||||||
),
|
|
||||||
);
|
/// Generates a random 4-digit code
|
||||||
} else {
|
String _generateVerificationCode() {
|
||||||
// Show error if credentials are wrong
|
final random = DateTime.now().millisecondsSinceEpoch % 9000 + 1000;
|
||||||
|
return random.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts a countdown timer for code expiration
|
||||||
|
void _startCountdown() {
|
||||||
|
if (_isCounting) return;
|
||||||
|
|
||||||
|
_isCounting = true;
|
||||||
|
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
|
||||||
|
if (_countdown == 0) {
|
||||||
|
_isCounting = false;
|
||||||
|
timer.cancel();
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_countdown--;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends user to Verification Screen with generated code
|
||||||
|
void _connect() {
|
||||||
|
final validation = _validatePhone(_phoneController.text);
|
||||||
|
if (validation != null) {
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(const SnackBar(content: Text('Invalid credentials')));
|
).showSnackBar(SnackBar(content: Text(validation)));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Registration logic: updates hardcoded credentials and flips back to login
|
// Generate new code
|
||||||
void _register() {
|
_generatedCode = _generateVerificationCode();
|
||||||
if (_emailController.text.isNotEmpty &&
|
|
||||||
_passwordController.text.isNotEmpty) {
|
// Show snackbar and auto-fill in verification screen
|
||||||
setState(() {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_correctEmail = _emailController.text; // Update email
|
|
||||||
_correctPassword = _passwordController.text; // Update password
|
|
||||||
isLogin = true; // Switch to login form
|
|
||||||
});
|
|
||||||
_animationController.reverse(); // Animate back to login
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text('Registration successful! Please login')),
|
SnackBar(content: Text("Auto-filled code: $_generatedCode")),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Password reset logic: updates the hardcoded password
|
|
||||||
void _resetPassword(String newPassword) {
|
|
||||||
setState(() {
|
|
||||||
_correctPassword = newPassword; // Update password
|
|
||||||
});
|
});
|
||||||
Navigator.pop(context); // Close dialog
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(content: Text('Password updated successfully!')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shows a dialog to enter a new password for reset
|
// Start countdown
|
||||||
void _showResetPasswordDialog() {
|
_countdown = 60;
|
||||||
final TextEditingController newPasswordController = TextEditingController();
|
_startCountdown();
|
||||||
|
|
||||||
showDialog(
|
// Navigate to verification screen
|
||||||
context: context,
|
Navigator.push(
|
||||||
builder: (context) => AlertDialog(
|
context,
|
||||||
title: Text(
|
MaterialPageRoute(
|
||||||
'Reset Password',
|
builder:
|
||||||
style: TextStyle(color: isDarkMode ? Colors.white : Colors.black),
|
(context) => VerificationScreen(
|
||||||
),
|
correctCode: _generatedCode!,
|
||||||
content: SizedBox(
|
autoFillCode: _generatedCode, // Pass generated code for auto-fill
|
||||||
height: 100,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
TextFormField(
|
|
||||||
controller: newPasswordController, // Controller for new password
|
|
||||||
obscureText: true, // Hide input
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'New Password',
|
|
||||||
labelStyle: TextStyle(
|
|
||||||
color: isDarkMode ? Colors.white70 : Colors.black54,
|
|
||||||
),
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
enabledBorder: OutlineInputBorder(
|
|
||||||
borderSide: BorderSide(
|
|
||||||
color: isDarkMode ? Colors.white70 : Colors.black54,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
style: TextStyle(
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
backgroundColor: isDarkMode ? Colors.grey[900] : Colors.white,
|
|
||||||
actions: [
|
|
||||||
// Cancel button
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: Text(
|
|
||||||
'Cancel',
|
|
||||||
style: TextStyle(
|
|
||||||
color: isDarkMode ? Colors.white70 : Colors.black54,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
// Update button
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
if (newPasswordController.text.isNotEmpty) {
|
|
||||||
_resetPassword(newPasswordController.text); // Update password
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'Update',
|
|
||||||
style: TextStyle(
|
|
||||||
color: const Color(0xFFC76723),
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login form widget
|
|
||||||
Widget _buildLoginForm() {
|
|
||||||
return Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 20), // Spacer
|
|
||||||
Center(
|
|
||||||
child: SizedBox(
|
|
||||||
height: 150,
|
|
||||||
width: 200,
|
|
||||||
child: Image.asset('assets/logo.png', fit: BoxFit.contain), // Logo
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Center(
|
|
||||||
child: TextUtil(
|
|
||||||
text: "Login",
|
|
||||||
weight: true,
|
|
||||||
size: 30,
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
TextUtil(
|
|
||||||
text: "Email",
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 35,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: TextFormField(
|
|
||||||
controller: _emailController, // Email input
|
|
||||||
style: TextStyle(color: isDarkMode ? Colors.white : Colors.black),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
suffixIcon: Icon(
|
|
||||||
Icons.mail,
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
border: InputBorder.none,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
TextUtil(
|
|
||||||
text: "Password",
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 35,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: TextFormField(
|
|
||||||
controller: _passwordController, // Password input
|
|
||||||
obscureText: true, // Hide input
|
|
||||||
style: TextStyle(color: isDarkMode ? Colors.white : Colors.black),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
suffixIcon: Icon(
|
|
||||||
Icons.lock,
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
border: InputBorder.none,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
// Remember Me checkbox
|
|
||||||
TextButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
isRemembered = !isRemembered;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
icon: Icon(
|
|
||||||
isRemembered ? Icons.check_box : Icons.check_box_outline_blank,
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
size: 18,
|
|
||||||
),
|
|
||||||
label: TextUtil(
|
|
||||||
text: "Remember Me",
|
|
||||||
size: 12,
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
style: TextButton.styleFrom(padding: EdgeInsets.zero),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
// Forget password button
|
|
||||||
TextButton(
|
|
||||||
onPressed: _showResetPasswordDialog, // Show reset dialog
|
|
||||||
style: TextButton.styleFrom(padding: EdgeInsets.zero),
|
|
||||||
child: TextUtil(
|
|
||||||
text: "FORGET PASSWORD",
|
|
||||||
size: 12,
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
// Login button
|
|
||||||
GestureDetector(
|
|
||||||
onTap: _login, // Call login logic
|
|
||||||
child: Container(
|
|
||||||
height: 40,
|
|
||||||
width: double.infinity,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
borderRadius: BorderRadius.circular(30),
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: TextUtil(
|
|
||||||
text: "Log In",
|
|
||||||
color: isDarkMode ? Colors.black : Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
// Switch to register form
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
isLogin = false; // Show register form
|
|
||||||
_emailController.clear();
|
|
||||||
_passwordController.clear();
|
|
||||||
});
|
|
||||||
_animationController.forward(); // Animate flip
|
|
||||||
},
|
|
||||||
child: Center(
|
|
||||||
child: TextUtil(
|
|
||||||
text: "Don't have an account? REGISTER",
|
|
||||||
size: 14,
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register form widget (flipped)
|
|
||||||
Widget _buildRegisterForm() {
|
|
||||||
return Transform(
|
|
||||||
transform: Matrix4.identity()..rotateY(3.14159), // Flip effect
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Center(
|
|
||||||
child: SizedBox(
|
|
||||||
height: 150,
|
|
||||||
width: 200,
|
|
||||||
child: Image.asset('assets/logo.png', fit: BoxFit.contain), // Logo
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Center(
|
|
||||||
child: TextUtil(
|
|
||||||
text: "Register",
|
|
||||||
weight: true,
|
|
||||||
size: 30,
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
TextUtil(
|
|
||||||
text: "Email",
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 35,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: TextFormField(
|
|
||||||
controller: _emailController, // Email input
|
|
||||||
style: TextStyle(color: isDarkMode ? Colors.white : Colors.black),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
suffixIcon: Icon(
|
|
||||||
Icons.mail,
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
border: InputBorder.none,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
TextUtil(
|
|
||||||
text: "Password",
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 35,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: TextFormField(
|
|
||||||
controller: _passwordController, // Password input
|
|
||||||
obscureText: true, // Hide input
|
|
||||||
style: TextStyle(color: isDarkMode ? Colors.white : Colors.black),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
suffixIcon: Icon(
|
|
||||||
Icons.lock,
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
border: InputBorder.none,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 40),
|
|
||||||
// Register button
|
|
||||||
GestureDetector(
|
|
||||||
onTap: _register, // Call register logic
|
|
||||||
child: Container(
|
|
||||||
height: 40,
|
|
||||||
width: double.infinity,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
borderRadius: BorderRadius.circular(30),
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: TextUtil(
|
|
||||||
text: "Register Me",
|
|
||||||
color: isDarkMode ? Colors.black : Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
// Switch to login form
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
isLogin = true; // Show login form
|
|
||||||
_emailController.clear();
|
|
||||||
_passwordController.clear();
|
|
||||||
});
|
|
||||||
_animationController.reverse(); // Animate flip
|
|
||||||
},
|
|
||||||
child: Center(
|
|
||||||
child: TextUtil(
|
|
||||||
text: "Already have an account? LOGIN",
|
|
||||||
size: 14,
|
|
||||||
color: isDarkMode ? Colors.white : Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -441,20 +111,18 @@ class _LoginScreenState extends State<LoginScreen>
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Theme(
|
return Theme(
|
||||||
data: isDarkMode ? ThemeData.dark() : ThemeData.light(), // Set theme
|
data: isDarkMode ? ThemeData.dark() : ThemeData.light(),
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: const Color(0xFFC76723), // Custom orange color
|
backgroundColor: Color(0xFFC76723),
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(
|
icon: Icon(isDarkMode ? Icons.light_mode : Icons.dark_mode),
|
||||||
isDarkMode ? Icons.light_mode : Icons.dark_mode,
|
color: Colors.white,
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
isDarkMode = !isDarkMode; // Toggle theme
|
isDarkMode = !isDarkMode;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -463,38 +131,218 @@ class _LoginScreenState extends State<LoginScreen>
|
|||||||
body: Container(
|
body: Container(
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
color: isDarkMode ? const Color.fromARGB(255, 26, 24, 24) : Colors.white, // Background color
|
color: isDarkMode ? Color.fromARGB(255, 26, 24, 24) : Colors.white,
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 30),
|
margin: EdgeInsets.symmetric(horizontal: 30),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isDarkMode
|
color:
|
||||||
? const Color(0xFF23272F) // Use a dark blue/grey for dark mode
|
isDarkMode
|
||||||
: const Color.fromARGB(136, 71, 69, 69),
|
? Color.fromARGB(255, 35, 38, 46)
|
||||||
|
: Color.fromARGB(136, 71, 69, 69),
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(15),
|
borderRadius: BorderRadius.circular(15),
|
||||||
color: isDarkMode
|
color:
|
||||||
? const Color.fromARGB(137, 132, 129, 129) // Use a dark blue/grey for dark mode
|
isDarkMode
|
||||||
: Colors.white70,
|
? Color.fromARGB(133, 93, 75, 75)
|
||||||
|
: Colors.white70,
|
||||||
),
|
),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(25),
|
padding: const EdgeInsets.all(20),
|
||||||
child: AnimatedBuilder(
|
child: Column(
|
||||||
animation: _animation,
|
mainAxisSize: MainAxisSize.min,
|
||||||
builder: (context, child) {
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
// Flip between login and register forms
|
children: [
|
||||||
return Transform(
|
// Logo with tagline directly underneath
|
||||||
transform: Matrix4.identity()
|
Center(
|
||||||
..setEntry(3, 2, 0.001)
|
child: Column(
|
||||||
..rotateY(3.14159 * _animation.value),
|
mainAxisSize: MainAxisSize.min,
|
||||||
alignment: Alignment.center,
|
children: [
|
||||||
child: isLogin ? _buildLoginForm() : _buildRegisterForm(),
|
// Logo
|
||||||
);
|
SizedBox(
|
||||||
},
|
height: 100,
|
||||||
|
width: 160,
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/logo.png',
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4), // Reduced space between logo and tagline
|
||||||
|
// Tagline
|
||||||
|
Text(
|
||||||
|
'Fast . Reliable . Affordable',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
letterSpacing: 1.0,
|
||||||
|
color:
|
||||||
|
isDarkMode ? Colors.white : Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Removed the blue line and adjusted spacing
|
||||||
|
const SizedBox(height: 24), // Added more space after tagline
|
||||||
|
// "Sign in here" text
|
||||||
|
TextUtil(
|
||||||
|
text: "Sign in here",
|
||||||
|
weight: true,
|
||||||
|
size: 20,
|
||||||
|
color: isDarkMode ? Colors.white : Colors.black,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextUtil(text: "Phone Number"),
|
||||||
|
Container(
|
||||||
|
height: 50,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isDarkMode ? Colors.grey[800] : Colors.white,
|
||||||
|
border: Border.all(
|
||||||
|
color:
|
||||||
|
_phoneHasFocus || _phoneContainsText
|
||||||
|
? (isDarkMode
|
||||||
|
? Colors.blueAccent
|
||||||
|
: Colors.blue)
|
||||||
|
: (isDarkMode ? Colors.white : Colors.black)
|
||||||
|
.withOpacity(0.4),
|
||||||
|
width: 1.5,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.phone,
|
||||||
|
color: isDarkMode ? Colors.white : Colors.black,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _phoneController,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
maxLength: 10,
|
||||||
|
style: TextStyle(
|
||||||
|
color:
|
||||||
|
isDarkMode ? Colors.white : Colors.black,
|
||||||
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Enter phone number",
|
||||||
|
counterText: "",
|
||||||
|
border: InputBorder.none,
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
color:
|
||||||
|
isDarkMode
|
||||||
|
? Colors.white70
|
||||||
|
: Colors.black45,
|
||||||
|
),
|
||||||
|
isDense: true,
|
||||||
|
),
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.digitsOnly,
|
||||||
|
LengthLimitingTextInputFormatter(10),
|
||||||
|
],
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_phoneContainsText = value.isNotEmpty;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_phoneHasFocus = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onEditingComplete: () {
|
||||||
|
setState(() {
|
||||||
|
_phoneHasFocus = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: _connect,
|
||||||
|
child: Container(
|
||||||
|
height: 40,
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue,
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: TextUtil(text: "Connect", color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: isDarkMode ? Colors.white24 : Colors.black26,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
color:
|
||||||
|
isDarkMode ? Colors.grey[850] : Colors.grey[200],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.headset_mic,
|
||||||
|
color:
|
||||||
|
isDarkMode ? Colors.blueAccent : Colors.blue,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'Need help? Contact support',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color:
|
||||||
|
isDarkMode
|
||||||
|
? Colors.white70
|
||||||
|
: Colors.black54,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: isDarkMode ? Colors.white24 : Colors.black26,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
color:
|
||||||
|
isDarkMode ? Colors.grey[850] : Colors.grey[200],
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'© Lence Amazons Ltd • All rights reserved • 2025',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color:
|
||||||
|
isDarkMode ? Colors.white70 : Colors.black54,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -504,4 +352,4 @@ class _LoginScreenState extends State<LoginScreen>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'login_screen.dart';
|
import 'package:flutter/services.dart'; // For hiding system UI
|
||||||
|
import 'login_screen.dart'; // Make sure this import path is correct
|
||||||
|
|
||||||
class SplashScreen extends StatefulWidget {
|
class SplashScreen extends StatefulWidget {
|
||||||
const SplashScreen({super.key});
|
const SplashScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SplashScreen> createState() => _SplashScreenState();
|
State<SplashScreen> createState() => _SplashScreenState();
|
||||||
}
|
}
|
||||||
@ -12,39 +13,96 @@ class _SplashScreenState extends State<SplashScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
// Wait for 3 seconds, then navigate to LoginScreen
|
// Hide system UI (status bar and bottom navigation bar)
|
||||||
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
|
||||||
|
// Navigate to Login Screen after 3 seconds
|
||||||
Future.delayed(const Duration(seconds: 3), () {
|
Future.delayed(const Duration(seconds: 3), () {
|
||||||
// ignore: use_build_context_synchronously
|
Navigator.pushReplacement(
|
||||||
Navigator.of(context).pushReplacement(
|
// ignore: use_build_context_synchronously
|
||||||
MaterialPageRoute(
|
context,
|
||||||
builder: (context) => const LoginScreen(),
|
MaterialPageRoute(builder: (context) => const LoginScreen()),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
backgroundColor: Colors.white,
|
value: SystemUiOverlayStyle.dark.copyWith(
|
||||||
body: Center(
|
statusBarColor: Colors.white, // Transparent or match background
|
||||||
child: TweenAnimationBuilder<double>(
|
systemNavigationBarColor: Colors.white,
|
||||||
tween: Tween(begin: 1.0, end: 1.2),
|
),
|
||||||
duration: const Duration(milliseconds: 700),
|
child: Scaffold(
|
||||||
curve: Curves.easeInOut,
|
backgroundColor: Colors.white,
|
||||||
builder: (context, scale, child) {
|
body: SafeArea(
|
||||||
return Transform.scale(
|
child: Column(
|
||||||
scale: scale,
|
children: [
|
||||||
child: child,
|
// Main content in the middle (with Expanded to take available space)
|
||||||
);
|
Expanded(
|
||||||
},
|
child: Center(
|
||||||
onEnd: () {
|
child: Padding(
|
||||||
setState(() {});
|
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||||
},
|
child: Column(
|
||||||
child: Image.asset(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
'assets/splash.png',
|
children: [
|
||||||
width: 150,
|
// Animated Logo
|
||||||
fit: BoxFit.contain,
|
TweenAnimationBuilder<double>(
|
||||||
|
tween: Tween<double>(begin: 1.0, end: 1.2),
|
||||||
|
duration: const Duration(milliseconds: 700),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
builder: (context, scale, child) {
|
||||||
|
return Transform.scale(scale: scale, child: child);
|
||||||
|
},
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/logo.png',
|
||||||
|
width: 200,
|
||||||
|
height: 50,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
// Loading Text
|
||||||
|
const Text(
|
||||||
|
"Just a moment...",
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
// Animated GIF
|
||||||
|
TweenAnimationBuilder<double>(
|
||||||
|
tween: Tween<double>(begin: 0.8, end: 1.0),
|
||||||
|
duration: const Duration(milliseconds: 600),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
builder: (context, scale, child) =>
|
||||||
|
Transform.scale(scale: scale, child: child),
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/wi-fi.gif',
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
// Info Text
|
||||||
|
const Text(
|
||||||
|
"Hang on as we redirect you to the WiFi Portal...",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 14, color: Colors.grey),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Footer at the bottom
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 16.0),
|
||||||
|
child: Text(
|
||||||
|
"© Lence Amazons Ltd All rights reserved.",
|
||||||
|
style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
186
lib/screens/verify.dart
Normal file
186
lib/screens/verify.dart
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:login_page/screens/home.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
class VerificationScreen extends StatefulWidget {
|
||||||
|
final String correctCode;
|
||||||
|
final String? autoFillCode;
|
||||||
|
|
||||||
|
const VerificationScreen({
|
||||||
|
super.key,
|
||||||
|
required this.correctCode,
|
||||||
|
this.autoFillCode,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<VerificationScreen> createState() => _VerificationScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VerificationScreenState extends State<VerificationScreen> {
|
||||||
|
late List<TextEditingController> _controllers;
|
||||||
|
late List<FocusNode> _focusNodes;
|
||||||
|
|
||||||
|
int _resendTimer = 60;
|
||||||
|
late Timer _timer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
// Initialize controllers and focus nodes
|
||||||
|
_controllers = List.generate(4, (_) => TextEditingController());
|
||||||
|
_focusNodes = List.generate(4, (_) => FocusNode());
|
||||||
|
|
||||||
|
// Auto-fill if provided
|
||||||
|
if (widget.autoFillCode != null && widget.autoFillCode!.length == 4) {
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
_controllers[i].text = widget.autoFillCode![i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start resend timer
|
||||||
|
_startResendTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startResendTimer() {
|
||||||
|
const oneSecond = Duration(seconds: 1);
|
||||||
|
_timer = Timer.periodic(oneSecond, (timer) {
|
||||||
|
if (_resendTimer <= 0) {
|
||||||
|
setState(() {
|
||||||
|
_resendTimer = 60;
|
||||||
|
});
|
||||||
|
timer.cancel();
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_resendTimer--;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
// Cancel the timer before disposing
|
||||||
|
_timer.cancel();
|
||||||
|
|
||||||
|
// Dispose all controllers and focus nodes
|
||||||
|
for (var c in _controllers) {
|
||||||
|
c.dispose();
|
||||||
|
}
|
||||||
|
for (var f in _focusNodes) {
|
||||||
|
f.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDigitField(int index) {
|
||||||
|
return SizedBox(
|
||||||
|
width: 60,
|
||||||
|
child: TextField(
|
||||||
|
controller: _controllers[index],
|
||||||
|
focusNode: _focusNodes[index],
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
maxLength: 1,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
counterText: '',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value.isNotEmpty && index < 3) {
|
||||||
|
FocusScope.of(context).requestFocus(_focusNodes[index + 1]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _verifyCode() {
|
||||||
|
String enteredCode = _controllers.map((c) => c.text).join();
|
||||||
|
|
||||||
|
if (enteredCode == widget.correctCode) {
|
||||||
|
Navigator.pushReplacement(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) => HomePage(email: "Verified")),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Invalid code")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildResendText() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Resend code in: $_resendTimer seconds",
|
||||||
|
style: TextStyle(color: Colors.grey),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text("Enter Code")),
|
||||||
|
body: Padding(
|
||||||
|
padding: EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// Logo Section
|
||||||
|
Image.asset('assets/logo.png', width: 200),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
"Fast. Reliable. Affordable",
|
||||||
|
style: TextStyle(fontSize: 16, color: Colors.grey),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Instruction
|
||||||
|
Text(
|
||||||
|
"Enter verification code sent via SMS",
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
|
||||||
|
// 4 Digit Input Fields
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: List.generate(4, (index) => _buildDigitField(index)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 30),
|
||||||
|
|
||||||
|
// Verify Button
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _verifyCode,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
minimumSize: Size(double.infinity, 50),
|
||||||
|
),
|
||||||
|
child: Text("Verify"),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 20),
|
||||||
|
_buildResendText(),
|
||||||
|
|
||||||
|
SizedBox(height: 30),
|
||||||
|
Text(
|
||||||
|
"Customer Service",
|
||||||
|
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"0702026554 | 0790882866",
|
||||||
|
style: TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user