diff --git a/lib/screens/pages/feed_page.dart b/lib/screens/pages/feed_page.dart index 8d77ebc..a380c4b 100644 --- a/lib/screens/pages/feed_page.dart +++ b/lib/screens/pages/feed_page.dart @@ -105,6 +105,15 @@ class _FeedPageState extends State { }, ), ), + floatingActionButton: FloatingActionButton( + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Create post functionality coming soon!')), + ); + }, + backgroundColor: Color(0xFF6A4C93), + child: Icon(Icons.edit, color: Colors.white), + ), ); } } diff --git a/lib/screens/pages/invitations_page.dart b/lib/screens/pages/invitations_page.dart index 77d0ca5..80d8680 100644 --- a/lib/screens/pages/invitations_page.dart +++ b/lib/screens/pages/invitations_page.dart @@ -253,6 +253,15 @@ class _InvitationsPageState extends State { ], ), ), + floatingActionButton: FloatingActionButton( + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Create invitation functionality coming soon!')), + ); + }, + backgroundColor: Color(0xFF6A4C93), + child: Icon(Icons.person_add, color: Colors.white), + ), ); } } diff --git a/lib/screens/pages/profile_page.dart b/lib/screens/pages/profile_page.dart index f962c7f..481d55e 100644 --- a/lib/screens/pages/profile_page.dart +++ b/lib/screens/pages/profile_page.dart @@ -12,6 +12,36 @@ class _ProfilePageState extends State { final TextEditingController _tokenController = TextEditingController(); bool isLoading = false; + final List> mockUserPosts = [ + { + 'id': '1', + 'content': + 'Just finished working on a new Flutter project! The development experience keeps getting better.', + 'timestamp': '2 hours ago', + 'likes': 15, + 'comments': 4, + 'isLiked': true, + }, + { + 'id': '2', + 'content': + 'Beautiful sunset from my office window today. Sometimes you need to take a moment to appreciate the simple things.', + 'timestamp': '1 day ago', + 'likes': 23, + 'comments': 8, + 'isLiked': false, + }, + { + 'id': '3', + 'content': + 'Excited to share that I completed my certification today! Hard work pays off.', + 'timestamp': '3 days ago', + 'likes': 42, + 'comments': 12, + 'isLiked': true, + }, + ]; + @override void initState() { super.initState(); @@ -28,7 +58,7 @@ class _ProfilePageState extends State { setState(() { isLoading = true; }); - + try { final token = await NotificationService().getToken(); setState(() { @@ -57,25 +87,333 @@ class _ProfilePageState extends State { } } + void _navigateToSettings() { + Navigator.of( + context, + ).push(MaterialPageRoute(builder: (context) => SettingsPage())); + } + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text( - 'Profile', - style: TextStyle( - fontWeight: FontWeight.w600, + title: Text('Profile', style: TextStyle(fontWeight: FontWeight.w600)), + backgroundColor: Colors.white, + foregroundColor: Color(0xFF6A4C93), + elevation: 0, + actions: [ + IconButton( + onPressed: _navigateToSettings, + icon: Icon(Icons.settings), ), + ], + bottom: PreferredSize( + preferredSize: Size.fromHeight(1), + child: Container(height: 1, color: Colors.grey[200]), ), + ), + body: SingleChildScrollView( + child: Column( + children: [ + Container( + padding: EdgeInsets.all(24), + child: Column( + children: [ + CircleAvatar( + radius: 50, + backgroundColor: Color(0xFF6A4C93), + child: Text( + 'A', + style: TextStyle( + color: Colors.white, + fontSize: 36, + fontWeight: FontWeight.bold, + ), + ), + ), + SizedBox(height: 16), + Text( + 'Abu Norah', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + SizedBox(height: 4), + Text( + '@yasser_hajri', + style: TextStyle(fontSize: 16, color: Colors.grey[600]), + ), + SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Column( + children: [ + Text( + '${mockUserPosts.length}', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Color(0xFF6A4C93), + ), + ), + Text( + 'Posts', + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + ], + ), + Column( + children: [ + Text( + '127', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Color(0xFF6A4C93), + ), + ), + Text( + 'Followers', + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + ], + ), + ], + ), + ], + ), + ), + Container( + height: 1, + color: Colors.grey[200], + margin: EdgeInsets.symmetric(horizontal: 16), + ), + SizedBox(height: 16), + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.symmetric(horizontal: 16), + itemCount: mockUserPosts.length, + itemBuilder: (context, index) { + final post = mockUserPosts[index]; + return Container( + margin: EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: Offset(0, 2), + ), + ], + ), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + CircleAvatar( + radius: 16, + backgroundColor: Color(0xFF6A4C93), + child: Text( + 'J', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Abu Norah', + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 14, + color: Colors.black87, + ), + ), + Text( + post['timestamp'], + style: TextStyle( + color: Colors.grey[600], + fontSize: 12, + ), + ), + ], + ), + ), + ], + ), + SizedBox(height: 12), + Text( + post['content'], + style: TextStyle( + fontSize: 15, + height: 1.4, + color: Colors.black87, + ), + ), + SizedBox(height: 12), + Row( + children: [ + Icon( + post['isLiked'] + ? Icons.favorite + : Icons.favorite_border, + color: post['isLiked'] + ? Colors.red + : Colors.grey[600], + size: 20, + ), + SizedBox(width: 4), + Text( + '${post['likes']}', + style: TextStyle( + color: Colors.grey[700], + fontSize: 14, + ), + ), + SizedBox(width: 16), + Icon( + Icons.chat_bubble_outline, + color: Colors.grey[600], + size: 20, + ), + SizedBox(width: 4), + Text( + '${post['comments']}', + style: TextStyle( + color: Colors.grey[700], + fontSize: 14, + ), + ), + ], + ), + ], + ), + ), + ); + }, + ), + SizedBox(height: 24), + ], + ), + ), + ); + } +} + +class SettingsPage extends StatefulWidget { + @override + _SettingsPageState createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State { + String? fcmToken; + final TextEditingController _tokenController = TextEditingController(); + bool isLoading = false; + + @override + void initState() { + super.initState(); + _loadFCMToken(); + } + + @override + void dispose() { + _tokenController.dispose(); + super.dispose(); + } + + Future _loadFCMToken() async { + setState(() { + isLoading = true; + }); + + try { + final token = await NotificationService().getToken(); + setState(() { + fcmToken = token; + _tokenController.text = token ?? 'Token not available'; + isLoading = false; + }); + } catch (e) { + setState(() { + fcmToken = null; + _tokenController.text = 'Error loading token: $e'; + isLoading = false; + }); + } + } + + Future _copyToClipboard() async { + if (fcmToken != null && fcmToken!.isNotEmpty) { + await Clipboard.setData(ClipboardData(text: fcmToken!)); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('FCM Token copied to clipboard'), + backgroundColor: Color(0xFF6A4C93), + ), + ); + } + } + + void _signOut() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Sign Out'), + content: Text('Are you sure you want to sign out?'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text('Cancel'), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Signed out successfully')), + ); + }, + child: Text('Sign Out', style: TextStyle(color: Colors.red)), + ), + ], + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Settings', 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], - ), + child: Container(height: 1, color: Colors.grey[200]), ), ), body: Padding( @@ -92,7 +430,7 @@ class _ProfilePageState extends State { ), ), SizedBox(height: 16), - + Text( 'FCM Token:', style: TextStyle( @@ -102,7 +440,7 @@ class _ProfilePageState extends State { ), ), SizedBox(height: 8), - + if (isLoading) Center( child: CircularProgressIndicator( @@ -122,10 +460,7 @@ class _ProfilePageState extends State { controller: _tokenController, readOnly: true, maxLines: 4, - style: TextStyle( - fontSize: 12, - fontFamily: 'monospace', - ), + style: TextStyle(fontSize: 12, fontFamily: 'monospace'), decoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.all(12), @@ -134,7 +469,7 @@ class _ProfilePageState extends State { ), ), SizedBox(height: 8), - + Row( children: [ ElevatedButton.icon( @@ -166,9 +501,28 @@ class _ProfilePageState extends State { ), ], ), + + SizedBox(height: 32), + + Container( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: _signOut, + icon: Icon(Icons.logout, color: Colors.red), + label: Text('Sign Out', style: TextStyle(color: Colors.red)), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + side: BorderSide(color: Colors.red), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + padding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), ], ), ), ); } -} \ No newline at end of file +} diff --git a/lib/services/notification_service.dart b/lib/services/notification_service.dart index a544a45..952c79e 100644 --- a/lib/services/notification_service.dart +++ b/lib/services/notification_service.dart @@ -16,7 +16,7 @@ class NotificationService { // Firebase project configuration static const String _projectId = 'wesalapp-bc676'; - + // Service account credentials (JSON string) static const String _serviceAccountJson = ''' { @@ -259,15 +259,16 @@ class NotificationService { Future _getAccessToken() async { try { - final credentials = ServiceAccountCredentials.fromJson(_serviceAccountJson); - final client = await clientViaServiceAccount( - credentials, - ['https://www.googleapis.com/auth/firebase.messaging'], + final credentials = ServiceAccountCredentials.fromJson( + _serviceAccountJson, ); - + final client = await clientViaServiceAccount(credentials, [ + 'https://www.googleapis.com/auth/firebase.messaging', + ]); + final token = client.credentials.accessToken.data; client.close(); - + return token; } catch (e) { print('Error getting access token: $e'); @@ -288,7 +289,9 @@ class NotificationService { return false; } - final url = Uri.parse('https://fcm.googleapis.com/v1/projects/$_projectId/messages:send'); + final url = Uri.parse( + 'https://fcm.googleapis.com/v1/projects/$_projectId/messages:send', + ); final headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer $accessToken', @@ -297,10 +300,7 @@ class NotificationService { final payload = { 'message': { 'token': token, - 'notification': { - 'title': title, - 'body': body, - }, + 'notification': {'title': title, 'body': body}, 'data': data ?? {}, 'webpush': { 'notification': { @@ -348,7 +348,9 @@ class NotificationService { return false; } - final url = Uri.parse('https://fcm.googleapis.com/v1/projects/$_projectId/messages:send'); + final url = Uri.parse( + 'https://fcm.googleapis.com/v1/projects/$_projectId/messages:send', + ); final headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer $accessToken', @@ -357,10 +359,7 @@ class NotificationService { final payload = { 'message': { 'topic': topic, - 'notification': { - 'title': title, - 'body': body, - }, + 'notification': {'title': title, 'body': body}, 'data': data ?? {}, 'webpush': { 'notification': { @@ -394,8 +393,9 @@ class NotificationService { Future sendCoffeeInviteAcceptedNotification() async { // Send to specific token for testing - const testToken = 'cetNA4PmdzwBS075-zdvbP:APA91bEDAqLC0D4txAaiYAdzzfk43yk99tKlC_x3Tii24qHdyhIzuQifsJG4Rm_oijvdVkbsoQUm3tQso34_71OvU-kQsqZTrMOQVIvHkIul7PYqOODFJUA'; - + const testToken = + 'evEF6UT53IkV98ku-MR1bH:APA91bGE52wWTUBm_cOoQ0GeGy6gUgpHhaZquB0Y0L_eyoosFZvE8lMuiQrhhVZ81tw-lB_baC7en0Bk1JIVGAuIj6KyFHKeuoPJVbntHh7HtEZDBEeJRkc'; + return await sendNotificationToToken( testToken, 'Coffee Time! ☕',