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:
parent
461bc3cb36
commit
514d3c7364
5 changed files with 150 additions and 16 deletions
|
|
@ -60,6 +60,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||
bool _loading = true;
|
||||
String? _peerName;
|
||||
String? _selfName;
|
||||
String? _peerPicture;
|
||||
String? _selfPicture;
|
||||
String? _peerG1;
|
||||
final _input = TextEditingController();
|
||||
|
||||
|
|
@ -86,10 +88,13 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||
// connected / unreachable) → null transports and the screen degrades.
|
||||
final session = await widget.connection.session();
|
||||
final cachedName = await widget.profileCache?.name(widget.peerPubkey);
|
||||
// My own display name, so my avatar shows my initial (not a bare glyph).
|
||||
final selfName = getIt.isRegistered<ProfileStore>()
|
||||
? await getIt<ProfileStore>().name()
|
||||
: '';
|
||||
final cachedPicture = await widget.profileCache?.picture(widget.peerPubkey);
|
||||
// My own name + avatar, so my side shows me (not a bare glyph).
|
||||
final selfStore = getIt.isRegistered<ProfileStore>()
|
||||
? getIt<ProfileStore>()
|
||||
: null;
|
||||
final selfName = selfStore == null ? '' : await selfStore.name();
|
||||
final selfAvatar = selfStore == null ? '' : await selfStore.avatar();
|
||||
if (!mounted) return; // shared session is owned by the connection, not us
|
||||
final self = widget.social.publicKeyHex;
|
||||
final messages = MessagesCubit(
|
||||
|
|
@ -117,6 +122,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||
_rating = rating;
|
||||
_peerName = cachedName;
|
||||
_selfName = selfName.isEmpty ? null : selfName;
|
||||
_peerPicture = cachedPicture;
|
||||
_selfPicture = selfAvatar.isEmpty ? null : selfAvatar;
|
||||
_loading = false;
|
||||
});
|
||||
|
||||
|
|
@ -133,8 +140,15 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||
profile.name,
|
||||
);
|
||||
}
|
||||
if (profile.picture.isNotEmpty) {
|
||||
await widget.profileCache!.setPicture(
|
||||
widget.peerPubkey,
|
||||
profile.picture,
|
||||
);
|
||||
}
|
||||
setState(() {
|
||||
if (profile.name.isNotEmpty) _peerName = profile.name;
|
||||
if (profile.picture.isNotEmpty) _peerPicture = profile.picture;
|
||||
if (profile.g1.isNotEmpty) _peerG1 = profile.g1;
|
||||
});
|
||||
}
|
||||
|
|
@ -230,6 +244,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||
onSend: _send,
|
||||
peerName: _peerName,
|
||||
selfName: _selfName,
|
||||
peerPicture: _peerPicture,
|
||||
selfPicture: _selfPicture,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -242,12 +258,16 @@ class _ChatBody extends StatelessWidget {
|
|||
required this.onSend,
|
||||
this.peerName,
|
||||
this.selfName,
|
||||
this.peerPicture,
|
||||
this.selfPicture,
|
||||
});
|
||||
|
||||
final TextEditingController controller;
|
||||
final Future<void> Function() onSend;
|
||||
final String? peerName;
|
||||
final String? selfName;
|
||||
final String? peerPicture;
|
||||
final String? selfPicture;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -278,7 +298,12 @@ class _ChatBody extends StatelessWidget {
|
|||
const _TrustBanner(),
|
||||
const _RatingStrip(),
|
||||
Expanded(
|
||||
child: ChatMessageList(peerName: peerName, selfName: selfName),
|
||||
child: ChatMessageList(
|
||||
peerName: peerName,
|
||||
selfName: selfName,
|
||||
peerPicture: peerPicture,
|
||||
selfPicture: selfPicture,
|
||||
),
|
||||
),
|
||||
_Composer(controller: controller, onSend: onSend),
|
||||
],
|
||||
|
|
@ -292,7 +317,13 @@ class _ChatBody extends StatelessWidget {
|
|||
/// in place instead of landing below the fold. Reads the [MessagesCubit] from
|
||||
/// context.
|
||||
class ChatMessageList extends StatelessWidget {
|
||||
const ChatMessageList({this.peerName, this.selfName, super.key});
|
||||
const ChatMessageList({
|
||||
this.peerName,
|
||||
this.selfName,
|
||||
this.peerPicture,
|
||||
this.selfPicture,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The peer's display name, for their avatar's initial (null → a glyph).
|
||||
final String? peerName;
|
||||
|
|
@ -300,6 +331,10 @@ class ChatMessageList extends StatelessWidget {
|
|||
/// My own display name, for my avatar's initial (null → a glyph).
|
||||
final String? selfName;
|
||||
|
||||
/// The peer's / my published avatar (null → the coloured-initial disc).
|
||||
final String? peerPicture;
|
||||
final String? selfPicture;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = context.t;
|
||||
|
|
@ -329,6 +364,8 @@ class ChatMessageList extends StatelessWidget {
|
|||
mine: cubit.isMine(message),
|
||||
peerName: peerName,
|
||||
selfName: selfName,
|
||||
peerPicture: peerPicture,
|
||||
selfPicture: selfPicture,
|
||||
selfPubkey: cubit.selfPubkey,
|
||||
),
|
||||
};
|
||||
|
|
@ -494,6 +531,8 @@ class _MessageRow extends StatelessWidget {
|
|||
required this.mine,
|
||||
this.peerName,
|
||||
this.selfName,
|
||||
this.peerPicture,
|
||||
this.selfPicture,
|
||||
this.selfPubkey,
|
||||
});
|
||||
|
||||
|
|
@ -501,17 +540,27 @@ class _MessageRow extends StatelessWidget {
|
|||
final bool mine;
|
||||
final String? peerName;
|
||||
final String? selfName;
|
||||
final String? peerPicture;
|
||||
final String? selfPicture;
|
||||
|
||||
/// My own pubkey, so my messages carry my (distinctly coloured) avatar.
|
||||
final String? selfPubkey;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Each avatar shows its owner's initial (falling back to a glyph when no
|
||||
// name is known) on a colour derived from their pubkey.
|
||||
// Each avatar shows its owner's photo/illustration when set, else their
|
||||
// initial (or a glyph) on a colour derived from their pubkey.
|
||||
final avatar = mine
|
||||
? PeerAvatar(pubkey: selfPubkey ?? message.fromPubkey, name: selfName)
|
||||
: PeerAvatar(pubkey: message.fromPubkey, name: peerName);
|
||||
? PeerAvatar(
|
||||
pubkey: selfPubkey ?? message.fromPubkey,
|
||||
name: selfName,
|
||||
picture: selfPicture,
|
||||
)
|
||||
: PeerAvatar(
|
||||
pubkey: message.fromPubkey,
|
||||
name: peerName,
|
||||
picture: peerPicture,
|
||||
);
|
||||
final bubble = Flexible(
|
||||
child: _Bubble(message: message, mine: mine),
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue