// Version: 1.1.0 | Created: 2026-04-02 | Updated: 2026-07-04 // Shared avatar widget used throughout the app. Displays a cached network // image when an HTTP avatar URL is available, or falls back to a circle // coloured deterministically from the display name (so each person gets a // stable, distinct disc) with the first letter of the display name. // // The [avatarUrl] MUST be a resolved HTTP URL — never pass an mxc:// URI. import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; class MatrixAvatar extends StatelessWidget { const MatrixAvatar({ super.key, required this.name, this.avatarUrl, this.radius = 20, }); /// Display name used for the initials fallback. final String name; /// Resolved HTTP URL for the avatar image. Must NOT be an mxc:// URI. final String? avatarUrl; /// Radius of the [CircleAvatar]. final double radius; /// Brand-compatible hues for initials avatars. All dark enough for white /// initials to remain legible. static const List _palette = [ Color(0xFF1265ED), // brand blue Color(0xFF2E7DE0), // sky Color(0xFF1E88A8), // teal Color(0xFF3949AB), // indigo Color(0xFF6A4CC0), // violet Color(0xFF00897B), // green-teal Color(0xFF5C6BC0), // periwinkle Color(0xFFB1631F), // warm amber ]; Color get _discColour => _palette[name.hashCode.abs() % _palette.length]; @override Widget build(BuildContext context) { final theme = Theme.of(context); final initials = name.isNotEmpty ? name[0].toUpperCase() : '?'; if (avatarUrl != null) { return CircleAvatar( radius: radius, backgroundImage: CachedNetworkImageProvider(avatarUrl!), backgroundColor: theme.colorScheme.surfaceContainerHighest, ); } return CircleAvatar( radius: radius, backgroundColor: _discColour, child: Text( initials, style: TextStyle( fontSize: radius * 0.7, fontWeight: FontWeight.bold, color: Colors.white, ), ), ); } }