diff --git a/mobile-v3/android/app/build.gradle b/mobile-v3/android/app/build.gradle index ddabf76c1d..f1cae6451d 100644 --- a/mobile-v3/android/app/build.gradle +++ b/mobile-v3/android/app/build.gradle @@ -39,14 +39,14 @@ if (localPropertiesFile.exists()) { } } -def googleMapApiKey = appProperties.getProperty('google.maps.key') +def googleMapApiKey = appProperties.getProperty('google.maps.key') ?: secrets.getProperty('MAPS_API_KEY') if (googleMapApiKey == null) { - throw new GradleException("Google Maps Key not found. Define google.maps.key in the key.properties file.") + throw new GradleException("Google Maps Key not found. Define either google.maps.key in key.properties or MAPS_API_KEY in secrets.properties") } -def googleMapApiKeyDev = appProperties.getProperty('google.maps.key.dev') +def googleMapApiKeyDev = appProperties.getProperty('google.maps.key.dev') ?: secrets.getProperty('MAPS_API_KEY_DEV') if (googleMapApiKeyDev == null) { - throw new GradleException("Google Maps Key not found. Define google.maps.key_dev in the key.properties file.") + throw new GradleException("Google Maps Dev Key not found. Define either google.maps.key.dev in key.properties or MAPS_API_KEY_DEV in secrets.properties") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') diff --git a/mobile-v3/android/app/src/main/AndroidManifest.xml b/mobile-v3/android/app/src/main/AndroidManifest.xml index 73f45b0c83..95007fda61 100644 --- a/mobile-v3/android/app/src/main/AndroidManifest.xml +++ b/mobile-v3/android/app/src/main/AndroidManifest.xml @@ -10,7 +10,12 @@ + android:value="${googleMapsKey}" /> + + + { -// final AuthRepository authRepository; -// AuthBloc(this.authRepository) : super(AuthInitial()) { -// on((event, emit) async { -// if (event is LoginUser) { -// try { -// emit(AuthLoading()); -// await authRepository.loginWithEmailAndPassword( -// event.username, event.password); -// -// emit(AuthLoaded(AuthPurpose.LOGIN)); -// } catch (e) { -// debugPrint(e.toString()); -// emit( -// AuthLoadingError( -// e.toString(), -// ), -// ); -// } -// } else if (event is RegisterUser) { -// try { -// emit(AuthLoading()); -// -// await authRepository.registerWithEmailAndPassword(event.model); -// -// emit(AuthLoaded(AuthPurpose.REGISTER)); -// } catch (e) { -// debugPrint(e.toString()); -// emit( -// AuthLoadingError( -// e.toString(), -// ), -// ); -// } -// } else if (event is UseAsGuest) { -// emit(GuestUser()); -// } -// }); -// } -// } class AuthBloc extends Bloc { final AuthRepository authRepository; AuthBloc(this.authRepository) : super(AuthInitial()) { - //debugPrint("AuthBloc initialized"); - on(_onAppStarted); - on(_onLoginUser); - on(_onRegisterUser); + on(_onLogoutUser); on((event, emit) => emit(GuestUser())); } - Future _onAppStarted(AppStarted event, Emitter emit) async { emit(AuthLoading()); try { @@ -85,15 +41,15 @@ class AuthBloc extends Bloc { } } - Future _onLoginUser(LoginUser event, Emitter emit) async { emit(AuthLoading()); try { - - final token = await authRepository.loginWithEmailAndPassword(event.username, event.password); + final token = await authRepository.loginWithEmailAndPassword( + event.username, event.password); await HiveRepository.saveData(HiveBoxNames.authBox, 'token', token); // Save token in Hive - final savedToken = await HiveRepository.getData('token', HiveBoxNames.authBox); + final savedToken = + await HiveRepository.getData('token', HiveBoxNames.authBox); //debugPrint("Saved token: $savedToken"); emit(AuthLoaded(AuthPurpose.LOGIN)); @@ -103,8 +59,8 @@ class AuthBloc extends Bloc { } } - - Future _onRegisterUser(RegisterUser event, Emitter emit) async { + Future _onRegisterUser( + RegisterUser event, Emitter emit) async { emit(AuthLoading()); try { await authRepository.registerWithEmailAndPassword(event.model); @@ -116,6 +72,18 @@ class AuthBloc extends Bloc { } + Future _onLogoutUser(LogoutUser event, Emitter emit) async { + emit(AuthLoading()); + try { + await HiveRepository.deleteData( + 'token', HiveBoxNames.authBox); // Remove token from Hive + emit(GuestUser()); // Emit guest state after logout + } catch (e) { + debugPrint("Logout error: $e"); + emit(AuthLoadingError("Failed to log out. Please try again.")); + } + } + String _extractErrorMessage(dynamic e) { if (e is Exception) { return e.toString().replaceAll("Exception:", "").trim(); diff --git a/mobile-v3/lib/src/app/auth/bloc/auth_event.dart b/mobile-v3/lib/src/app/auth/bloc/auth_event.dart index 8bbdcabf1e..16f752305e 100644 --- a/mobile-v3/lib/src/app/auth/bloc/auth_event.dart +++ b/mobile-v3/lib/src/app/auth/bloc/auth_event.dart @@ -26,3 +26,7 @@ class RegisterUser extends AuthEvent { class UseAsGuest extends AuthEvent { const UseAsGuest(); } + +class LogoutUser extends AuthEvent { + const LogoutUser(); +} \ No newline at end of file diff --git a/mobile-v3/lib/src/app/dashboard/pages/dashboard_page.dart b/mobile-v3/lib/src/app/dashboard/pages/dashboard_page.dart index 6d88eaa80b..889c726397 100644 --- a/mobile-v3/lib/src/app/dashboard/pages/dashboard_page.dart +++ b/mobile-v3/lib/src/app/dashboard/pages/dashboard_page.dart @@ -106,24 +106,24 @@ class _DashboardPageState extends State { ), SizedBox(width: 8), GestureDetector( - onTap: () { - final authState = context.read().state; - if (authState is GuestUser) { + // onTap: () { + // final authState = context.read().state; + // if (authState is GuestUser) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => GuestProfilePage(), - ), - ); - } else { - // Navigate to the regular profile page - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => ProfilePage(), - ), - ); - } - }, + // Navigator.of(context).push( + // MaterialPageRoute( + // builder: (context) => GuestProfilePage(), + // ), + // ); + // } else { + // // Navigate to the regular profile page + // Navigator.of(context).push( + // MaterialPageRoute( + // builder: (context) => ProfilePage(), + // ), + // ); + // } + // }, child: BlocBuilder( builder: (context, authState) { if (authState is GuestUser) { diff --git a/mobile-v3/lib/src/app/learn/pages/lesson_finished.dart b/mobile-v3/lib/src/app/learn/pages/lesson_finished.dart index 8ef0b579c4..88674000d3 100644 --- a/mobile-v3/lib/src/app/learn/pages/lesson_finished.dart +++ b/mobile-v3/lib/src/app/learn/pages/lesson_finished.dart @@ -14,19 +14,19 @@ class LessonFinishedWidget extends StatelessWidget { Text("👋🏼 Great Job !", style: TextStyle(fontSize: 20, fontWeight: FontWeight.w700)), Text( - "You can invite your friends to learn a thing about Air Pollution", + "You can now teach your friends to learn a thing about Air Pollution", textAlign: TextAlign.center, style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500)), SizedBox(height: 64), - SmallRoundedButton( - label: "Share", - imagePath: "assets/images/shared/share_icon.svg", - ), - SizedBox(height: 16), - SmallRoundedButton( - label: "Rate the App", - imagePath: "assets/images/shared/bookmark_icon.svg", - ), + // SmallRoundedButton( + // label: "Share", + // imagePath: "assets/images/shared/share_icon.svg", + // ), + // SizedBox(height: 16), + // SmallRoundedButton( + // label: "Rate the App", + // imagePath: "assets/images/shared/bookmark_icon.svg", + // ), ], ), ); diff --git a/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart b/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart index 1a02748f34..2410a330d3 100644 --- a/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart +++ b/mobile-v3/lib/src/app/profile/pages/widgets/settings_widget.dart @@ -1,3 +1,6 @@ +import 'package:airqo/src/app/auth/pages/welcome_screen.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:airqo/src/app/auth/bloc/auth_bloc.dart'; import 'package:flutter/material.dart'; import 'package:airqo/src/app/profile/pages/widgets/settings_tile.dart'; import 'package:flutter_svg/svg.dart'; @@ -9,6 +12,7 @@ class SettingsWidget extends StatefulWidget { @override State createState() => _SettingsWidgetState(); } + class _SettingsWidgetState extends State { String _appVersion = ''; bool _locationEnabled = true; @@ -30,20 +34,16 @@ class _SettingsWidgetState extends State { void _showLogoutConfirmation() { showDialog( context: context, - builder: (context) => AlertDialog( + builder: (dialogContext) => AlertDialog( title: const Text('Confirm Logout'), content: const Text('Are you sure you want to log out?'), actions: [ TextButton( - onPressed: () => Navigator.pop(context), + onPressed: () => Navigator.pop(dialogContext), child: const Text('Cancel'), ), ElevatedButton( - onPressed: () { - // TODO: Implement actual logout logic - // e.g., clear user session, revoke tokens - Navigator.of(context).pushReplacementNamed('/login'); - }, + onPressed: () => _handleLogout(dialogContext), child: const Text('Log Out'), ), ], @@ -51,6 +51,47 @@ class _SettingsWidgetState extends State { ); } + Future _handleLogout(BuildContext dialogContext) async { + Navigator.pop(dialogContext); // Close confirmation dialog + + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const Center(child: CircularProgressIndicator()), + ); + + try { + context.read().add(LogoutUser()); + + await for (final state in context.read().stream) { + if (state is GuestUser) { + Navigator.pop(context); + + await Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (_) => WelcomeScreen()), + (route) => false, + ); + break; + } else if (state is AuthLoadingError) { + Navigator.pop(context); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(state.message)), + ); + break; + } + } + } catch (e) { + Navigator.pop(context); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('An unexpected error occurred')), + ); + } +} + + void _showDeleteAccountDialog() { final TextEditingController passwordController = TextEditingController();