complete captive portal

This commit is contained in:
Papaa14 2025-05-17 21:43:57 +03:00
parent f10745072a
commit f0b7f33b8d
7 changed files with 746 additions and 495 deletions

BIN
assets/gas.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
assets/wi-fi.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -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
); );
} }
} }

View File

@ -1,51 +1,207 @@
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"),
),
],
), ),
), ),
); );

View File

@ -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,
),
),
),
),
],
), ),
), ),
), ),

View File

@ -1,5 +1,6 @@
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});
@ -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
View 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),
),
],
),
),
);
}
}