feat: display images within posts in UI
This commit is contained in:
parent
55c2d231c7
commit
2e43ea0dea
@ -31,6 +31,7 @@ class Post {
|
|||||||
final int comments;
|
final int comments;
|
||||||
final DateTime creationDate;
|
final DateTime creationDate;
|
||||||
final bool liked;
|
final bool liked;
|
||||||
|
final List<String>? images;
|
||||||
|
|
||||||
Post({
|
Post({
|
||||||
required this.id,
|
required this.id,
|
||||||
@ -41,6 +42,7 @@ class Post {
|
|||||||
required this.comments,
|
required this.comments,
|
||||||
required this.creationDate,
|
required this.creationDate,
|
||||||
this.liked = false,
|
this.liked = false,
|
||||||
|
this.images,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory Post.fromJson(Map<String, dynamic> json) {
|
factory Post.fromJson(Map<String, dynamic> json) {
|
||||||
@ -53,6 +55,7 @@ class Post {
|
|||||||
comments: int.tryParse(json['comments']?.toString() ?? '0') ?? 0,
|
comments: int.tryParse(json['comments']?.toString() ?? '0') ?? 0,
|
||||||
creationDate: DateTime.parse(json['creationDate'] ?? DateTime.now().toIso8601String()),
|
creationDate: DateTime.parse(json['creationDate'] ?? DateTime.now().toIso8601String()),
|
||||||
liked: json['liked'] == true,
|
liked: json['liked'] == true,
|
||||||
|
images: json['images'] != null ? List<String>.from(json['images']) : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,6 +69,7 @@ class Post {
|
|||||||
'comments': comments,
|
'comments': comments,
|
||||||
'creationDate': creationDate.toIso8601String(),
|
'creationDate': creationDate.toIso8601String(),
|
||||||
'liked': liked,
|
'liked': liked,
|
||||||
|
'images': images,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,6 +82,7 @@ class Post {
|
|||||||
int? comments,
|
int? comments,
|
||||||
DateTime? creationDate,
|
DateTime? creationDate,
|
||||||
bool? liked,
|
bool? liked,
|
||||||
|
List<String>? images,
|
||||||
}) {
|
}) {
|
||||||
return Post(
|
return Post(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
@ -88,6 +93,7 @@ class Post {
|
|||||||
comments: comments ?? this.comments,
|
comments: comments ?? this.comments,
|
||||||
creationDate: creationDate ?? this.creationDate,
|
creationDate: creationDate ?? this.creationDate,
|
||||||
liked: liked ?? this.liked,
|
liked: liked ?? this.liked,
|
||||||
|
images: images ?? this.images,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,6 +135,7 @@ class DetailedPost {
|
|||||||
final bool liked;
|
final bool liked;
|
||||||
final DateTime creationDate;
|
final DateTime creationDate;
|
||||||
final List<LikedUser> likedUsers;
|
final List<LikedUser> likedUsers;
|
||||||
|
final List<String>? images;
|
||||||
|
|
||||||
DetailedPost({
|
DetailedPost({
|
||||||
required this.id,
|
required this.id,
|
||||||
@ -139,6 +146,7 @@ class DetailedPost {
|
|||||||
required this.liked,
|
required this.liked,
|
||||||
required this.creationDate,
|
required this.creationDate,
|
||||||
required this.likedUsers,
|
required this.likedUsers,
|
||||||
|
this.images,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory DetailedPost.fromJson(Map<String, dynamic> json) {
|
factory DetailedPost.fromJson(Map<String, dynamic> json) {
|
||||||
@ -153,6 +161,7 @@ class DetailedPost {
|
|||||||
likedUsers: (json['likedUsers'] as List?)
|
likedUsers: (json['likedUsers'] as List?)
|
||||||
?.map((user) => LikedUser.fromJson(user))
|
?.map((user) => LikedUser.fromJson(user))
|
||||||
.toList() ?? [],
|
.toList() ?? [],
|
||||||
|
images: json['images'] != null ? List<String>.from(json['images']) : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,6 +175,7 @@ class DetailedPost {
|
|||||||
'liked': liked,
|
'liked': liked,
|
||||||
'creationDate': creationDate.toIso8601String(),
|
'creationDate': creationDate.toIso8601String(),
|
||||||
'likedUsers': likedUsers.map((user) => user.toJson()).toList(),
|
'likedUsers': likedUsers.map((user) => user.toJson()).toList(),
|
||||||
|
'images': images,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,7 +59,9 @@ class PostsListState extends State<PostsList> {
|
|||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
_errorMessage = '';
|
_errorMessage = '';
|
||||||
});
|
});
|
||||||
print('🎯 Posts UI updated with ${updatedPosts.length} posts - setState called');
|
print(
|
||||||
|
'🎯 Posts UI updated with ${updatedPosts.length} posts - setState called',
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
print('⚠️ Widget not mounted, skipping UI update');
|
print('⚠️ Widget not mounted, skipping UI update');
|
||||||
}
|
}
|
||||||
@ -217,6 +219,167 @@ class PostsListState extends State<PostsList> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PostImageViewer extends StatelessWidget {
|
||||||
|
final String imageUrl;
|
||||||
|
|
||||||
|
const PostImageViewer({Key? key, required this.imageUrl}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
|
iconTheme: IconThemeData(color: Colors.white),
|
||||||
|
),
|
||||||
|
body: Center(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => Navigator.of(context).pop(),
|
||||||
|
child: InteractiveViewer(
|
||||||
|
child: Image.network(
|
||||||
|
imageUrl,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
loadingBuilder: (context, child, loadingProgress) {
|
||||||
|
if (loadingProgress == null) return child;
|
||||||
|
return Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
value: loadingProgress.expectedTotalBytes != null
|
||||||
|
? loadingProgress.cumulativeBytesLoaded /
|
||||||
|
loadingProgress.expectedTotalBytes!
|
||||||
|
: null,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.broken_image, size: 64, color: Colors.white54),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'Failed to load image',
|
||||||
|
style: TextStyle(color: Colors.white70, fontSize: 16),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PostImagesCarousel extends StatelessWidget {
|
||||||
|
final List<String> images;
|
||||||
|
final Function(String imageUrl)? onImageTap;
|
||||||
|
|
||||||
|
const PostImagesCarousel({Key? key, required this.images, this.onImageTap})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (images.isEmpty) return SizedBox.shrink();
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
height: 200,
|
||||||
|
child: ListView.builder(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
itemCount: images.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return Container(
|
||||||
|
width: 280,
|
||||||
|
margin: EdgeInsets.only(right: index < images.length - 1 ? 12 : 0),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
if (onImageTap != null) {
|
||||||
|
onImageTap!(images[index]);
|
||||||
|
} else {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) =>
|
||||||
|
PostImageViewer(imageUrl: images[index]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Image.network(
|
||||||
|
images[index],
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
width: 280,
|
||||||
|
height: 200,
|
||||||
|
loadingBuilder: (context, child, loadingProgress) {
|
||||||
|
if (loadingProgress == null) return child;
|
||||||
|
return Container(
|
||||||
|
width: 280,
|
||||||
|
height: 200,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[200],
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
value: loadingProgress.expectedTotalBytes != null
|
||||||
|
? loadingProgress.cumulativeBytesLoaded /
|
||||||
|
loadingProgress.expectedTotalBytes!
|
||||||
|
: null,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
|
Color(0xFF6A4C93),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
print('Image load error: $error');
|
||||||
|
print('Stack trace: $stackTrace');
|
||||||
|
print('Image URL: ${images[index]}');
|
||||||
|
return Container(
|
||||||
|
width: 280,
|
||||||
|
height: 200,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[200],
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.broken_image,
|
||||||
|
size: 48,
|
||||||
|
color: Colors.grey[400],
|
||||||
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Failed to load image',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey[600],
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class PostCard extends StatefulWidget {
|
class PostCard extends StatefulWidget {
|
||||||
final Post post;
|
final Post post;
|
||||||
|
|
||||||
@ -422,6 +585,11 @@ class _PostCardState extends State<PostCard> {
|
|||||||
color: Colors.black87,
|
color: Colors.black87,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (_currentPost.images != null &&
|
||||||
|
_currentPost.images!.isNotEmpty) ...[
|
||||||
|
SizedBox(height: 12),
|
||||||
|
PostImagesCarousel(images: _currentPost.images!),
|
||||||
|
],
|
||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@ -528,7 +696,9 @@ class _ProfilePostsListState extends State<ProfilePostsList> {
|
|||||||
|
|
||||||
_userPostsStreamSubscription = PostService.getUserPostsStream().listen(
|
_userPostsStreamSubscription = PostService.getUserPostsStream().listen(
|
||||||
(updatedPosts) {
|
(updatedPosts) {
|
||||||
print('📱 User posts stream received data: ${updatedPosts.length} posts');
|
print(
|
||||||
|
'📱 User posts stream received data: ${updatedPosts.length} posts',
|
||||||
|
);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
// Update the posts directly instead of fetching again
|
// Update the posts directly instead of fetching again
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -541,7 +711,9 @@ class _ProfilePostsListState extends State<ProfilePostsList> {
|
|||||||
final totalLikes = _posts.fold(0, (sum, post) => sum + post.likes);
|
final totalLikes = _posts.fold(0, (sum, post) => sum + post.likes);
|
||||||
widget.onStatsUpdate!(_posts.length, totalLikes);
|
widget.onStatsUpdate!(_posts.length, totalLikes);
|
||||||
}
|
}
|
||||||
print('🎯 User posts UI updated with ${updatedPosts.length} posts - setState called');
|
print(
|
||||||
|
'🎯 User posts UI updated with ${updatedPosts.length} posts - setState called',
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
print('⚠️ Widget not mounted, skipping UI update');
|
print('⚠️ Widget not mounted, skipping UI update');
|
||||||
}
|
}
|
||||||
@ -583,7 +755,10 @@ class _ProfilePostsListState extends State<ProfilePostsList> {
|
|||||||
_posts = result['posts'];
|
_posts = result['posts'];
|
||||||
// Update parent stats
|
// Update parent stats
|
||||||
if (widget.onStatsUpdate != null) {
|
if (widget.onStatsUpdate != null) {
|
||||||
final totalLikes = _posts.fold(0, (sum, post) => sum + post.likes);
|
final totalLikes = _posts.fold(
|
||||||
|
0,
|
||||||
|
(sum, post) => sum + post.likes,
|
||||||
|
);
|
||||||
widget.onStatsUpdate!(_posts.length, totalLikes);
|
widget.onStatsUpdate!(_posts.length, totalLikes);
|
||||||
}
|
}
|
||||||
_errorMessage = '';
|
_errorMessage = '';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user