diff --git a/assets/gas.jpeg b/assets/gas.jpeg new file mode 100644 index 0000000..c5b9b99 Binary files /dev/null and b/assets/gas.jpeg differ diff --git a/assets/wi-fi.gif b/assets/wi-fi.gif new file mode 100644 index 0000000..8ed32b8 Binary files /dev/null and b/assets/wi-fi.gif differ diff --git a/lib/main.dart b/lib/main.dart index eaf26fe..bb7560f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,9 +15,12 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( + return MaterialApp( debugShowCheckedModeBanner: false, - home: SplashScreen(), // Start with SplashScreen + theme:ThemeData( + fontFamily: 'Poppins', + ), + home: const SplashScreen(), // Start with SplashScreen ); } } \ No newline at end of file diff --git a/lib/screens/home.dart b/lib/screens/home.dart index 4b8a313..fd439b6 100644 --- a/lib/screens/home.dart +++ b/lib/screens/home.dart @@ -1,53 +1,209 @@ 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 { final String email; + const HomePage({super.key, required this.email}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - automaticallyImplyLeading: true, - title: Text('Hello $email'), - centerTitle: true, - actions: [ - IconButton( - icon: const Icon(Icons.logout), - tooltip: 'Logout', - onPressed: () { - Navigator.of(context).pop(); // Go back to login screen - }, - ), - ], + title: Text("Dashboard"), + automaticallyImplyLeading: false, + actions: [ + // Logout Icon Button + IconButton( + icon: Icon(Icons.logout, color: Colors.black), + onPressed: () { + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute(builder: (context) => LoginScreen()), + (route) => false, + ); + }, + ), + ], ), - body: Padding( - padding: const EdgeInsets.all(12.0), - child: ListView.builder( - itemCount: bgList.length, - itemBuilder: (context, index) { - return Container( - margin: const EdgeInsets.symmetric(vertical: 8), - height: 180, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: Colors.black26, - blurRadius: 8, - offset: Offset(0, 4), - ), - ], - image: DecorationImage( - image: AssetImage(bgList[index]), - fit: BoxFit.cover, + body: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Welcome Section Container with Greeting Inside + Container( + padding: EdgeInsets.all(15), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + 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, + ), + ), + ), + + 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"), + ], + ), ), ), ); } -} \ No newline at end of file + + // 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"), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/login_screen.dart b/lib/screens/login_screen.dart index 393cbd1..1c2fa75 100644 --- a/lib/screens/login_screen.dart +++ b/lib/screens/login_screen.dart @@ -1,6 +1,8 @@ +import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import '../utils/text_utils.dart'; -import 'home.dart'; +import 'verify.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @@ -9,431 +11,99 @@ class LoginScreen extends StatefulWidget { State createState() => _LoginScreenState(); } -// State class for LoginScreen, with animation support for flipping forms class _LoginScreenState extends State with SingleTickerProviderStateMixin { - bool isDarkMode = false; // Tracks dark mode state - bool isRemembered = false; // Tracks "Remember Me" checkbox - bool isLogin = true; // Tracks if login or register form is shown + bool isDarkMode = false; + bool _phoneHasFocus = false; + bool _phoneContainsText = false; - // Controllers for email and password input fields - final TextEditingController _emailController = TextEditingController(); - final TextEditingController _passwordController = TextEditingController(); + final TextEditingController _phoneController = TextEditingController(); - // Hardcoded credentials, updated on registration or password reset - String _correctEmail = 'Amazons@tech.com'; - String _correctPassword = '12345'; + // Auto-generated verification code + String? _generatedCode; - // Animation controller and animation for flipping between forms - late AnimationController _animationController; - late Animation _animation; + // Timer variables + late Timer _timer; + int _countdown = 60; + bool _isCounting = false; @override void initState() { super.initState(); - // Initialize animation controller for flip effect - _animationController = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 600), - ); - // Tween for flip animation - _animation = Tween(begin: 0, end: 1).animate( - CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), - ); } @override void dispose() { - // Dispose controllers to free resources - _animationController.dispose(); - _emailController.dispose(); - _passwordController.dispose(); + _phoneController.dispose(); + if (_isCounting) _timer.cancel(); super.dispose(); } - // Login logic: checks credentials and navigates to HomePage if correct - void _login() { - if (_emailController.text == _correctEmail && - _passwordController.text == _correctPassword) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => HomePage(email: _emailController.text), // Go to HomePage - ), - ); - } else { - // Show error if credentials are wrong + /// Validates phone number input + String? _validatePhone(String value) { + if (value.isEmpty) return 'Phone number cannot be empty'; + if (!RegExp(r'^\d{10}$').hasMatch(value)) { + return 'Enter a valid 10-digit phone number'; + } + return null; + } + + /// Generates a random 4-digit code + String _generateVerificationCode() { + 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( context, - ).showSnackBar(const SnackBar(content: Text('Invalid credentials'))); + ).showSnackBar(SnackBar(content: Text(validation))); + return; } - } - // Registration logic: updates hardcoded credentials and flips back to login - void _register() { - if (_emailController.text.isNotEmpty && - _passwordController.text.isNotEmpty) { - setState(() { - _correctEmail = _emailController.text; // Update email - _correctPassword = _passwordController.text; // Update password - isLogin = true; // Switch to login form - }); - _animationController.reverse(); // Animate back to login + // Generate new code + _generatedCode = _generateVerificationCode(); + + // Show snackbar and auto-fill in verification screen + WidgetsBinding.instance.addPostFrameCallback((_) { 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 - void _showResetPasswordDialog() { - final TextEditingController newPasswordController = TextEditingController(); + // Start countdown + _countdown = 60; + _startCountdown(); - showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text( - 'Reset Password', - style: TextStyle(color: isDarkMode ? Colors.white : Colors.black), - ), - 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, - ), + // Navigate to verification screen + Navigator.push( + context, + MaterialPageRoute( + builder: + (context) => VerificationScreen( + correctCode: _generatedCode!, + autoFillCode: _generatedCode, // Pass generated code for auto-fill ), - ), - // 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 @override Widget build(BuildContext context) { return Theme( - data: isDarkMode ? ThemeData.dark() : ThemeData.light(), // Set theme + data: isDarkMode ? ThemeData.dark() : ThemeData.light(), child: Scaffold( appBar: AppBar( - backgroundColor: const Color(0xFFC76723), // Custom orange color + backgroundColor: Color(0xFFC76723), elevation: 0, actions: [ IconButton( - icon: Icon( - isDarkMode ? Icons.light_mode : Icons.dark_mode, - color: Colors.white, - ), + icon: Icon(isDarkMode ? Icons.light_mode : Icons.dark_mode), + color: Colors.white, onPressed: () { setState(() { - isDarkMode = !isDarkMode; // Toggle theme + isDarkMode = !isDarkMode; }); }, ), @@ -463,38 +131,218 @@ class _LoginScreenState extends State body: Container( height: 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( alignment: Alignment.center, child: Container( width: double.infinity, - margin: const EdgeInsets.symmetric(horizontal: 30), + margin: EdgeInsets.symmetric(horizontal: 30), decoration: BoxDecoration( border: Border.all( - color: isDarkMode - ? const Color(0xFF23272F) // Use a dark blue/grey for dark mode - : const Color.fromARGB(136, 71, 69, 69), + color: + isDarkMode + ? Color.fromARGB(255, 35, 38, 46) + : Color.fromARGB(136, 71, 69, 69), ), borderRadius: BorderRadius.circular(15), - color: isDarkMode - ? const Color.fromARGB(137, 132, 129, 129) // Use a dark blue/grey for dark mode - : Colors.white70, + color: + isDarkMode + ? Color.fromARGB(133, 93, 75, 75) + : Colors.white70, ), child: SingleChildScrollView( child: Padding( - padding: const EdgeInsets.all(25), - child: AnimatedBuilder( - animation: _animation, - builder: (context, child) { - // Flip between login and register forms - return Transform( - transform: Matrix4.identity() - ..setEntry(3, 2, 0.001) - ..rotateY(3.14159 * _animation.value), - alignment: Alignment.center, - child: isLogin ? _buildLoginForm() : _buildRegisterForm(), - ); - }, + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Logo with tagline directly underneath + Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 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 ), ); } -} +} \ No newline at end of file diff --git a/lib/screens/splash_screen.dart b/lib/screens/splash_screen.dart index e44a5b6..eb2a37e 100644 --- a/lib/screens/splash_screen.dart +++ b/lib/screens/splash_screen.dart @@ -1,9 +1,10 @@ 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 { const SplashScreen({super.key}); - + @override State createState() => _SplashScreenState(); } @@ -12,39 +13,96 @@ class _SplashScreenState extends State { @override void 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), () { - // ignore: use_build_context_synchronously - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (context) => const LoginScreen(), - ), + Navigator.pushReplacement( + // ignore: use_build_context_synchronously + context, + MaterialPageRoute(builder: (context) => const LoginScreen()), ); }); } @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.white, - body: Center( - child: TweenAnimationBuilder( - tween: Tween(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, - ); - }, - onEnd: () { - setState(() {}); - }, - child: Image.asset( - 'assets/splash.png', - width: 150, - fit: BoxFit.contain, + return AnnotatedRegion( + value: SystemUiOverlayStyle.dark.copyWith( + statusBarColor: Colors.white, // Transparent or match background + systemNavigationBarColor: Colors.white, + ), + child: Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + child: Column( + children: [ + // Main content in the middle (with Expanded to take available space) + Expanded( + child: Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Animated Logo + TweenAnimationBuilder( + tween: Tween(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( + tween: Tween(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), + ), + ), + ], ), ), ), diff --git a/lib/screens/verify.dart b/lib/screens/verify.dart new file mode 100644 index 0000000..a1ed4d1 --- /dev/null +++ b/lib/screens/verify.dart @@ -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 createState() => _VerificationScreenState(); +} + +class _VerificationScreenState extends State { + late List _controllers; + late List _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), + ), + ], + ), + ), + ); + } +} \ No newline at end of file