feat(profile): show other people's avatars in chat, chat list, your people

Follow-up to the avatar feature: peers' published photos/illustrations
now actually render where people appear.

- CachedAvatar: a PeerAvatar that resolves the person's picture from the
  ProfileCache (FutureBuilder), falling back to the coloured-initial disc.
  For list/row sites — one avatar each.
- Chat bubbles: the peer's and my own avatar are resolved once into state
  (_peerPicture/_selfPicture — from the cache / ProfileStore, freshened
  from the peer's published profile) and threaded to each bubble, so many
  bubbles of two people don't each hit the cache.
- Chat list + your-people rows: swapped the static person-icon disc for
  CachedAvatar, so photos/illustrations show there too.
- Tests: CachedAvatar render modes (cached illustration / initial / no
  cache). analyze clean; chat + your-people suites green.

Market offers show no author avatar today (would be a new element, not a
swap) — left out of scope.
This commit is contained in:
vjrj 2026-07-11 22:50:10 +02:00
parent 461bc3cb36
commit 514d3c7364
5 changed files with 150 additions and 16 deletions

View file

@ -6,6 +6,7 @@ import 'package:go_router/go_router.dart';
import '../i18n/strings.g.dart';
import '../services/profile_cache.dart';
import 'peer_avatar.dart';
import '../services/social_connection.dart';
import '../services/social_service.dart';
import 'theme.dart';
@ -151,7 +152,9 @@ class _YourPeopleScreenState extends State<YourPeopleScreen> {
for (final peer in _youVouchFor)
_PersonTile(
key: Key('yourPeople.given.$peer'),
pubkey: peer,
name: _names[peer] ?? shortPubkey(peer),
cache: widget.profileCache,
onOpen: () => context.push('/chat/$peer'),
trailing: TextButton(
onPressed: _busy ? null : () => _revoke(peer),
@ -166,7 +169,9 @@ class _YourPeopleScreenState extends State<YourPeopleScreen> {
for (final peer in _vouchForYou)
_PersonTile(
key: Key('yourPeople.received.$peer'),
pubkey: peer,
name: _names[peer] ?? shortPubkey(peer),
cache: widget.profileCache,
onOpen: () => context.push('/chat/$peer'),
),
],
@ -217,20 +222,26 @@ class _PersonTile extends StatelessWidget {
const _PersonTile({
required this.name,
required this.onOpen,
required this.pubkey,
this.cache,
this.trailing,
super.key,
});
final String pubkey;
final String name;
final ProfileCache? cache;
final VoidCallback onOpen;
final Widget? trailing;
@override
Widget build(BuildContext context) {
return ListTile(
leading: const CircleAvatar(
backgroundColor: seedAvatar,
child: Icon(Icons.person, color: seedOnAvatar),
leading: CachedAvatar(
pubkey: pubkey,
name: name,
cache: cache,
radius: 20,
),
title: Text(name),
trailing: trailing,