diff --git a/frontend/lib/constants/api_constants.dart b/frontend/lib/constants/api_constants.dart index 93b8cf7..e0f177f 100644 --- a/frontend/lib/constants/api_constants.dart +++ b/frontend/lib/constants/api_constants.dart @@ -1,5 +1,5 @@ class ApiConstants { - static const String baseUrl = 'https://api.wesal.online'; + static const String baseUrl = 'http://localhost:8080'; // Auth endpoints static const String loginEndpoint = '/login'; diff --git a/frontend/lib/screens/notifications_settings_screen.dart b/frontend/lib/screens/notifications_settings_screen.dart new file mode 100644 index 0000000..5b7679f --- /dev/null +++ b/frontend/lib/screens/notifications_settings_screen.dart @@ -0,0 +1,254 @@ +import 'package:flutter/material.dart'; +import '../services/user_service.dart'; + +class NotificationsSettingsScreen extends StatefulWidget { + const NotificationsSettingsScreen({super.key}); + + @override + _NotificationsSettingsScreenState createState() => _NotificationsSettingsScreenState(); +} + +class _NotificationsSettingsScreenState extends State { + bool isLoading = true; + bool isSaving = false; + String? errorMessage; + Map? userData; + + final Map subscriptionLabels = { + 'newposts': 'New Posts', + 'postcomments': 'Post Comments', + 'newinvites': 'New Invitations', + 'invitesfollowup': 'Invitation Follow-ups', + 'appnews': 'App News', + }; + + Map subscriptionStates = {}; + + @override + void initState() { + super.initState(); + _loadUserData(); + } + + Future _loadUserData() async { + setState(() { + isLoading = true; + errorMessage = null; + }); + + final result = await UserService.getCurrentUser(); + + if (mounted) { + setState(() { + isLoading = false; + if (result['success'] == true) { + userData = result['data']; + _initializeSubscriptions(); + } else { + errorMessage = result['message'] ?? 'Failed to load user data'; + } + }); + } + } + + void _initializeSubscriptions() { + List userSubscriptions = []; + + if (userData != null && userData!['subscriptions'] != null) { + userSubscriptions = List.from(userData!['subscriptions']); + } + + for (String key in subscriptionLabels.keys) { + subscriptionStates[key] = userSubscriptions.contains(key); + } + } + + Future _saveSettings() async { + setState(() { + isSaving = true; + errorMessage = null; + }); + + List activeSubscriptions = subscriptionStates.entries + .where((entry) => entry.value) + .map((entry) => entry.key) + .toList(); + + final result = await UserService.updateUserSubscriptions(activeSubscriptions); + + if (mounted) { + setState(() { + isSaving = false; + }); + + if (result['success'] == true) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Notification settings saved'), + backgroundColor: Color(0xFF6A4C93), + ), + ); + Navigator.of(context).pop(); + } else { + setState(() { + errorMessage = result['message'] ?? 'Failed to save settings'; + }); + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Notifications', style: TextStyle(fontWeight: FontWeight.w600)), + backgroundColor: Colors.white, + foregroundColor: Color(0xFF6A4C93), + elevation: 0, + bottom: PreferredSize( + preferredSize: Size.fromHeight(1), + child: Container(height: 1, color: Colors.grey[200]), + ), + ), + body: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (isLoading) ...[ + Expanded( + child: Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Color(0xFF6A4C93)), + ), + ), + ), + ] else if (errorMessage != null && userData == null) ...[ + Expanded( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.error_outline, + size: 50, + color: Colors.grey[400], + ), + SizedBox(height: 16), + Text( + errorMessage!, + style: TextStyle(fontSize: 16, color: Colors.grey[600]), + textAlign: TextAlign.center, + ), + SizedBox(height: 16), + ElevatedButton( + onPressed: _loadUserData, + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF6A4C93), + foregroundColor: Colors.white, + ), + child: Text('Retry'), + ), + ], + ), + ), + ), + ] else ...[ + Text( + 'Choose which notifications you want to receive:', + style: TextStyle( + fontSize: 16, + color: Colors.grey[700], + ), + ), + SizedBox(height: 24), + + Expanded( + child: ListView( + children: subscriptionLabels.entries.map((entry) { + return Container( + margin: EdgeInsets.only(bottom: 8), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey[300]!), + borderRadius: BorderRadius.circular(12), + ), + child: SwitchListTile( + title: Text( + entry.value, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + value: subscriptionStates[entry.key] ?? false, + onChanged: (bool value) { + setState(() { + subscriptionStates[entry.key] = value; + }); + }, + activeColor: Color(0xFF6A4C93), + contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 4), + ), + ); + }).toList(), + ), + ), + + if (errorMessage != null) ...[ + Container( + padding: EdgeInsets.all(12), + margin: EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: Colors.red[50], + border: Border.all(color: Colors.red[300]!), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + Icon(Icons.error_outline, color: Colors.red[700], size: 20), + SizedBox(width: 8), + Expanded( + child: Text( + errorMessage!, + style: TextStyle(color: Colors.red[700]), + ), + ), + ], + ), + ), + ], + + Container( + width: double.infinity, + height: 56, + child: ElevatedButton( + onPressed: isSaving ? null : _saveSettings, + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF6A4C93), + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: isSaving + ? SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ) + : Text( + 'Save Settings', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + ), + ), + ], + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/frontend/lib/screens/pages/profile_page.dart b/frontend/lib/screens/pages/profile_page.dart index 7322b54..2b03125 100644 --- a/frontend/lib/screens/pages/profile_page.dart +++ b/frontend/lib/screens/pages/profile_page.dart @@ -12,6 +12,7 @@ import '../../utils/password_generator.dart'; import '../edit_profile_screen.dart'; import '../support_screen.dart'; import '../information_screen.dart'; +import '../notifications_settings_screen.dart'; class ProfilePage extends StatefulWidget { @override @@ -771,6 +772,30 @@ class _SettingsPageState extends State { ), ), + SizedBox(height: 16), + + Container( + width: double.infinity, + height: 56, + child: ElevatedButton.icon( + onPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => NotificationsSettingsScreen(), + ), + ), + icon: Icon(Icons.notifications_outlined, size: 20), + label: Text('Notifications'), + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF6A4C93), + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + padding: EdgeInsets.symmetric(horizontal: 16), + ), + ), + ), + SizedBox(height: 32), Container( diff --git a/frontend/lib/services/user_service.dart b/frontend/lib/services/user_service.dart index d93cc9d..c458e50 100644 --- a/frontend/lib/services/user_service.dart +++ b/frontend/lib/services/user_service.dart @@ -169,12 +169,14 @@ class UserService { String? displayName, String? username, String? avatar, + List? subscriptions, }) async { try { final Map updateData = {}; if (displayName != null) updateData['displayName'] = displayName; if (username != null) updateData['username'] = username; if (avatar != null) updateData['avatar'] = avatar; + if (subscriptions != null) updateData['subscriptions'] = subscriptions; final response = await HttpService.post(ApiConstants.updateUserEndpoint, updateData); @@ -254,4 +256,8 @@ class UserService { }; } } + + static Future> updateUserSubscriptions(List subscriptions) async { + return updateUser(subscriptions: subscriptions); + } }