feat(chat): sender avatar on both sides, coloured per person
Feedback: avatars only on the left, all the same colour, so it was hard to tell who said what. In a 1:1 only the peer had an avatar (one colour). Now each message carries its sender's avatar on their own side — mine trailing, the peer's leading — each tinted by a deterministic colour from their pubkey. Two sides, two colours: who-said-what is obvious at a glance.
This commit is contained in:
parent
4af90876f4
commit
dc40640682
2 changed files with 36 additions and 19 deletions
|
|
@ -302,6 +302,7 @@ class ChatMessageList extends StatelessWidget {
|
||||||
message: message,
|
message: message,
|
||||||
mine: cubit.isMine(message),
|
mine: cubit.isMine(message),
|
||||||
peerName: peerName,
|
peerName: peerName,
|
||||||
|
selfPubkey: cubit.selfPubkey,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -408,32 +409,44 @@ class _DaySeparator extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One message line: my messages align to the trailing edge with no avatar;
|
/// One message line. Each sender gets their own coloured avatar on their side —
|
||||||
/// the peer's align to the leading edge with their avatar (1:1 convention).
|
/// mine on the trailing edge, the peer's on the leading edge — so at a glance
|
||||||
|
/// the two sides are two people, not one column of look-alike bubbles.
|
||||||
class _MessageRow extends StatelessWidget {
|
class _MessageRow extends StatelessWidget {
|
||||||
const _MessageRow({required this.message, required this.mine, this.peerName});
|
const _MessageRow({
|
||||||
|
required this.message,
|
||||||
|
required this.mine,
|
||||||
|
this.peerName,
|
||||||
|
this.selfPubkey,
|
||||||
|
});
|
||||||
|
|
||||||
final PrivateMessage message;
|
final PrivateMessage message;
|
||||||
final bool mine;
|
final bool mine;
|
||||||
final String? peerName;
|
final String? peerName;
|
||||||
|
|
||||||
|
/// My own pubkey, so my messages carry my (distinctly coloured) avatar.
|
||||||
|
final String? selfPubkey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (mine) {
|
// My avatar has no name (it's me) → a glyph on my colour; the peer's shows
|
||||||
return Align(
|
// their initial on theirs. Different pubkeys → different colours.
|
||||||
alignment: AlignmentDirectional.centerEnd,
|
final avatar = mine
|
||||||
child: _Bubble(message: message, mine: true),
|
? PeerAvatar(pubkey: selfPubkey ?? message.fromPubkey)
|
||||||
);
|
: PeerAvatar(pubkey: message.fromPubkey, name: peerName);
|
||||||
}
|
final bubble = Flexible(
|
||||||
|
child: _Bubble(message: message, mine: mine),
|
||||||
|
);
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
mainAxisAlignment: mine
|
||||||
PeerAvatar(pubkey: message.fromPubkey, name: peerName),
|
? MainAxisAlignment.end
|
||||||
const SizedBox(width: 8),
|
: MainAxisAlignment.start,
|
||||||
Flexible(child: _Bubble(message: message, mine: false)),
|
children: mine
|
||||||
],
|
? [bubble, const SizedBox(width: 8), avatar]
|
||||||
|
: [avatar, const SizedBox(width: 8), bubble],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -113,9 +113,7 @@ void main() {
|
||||||
expect(find.byKey(const Key('chat.daySeparator')), findsNWidgets(2));
|
expect(find.byKey(const Key('chat.daySeparator')), findsNWidgets(2));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('the peer bubble carries an avatar, mine does not', (
|
testWidgets('each sender gets a distinctly coloured avatar', (tester) async {
|
||||||
tester,
|
|
||||||
) async {
|
|
||||||
final transport = _FakeMessageTransport();
|
final transport = _FakeMessageTransport();
|
||||||
final cubit = MessagesCubit(transport, peerPubkey: peer, selfPubkey: me)
|
final cubit = MessagesCubit(transport, peerPubkey: peer, selfPubkey: me)
|
||||||
..start();
|
..start();
|
||||||
|
|
@ -126,8 +124,14 @@ void main() {
|
||||||
await tester.pumpWidget(host(cubit));
|
await tester.pumpWidget(host(cubit));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
// One avatar, for the single peer message; my own bubble has none.
|
// Both sides carry an avatar, and the two people are different colours —
|
||||||
expect(find.byType(PeerAvatar), findsOneWidget);
|
// so you can tell who said what at a glance.
|
||||||
|
expect(find.byType(PeerAvatar), findsNWidgets(2));
|
||||||
|
final discs = tester
|
||||||
|
.widgetList<CircleAvatar>(find.byType(CircleAvatar))
|
||||||
|
.toList();
|
||||||
|
expect(discs, hasLength(2));
|
||||||
|
expect(discs.first.backgroundColor, isNot(discs.last.backgroundColor));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('a new message lands in view at the bottom, not below the fold', (
|
testWidgets('a new message lands in view at the bottom, not below the fold', (
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue