From ad7a2d9a01bbb6c8ccf50fd430d4ba3627128dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Wed, 25 Feb 2026 08:25:34 +0100 Subject: [PATCH] chore: Follow up user dialog --- lib/widgets/adaptive_dialogs/user_dialog.dart | 382 ++++++++++-------- 1 file changed, 216 insertions(+), 166 deletions(-) diff --git a/lib/widgets/adaptive_dialogs/user_dialog.dart b/lib/widgets/adaptive_dialogs/user_dialog.dart index b20ac2ce8..a5f65acb2 100644 --- a/lib/widgets/adaptive_dialogs/user_dialog.dart +++ b/lib/widgets/adaptive_dialogs/user_dialog.dart @@ -11,6 +11,7 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/fluffy_share.dart'; +import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/presence_builder.dart'; import '../../utils/url_launcher.dart'; @@ -47,186 +48,238 @@ class UserDialog extends StatelessWidget { final theme = Theme.of(context); final avatar = profile.avatarUrl; return AlertDialog.adaptive( - content: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 256), - child: PresenceBuilder( - userId: profile.userId, - client: Matrix.of(context).client, - builder: (context, presence) { - if (presence == null) return const SizedBox.shrink(); - final statusMsg = presence.statusMsg; - final lastActiveTimestamp = presence.lastActiveTimestamp; - final presenceText = presence.currentlyActive == true - ? L10n.of(context).currentlyActive - : lastActiveTimestamp != null - ? L10n.of(context).lastActiveAgo( - lastActiveTimestamp.localizedTimeShort(context), - ) - : null; - return Column( - spacing: 16, - mainAxisSize: .min, - crossAxisAlignment: .stretch, - children: [ - Row( - spacing: 12, - children: [ - Avatar( - mxContent: avatar, - name: displayname, - size: Avatar.defaultSize * 1.5, - onTap: avatar != null - ? () => showDialog( - context: context, - builder: (_) => MxcImageViewer(avatar), - ) - : 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, - ), + content: PresenceBuilder( + userId: profile.userId, + client: Matrix.of(context).client, + builder: (context, presence) { + if (presence == null) return const SizedBox.shrink(); + final statusMsg = presence.statusMsg; + final lastActiveTimestamp = presence.lastActiveTimestamp; + final presenceText = presence.currentlyActive == true + ? L10n.of(context).currentlyActive + : lastActiveTimestamp != null + ? L10n.of( + context, + ).lastActiveAgo(lastActiveTimestamp.localizedTimeShort(context)) + : null; + return Column( + spacing: 16, + mainAxisSize: .min, + crossAxisAlignment: .stretch, + children: [ + Row( + spacing: 12, + children: [ + Avatar( + mxContent: avatar, + name: displayname, + size: Avatar.defaultSize * 1.5, + onTap: avatar != null + ? () => showDialog( + context: context, + builder: (_) => MxcImageViewer(avatar), + ) + : 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), + ), + TextSpan(text: profile.userId), + ], + style: theme.textTheme.bodyMedium?.copyWith( + fontSize: 10, ), - maxLines: 1, - overflow: .ellipsis, - textAlign: TextAlign.center, ), + maxLines: 1, + overflow: .ellipsis, + textAlign: TextAlign.center, ), ), ), ), - if (presenceText != null) - Text( - presenceText, - style: theme.textTheme.bodyMedium?.copyWith( - fontSize: 10, - ), + ), + if (presenceText != null) + Text( + presenceText, + style: theme.textTheme.bodyMedium?.copyWith( + fontSize: 10, ), - ], - ), + ), + ], ), - ], - ), - - 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(), ), - 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'); - }, - ), - _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), - ), - ], + ], + ), + + 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(), ), - ], - ); - }, + Row( + mainAxisAlignment: .spaceBetween, + spacing: 4, + children: [ + _AdaptiveIconTextButton( + 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, + ); + }, + ), + _AdaptiveIconTextButton( + label: L10n.of(context).report, + icon: Icons.gavel_outlined, + onTap: () async { + Navigator.of(context).pop(); + final reason = await showTextInputDialog( + context: context, + title: L10n.of(context).whyDoYouWantToReportThis, + okLabel: L10n.of(context).report, + cancelLabel: L10n.of(context).cancel, + hintText: L10n.of(context).reason, + ); + if (reason == null || reason.isEmpty) return; + await showFutureLoadingDialog( + context: context, + future: () => Matrix.of( + context, + ).client.reportUser(profile.userId, reason), + ); + }, + ), + _AdaptiveIconTextButton( + label: L10n.of(context).share, + icon: Icons.adaptive.share, + onTap: () => FluffyShare.share(profile.userId, context), + ), + ], + ), + _AdaptiveDialogInkWell( + 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'); + }, + child: Text( + L10n.of(context).sendAMessage, + style: TextStyle(color: theme.colorScheme.secondary), + ), + ), + ], + ); + }, + ), + ); + } +} + +class _AdaptiveDialogInkWell extends StatelessWidget { + final Widget child; + final VoidCallback onTap; + const _AdaptiveDialogInkWell({required this.onTap, required this.child}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + if ({TargetPlatform.iOS, TargetPlatform.macOS}.contains(theme.platform)) { + return CupertinoButton( + onPressed: onTap, + borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), + color: theme.colorScheme.surfaceBright, + padding: EdgeInsets.all(8), + child: child, + ); + } + return Material( + color: theme.colorScheme.surfaceBright, + borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), + child: InkWell( + borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center(child: child), ), ), ); } } -class _IconTextButton extends StatelessWidget { +class _AdaptiveIconTextButton extends StatelessWidget { final String label; final IconData icon; final VoidCallback onTap; - const _IconTextButton({ + const _AdaptiveIconTextButton({ required this.label, required this.icon, required this.onTap, @@ -234,20 +287,17 @@ class _IconTextButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = Theme.of(context); + final color = Theme.of(context).colorScheme.secondary; return Expanded( - child: CupertinoButton( - onPressed: onTap, - borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), - color: theme.colorScheme.surfaceBright, - padding: EdgeInsets.all(8), + child: _AdaptiveDialogInkWell( + onTap: onTap, child: Column( mainAxisSize: .min, children: [ - Icon(icon), + Icon(icon, color: color), Text( label, - style: TextStyle(fontSize: 12), + style: TextStyle(fontSize: 12, color: color), maxLines: 1, overflow: .ellipsis, ),