From 86e2d40e864a788524b7ebbea01b3d553dada0d8 Mon Sep 17 00:00:00 2001 From: Filip Rojek Date: Sat, 7 Jun 2025 12:02:25 +0200 Subject: [PATCH] Added: login/logout logic, session storage --- lib/main.dart | 157 ++++++++++-------- lib/screens/login.dart | 28 +++- lib/screens/user_settings.dart | 24 ++- lib/services/session_manager.dart | 58 +++++++ macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 122 +++++++++++++- pubspec.yaml | 2 + 7 files changed, 310 insertions(+), 83 deletions(-) create mode 100644 lib/services/session_manager.dart diff --git a/lib/main.dart b/lib/main.dart index a034cae..9519d51 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'services/session_manager.dart'; import 'screens/home_screen.dart'; import 'screens/add_screen.dart'; import 'screens/vehicles_screen.dart'; @@ -7,8 +9,16 @@ import 'screens/user_settings.dart'; import 'screens/login.dart'; import 'screens/signup.dart'; -void main() { - runApp(FuelStatsApp()); +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await SessionManager().init(); + + runApp( + ChangeNotifierProvider( + create: (_) => SessionManager(), + child: FuelStatsApp(), + ), + ); } class FuelStatsApp extends StatelessWidget { @@ -19,7 +29,6 @@ class FuelStatsApp extends StatelessWidget { themeMode: ThemeMode.dark, debugShowCheckedModeBanner: false, home: MainNavigation(), - //home: LoginScreen(), ); } } @@ -33,60 +42,62 @@ class MainNavigation extends StatefulWidget { class _MainNavigationState extends State { int _currentIndex = 0; - bool loggedIn = false; + bool get isAuthScreen => _currentIndex == 5 || _currentIndex == 6; - @override - void initState() { - super.initState(); - - if(loggedIn == false) { - _currentIndex = 5; + Widget get currentTitle { + switch (_currentIndex) { + case 0: + return Text("Fuel Stats"); + case 1: + return Text("Add record"); + case 2: + return Text("Vehicles"); + case 3: + return Text("History"); + case 4: + return Text("Settings"); + case 5: + return Text("Login"); + case 6: + return Text("Sign up"); + default: + return Text("Fuel Stats"); } } - final List titles = [ - Text("Fuel Stats"), - Text("Add record"), - Text("Vehicles"), - Text("History"), - ]; - - Widget get currentTitle { - switch (_currentIndex) { - case 0: - return Text("Fuel Stats"); - case 1: - return Text("Add record"); - case 2: - return Text("Vehicles"); - case 3: - return Text("History"); - case 4: - return Text("Settings"); - case 5: - return Text("Login"); - case 6: - return Text("Sign up"); - default: - return Text("Fuel Stats"); - } -} - @override Widget build(BuildContext context) { - List screens = [ + final session = Provider.of(context); + + // Auto-redirect to login if not logged in and not already on auth screens + Future.delayed(Duration(milliseconds: 100), () { + if (!session.isLoggedIn && !isAuthScreen) { + setState(() => _currentIndex = 5); + } + }); + + final screens = [ HomeScreen(), AddScreen(), VehiclesScreen(), HistoryScreen(), - UserSettingsScreen(), + UserSettingsScreen( + onLogout: () { + setState(() => _currentIndex = 5); // Go to login + }, + ), LoginScreen( onSwitchToSignup: () { WidgetsBinding.instance.addPostFrameCallback((_) { setState(() => _currentIndex = 6); }); }, + onLoginSuccess: () { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() => _currentIndex = 0); // Go to Home + }); + }, ), SignupScreen( onSwitchToLogin: () { @@ -100,37 +111,45 @@ class _MainNavigationState extends State { return Scaffold( appBar: AppBar( title: currentTitle, - actions: !isAuthScreen ? [ - IconButton( - icon: const Icon(Icons.person), - tooltip: "User settings", - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => UserSettingsScreen()), - ); - }, - ), - ] : null, + actions: !isAuthScreen + ? [ + IconButton( + icon: const Icon(Icons.person), + tooltip: "User settings", + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => UserSettingsScreen( + onLogout: () { + Navigator.pop(context); // Close settings + setState(() => _currentIndex = 5); // Go to Login + }, + ), + ), + ); + }, + ), + ] + : null, ), body: screens[_currentIndex], - bottomNavigationBar: !isAuthScreen ? - BottomNavigationBar( - currentIndex: _currentIndex <= 3 ? _currentIndex : 0, - onTap: (index) => setState(() => _currentIndex = index), - backgroundColor: Colors.grey[900], - selectedItemColor: Colors.white, - unselectedItemColor: Colors.grey, - items: const [ - BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'), - BottomNavigationBarItem(icon: Icon(Icons.add), label: 'Add'), - BottomNavigationBarItem( - icon: Icon(Icons.directions_car), - label: 'Vehicles', - ), - BottomNavigationBarItem(icon: Icon(Icons.history), label: 'History'), - ], - ) : null, + bottomNavigationBar: !isAuthScreen + ? BottomNavigationBar( + currentIndex: _currentIndex <= 3 ? _currentIndex : 0, + onTap: (index) => setState(() => _currentIndex = index), + backgroundColor: Colors.grey[900], + selectedItemColor: Colors.white, + unselectedItemColor: Colors.grey, + items: const [ + BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'), + BottomNavigationBarItem(icon: Icon(Icons.add), label: 'Add'), + BottomNavigationBarItem(icon: Icon(Icons.directions_car), label: 'Vehicles'), + BottomNavigationBarItem(icon: Icon(Icons.history), label: 'History'), + ], + ) + : null, ); } } + diff --git a/lib/screens/login.dart b/lib/screens/login.dart index 6be6fda..03e3af8 100644 --- a/lib/screens/login.dart +++ b/lib/screens/login.dart @@ -1,9 +1,16 @@ import 'package:flutter/material.dart'; -import 'signup.dart'; +import 'package:provider/provider.dart'; +import '../services/session_manager.dart'; class LoginScreen extends StatefulWidget { final VoidCallback onSwitchToSignup; - const LoginScreen({required this.onSwitchToSignup, super.key}); + final VoidCallback onLoginSuccess; // ✅ ADD THIS + + const LoginScreen({ + required this.onSwitchToSignup, + required this.onLoginSuccess, // ✅ ADD THIS + super.key, + }); @override State createState() => _LoginScreenState(); @@ -18,10 +25,18 @@ class _LoginScreenState extends State { if (_formKey.currentState!.validate()) { final email = _emailController.text; final password = _passwordController.text; - // TODO: Replace with actual login logic - print('Logging in with $email and $password'); - if(email == "test@test.com" && password == "Test1234") { - + + if (email == "test@test.com" && password == "Test1234") { + Provider.of(context, listen: false).login( + token: "dummy_token", + email: email, + name: "John Doe", + ); + widget.onLoginSuccess(); // ✅ FIXED: now defined + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Invalid credentials")), + ); } } } @@ -29,7 +44,6 @@ class _LoginScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - //appBar: AppBar(title: Text('User Login')), body: Padding( padding: const EdgeInsets.all(24.0), child: Form( diff --git a/lib/screens/user_settings.dart b/lib/screens/user_settings.dart index e568ffe..1cefe58 100644 --- a/lib/screens/user_settings.dart +++ b/lib/screens/user_settings.dart @@ -1,30 +1,41 @@ import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:provider/provider.dart'; +import '../services/session_manager.dart'; + class UserSettingsScreen extends StatelessWidget { + final VoidCallback onLogout; + + const UserSettingsScreen({required this.onLogout, super.key}); + Future _getVersion() async { final info = await PackageInfo.fromPlatform(); - //return 'Version: ${info.version}+${info.buildNumber}'; return 'Version: ${info.version}'; } @override Widget build(BuildContext context) { + final session = Provider.of(context); + final userName = session.name ?? "Unknown User"; // fallback just in case + return Scaffold( appBar: AppBar(title: Text('User settings')), body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Text( - "Test User", + Text( + userName, style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold), ), - SizedBox(height: 20), ElevatedButton.icon( - onPressed: () { - // TODO: Add sign-out logic here + onPressed: () async { + await session.logout(); + onLogout(); }, icon: Icon(Icons.logout), label: Text("Sign Out"), @@ -53,3 +64,4 @@ class UserSettingsScreen extends StatelessWidget { ); } } + diff --git a/lib/services/session_manager.dart b/lib/services/session_manager.dart new file mode 100644 index 0000000..b08166d --- /dev/null +++ b/lib/services/session_manager.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class SessionManager extends ChangeNotifier { + static final SessionManager _instance = SessionManager._internal(); + factory SessionManager() => _instance; + SessionManager._internal(); + + bool _loggedIn = false; + String? _token; + String? _email; + String? _name; + + bool get isLoggedIn => _loggedIn; + String? get token => _token; + String? get email => _email; + String? get name => _name; + + final _prefs = SharedPreferencesAsync(); // ✅ New API + + Future init() async { + _token = await _prefs.getString('token'); + _email = await _prefs.getString('email'); + _name = await _prefs.getString('name'); + + _loggedIn = _token != null; + notifyListeners(); + } + + Future login({ + required String token, + required String email, + String? name, + }) async { + await _prefs.setString('token', token); + await _prefs.setString('email', email); + if (name != null) await _prefs.setString('name', name); + + _token = token; + _email = email; + _name = name; + _loggedIn = true; + notifyListeners(); + } + + Future logout() async { + await _prefs.remove('token'); + await _prefs.remove('email'); + await _prefs.remove('name'); + + _token = null; + _email = null; + _name = null; + _loggedIn = false; + notifyListeners(); + } +} + diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index a8b637f..55e04aa 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,7 +6,9 @@ import FlutterMacOS import Foundation import package_info_plus +import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 0225765..66c2bd1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -65,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" flutter: dependency: "direct main" description: flutter @@ -160,6 +168,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" package_info_plus: dependency: "direct main" description: @@ -184,6 +200,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -192,6 +240,70 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" + url: "https://pub.dev" + source: hosted + version: "6.1.5" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" + url: "https://pub.dev" + source: hosted + version: "2.4.10" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + 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: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + 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 @@ -285,6 +397,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.12.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" sdks: dart: ">=3.7.2 <4.0.0" - flutter: ">=3.19.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index 6db3ee3..18df0de 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,6 +30,8 @@ environment: dependencies: flutter: sdk: flutter + shared_preferences: ^2.5.3 + provider: ^6.1.5 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons.