import 'package:flutter/material.dart'; /// Native Flutter profile avatar that displays either: /// 1. User's avatar image URL (if provided) /// 2. Initials derived from full name (fallback) /// /// Pass [heroTag] to participate in a Hero transition to/from a destination /// that uses the same tag (e.g., the profile screen's large avatar). class ProfileAvatar extends StatelessWidget { const ProfileAvatar({ super.key, required this.fullName, this.avatarUrl, this.radius = 18, this.heroTag, }); final String fullName; final String? avatarUrl; final double radius; /// When non-null, wraps the avatar in a [Hero] with this tag. final Object? heroTag; String _getInitials() { final trimmed = fullName.trim(); if (trimmed.isEmpty) return 'U'; final parts = trimmed.split(RegExp(r'\s+')); if (parts.length == 1) { return parts[0].substring(0, 1).toUpperCase(); } // Get first letter of first and last name return '${parts.first[0]}${parts.last[0]}'.toUpperCase(); } /// Returns a (background, foreground) pair from the M3 tonal palette. /// /// Uses a deterministic hash of the initials to cycle through the scheme's /// semantic container colors so every avatar is theme-aware and accessible. (Color, Color) _getTonalColors(String initials, ColorScheme cs) { final hash = initials.codeUnitAt(0) + (initials.length > 1 ? initials.codeUnitAt(1) * 256 : 0); // Six M3-compliant container pairs (background / on-color text). final pairs = [ (cs.primaryContainer, cs.onPrimaryContainer), (cs.secondaryContainer, cs.onSecondaryContainer), (cs.tertiaryContainer, cs.onTertiaryContainer), (cs.errorContainer, cs.onErrorContainer), (cs.primary, cs.onPrimary), (cs.secondary, cs.onSecondary), ]; return pairs[hash % pairs.length]; } @override Widget build(BuildContext context) { final initials = _getInitials(); Widget avatar; // If avatar URL is provided, attempt to load the image if (avatarUrl != null && avatarUrl!.isNotEmpty) { avatar = CircleAvatar( radius: radius, backgroundImage: NetworkImage(avatarUrl!), onBackgroundImageError: (_, _) { // Silently fall back to initials if image fails }, child: null, // Image will display if loaded successfully ); } else { final (bg, fg) = _getTonalColors( initials, Theme.of(context).colorScheme, ); // Fallback to initials avatar = CircleAvatar( radius: radius, backgroundColor: bg, child: Text( initials, style: TextStyle( color: fg, fontSize: radius * 0.8, fontWeight: FontWeight.w600, ), ), ); } if (heroTag != null) { return Hero(tag: heroTag!, child: avatar); } return avatar; } }