From 2a05a9f8df1b5e6bf28d210796d4b727b897757a Mon Sep 17 00:00:00 2001 From: sBubshait Date: Wed, 6 Aug 2025 00:00:06 +0300 Subject: [PATCH] feat: display and crop the user avatar correctly --- frontend/lib/models/post_models.dart | 38 ++++---- frontend/lib/widgets/posts_list.dart | 125 ++++++++++++++++++++------- 2 files changed, 115 insertions(+), 48 deletions(-) diff --git a/frontend/lib/models/post_models.dart b/frontend/lib/models/post_models.dart index 0e18493..ff287e1 100644 --- a/frontend/lib/models/post_models.dart +++ b/frontend/lib/models/post_models.dart @@ -1,24 +1,20 @@ class PostCreator { final String id; final String displayName; + final String? avatar; - PostCreator({ - required this.id, - required this.displayName, - }); + PostCreator({required this.id, required this.displayName, this.avatar}); factory PostCreator.fromJson(Map json) { return PostCreator( id: json['id']?.toString() ?? '', displayName: json['displayName'] ?? '', + avatar: json['avatar']?.toString(), ); } Map toJson() { - return { - 'id': id, - 'displayName': displayName, - }; + return {'id': id, 'displayName': displayName, 'avatar': avatar}; } } @@ -53,7 +49,9 @@ class Post { body: json['body'] ?? '', likes: int.tryParse(json['likes']?.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, images: json['images'] != null ? List.from(json['images']) : null, ); @@ -113,7 +111,9 @@ class LikedUser { return LikedUser( id: json['id']?.toString() ?? '', displayName: json['displayName'] ?? '', - likeTime: DateTime.parse(json['likeTime'] ?? DateTime.now().toIso8601String()), + likeTime: DateTime.parse( + json['likeTime'] ?? DateTime.now().toIso8601String(), + ), ); } @@ -157,10 +157,14 @@ class DetailedPost { likes: int.tryParse(json['likes']?.toString() ?? '0') ?? 0, comments: int.tryParse(json['comments']?.toString() ?? '0') ?? 0, liked: json['liked'] == true, - creationDate: DateTime.parse(json['creationDate'] ?? DateTime.now().toIso8601String()), - likedUsers: (json['likedUsers'] as List?) - ?.map((user) => LikedUser.fromJson(user)) - .toList() ?? [], + creationDate: DateTime.parse( + json['creationDate'] ?? DateTime.now().toIso8601String(), + ), + likedUsers: + (json['likedUsers'] as List?) + ?.map((user) => LikedUser.fromJson(user)) + .toList() ?? + [], images: json['images'] != null ? List.from(json['images']) : null, ); } @@ -186,8 +190,6 @@ class CreatePostRequest { CreatePostRequest({required this.body}); Map toJson() { - return { - 'body': body, - }; + return {'body': body}; } -} \ No newline at end of file +} diff --git a/frontend/lib/widgets/posts_list.dart b/frontend/lib/widgets/posts_list.dart index edd4eed..d9ede38 100644 --- a/frontend/lib/widgets/posts_list.dart +++ b/frontend/lib/widgets/posts_list.dart @@ -380,6 +380,98 @@ class PostImagesCarousel extends StatelessWidget { } } +class UserAvatar extends StatelessWidget { + final String displayName; + final String? avatarUrl; + final double radius; + final Color? backgroundColor; + + const UserAvatar({ + Key? key, + required this.displayName, + this.avatarUrl, + this.radius = 20, + this.backgroundColor, + }) : super(key: key); + + Color _getAvatarColor(String displayName) { + final colors = [ + Color(0xFF32B0A5), + Color(0xFF4600B9), + Color(0xFF6A4C93), + Color(0xFFFF6347), + Color(0xFF32CD32), + Color(0xFF9932CC), + ]; + + int hash = displayName.hashCode; + return colors[hash.abs() % colors.length]; + } + + String _getAvatarLetter(String displayName) { + return displayName.isNotEmpty ? displayName[0].toUpperCase() : '?'; + } + + bool _isValidImageUrl(String? url) { + return url != null && url.isNotEmpty && url.trim().isNotEmpty; + } + + @override + Widget build(BuildContext context) { + final color = backgroundColor ?? _getAvatarColor(displayName); + final letter = _getAvatarLetter(displayName); + + return CircleAvatar( + radius: radius, + backgroundColor: color, + child: _isValidImageUrl(avatarUrl) + ? ClipOval( + child: Image.network( + avatarUrl!, + width: radius * 2, + height: radius * 2, + fit: BoxFit.cover, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return SizedBox( + width: radius * 2, + height: radius * 2, + child: Center( + child: SizedBox( + width: radius * 0.6, + height: radius * 0.6, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ), + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + return Text( + letter, + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: radius * 0.8, + ), + ); + }, + ), + ) + : Text( + letter, + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: radius * 0.8, + ), + ), + ); + } +} + class PostCard extends StatefulWidget { final Post post; @@ -413,24 +505,6 @@ class _PostCardState extends State { } } - Color _getAvatarColor(String displayName) { - final colors = [ - Color(0xFF32B0A5), - Color(0xFF4600B9), - Color(0xFF6A4C93), - Color(0xFFFF6347), - Color(0xFF32CD32), - Color(0xFF9932CC), - ]; - - int hash = displayName.hashCode; - return colors[hash.abs() % colors.length]; - } - - String _getAvatarLetter(String displayName) { - return displayName.isNotEmpty ? displayName[0].toUpperCase() : '?'; - } - void _sharePost(Post post) { final shareText = '${post.creator.displayName} posted on Wesal.online:\n\n${post.body}'; @@ -503,8 +577,6 @@ class _PostCardState extends State { @override Widget build(BuildContext context) { final creator = _currentPost.creator; - final avatarColor = _getAvatarColor(creator.displayName); - final avatarLetter = _getAvatarLetter(creator.displayName); final relativeTime = InvitationUtils.getRelativeTime( _currentPost.creationDate, ); @@ -538,17 +610,10 @@ class _PostCardState extends State { children: [ Row( children: [ - CircleAvatar( + UserAvatar( + displayName: creator.displayName, + avatarUrl: creator.avatar, radius: 20, - backgroundColor: avatarColor, - child: Text( - avatarLetter, - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ), ), SizedBox(width: 12), Expanded(