diff --git a/lib/src/screens/login.dart b/lib/src/screens/login.dart index 744d6dc..129ee3b 100644 --- a/lib/src/screens/login.dart +++ b/lib/src/screens/login.dart @@ -1,11 +1,12 @@ -// ignore_for_file: library_private_types_in_public_api - +import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:mindful_reader/src/widgets/bottomnav.dart'; - -// Import the HomeScreen +import 'package:http/http.dart' as http; +import 'dart:convert'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @@ -16,6 +17,10 @@ class LoginScreen extends StatefulWidget { class _LoginScreenState extends State { bool isLogin = true; + final TextEditingController _usernameController = TextEditingController(); + final TextEditingController _emailController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); + final TextEditingController _confirmPasswordController = TextEditingController(); void toggleForm() { setState(() { @@ -23,11 +28,131 @@ class _LoginScreenState extends State { }); } - void _login() { - // Navigate to the HomeScreen when the login button is pressed - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (context) => const BottomNavBar()), - ); + // Perform Login + Future _login() async { + final username = _usernameController.text; + final password = _passwordController.text; + await dotenv.load(fileName: "assets/config/.env"); + + try { + final response = await Dio().post('${dotenv.env['API_BASE_URL']}/login', + data: { + 'username': username, + 'password': password, + }, + ); + + if (response.statusCode == 200) { + final data = response.data; + final token = data['token']; + + // Save the JWT token in shared preferences + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setString('token', token); + + ScaffoldMessenger.of(context).showMaterialBanner( + MaterialBanner( + padding: const EdgeInsets.all(20), + content: const Text('Login successful! Redirecting...'), + leading: const Icon(Icons.check_circle_outline, color: Colors.green), + backgroundColor: Colors.green[300], + actions: [ + TextButton( + onPressed: () => ScaffoldMessenger.of(context).hideCurrentMaterialBanner(), + child: const Text('DISMISS'), + ), + ], + ), + ); + + Future.delayed(const Duration(seconds: 2), () { + ScaffoldMessenger.of(context).hideCurrentMaterialBanner(); + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (context) => const BottomNavBar()), + ); + }); + } else { + // Handle errors + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Invalid login credentials')), + ); + } + } catch (error) { + debugPrint('error: $error'); + // Handle server errors + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Error connecting to the server')), + ); + } + } + + // Perform Signup + Future _signup() async { + final username = _usernameController.text; + final email = _emailController.text; + final password = _passwordController.text; + final confirmPassword = _confirmPasswordController.text; + await dotenv.load(fileName: "assets/config/.env"); + + if (password != confirmPassword) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Passwords do not match')), + ); + return; + } + + try { + final response = await http.post( + Uri.parse('${dotenv.env['API_BASE_URL']}/signup'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({ + 'username': username, + 'email': email, + 'password': password, + }), + ); + + if (response.statusCode == 201) { + final data = jsonDecode(response.body); + final token = data['token']; + + // Save the JWT token in shared preferences + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setString('token', token); + + ScaffoldMessenger.of(context).showMaterialBanner( + MaterialBanner( + padding: const EdgeInsets.all(20), + content: const Text('Signup successful! Redirecting...'), + leading: const Icon(Icons.check_circle_outline, color: Colors.green), + backgroundColor: Colors.green[300], + actions: [ + TextButton( + onPressed: () => ScaffoldMessenger.of(context).hideCurrentMaterialBanner(), + child: const Text('DISMISS'), + ), + ], + ), + ); + + Future.delayed(const Duration(seconds: 2), () { + ScaffoldMessenger.of(context).hideCurrentMaterialBanner(); + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (context) => const BottomNavBar()), + ); + }); + } else { + // Handle errors + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Signup failed')), + ); + } + } catch (error) { + // Handle server errors + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Error connecting to the server')), + ); + } } @override @@ -49,43 +174,53 @@ class _LoginScreenState extends State { ), ), ) - .animate() - .slideY(begin: -0.3, duration: 600.ms) - .fadeIn(duration: 600.ms) - .then(delay: 200.ms), - + .animate() + .slideY(begin: -0.3, duration: 600.ms) + .fadeIn(duration: 600.ms) + .then(delay: 200.ms), + const SizedBox(height: 40), Padding( padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: _buildTextField('Email', false) - .animate() - .fadeIn(duration: 800.ms), + child: _buildTextField('Username', false, _usernameController) + .animate() + .fadeIn(duration: 700.ms), ), + if (!isLogin) ...[ + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 30.0), + child: _buildTextField('Email', false, _emailController) + .animate() + .fadeIn(duration: 800.ms), + ), + ], + const SizedBox(height: 20), Padding( padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: _buildTextField('Password', true) - .animate() - .fadeIn(duration: 900.ms), + child: _buildTextField('Password', true, _passwordController) + .animate() + .fadeIn(duration: 900.ms), ), if (!isLogin) ...[ const SizedBox(height: 20), Padding( padding: const EdgeInsets.symmetric(horizontal: 30.0), - child: _buildTextField('Confirm Password', true) - .animate() - .fadeIn(duration: 1000.ms), + child: _buildTextField('Confirm Password', true, _confirmPasswordController) + .animate() + .fadeIn(duration: 1000.ms), ), ], const SizedBox(height: 40), ElevatedButton( - onPressed: _login, // Call _login when pressed + onPressed: isLogin ? _login : _signup, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 60, vertical: 16), backgroundColor: Colors.white, @@ -102,9 +237,9 @@ class _LoginScreenState extends State { ), ), ) - .animate() - .scale(duration: 500.ms, curve: Curves.easeInOut) - .fadeIn(duration: 500.ms), + .animate() + .scale(duration: 500.ms, curve: Curves.easeInOut) + .fadeIn(duration: 500.ms), const SizedBox(height: 20), @@ -121,9 +256,9 @@ class _LoginScreenState extends State { ), ), ) - .animate() - .slideY(begin: 0.3, duration: 600.ms) - .fadeIn(duration: 600.ms), + .animate() + .slideY(begin: 0.3, duration: 600.ms) + .fadeIn(duration: 600.ms), ], ), ), @@ -131,8 +266,9 @@ class _LoginScreenState extends State { ); } - Widget _buildTextField(String hint, bool obscureText) { + Widget _buildTextField(String hint, bool obscureText, TextEditingController controller) { return TextField( + controller: controller, obscureText: obscureText, style: const TextStyle(color: Colors.white), decoration: InputDecoration( diff --git a/pubspec.lock b/pubspec.lock index b03251a..b41abd1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -121,6 +121,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" flutter: dependency: "direct main" description: flutter @@ -381,6 +389,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f + url: "https://pub.dev" + source: hosted + version: "2.5.2" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 84e0fd3..e6ce6f4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,6 +25,7 @@ dependencies: flutter_pdfview: ^1.3.2 path_provider: ^2.1.4 flutter_dotenv: ^5.1.0 + shared_preferences: ^2.3.2 dev_dependencies: flutter_launcher_icons: "^0.13.1"