diff --git a/frontend/lib/constants/api_constants.dart b/frontend/lib/constants/api_constants.dart index 1f91e4c..7b4f6ad 100644 --- a/frontend/lib/constants/api_constants.dart +++ b/frontend/lib/constants/api_constants.dart @@ -6,4 +6,5 @@ class ApiConstants { // User endpoints static const String getUserEndpoint = '/getUser'; + static const String updateUserEndpoint = '/updateUser'; } \ No newline at end of file diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index 04a03c2..d30af18 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; import 'firebase_options.dart'; import 'screens/home_screen.dart'; +import 'screens/notification_permission_screen.dart'; import 'services/notification_service.dart'; import 'services/auth_service.dart'; import 'services/user_service.dart'; @@ -115,9 +116,6 @@ class LandingPage extends StatefulWidget { class _LandingPageState extends State { void _navigateDirectlyToLogin() { - // Request notification permissions on user action - NotificationService().requestPermissionAndSetup(); - Navigator.of( context, ).push(MaterialPageRoute(builder: (context) => SignInPage())); @@ -338,7 +336,7 @@ class _SignInPageState extends State { final userResult = await UserService.getCurrentUser(forceRefresh: true); Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (context) => HomeScreen()), + MaterialPageRoute(builder: (context) => NotificationPermissionScreen()), ); } else { _showErrorAlert(result['message'] ?? 'Login failed'); diff --git a/frontend/lib/screens/notification_permission_screen.dart b/frontend/lib/screens/notification_permission_screen.dart new file mode 100644 index 0000000..3f5e2f5 --- /dev/null +++ b/frontend/lib/screens/notification_permission_screen.dart @@ -0,0 +1,242 @@ +import 'package:flutter/material.dart'; +import '../services/notification_service.dart'; +import '../services/user_service.dart'; +import 'home_screen.dart'; + +class NotificationPermissionScreen extends StatefulWidget { + @override + _NotificationPermissionScreenState createState() => + _NotificationPermissionScreenState(); +} + +class _NotificationPermissionScreenState + extends State { + bool _isLoading = false; + + Future _requestNotificationPermission() async { + setState(() { + _isLoading = true; + }); + + try { + final notificationService = NotificationService(); + await notificationService.requestPermissionAndSetup(); + + final fcmToken = await notificationService.getToken(); + + if (fcmToken != null) { + final result = await UserService.updateFCMToken(fcmToken); + + if (result['success'] == true) { + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (context) => HomeScreen()), + ); + } else { + _showErrorAndContinue( + result['message'] ?? 'Failed to update notification settings', + ); + } + } else { + _showErrorAndContinue('Failed to get notification token'); + } + } catch (e) { + _showErrorAndContinue('Error setting up notifications: $e'); + } + + setState(() { + _isLoading = false; + }); + } + + void _showErrorAndContinue(String message) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('Notification Setup'), + content: Text( + '$message\n\nYou can enable notifications later in settings.', + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (context) => HomeScreen()), + ); + }, + child: Text('Continue', style: TextStyle(color: Color(0xFF6A4C93))), + ), + ], + ), + ); + } + + void _skipNotifications() { + Navigator.of( + context, + ).pushReplacement(MaterialPageRoute(builder: (context) => HomeScreen())); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0xFF32B0A5), Color(0xFF4600B9)], + stops: [0.0, 0.5], + ), + ), + child: SafeArea( + child: Padding( + padding: EdgeInsets.all(24), + child: Column( + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 200, + height: 200, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + shape: BoxShape.circle, + ), + child: Icon( + Icons.rocket_launch, + size: 100, + color: Colors.white, + ), + ), + + SizedBox(height: 40), + + Text( + 'All Set!', + style: TextStyle( + fontSize: 32, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + + SizedBox(height: 16), + + Text( + 'But one last thing...', + style: TextStyle( + fontSize: 18, + color: Colors.white.withOpacity(0.9), + ), + ), + + SizedBox(height: 40), + + Container( + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.1), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: Colors.white.withOpacity(0.3), + ), + ), + child: Column( + children: [ + Icon( + Icons.notifications_active, + size: 40, + color: Colors.white, + ), + SizedBox(height: 16), + Text( + 'Stay Connected', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + SizedBox(height: 8), + Text( + 'We will only send you updates on your own invitations or posts. You can always change your notification settings later.', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Colors.white.withOpacity(0.8), + height: 1.4, + ), + ), + ], + ), + ), + ], + ), + ), + + Column( + children: [ + Container( + width: double.infinity, + height: 56, + child: ElevatedButton( + onPressed: _isLoading + ? null + : _requestNotificationPermission, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: Color(0xFF6A4C93), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 4, + ), + child: _isLoading + ? CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + Color(0xFF6A4C93), + ), + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.notifications, size: 20), + SizedBox(width: 8), + Text( + 'Enable Notifications', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + + SizedBox(height: 16), + + TextButton( + onPressed: _isLoading ? null : _skipNotifications, + child: Text( + 'Skip for now', + style: TextStyle( + color: Colors.white.withOpacity(0.8), + fontSize: 16, + decoration: TextDecoration.underline, + ), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/frontend/lib/services/user_service.dart b/frontend/lib/services/user_service.dart index 00bde84..6793c47 100644 --- a/frontend/lib/services/user_service.dart +++ b/frontend/lib/services/user_service.dart @@ -4,7 +4,9 @@ import 'http_service.dart'; import 'auth_service.dart'; class UserService { - static Future> getCurrentUser({bool forceRefresh = false}) async { + static Future> getCurrentUser({ + bool forceRefresh = false, + }) async { if (!forceRefresh) { final cachedData = await AuthService.getCachedUserData(); if (cachedData != null) { @@ -14,21 +16,66 @@ class UserService { try { final response = await HttpService.get(ApiConstants.getUserEndpoint); - + if (response.statusCode == 200) { final data = jsonDecode(response.body); await AuthService.saveUserData(data); return {'success': true, 'data': data}; } else if (response.statusCode == 401) { - return {'success': false, 'message': 'Session expired. Please login again.'}; + return { + 'success': false, + 'message': 'Session expired. Please login again.', + }; } else if (response.statusCode == 403) { - return {'success': false, 'message': 'Access denied. Invalid credentials.'}; + return { + 'success': false, + 'message': 'Access denied. Invalid credentials.', + }; } else { - return {'success': false, 'message': 'Server error (${response.statusCode})'}; + return { + 'success': false, + 'message': 'Server error (${response.statusCode})', + }; } } catch (e) { print('Error fetching user: $e'); - return {'success': false, 'message': 'Network error. Please check your connection.'}; + return { + 'success': false, + 'message': 'Network error. Please check your connection.', + }; } } -} \ No newline at end of file + + static Future> updateFCMToken(String fcmToken) async { + try { + final response = await HttpService.post(ApiConstants.updateUserEndpoint, { + 'fcmToken': fcmToken, + }); + + if (response.statusCode == 200) { + return {'success': true}; + } else if (response.statusCode == 401) { + return { + 'success': false, + 'message': 'Session expired. Please login again.', + }; + } else if (response.statusCode == 403) { + return { + 'success': false, + 'message': 'Access denied. Invalid credentials.', + }; + } else { + return { + 'success': false, + 'message': 'Server error (${response.statusCode})', + }; + } + } catch (e) { + print('Error updating FCM token: $e'); + return { + 'success': false, + 'message': 'Network error. Please check your connection.', + }; + } + } +}