import 'package:flutter/material.dart'; import '../services/offer_thumbnail.dart' show decodeDataUri; import 'avatar.dart'; import 'seed_glyph.dart'; /// A small round avatar for a person. When they've set an avatar ([picture] — a /// `data:` photo thumbnail or a `tane:seed:` illustration) it's shown; /// otherwise it falls back to a deterministic disc coloured from their [pubkey], /// with the first letter of their [name] (else a person glyph). Same pubkey → /// same colour on every device, so a person stays visually recognizable. class PeerAvatar extends StatelessWidget { const PeerAvatar({ required this.pubkey, this.name, this.picture, this.radius = 14, super.key, }); final String pubkey; final String? name; /// The person's chosen avatar value; null/empty → the coloured-initial disc. final String? picture; final double radius; @override Widget build(BuildContext context) { final pic = picture?.trim() ?? ''; if (pic.startsWith('data:')) { final bytes = decodeDataUri(pic); if (bytes != null) { return CircleAvatar(radius: radius, backgroundImage: MemoryImage(bytes)); } } else if (pic.startsWith(avatarGlyphPrefix)) { final glyph = avatarGlyphChar(pic.substring(avatarGlyphPrefix.length)); if (glyph != null) { return CircleAvatar( radius: radius, backgroundColor: peerAvatarColor(pubkey), child: SeedGlyph(glyph, size: radius * 1.15, color: Colors.white), ); } } final letter = _initial(name); return CircleAvatar( radius: radius, backgroundColor: peerAvatarColor(pubkey), child: letter == null ? Icon(Icons.person_outline, size: radius, color: Colors.white) : Text( letter, style: TextStyle( color: Colors.white, fontSize: radius, fontWeight: FontWeight.w600, ), ), ); } static String? _initial(String? name) { if (name == null) return null; final trimmed = name.trim(); if (trimmed.isEmpty) return null; // characters.first handles emoji/combining marks safely. return trimmed.characters.first.toUpperCase(); } } /// A deterministic, readable avatar colour derived from [pubkey]. Hashes the /// key to a hue, then fixes saturation/lightness so white text stays legible on /// top. Pure and stable — exposed for testing. Color peerAvatarColor(String pubkey) { // FNV-1a over the code units — cheap, stable, well-spread across hues. var hash = 0x811c9dc5; for (final unit in pubkey.codeUnits) { hash = (hash ^ unit) * 0x01000193; hash &= 0xffffffff; } final hue = (hash % 360).toDouble(); return HSLColor.fromAHSL(1, hue, 0.45, 0.42).toColor(); }