Files
Eventify-frontend/lib/widgets/tier_avatar_ring.dart
Sicherhaven 754b04dc05 perf: fix image loading performance across all screens
- Replace Image.network (no cache) with CachedNetworkImage in contributor_profile_screen
- Replace NetworkImage (no cache) with CachedNetworkImageProvider in desktop_topbar and contribute_screen (leaderboard avatars)
- Add maxWidthDiskCache + maxHeightDiskCache to all 23 CachedNetworkImage calls
- Add missing memCacheWidth/Height to review_card (36x36 avatar) and learn_more related events (140x100)
- Add dynamic memCache sizing to tier_avatar_ring based on widget size

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 20:03:03 +05:30

122 lines
3.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
class TierAvatarRing extends StatelessWidget {
final String username;
final String tier;
final double size;
final bool showDiceBear;
final String? imageUrl;
final VoidCallback? onTap;
static const Map<String, Color> _tierColors = {
'Bronze': Color(0xFFFED7AA),
'Silver': Color(0xFFE2E8F0),
'Gold': Color(0xFFFEF3C7),
'Platinum': Color(0xFFEDE9FE),
'Diamond': Color(0xFFE0E7FF),
};
static const Color _fallbackColor = Color(0xFF475569);
const TierAvatarRing({
super.key,
required this.username,
required this.tier,
this.size = 48.0,
this.showDiceBear = true,
this.imageUrl,
this.onTap,
});
Color get _ringColor => _tierColors[tier] ?? _fallbackColor;
String get _avatarUrl {
if (imageUrl != null && imageUrl!.isNotEmpty) {
return imageUrl!;
}
return 'https://api.dicebear.com/9.x/notionists/svg?seed=$username';
}
Widget _buildAvatar() {
final double radius = size / 2 - 5;
if (!showDiceBear) {
return CircleAvatar(
radius: radius,
backgroundColor: const Color(0xFF1E293B),
child: Icon(
Icons.person,
color: Colors.white54,
size: size * 0.5,
),
);
}
return CachedNetworkImage(
imageUrl: _avatarUrl,
memCacheWidth: (size * 2).round(),
memCacheHeight: (size * 2).round(),
maxWidthDiskCache: (size * 4).round(),
maxHeightDiskCache: (size * 4).round(),
imageBuilder: (context, imageProvider) => CircleAvatar(
radius: radius,
backgroundImage: imageProvider,
),
placeholder: (context, url) => CircleAvatar(
radius: radius,
backgroundColor: const Color(0xFF1E293B),
child: SizedBox(
width: size * 0.4,
height: size * 0.4,
child: const CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white38,
),
),
),
errorWidget: (context, url, error) => CircleAvatar(
radius: radius,
backgroundColor: const Color(0xFF1E293B),
child: Icon(
Icons.person,
color: Colors.white54,
size: size * 0.5,
),
),
);
}
@override
Widget build(BuildContext context) {
final Color ringColor = _ringColor;
final double containerSize = size + 6;
final Widget ring = Container(
width: containerSize,
height: containerSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: ringColor, width: 3),
boxShadow: [
BoxShadow(
color: ringColor.withOpacity(0.4),
blurRadius: 8,
spreadRadius: 1,
),
],
),
child: Center(child: _buildAvatar()),
);
if (onTap != null) {
return GestureDetector(
onTap: onTap,
child: ring,
);
}
return ring;
}
}