feat(chat): lighter bubbles, my own name on my avatar
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.
This commit is contained in:
parent
73e96a3ce5
commit
d481e62ff7
1 changed files with 30 additions and 14 deletions
|
|
@ -11,6 +11,7 @@ import '../domain/chat_timeline.dart';
|
||||||
import '../i18n/strings.g.dart';
|
import '../i18n/strings.g.dart';
|
||||||
import '../services/message_store.dart';
|
import '../services/message_store.dart';
|
||||||
import '../services/profile_cache.dart';
|
import '../services/profile_cache.dart';
|
||||||
|
import '../services/profile_store.dart';
|
||||||
import '../services/social_connection.dart';
|
import '../services/social_connection.dart';
|
||||||
import '../services/social_service.dart';
|
import '../services/social_service.dart';
|
||||||
import '../services/trust_referents.dart';
|
import '../services/trust_referents.dart';
|
||||||
|
|
@ -62,6 +63,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||||
TrustCubit? _trust;
|
TrustCubit? _trust;
|
||||||
bool _loading = true;
|
bool _loading = true;
|
||||||
String? _peerName;
|
String? _peerName;
|
||||||
|
String? _selfName;
|
||||||
String? _peerG1;
|
String? _peerG1;
|
||||||
final _input = TextEditingController();
|
final _input = TextEditingController();
|
||||||
|
|
||||||
|
|
@ -88,6 +90,10 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||||
// connected / unreachable) → null transports and the screen degrades.
|
// connected / unreachable) → null transports and the screen degrades.
|
||||||
final session = await widget.connection.session();
|
final session = await widget.connection.session();
|
||||||
final cachedName = await widget.profileCache?.name(widget.peerPubkey);
|
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 referents = await widget.trustReferents?.all() ?? const <String>{};
|
final referents = await widget.trustReferents?.all() ?? const <String>{};
|
||||||
final wotParams = await widget.wotSettings?.params() ?? WotParams.duniter;
|
final wotParams = await widget.wotSettings?.params() ?? WotParams.duniter;
|
||||||
if (!mounted) return; // shared session is owned by the connection, not us
|
if (!mounted) return; // shared session is owned by the connection, not us
|
||||||
|
|
@ -110,6 +116,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||||
_messages = messages;
|
_messages = messages;
|
||||||
_trust = trust;
|
_trust = trust;
|
||||||
_peerName = cachedName;
|
_peerName = cachedName;
|
||||||
|
_selfName = selfName.isEmpty ? null : selfName;
|
||||||
_loading = false;
|
_loading = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -212,6 +219,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||||
controller: _input,
|
controller: _input,
|
||||||
onSend: _send,
|
onSend: _send,
|
||||||
peerName: _peerName,
|
peerName: _peerName,
|
||||||
|
selfName: _selfName,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -223,11 +231,13 @@ class _ChatBody extends StatelessWidget {
|
||||||
required this.controller,
|
required this.controller,
|
||||||
required this.onSend,
|
required this.onSend,
|
||||||
this.peerName,
|
this.peerName,
|
||||||
|
this.selfName,
|
||||||
});
|
});
|
||||||
|
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
final Future<void> Function() onSend;
|
final Future<void> Function() onSend;
|
||||||
final String? peerName;
|
final String? peerName;
|
||||||
|
final String? selfName;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
@ -256,7 +266,9 @@ class _ChatBody extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const _TrustBanner(),
|
const _TrustBanner(),
|
||||||
Expanded(child: ChatMessageList(peerName: peerName)),
|
Expanded(
|
||||||
|
child: ChatMessageList(peerName: peerName, selfName: selfName),
|
||||||
|
),
|
||||||
_Composer(controller: controller, onSend: onSend),
|
_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
|
/// in place instead of landing below the fold. Reads the [MessagesCubit] from
|
||||||
/// context.
|
/// context.
|
||||||
class ChatMessageList extends StatelessWidget {
|
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).
|
/// The peer's display name, for their avatar's initial (null → a glyph).
|
||||||
final String? peerName;
|
final String? peerName;
|
||||||
|
|
||||||
|
/// My own display name, for my avatar's initial (null → a glyph).
|
||||||
|
final String? selfName;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final t = context.t;
|
final t = context.t;
|
||||||
|
|
@ -302,6 +317,7 @@ class ChatMessageList extends StatelessWidget {
|
||||||
message: message,
|
message: message,
|
||||||
mine: cubit.isMine(message),
|
mine: cubit.isMine(message),
|
||||||
peerName: peerName,
|
peerName: peerName,
|
||||||
|
selfName: selfName,
|
||||||
selfPubkey: cubit.selfPubkey,
|
selfPubkey: cubit.selfPubkey,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
@ -417,22 +433,24 @@ class _MessageRow extends StatelessWidget {
|
||||||
required this.message,
|
required this.message,
|
||||||
required this.mine,
|
required this.mine,
|
||||||
this.peerName,
|
this.peerName,
|
||||||
|
this.selfName,
|
||||||
this.selfPubkey,
|
this.selfPubkey,
|
||||||
});
|
});
|
||||||
|
|
||||||
final PrivateMessage message;
|
final PrivateMessage message;
|
||||||
final bool mine;
|
final bool mine;
|
||||||
final String? peerName;
|
final String? peerName;
|
||||||
|
final String? selfName;
|
||||||
|
|
||||||
/// My own pubkey, so my messages carry my (distinctly coloured) avatar.
|
/// My own pubkey, so my messages carry my (distinctly coloured) avatar.
|
||||||
final String? selfPubkey;
|
final String? selfPubkey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// My avatar has no name (it's me) → a glyph on my colour; the peer's shows
|
// Each avatar shows its owner's initial (falling back to a glyph when no
|
||||||
// their initial on theirs. Different pubkeys → different colours.
|
// name is known) on a colour derived from their pubkey.
|
||||||
final avatar = mine
|
final avatar = mine
|
||||||
? PeerAvatar(pubkey: selfPubkey ?? message.fromPubkey)
|
? PeerAvatar(pubkey: selfPubkey ?? message.fromPubkey, name: selfName)
|
||||||
: PeerAvatar(pubkey: message.fromPubkey, name: peerName);
|
: PeerAvatar(pubkey: message.fromPubkey, name: peerName);
|
||||||
final bubble = Flexible(
|
final bubble = Flexible(
|
||||||
child: _Bubble(message: message, mine: mine),
|
child: _Bubble(message: message, mine: mine),
|
||||||
|
|
@ -468,9 +486,13 @@ class _Bubble extends StatelessWidget {
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: MediaQuery.of(context).size.width * 0.75,
|
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(
|
decoration: BoxDecoration(
|
||||||
color: mine ? seedGreen : seedPrimaryContainer,
|
color: mine ? seedPrimaryContainer : Colors.white,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: mine ? null : Border.all(color: seedOutline),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|
@ -479,10 +501,7 @@ class _Bubble extends StatelessWidget {
|
||||||
// Long text can be selected/copied — useful for addresses, links.
|
// Long text can be selected/copied — useful for addresses, links.
|
||||||
SelectableText(
|
SelectableText(
|
||||||
message.text,
|
message.text,
|
||||||
style: TextStyle(
|
style: const TextStyle(color: seedOnSurface, fontSize: 15),
|
||||||
color: mine ? Colors.white : seedOnSurface,
|
|
||||||
fontSize: 15,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
// A subtle timestamp, trailing edge (RTL-aware).
|
// A subtle timestamp, trailing edge (RTL-aware).
|
||||||
|
|
@ -490,10 +509,7 @@ class _Bubble extends StatelessWidget {
|
||||||
alignment: AlignmentDirectional.centerEnd,
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
child: Text(
|
child: Text(
|
||||||
time,
|
time,
|
||||||
style: TextStyle(
|
style: const TextStyle(color: seedMuted, fontSize: 11),
|
||||||
color: mine ? Colors.white70 : seedMuted,
|
|
||||||
fontSize: 11,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue