Compare commits
No commits in common. "master" and "main" have entirely different histories.
BIN
assets/gas.jpeg
BIN
assets/gas.jpeg
Binary file not shown.
Before Width: | Height: | Size: 66 KiB |
BIN
assets/wi-fi.gif
BIN
assets/wi-fi.gif
Binary file not shown.
Before Width: | Height: | Size: 3.9 KiB |
@ -15,12 +15,9 @@ class MyApp extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return const MaterialApp(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme:ThemeData(
|
home: SplashScreen(), // Start with SplashScreen
|
||||||
fontFamily: 'Poppins',
|
|
||||||
),
|
|
||||||
home: const SplashScreen(), // Start with SplashScreen
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,209 +1,53 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:login_page/screens/login_screen.dart'; // Make sure this import path is correct
|
import 'package:login_page/data/bg_data.dart'; // Import your bgList
|
||||||
|
|
||||||
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(
|
||||||
title: Text("Dashboard"),
|
automaticallyImplyLeading: true,
|
||||||
automaticallyImplyLeading: false,
|
title: Text('Hello $email'),
|
||||||
actions: [
|
centerTitle: true,
|
||||||
// Logout Icon Button
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.logout, color: Colors.black),
|
icon: const Icon(Icons.logout),
|
||||||
onPressed: () {
|
tooltip: 'Logout',
|
||||||
Navigator.of(context).pushAndRemoveUntil(
|
onPressed: () {
|
||||||
MaterialPageRoute(builder: (context) => LoginScreen()),
|
Navigator.of(context).pop(); // Go back to login screen
|
||||||
(route) => false,
|
},
|
||||||
);
|
),
|
||||||
},
|
],
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.all(12.0),
|
||||||
padding: EdgeInsets.all(20),
|
child: ListView.builder(
|
||||||
child: Column(
|
itemCount: bgList.length,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
itemBuilder: (context, index) {
|
||||||
children: [
|
return Container(
|
||||||
// Welcome Section Container with Greeting Inside
|
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||||
Container(
|
height: 180,
|
||||||
padding: EdgeInsets.all(15),
|
decoration: BoxDecoration(
|
||||||
decoration: BoxDecoration(
|
borderRadius: BorderRadius.circular(16),
|
||||||
border: Border.all(color: Colors.grey.shade300),
|
boxShadow: [
|
||||||
borderRadius: BorderRadius.circular(8),
|
BoxShadow(
|
||||||
),
|
color: Colors.black26,
|
||||||
child: Column(
|
blurRadius: 8,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
offset: Offset(0, 4),
|
||||||
children: [
|
|
||||||
// Greeting + Waving Hand Icon
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
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,
|
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
image: DecorationImage(
|
||||||
|
image: AssetImage(bgList[index]),
|
||||||
|
fit: BoxFit.cover,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
);
|
||||||
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,8 +1,6 @@
|
|||||||
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 'verify.dart';
|
import 'home.dart';
|
||||||
|
|
||||||
class LoginScreen extends StatefulWidget {
|
class LoginScreen extends StatefulWidget {
|
||||||
const LoginScreen({super.key});
|
const LoginScreen({super.key});
|
||||||
@ -11,99 +9,431 @@ 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;
|
bool isDarkMode = false; // Tracks dark mode state
|
||||||
bool _phoneHasFocus = false;
|
bool isRemembered = false; // Tracks "Remember Me" checkbox
|
||||||
bool _phoneContainsText = false;
|
bool isLogin = true; // Tracks if login or register form is shown
|
||||||
|
|
||||||
final TextEditingController _phoneController = TextEditingController();
|
// Controllers for email and password input fields
|
||||||
|
final TextEditingController _emailController = TextEditingController();
|
||||||
|
final TextEditingController _passwordController = TextEditingController();
|
||||||
|
|
||||||
// Auto-generated verification code
|
// Hardcoded credentials, updated on registration or password reset
|
||||||
String? _generatedCode;
|
String _correctEmail = 'Amazons@tech.com';
|
||||||
|
String _correctPassword = '12345';
|
||||||
|
|
||||||
// Timer variables
|
// Animation controller and animation for flipping between forms
|
||||||
late Timer _timer;
|
late AnimationController _animationController;
|
||||||
int _countdown = 60;
|
late Animation<double> _animation;
|
||||||
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() {
|
||||||
_phoneController.dispose();
|
// Dispose controllers to free resources
|
||||||
if (_isCounting) _timer.cancel();
|
_animationController.dispose();
|
||||||
|
_emailController.dispose();
|
||||||
|
_passwordController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validates phone number input
|
// Login logic: checks credentials and navigates to HomePage if correct
|
||||||
String? _validatePhone(String value) {
|
void _login() {
|
||||||
if (value.isEmpty) return 'Phone number cannot be empty';
|
if (_emailController.text == _correctEmail &&
|
||||||
if (!RegExp(r'^\d{10}$').hasMatch(value)) {
|
_passwordController.text == _correctPassword) {
|
||||||
return 'Enter a valid 10-digit phone number';
|
Navigator.push(
|
||||||
}
|
context,
|
||||||
return null;
|
MaterialPageRoute(
|
||||||
}
|
builder: (context) => HomePage(email: _emailController.text), // Go to HomePage
|
||||||
|
),
|
||||||
/// Generates a random 4-digit code
|
);
|
||||||
String _generateVerificationCode() {
|
} else {
|
||||||
final random = DateTime.now().millisecondsSinceEpoch % 9000 + 1000;
|
// Show error if credentials are wrong
|
||||||
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(SnackBar(content: Text(validation)));
|
).showSnackBar(const SnackBar(content: Text('Invalid credentials')));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Generate new code
|
// Registration logic: updates hardcoded credentials and flips back to login
|
||||||
_generatedCode = _generateVerificationCode();
|
void _register() {
|
||||||
|
if (_emailController.text.isNotEmpty &&
|
||||||
// Show snackbar and auto-fill in verification screen
|
_passwordController.text.isNotEmpty) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
setState(() {
|
||||||
|
_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(
|
||||||
SnackBar(content: Text("Auto-filled code: $_generatedCode")),
|
const SnackBar(content: Text('Registration successful! Please login')),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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!')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Start countdown
|
// Shows a dialog to enter a new password for reset
|
||||||
_countdown = 60;
|
void _showResetPasswordDialog() {
|
||||||
_startCountdown();
|
final TextEditingController newPasswordController = TextEditingController();
|
||||||
|
|
||||||
// Navigate to verification screen
|
showDialog(
|
||||||
Navigator.push(
|
context: context,
|
||||||
context,
|
builder: (context) => AlertDialog(
|
||||||
MaterialPageRoute(
|
title: Text(
|
||||||
builder:
|
'Reset Password',
|
||||||
(context) => VerificationScreen(
|
style: TextStyle(color: isDarkMode ? Colors.white : Colors.black),
|
||||||
correctCode: _generatedCode!,
|
),
|
||||||
autoFillCode: _generatedCode, // Pass generated code for auto-fill
|
content: SizedBox(
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -111,18 +441,20 @@ 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(),
|
data: isDarkMode ? ThemeData.dark() : ThemeData.light(), // Set theme
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: Color(0xFFC76723),
|
backgroundColor: const Color(0xFFC76723), // Custom orange color
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(isDarkMode ? Icons.light_mode : Icons.dark_mode),
|
icon: Icon(
|
||||||
color: Colors.white,
|
isDarkMode ? Icons.light_mode : Icons.dark_mode,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
isDarkMode = !isDarkMode;
|
isDarkMode = !isDarkMode; // Toggle theme
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -131,218 +463,38 @@ class _LoginScreenState extends State<LoginScreen>
|
|||||||
body: Container(
|
body: Container(
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
color: isDarkMode ? Color.fromARGB(255, 26, 24, 24) : Colors.white,
|
color: isDarkMode ? const Color.fromARGB(255, 26, 24, 24) : Colors.white, // Background color
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
margin: EdgeInsets.symmetric(horizontal: 30),
|
margin: const EdgeInsets.symmetric(horizontal: 30),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color:
|
color: isDarkMode
|
||||||
isDarkMode
|
? const Color(0xFF23272F) // Use a dark blue/grey for dark mode
|
||||||
? Color.fromARGB(255, 35, 38, 46)
|
: const Color.fromARGB(136, 71, 69, 69),
|
||||||
: Color.fromARGB(136, 71, 69, 69),
|
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(15),
|
borderRadius: BorderRadius.circular(15),
|
||||||
color:
|
color: isDarkMode
|
||||||
isDarkMode
|
? const Color.fromARGB(137, 132, 129, 129) // Use a dark blue/grey for dark mode
|
||||||
? Color.fromARGB(133, 93, 75, 75)
|
: Colors.white70,
|
||||||
: Colors.white70,
|
|
||||||
),
|
),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(25),
|
||||||
child: Column(
|
child: AnimatedBuilder(
|
||||||
mainAxisSize: MainAxisSize.min,
|
animation: _animation,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
builder: (context, child) {
|
||||||
children: [
|
// Flip between login and register forms
|
||||||
// Logo with tagline directly underneath
|
return Transform(
|
||||||
Center(
|
transform: Matrix4.identity()
|
||||||
child: Column(
|
..setEntry(3, 2, 0.001)
|
||||||
mainAxisSize: MainAxisSize.min,
|
..rotateY(3.14159 * _animation.value),
|
||||||
children: [
|
alignment: Alignment.center,
|
||||||
// Logo
|
child: isLogin ? _buildLoginForm() : _buildRegisterForm(),
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -352,4 +504,4 @@ class _LoginScreenState extends State<LoginScreen>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart'; // For hiding system UI
|
import 'login_screen.dart';
|
||||||
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();
|
||||||
}
|
}
|
||||||
@ -13,96 +12,39 @@ class _SplashScreenState extends State<SplashScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
// Hide system UI (status bar and bottom navigation bar)
|
// Wait for 3 seconds, then navigate to LoginScreen
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
|
|
||||||
// Navigate to Login Screen after 3 seconds
|
|
||||||
Future.delayed(const Duration(seconds: 3), () {
|
Future.delayed(const Duration(seconds: 3), () {
|
||||||
Navigator.pushReplacement(
|
// ignore: use_build_context_synchronously
|
||||||
// ignore: use_build_context_synchronously
|
Navigator.of(context).pushReplacement(
|
||||||
context,
|
MaterialPageRoute(
|
||||||
MaterialPageRoute(builder: (context) => const LoginScreen()),
|
builder: (context) => const LoginScreen(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
return Scaffold(
|
||||||
value: SystemUiOverlayStyle.dark.copyWith(
|
backgroundColor: Colors.white,
|
||||||
statusBarColor: Colors.white, // Transparent or match background
|
body: Center(
|
||||||
systemNavigationBarColor: Colors.white,
|
child: TweenAnimationBuilder<double>(
|
||||||
),
|
tween: Tween(begin: 1.0, end: 1.2),
|
||||||
child: Scaffold(
|
duration: const Duration(milliseconds: 700),
|
||||||
backgroundColor: Colors.white,
|
curve: Curves.easeInOut,
|
||||||
body: SafeArea(
|
builder: (context, scale, child) {
|
||||||
child: Column(
|
return Transform.scale(
|
||||||
children: [
|
scale: scale,
|
||||||
// Main content in the middle (with Expanded to take available space)
|
child: child,
|
||||||
Expanded(
|
);
|
||||||
child: Center(
|
},
|
||||||
child: Padding(
|
onEnd: () {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
setState(() {});
|
||||||
child: Column(
|
},
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
child: Image.asset(
|
||||||
children: [
|
'assets/splash.png',
|
||||||
// Animated Logo
|
width: 150,
|
||||||
TweenAnimationBuilder<double>(
|
fit: BoxFit.contain,
|
||||||
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),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,186 +0,0 @@
|
|||||||
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