From 0052a15b5416310a7f650426b00de22fa9c700ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Tue, 24 Feb 2026 21:28:33 +0100 Subject: [PATCH] chore: Adjust design of user viewer and popup buttons --- lib/pages/chat_list/chat_list.dart | 15 +- lib/widgets/adaptive_dialogs/user_dialog.dart | 294 ++++++++++-------- .../member_actions_popup_menu_button.dart | 34 +- 3 files changed, 181 insertions(+), 162 deletions(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index e55b8251d..d8751f9cb 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -440,25 +440,16 @@ class ChatListController extends State PopupMenuItem( value: ChatContextAction.open, child: Row( - mainAxisSize: .min, spacing: 12.0, children: [ - Avatar(mxContent: room.avatar, name: displayname), + Avatar(mxContent: room.avatar, name: displayname, size: 24), ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 128), - child: Text( - displayname, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), + constraints: const BoxConstraints(maxWidth: 200), + child: Text(displayname, maxLines: 1, overflow: .ellipsis), ), ], ), ), - const PopupMenuDivider(), if (space != null) PopupMenuItem( value: ChatContextAction.goToSpace, diff --git a/lib/widgets/adaptive_dialogs/user_dialog.dart b/lib/widgets/adaptive_dialogs/user_dialog.dart index 503615197..b20ac2ce8 100644 --- a/lib/widgets/adaptive_dialogs/user_dialog.dart +++ b/lib/widgets/adaptive_dialogs/user_dialog.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -5,10 +6,11 @@ import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; +import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; -import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart'; +import 'package:fluffychat/utils/fluffy_share.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/presence_builder.dart'; import '../../utils/url_launcher.dart'; @@ -37,7 +39,6 @@ class UserDialog extends StatelessWidget { @override Widget build(BuildContext context) { final client = Matrix.of(context).client; - final dmRoomId = client.getDirectChatFromUserId(profile.userId); final displayname = profile.displayName ?? profile.userId.localpart ?? @@ -46,12 +47,8 @@ class UserDialog extends StatelessWidget { final theme = Theme.of(context); final avatar = profile.avatarUrl; return AlertDialog.adaptive( - title: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 256), - child: Center(child: Text(displayname, textAlign: TextAlign.center)), - ), content: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 256, maxHeight: 256), + constraints: const BoxConstraints(maxWidth: 256), child: PresenceBuilder( userId: profile.userId, client: Matrix.of(context).client, @@ -66,17 +63,18 @@ class UserDialog extends StatelessWidget { lastActiveTimestamp.localizedTimeShort(context), ) : null; - return SingleChildScrollView( - child: Column( - spacing: 8, - mainAxisSize: .min, - crossAxisAlignment: .stretch, - children: [ - Center( - child: Avatar( + return Column( + spacing: 16, + mainAxisSize: .min, + crossAxisAlignment: .stretch, + children: [ + Row( + spacing: 12, + children: [ + Avatar( mxContent: avatar, name: displayname, - size: Avatar.defaultSize * 2, + size: Avatar.defaultSize * 1.5, onTap: avatar != null ? () => showDialog( context: context, @@ -84,130 +82,178 @@ class UserDialog extends StatelessWidget { ) : null, ), - ), - HoverBuilder( - builder: (context, hovered) => StatefulBuilder( - builder: (context, setState) => MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: () { - Clipboard.setData( - ClipboardData(text: profile.userId), - ); - setState(() { - copied = true; - }); - }, - child: RichText( - text: TextSpan( - children: [ - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(right: 4.0), - child: AnimatedScale( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - scale: hovered - ? 1.33 - : copied - ? 1.25 - : 1.0, - child: Icon( - copied - ? Icons.check_circle - : Icons.copy, - size: 12, - color: copied ? Colors.green : null, - ), + Expanded( + child: Column( + crossAxisAlignment: .start, + children: [ + Text( + displayname, + maxLines: 1, + overflow: .ellipsis, + style: TextStyle(fontSize: 16), + ), + const SizedBox(height: 8), + HoverBuilder( + builder: (context, hovered) => StatefulBuilder( + builder: (context, setState) => MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + Clipboard.setData( + ClipboardData(text: profile.userId), + ); + setState(() { + copied = true; + }); + }, + child: RichText( + text: TextSpan( + children: [ + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only( + right: 4.0, + ), + child: AnimatedScale( + duration: FluffyThemes + .animationDuration, + curve: + FluffyThemes.animationCurve, + scale: hovered + ? 1.33 + : copied + ? 1.25 + : 1.0, + child: Icon( + copied + ? Icons.check_circle + : Icons.copy, + size: 12, + color: copied + ? Colors.green + : null, + ), + ), + ), + ), + TextSpan(text: profile.userId), + ], + style: theme.textTheme.bodyMedium + ?.copyWith(fontSize: 10), ), + maxLines: 1, + overflow: .ellipsis, + textAlign: TextAlign.center, ), ), - TextSpan(text: profile.userId), - ], + ), + ), + ), + if (presenceText != null) + Text( + presenceText, style: theme.textTheme.bodyMedium?.copyWith( fontSize: 10, ), ), - textAlign: TextAlign.center, - ), - ), + ], ), ), + ], + ), + + if (statusMsg != null) + SelectableLinkify( + text: statusMsg, + textScaleFactor: MediaQuery.textScalerOf(context).scale(1), + textAlign: TextAlign.start, + options: const LinkifyOptions(humanize: false), + linkStyle: TextStyle( + color: theme.colorScheme.primary, + decoration: TextDecoration.underline, + decorationColor: theme.colorScheme.primary, + ), + onOpen: (url) => UrlLauncher(context, url.url).launchUrl(), ), - if (presenceText != null) - Text( - presenceText, - style: const TextStyle(fontSize: 10), - textAlign: TextAlign.center, + Row( + mainAxisAlignment: .spaceBetween, + spacing: 4, + children: [ + _IconTextButton( + label: L10n.of(context).chat, + icon: Icons.chat_outlined, + onTap: () async { + final router = GoRouter.of(context); + final roomIdResult = await showFutureLoadingDialog( + context: context, + future: () => client.startDirectChat(profile.userId), + ); + final roomId = roomIdResult.result; + if (roomId == null) return; + if (context.mounted) Navigator.of(context).pop(); + router.go('/rooms/$roomId'); + }, ), - if (statusMsg != null) - SelectableLinkify( - text: statusMsg, - textScaleFactor: MediaQuery.textScalerOf( - context, - ).scale(1), - textAlign: TextAlign.center, - options: const LinkifyOptions(humanize: false), - linkStyle: TextStyle( - color: theme.colorScheme.primary, - decoration: TextDecoration.underline, - decorationColor: theme.colorScheme.primary, - ), - onOpen: (url) => - UrlLauncher(context, url.url).launchUrl(), + _IconTextButton( + label: L10n.of(context).block, + icon: Icons.block_outlined, + onTap: () { + final router = GoRouter.of(context); + Navigator.of(context).pop(); + router.go( + '/rooms/settings/security/ignorelist', + extra: profile.userId, + ); + }, ), - ], - ), + _IconTextButton( + label: L10n.of(context).share, + icon: Icons.adaptive.share, + onTap: () => FluffyShare.share(profile.userId, context), + ), + ], + ), + ], ); }, ), ), - actions: [ - if (client.userID != profile.userId) ...[ - AdaptiveDialogAction( - borderRadius: AdaptiveDialogAction.topRadius, - bigButtons: true, - onPressed: () async { - final router = GoRouter.of(context); - final roomIdResult = await showFutureLoadingDialog( - context: context, - future: () => client.startDirectChat(profile.userId), - ); - final roomId = roomIdResult.result; - if (roomId == null) return; - if (context.mounted) Navigator.of(context).pop(); - router.go('/rooms/$roomId'); - }, - child: Text( - dmRoomId == null - ? L10n.of(context).startConversation - : L10n.of(context).sendAMessage, - ), - ), - AdaptiveDialogAction( - bigButtons: true, - borderRadius: AdaptiveDialogAction.centerRadius, - onPressed: () { - final router = GoRouter.of(context); - Navigator.of(context).pop(); - router.go( - '/rooms/settings/security/ignorelist', - extra: profile.userId, - ); - }, - child: Text( - L10n.of(context).ignoreUser, - style: TextStyle(color: theme.colorScheme.error), - ), - ), - ], - AdaptiveDialogAction( - bigButtons: true, - borderRadius: AdaptiveDialogAction.bottomRadius, - onPressed: Navigator.of(context).pop, - child: Text(L10n.of(context).close), - ), - ], + ); + } +} + +class _IconTextButton extends StatelessWidget { + final String label; + final IconData icon; + final VoidCallback onTap; + const _IconTextButton({ + required this.label, + required this.icon, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Expanded( + child: CupertinoButton( + onPressed: onTap, + borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), + color: theme.colorScheme.surfaceBright, + padding: EdgeInsets.all(8), + child: Column( + mainAxisSize: .min, + children: [ + Icon(icon), + Text( + label, + style: TextStyle(fontSize: 12), + maxLines: 1, + overflow: .ellipsis, + ), + ], + ), + ), ); } } diff --git a/lib/widgets/member_actions_popup_menu_button.dart b/lib/widgets/member_actions_popup_menu_button.dart index e985be01c..55eea2cb3 100644 --- a/lib/widgets/member_actions_popup_menu_button.dart +++ b/lib/widgets/member_actions_popup_menu_button.dart @@ -45,40 +45,22 @@ Future showMemberActionsPopupMenu({ children: [ Avatar( name: displayname, + size: 30, mxContent: user.avatarUrl, presenceUserId: user.id, presenceBackgroundColor: theme.colorScheme.surfaceContainer, ), - Column( - mainAxisSize: .min, - crossAxisAlignment: .start, - children: [ - ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 128), - child: Text( - displayname, - textAlign: TextAlign.center, - style: theme.textTheme.labelLarge, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 128), - child: Text( - user.id, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 10), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - ], + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 200), + child: Text( + displayname, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), ), ], ), ), - const PopupMenuDivider(), if (onMention != null) PopupMenuItem( value: _MemberActions.mention,