From d481e62ff73a5897a94c6f5e71516486dbd84b18 Mon Sep 17 00:00:00 2001 From: vjrj Date: Sat, 11 Jul 2026 07:08:54 +0200 Subject: [PATCH] feat(chat): lighter bubbles, my own name on my avatar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Feedback: my bubbles were too dark, and avatars showed only a person glyph, never the profile name. Now both bubbles are light with dark text (mine a tonal green, the peer's white with a hairline) — the side and the per-person avatar colour carry the "who", so nothing is heavy to read. Load my own display name from ProfileStore so my avatar shows my initial too, not just the peer's; each avatar still falls back to a glyph when no name is known. --- apps/app_seeds/lib/ui/chat_screen.dart | 44 ++++++++++++++++++-------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/apps/app_seeds/lib/ui/chat_screen.dart b/apps/app_seeds/lib/ui/chat_screen.dart index eb00ab9..735d39c 100644 --- a/apps/app_seeds/lib/ui/chat_screen.dart +++ b/apps/app_seeds/lib/ui/chat_screen.dart @@ -11,6 +11,7 @@ import '../domain/chat_timeline.dart'; import '../i18n/strings.g.dart'; import '../services/message_store.dart'; import '../services/profile_cache.dart'; +import '../services/profile_store.dart'; import '../services/social_connection.dart'; import '../services/social_service.dart'; import '../services/trust_referents.dart'; @@ -62,6 +63,7 @@ class _ChatScreenState extends State { TrustCubit? _trust; bool _loading = true; String? _peerName; + String? _selfName; String? _peerG1; final _input = TextEditingController(); @@ -88,6 +90,10 @@ class _ChatScreenState extends State { // 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() + ? await getIt().name() + : ''; final referents = await widget.trustReferents?.all() ?? const {}; final wotParams = await widget.wotSettings?.params() ?? WotParams.duniter; if (!mounted) return; // shared session is owned by the connection, not us @@ -110,6 +116,7 @@ class _ChatScreenState extends State { _messages = messages; _trust = trust; _peerName = cachedName; + _selfName = selfName.isEmpty ? null : selfName; _loading = false; }); @@ -212,6 +219,7 @@ class _ChatScreenState extends State { controller: _input, onSend: _send, peerName: _peerName, + selfName: _selfName, ), ), ); @@ -223,11 +231,13 @@ class _ChatBody extends StatelessWidget { required this.controller, required this.onSend, this.peerName, + this.selfName, }); final TextEditingController controller; final Future Function() onSend; final String? peerName; + final String? selfName; @override Widget build(BuildContext context) { @@ -256,7 +266,9 @@ class _ChatBody extends StatelessWidget { child: Column( children: [ const _TrustBanner(), - Expanded(child: ChatMessageList(peerName: peerName)), + Expanded( + child: ChatMessageList(peerName: peerName, selfName: selfName), + ), _Composer(controller: controller, onSend: onSend), ], ), @@ -269,11 +281,14 @@ 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, super.key}); + const ChatMessageList({this.peerName, this.selfName, super.key}); /// The peer's display name, for their avatar's initial (null → a glyph). final String? peerName; + /// My own display name, for my avatar's initial (null → a glyph). + final String? selfName; + @override Widget build(BuildContext context) { final t = context.t; @@ -302,6 +317,7 @@ class ChatMessageList extends StatelessWidget { message: message, mine: cubit.isMine(message), peerName: peerName, + selfName: selfName, selfPubkey: cubit.selfPubkey, ), }; @@ -417,22 +433,24 @@ class _MessageRow extends StatelessWidget { required this.message, required this.mine, this.peerName, + this.selfName, this.selfPubkey, }); final PrivateMessage message; final bool mine; final String? peerName; + final String? selfName; /// My own pubkey, so my messages carry my (distinctly coloured) avatar. final String? selfPubkey; @override Widget build(BuildContext context) { - // My avatar has no name (it's me) → a glyph on my colour; the peer's shows - // their initial on theirs. Different pubkeys → different colours. + // Each avatar shows its owner's initial (falling back to a glyph when no + // name is known) on a colour derived from their pubkey. final avatar = mine - ? PeerAvatar(pubkey: selfPubkey ?? message.fromPubkey) + ? PeerAvatar(pubkey: selfPubkey ?? message.fromPubkey, name: selfName) : PeerAvatar(pubkey: message.fromPubkey, name: peerName); final bubble = Flexible( child: _Bubble(message: message, mine: mine), @@ -468,9 +486,13 @@ class _Bubble extends StatelessWidget { constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width * 0.75, ), + // Soft, light bubbles both ways (dark text): mine a tonal green, the + // peer's white with a hairline. Sides + avatar colours carry the + // "who", so the bubbles themselves stay light and easy on the eye. decoration: BoxDecoration( - color: mine ? seedGreen : seedPrimaryContainer, + color: mine ? seedPrimaryContainer : Colors.white, borderRadius: BorderRadius.circular(16), + border: mine ? null : Border.all(color: seedOutline), ), child: Column( mainAxisSize: MainAxisSize.min, @@ -479,10 +501,7 @@ class _Bubble extends StatelessWidget { // Long text can be selected/copied — useful for addresses, links. SelectableText( message.text, - style: TextStyle( - color: mine ? Colors.white : seedOnSurface, - fontSize: 15, - ), + style: const TextStyle(color: seedOnSurface, fontSize: 15), ), const SizedBox(height: 2), // A subtle timestamp, trailing edge (RTL-aware). @@ -490,10 +509,7 @@ class _Bubble extends StatelessWidget { alignment: AlignmentDirectional.centerEnd, child: Text( time, - style: TextStyle( - color: mine ? Colors.white70 : seedMuted, - fontSize: 11, - ), + style: const TextStyle(color: seedMuted, fontSize: 11), ), ), ],