From 86a4535f9aaa5eccc685b37cf6adc11074211ff5 Mon Sep 17 00:00:00 2001 From: sBubshait Date: Wed, 23 Jul 2025 14:07:34 +0300 Subject: [PATCH] feat: implement profile page user feed --- frontend/lib/screens/pages/profile_page.dart | 344 +++++++++++-------- frontend/lib/services/post_service.dart | 29 ++ 2 files changed, 228 insertions(+), 145 deletions(-) diff --git a/frontend/lib/screens/pages/profile_page.dart b/frontend/lib/screens/pages/profile_page.dart index e0c9482..499979c 100644 --- a/frontend/lib/screens/pages/profile_page.dart +++ b/frontend/lib/screens/pages/profile_page.dart @@ -3,6 +3,8 @@ import 'package:flutter/services.dart'; import '../../services/notification_service.dart'; import '../../services/user_service.dart'; import '../../services/auth_service.dart'; +import '../../services/post_service.dart'; +import '../../models/post_models.dart'; class ProfilePage extends StatefulWidget { @override @@ -15,42 +17,16 @@ class _ProfilePageState extends State { bool isLoading = false; Map? userData; bool isLoadingUser = true; - - 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, - }, - ]; + List userPosts = []; + bool isLoadingPosts = true; + int totalLikes = 0; @override void initState() { super.initState(); _loadFCMToken(); _loadUserData(); + _loadUserPosts(); } @override @@ -98,6 +74,26 @@ class _ProfilePageState extends State { }); } + Future _loadUserPosts() async { + setState(() { + isLoadingPosts = true; + }); + + final result = await PostService.getUserPosts(); + + setState(() { + isLoadingPosts = false; + if (result['success'] == true) { + userPosts = result['posts'] as List; + totalLikes = userPosts.fold(0, (sum, post) => sum + post.likes); + } else { + userPosts = []; + totalLikes = 0; + _showErrorAlert(result['message'] ?? 'Failed to load posts'); + } + }); + } + void _showErrorAlert(String message) { showDialog( context: context, @@ -132,6 +128,21 @@ class _ProfilePageState extends State { ).push(MaterialPageRoute(builder: (context) => SettingsPage())); } + String _formatTimestamp(DateTime dateTime) { + final now = DateTime.now(); + final difference = now.difference(dateTime); + + if (difference.inDays > 0) { + return '${difference.inDays}d ago'; + } else if (difference.inHours > 0) { + return '${difference.inHours}h ago'; + } else if (difference.inMinutes > 0) { + return '${difference.inMinutes}m ago'; + } else { + return 'Just now'; + } + } + @override Widget build(BuildContext context) { return Scaffold( @@ -223,7 +234,7 @@ class _ProfilePageState extends State { Column( children: [ Text( - '${mockUserPosts.length}', + '${userPosts.length}', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, @@ -242,7 +253,7 @@ class _ProfilePageState extends State { Column( children: [ Text( - '127', + '$totalLikes', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, @@ -250,7 +261,7 @@ class _ProfilePageState extends State { ), ), Text( - 'Followers', + 'Likes Received', style: TextStyle( fontSize: 14, color: Colors.grey[600], @@ -270,124 +281,167 @@ class _ProfilePageState extends State { 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), + if (isLoadingPosts) + Container( + padding: EdgeInsets.all(32), + child: Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + Color(0xFF6A4C93), + ), + ), + ), + ) + else if (userPosts.isEmpty) + Container( + padding: EdgeInsets.all(32), + child: Center( + child: Column( + children: [ + Icon( + Icons.post_add, + size: 64, + color: Colors.grey[400], + ), + SizedBox(height: 16), + Text( + 'No posts yet', + style: TextStyle( + fontSize: 18, + color: Colors.grey[600], + fontWeight: FontWeight.w500, + ), + ), + SizedBox(height: 8), + Text( + 'Start sharing your thoughts!', + style: TextStyle( + fontSize: 14, + color: Colors.grey[500], + ), ), ], ), - child: Padding( - padding: EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - CircleAvatar( - radius: 16, - backgroundColor: Color(0xFF6A4C93), - child: Text( - (userData!['displayName'] ?? 'U') - .substring(0, 1) - .toUpperCase(), - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 14, - ), - ), - ), - SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - userData!['displayName'] ?? - 'Unknown User', - 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, - ), - ), - ], + ), + ) + else + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.symmetric(horizontal: 16), + itemCount: userPosts.length, + itemBuilder: (context, index) { + final post = userPosts[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( + post.creator.displayName.isNotEmpty + ? post.creator.displayName + .substring(0, 1) + .toUpperCase() + : 'U', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + post.creator.displayName.isNotEmpty + ? post.creator.displayName + : 'Unknown User', + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 14, + color: Colors.black87, + ), + ), + Text( + _formatTimestamp(post.creationDate), + style: TextStyle( + color: Colors.grey[600], + fontSize: 12, + ), + ), + ], + ), + ), + ], + ), + SizedBox(height: 12), + Text( + post.body, + style: TextStyle( + fontSize: 15, + height: 1.4, + color: Colors.black87, + ), + ), + SizedBox(height: 12), + Row( + children: [ + Icon( + Icons.favorite_border, + color: 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), ], diff --git a/frontend/lib/services/post_service.dart b/frontend/lib/services/post_service.dart index 2773220..e735550 100644 --- a/frontend/lib/services/post_service.dart +++ b/frontend/lib/services/post_service.dart @@ -36,6 +36,35 @@ class PostService { } } + static Future> getUserPosts() async { + try { + final response = await HttpService.get('/posts/user'); + + final responseData = jsonDecode(response.body); + + if (response.statusCode == 200) { + return { + 'success': responseData['status'] ?? false, + 'message': responseData['message'] ?? '', + 'posts': (responseData['data'] as List?)?.map((post) => Post.fromJson(post)).toList() ?? [], + }; + } else { + return { + 'success': false, + 'message': responseData['message'] ?? 'Failed to fetch user posts', + 'posts': [], + }; + } + } catch (e) { + print('Error fetching user posts: $e'); + return { + 'success': false, + 'message': 'Network error: $e', + 'posts': [], + }; + } + } + static Future> createPost(String body) async { try { final createPostRequest = CreatePostRequest(body: body);