login_form.dart

With instruction
mail@pastecode.io avatar
unknown
dart
a month ago
20 kB
4
Indexable
Never
import 'dart:async';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:web_application_team_space/Designs/Colors/palettes.dart';

import '../../../Users/Admin/Admin_hompage.dart';
import '../../../Users/Human Resource/HR_homepage.dart';
import '../../Resoponsive/login_color_constant.dart';

class LoginForm extends StatefulWidget {
  const LoginForm({super.key});

  @override
  _LoginFormState createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
  final _formKey = GlobalKey<FormState>();
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();
  final TextEditingController _resetEmailController = TextEditingController();

  bool _obscureText = true;
  bool _isLoading = false;
  String? _errorMessage;
  String? _emailError;
  String? _passwordError;
  String? _resetEmailError;

  int _attemptCounter = 0;
  final int _maxAttempts = 3;
  DateTime? _lockoutEndTime;
  Timer? _timer;

  @override
  void initState() {
    super.initState();
    _loadLockoutData();
  }

  @override
  void dispose() {
    _timer?.cancel();
    super.dispose();
  }

  // This function loads the lockout data from shared preferences.
  // If the user has exceeded the maximum number of login attempts, it retrieves the lockout end time.
  // If the lockout period has not expired, it shows a lockout dialog and starts a timer to countdown the remaining lockout time.
  Future<void> _loadLockoutData() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    String? lockoutEndTimeStr = prefs.getString('lockoutEndTime');
    int? attemptCounter = prefs.getInt('attemptCounter');

    if (lockoutEndTimeStr != null) {
      DateTime lockoutEndTime = DateTime.parse(lockoutEndTimeStr);
      if (DateTime.now().isBefore(lockoutEndTime)) {
        setState(() {
          _lockoutEndTime = lockoutEndTime;
          _attemptCounter = attemptCounter ?? 0;
        });
        _startLockoutTimer();
        _showLockoutDialog();
      } else {
        _clearLockoutData();
      }
    }
  }

  Future<void> _saveLockoutData() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setString('lockoutEndTime', _lockoutEndTime.toString());
    await prefs.setInt('attemptCounter', _attemptCounter);
  }

  Future<void> _clearLockoutData() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.remove('lockoutEndTime');
    prefs.remove('attemptCounter');
    setState(() {
      _lockoutEndTime = null;
      _attemptCounter = 0;
    });
  }

  // This function starts a timer that checks every second whether the lockout period has ended.
  // If the lockout has ended, the timer is canceled, the lockout data is cleared, and the lockout dialog is closed.
  void _startLockoutTimer() {
    _timer?.cancel();
    _timer = Timer.periodic(Duration(seconds: 1), (timer) {
      if (mounted) {
        setState(() {});
        if (_lockoutEndTime == null || DateTime.now().isAfter(_lockoutEndTime!)) {
          timer.cancel();
          _clearLockoutData();
          if (Navigator.canPop(context)) {
            Navigator.of(context).pop(); // Close the dialog when the lockout ends
          }
        }
      }
    });
  }

  // This function shows a dialog indicating that the user is locked out due to too many login attempts.
  // The dialog contains a countdown timer showing the remaining lockout time.
  void _showLockoutDialog() {
    showDialog(
      context: context,
      barrierDismissible: false,
      builder: (BuildContext context) {
        return WillPopScope(
          onWillPop: () async => false, // Prevent the dialog from closing
          child: AlertDialog(
            backgroundColor: Colors.white,
            contentPadding: EdgeInsets.symmetric(horizontal: 24.0, vertical: 20.0),
            content: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Center(
                  child: Image.asset(
                    'assets/images/warning.gif',
                    height: 60, // Adjust height as needed
                    width: 60, // Adjust width as needed
                  ),
                ),
                SizedBox(height: 20),
                Text(
                  "Too Many Attempts",
                  style: TextStyle(
                    fontWeight: FontWeight.bold,
                    fontSize: 18, // Adjust font size as needed
                  ),
                  textAlign: TextAlign.center,
                ),
                SizedBox(height: 20),
                Text(
                  "Please wait for the timer to expire before retrying.",
                  textAlign: TextAlign.center,
                ),
                SizedBox(height: 20),
                StreamBuilder<int>(
                  stream: Stream.periodic(Duration(seconds: 1), (_) {
                    return _lockoutEndTime != null
                        ? _lockoutEndTime!.difference(DateTime.now()).inSeconds
                        : 0;
                  }),
                  builder: (context, snapshot) {
                    return Text(
                      "${snapshot.data ?? 0} seconds remaining",
                      style: TextStyle(fontWeight: FontWeight.bold),
                    );
                  },
                ),
              ],
            ),
          ),
        );
      },
    );
  }

  Future<DocumentSnapshot> getUserDocumentWithRetry(String userId,
      {int retryCount = 3}) async {
    int attempt = 0;
    while (attempt < retryCount) {
      try {
        return await FirebaseFirestore.instance
            .collection('users')
            .doc(userId)
            .get();
      } catch (e) {
        attempt++;
        if (attempt >= retryCount) {
          rethrow;
        }
        await Future.delayed(Duration(seconds: attempt * attempt));
      }
    }
    throw Exception('Failed to retrieve document after $retryCount attempts');
  }

  String getErrorMessage(String errorCode) {
    switch (errorCode) {
      case 'firebase_auth/invalid-email':
        return 'The email address is not valid.';
      case 'firebase_auth/user-disabled':
        return 'The user account has been disabled.';
      case 'firebase_auth/user-not-found':
        return 'No user found with this email.';
      case 'firebase_auth/wrong-password':
        return 'Incorrect password. Please try again.';
      case 'firebase_auth/missing-email':
        return 'Email address is required.';
      case 'firebase_auth/missing-password':
        return 'Password is required.';
      default:
        return 'An unknown error occurred. Please try again.';
    }
  }

  Future<void> _login(BuildContext context) async {
    setState(() {
      _emailError =
          _emailController.text.isEmpty ? "Email address is required." : null;
      _passwordError =
          _passwordController.text.isEmpty ? "Password is required." : null;
      _errorMessage = null;
    });

    if (_emailError != null || _passwordError != null) {
      return;
    }

    if (_lockoutEndTime != null && DateTime.now().isBefore(_lockoutEndTime!)) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(
              'Please wait until ${_lockoutEndTime!.minute}:${_lockoutEndTime!.second} to retry'),
        ),
      );
      return;
    }

    setState(() {
      _isLoading = true;
    });

    try {
      UserCredential userCredential =
          await FirebaseAuth.instance.signInWithEmailAndPassword(
        email: _emailController.text,
        password: _passwordController.text,
      );

      User? user = userCredential.user;

      if (user != null) {
        DocumentSnapshot userDoc = await getUserDocumentWithRetry(user.uid);
        String role = userDoc['role'];

        SharedPreferences prefs = await SharedPreferences.getInstance();
        await prefs.setBool('isLoggedIn', true);
        await prefs.setString('userRole', role);

        if (role == 'Admin') {
          Navigator.pushReplacement(context,
              MaterialPageRoute(builder: (context) => const AdminHompage()));
        } else if (role == 'Human Resource') {
          Navigator.pushReplacement(
              context,
              MaterialPageRoute(
                  builder: (context) => const HumanResourceHomepage()));
        }
        _clearLockoutData(); // Clear lockout data on successful login
      }
    } catch (e) {
      _attemptCounter++;
      int reattemptsLeft = _maxAttempts - _attemptCounter;
      if (_attemptCounter >= _maxAttempts) {
        _lockoutEndTime = DateTime.now().add(Duration(minutes: 1));
        _attemptCounter = 0;
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text(
                'Too many attempts. Please wait for 1 minute before retrying.'),
            backgroundColor: Colors.red,
          ),
        );
        await _saveLockoutData();
        _startLockoutTimer();
        _showLockoutDialog();
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(
              'Please check your Email or Password.\nYou have $reattemptsLeft reattempts left.',
              style: TextStyle(color: Colors.white),
            ),
            backgroundColor: Colors.red,
          ),
        );
      }
      setState(() {
        _errorMessage = getErrorMessage(e.toString());
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  Future<void> _resetPassword() async {
    String email = _resetEmailController.text;
    if (email.isEmpty) {
      setState(() {
        _resetEmailError = "Please enter your email address.";
      });
      return;
    }

    setState(() {
      _resetEmailError = null;
    });

    try {
      await FirebaseAuth.instance.sendPasswordResetEmail(email: email);
      showDialog(
        context: context,
        builder: (BuildContext context) {
          return AlertDialog(
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(10.0),
            ),
            title: Text('Password Reset'),
            content: Text('A password reset link has been sent to $email.'),
            actions: <Widget>[
              TextButton(
                child: Text('OK', style: TextStyle(color: kPrimaryColor)),
                onPressed: () {
                  Navigator.of(context).pop();
                },
              ),
            ],
          );
        },
      );
    } catch (e) {
      setState(() {
        _resetEmailError = getErrorMessage(e.toString());
      });
    }
  }

  void _showResetPasswordDialog() {
    setState(() {
      _resetEmailError = null;
    });

    showDialog(
      context: context,
      builder: (BuildContext context) {
        return StatefulBuilder(
          builder: (context, setState) {
            return AlertDialog(
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(10.0),
              ),
              title: Column(
                children: [
                  Image.asset('assets/images/bpsc.png', height: 100),
                  SizedBox(height: 10),
                  Text('Forgot your password?'),
                ],
              ),
              content: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text(
                      'Enter your Email and we’ll help you reset your password.'),
                  SizedBox(height: 10),
                  TextField(
                    controller: _resetEmailController,
                    keyboardType: TextInputType.emailAddress,
                    decoration: InputDecoration(
                      hintText: "Enter Email ",
                      border: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(10.0),
                        borderSide: BorderSide(color: kPrimaryColor),
                      ),
                      focusedBorder: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(10.0),
                        borderSide: BorderSide(color: kPrimaryColor),
                      ),
                      errorText: _resetEmailError,
                    ),
                  ),
                ],
              ),
              actions: <Widget>[
                TextButton(
                  child: Text('Cancel', style: TextStyle(color: Colors.black)),
                  style: TextButton.styleFrom(
                    foregroundColor: Colors.white,
                    backgroundColor: Palettes.blueVeryPale,
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(8.0),
                    ),
                  ),
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                ),
                ElevatedButton(
                    onPressed: () {
                      setState(() {
                        _resetEmailError = null;
                      });
                      _resetPassword();
                    },
                    child: Text('Reset'),
                    style: TextButton.styleFrom(
                      foregroundColor: Colors.white,
                      backgroundColor: Palettes.green,
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(8.0),
                      ),
                    )),
              ],
            );
          },
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    bool isLockedOut =
        _lockoutEndTime != null && DateTime.now().isBefore(_lockoutEndTime!);

    return Stack(
      children: [
        Form(
          key: _formKey,
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              TextFormField(
                controller: _emailController,
                keyboardType: TextInputType.emailAddress,
                textInputAction: TextInputAction.next,
                cursorColor: kPrimaryColor,
                decoration: InputDecoration(
                  hintText: "Your email",
                  prefixIcon: const Padding(
                    padding: EdgeInsets.all(defaultPadding),
                    child: Icon(Icons.person, color: Colors.black),
                  ),
                  border: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(10.0),
                    borderSide: const BorderSide(color: Colors.black),
                  ),
                  focusedBorder: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(10.0),
                    borderSide: const BorderSide(color: kPrimaryColor),
                  ),
                  errorText: _emailError,
                ),
              ),
              Padding(
                padding: const EdgeInsets.symmetric(vertical: defaultPadding),
                child: TextFormField(
                  controller: _passwordController,
                  textInputAction: TextInputAction.done,
                  obscureText: _obscureText,
                  cursorColor: kPrimaryColor,
                  decoration: InputDecoration(
                    hintText: "Your password",
                    prefixIcon: const Padding(
                      padding: EdgeInsets.all(defaultPadding),
                      child: Icon(Icons.lock, color: Colors.black),
                    ),
                    border: OutlineInputBorder(
                      borderRadius: BorderRadius.circular(10.0),
                      borderSide: const BorderSide(color: Colors.black),
                    ),
                    focusedBorder: OutlineInputBorder(
                      borderRadius: BorderRadius.circular(10.0),
                      borderSide: const BorderSide(color: kPrimaryColor),
                    ),
                    suffixIcon: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Container(
                          width: 1,
                          height: 24,
                          color: Colors.grey,
                        ),
                        const SizedBox(width: 10),
                        GestureDetector(
                          onTap: () {
                            setState(() {
                              _obscureText = !_obscureText;
                            });
                          },
                          child: Icon(
                            _obscureText
                                ? Icons.visibility
                                : Icons.visibility_off,
                            color: Colors.black,
                          ),
                        ),
                        const SizedBox(width: 10),
                      ],
                    ),
                    errorText: _passwordError,
                  ),
                ),
              ),
              Align(
                alignment: Alignment.centerLeft,
                child: TextButton(
                  onPressed: _showResetPasswordDialog,
                  style: ButtonStyle(
                    overlayColor: MaterialStateProperty.all(Colors.transparent),
                  ),
                  child: const Text(
                    "Forgot Password?",
                    style: TextStyle(
                        color: kPrimaryColor, fontWeight: FontWeight.bold),
                  ),
                ),
              ),
              const SizedBox(height: defaultPadding),
              SizedBox(
                width: double.infinity,
                child: ElevatedButton.icon(
                  onPressed: isLockedOut
                      ? null
                      : () => _login(context), // Disable button if locked out
                  style: ElevatedButton.styleFrom(
                    backgroundColor: isLockedOut
                        ? Colors.grey
                        : Palettes.green, // Change color if locked out
                    foregroundColor: Palettes.white,
                    padding: const EdgeInsets.symmetric(
                        horizontal: 40, vertical: 15),
                    textStyle: const TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.bold,
                    ),
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(8.0),
                    ),
                    elevation: 2,
                  ),
                  icon: const Icon(Icons.login),
                  label: const Text("Login"),
                ),
              ),
              // if (_errorMessage != null) ...[
              //   Padding(
              //     padding: const EdgeInsets.only(top: defaultPadding),
              //     child: Text(
              //       _errorMessage!,
              //       style: const TextStyle(color: Colors.red),
              //     ),
              //   ),
              // ],
            ],
          ),
        ),
        if (_isLoading)
          const Center(
            child: CircularProgressIndicator(),
          ),
      ],
    );
  }
}
Leave a Comment