feat: display and crop the user avatar correctly

This commit is contained in:
sBubshait 2025-08-06 00:00:06 +03:00
parent 1c8e357722
commit 2a05a9f8df
2 changed files with 115 additions and 48 deletions

View File

@ -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<String, dynamic> json) {
return PostCreator(
id: json['id']?.toString() ?? '',
displayName: json['displayName'] ?? '',
avatar: json['avatar']?.toString(),
);
}
Map<String, dynamic> 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<String>.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<String>.from(json['images']) : null,
);
}
@ -186,8 +190,6 @@ class CreatePostRequest {
CreatePostRequest({required this.body});
Map<String, dynamic> toJson() {
return {
'body': body,
};
return {'body': body};
}
}
}

View File

@ -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<Color>(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<PostCard> {
}
}
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<PostCard> {
@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<PostCard> {
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(