From 524a63e1d619adb08a34d3a86f0378a2e5411f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ku=C3=9Fowski?= Date: Thu, 25 Sep 2025 14:47:42 +0200 Subject: [PATCH] chore: Move emote settings button to chat popup menu --- lib/pages/chat_details/chat_details.dart | 15 --- lib/pages/chat_details/chat_details_view.dart | 19 +--- lib/widgets/avatar_page_header.dart | 100 ++++++++++++++++++ lib/widgets/chat_settings_popup_menu.dart | 28 ++++- 4 files changed, 130 insertions(+), 32 deletions(-) create mode 100644 lib/widgets/avatar_page_header.dart diff --git a/lib/pages/chat_details/chat_details.dart b/lib/pages/chat_details/chat_details.dart index 6049f078a..0952b07f3 100644 --- a/lib/pages/chat_details/chat_details.dart +++ b/lib/pages/chat_details/chat_details.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; -import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; import 'package:matrix/matrix.dart'; @@ -91,20 +90,6 @@ class ChatDetailsController extends State { } } - void goToEmoteSettings() async { - final room = Matrix.of(context).client.getRoomById(roomId!)!; - // okay, we need to test if there are any emote state events other than the default one - // if so, we need to be directed to a selection screen for which pack we want to look at - // otherwise, we just open the normal one. - if ((room.states['im.ponies.room_emotes'] ?? {}) - .keys - .any((String s) => s.isNotEmpty)) { - context.push('/rooms/${room.id}/details/multiple_emotes'); - } else { - context.push('/rooms/${room.id}/details/emotes'); - } - } - void setAvatarAction() async { final room = Matrix.of(context).client.getRoomById(roomId!); final actions = [ diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index 7a8aa67d1..1bcb4d564 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -258,21 +258,8 @@ class ChatDetailsView extends StatelessWidget { ), const SizedBox(height: 16), ], - Divider(color: theme.dividerColor), - ListTile( - leading: CircleAvatar( - backgroundColor: theme.scaffoldBackgroundColor, - foregroundColor: iconColor, - child: const Icon( - Icons.insert_emoticon_outlined, - ), - ), - title: Text(L10n.of(context).customEmojisAndStickers), - subtitle: Text(L10n.of(context).setCustomEmotes), - onTap: controller.goToEmoteSettings, - trailing: const Icon(Icons.chevron_right_outlined), - ), - if (!room.isDirectChat) + if (!room.isDirectChat) ...[ + Divider(color: theme.dividerColor), ListTile( leading: CircleAvatar( backgroundColor: theme.scaffoldBackgroundColor, @@ -289,7 +276,6 @@ class ChatDetailsView extends StatelessWidget { .push('/rooms/${room.id}/details/access'), trailing: const Icon(Icons.chevron_right_outlined), ), - if (!room.isDirectChat) ListTile( title: Text(L10n.of(context).chatPermissions), subtitle: Text( @@ -306,6 +292,7 @@ class ChatDetailsView extends StatelessWidget { onTap: () => context .push('/rooms/${room.id}/details/permissions'), ), + ], Divider(color: theme.dividerColor), ListTile( title: Text( diff --git a/lib/widgets/avatar_page_header.dart b/lib/widgets/avatar_page_header.dart new file mode 100644 index 000000000..e00eca90e --- /dev/null +++ b/lib/widgets/avatar_page_header.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; + +import 'package:fluffychat/config/themes.dart'; + +class AvatarPageHeader extends StatelessWidget { + final Widget avatar; + final void Function()? onAvatarEdit; + final Widget? textButtonLeft, textButtonRight; + final List iconButtons; + + const AvatarPageHeader({ + super.key, + required this.avatar, + this.onAvatarEdit, + this.iconButtons = const [], + this.textButtonLeft, + this.textButtonRight, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final onAvatarEdit = this.onAvatarEdit; + return Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: FluffyThemes.columnWidth), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + spacing: 8.0, + children: [ + Stack( + children: [ + avatar, + if (onAvatarEdit != null) + Positioned( + bottom: 0, + right: 0, + child: FloatingActionButton.small( + elevation: 2, + onPressed: onAvatarEdit, + heroTag: null, + child: const Icon(Icons.camera_alt_outlined), + ), + ), + ], + ), + TextButtonTheme( + data: TextButtonThemeData( + style: TextButton.styleFrom( + disabledForegroundColor: theme.colorScheme.onSurface, + foregroundColor: theme.colorScheme.onSurface, + textStyle: const TextStyle(fontWeight: FontWeight.normal), + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: LayoutBuilder( + builder: (context, constraints) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: constraints.maxWidth / 2, + ), + child: textButtonLeft, + ), + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: constraints.maxWidth / 2, + ), + child: textButtonRight, + ), + ], + ); + }, + ), + ), + ), + IconButtonTheme( + data: IconButtonThemeData( + style: IconButton.styleFrom( + backgroundColor: theme.colorScheme.surfaceContainer, + iconSize: 24, + padding: const EdgeInsets.all(16), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: iconButtons, + ), + ), + const SizedBox(height: 0.0), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/chat_settings_popup_menu.dart b/lib/widgets/chat_settings_popup_menu.dart index 069f21da4..6969fd901 100644 --- a/lib/widgets/chat_settings_popup_menu.dart +++ b/lib/widgets/chat_settings_popup_menu.dart @@ -10,7 +10,7 @@ import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog. import 'package:fluffychat/widgets/future_loading_dialog.dart'; import 'matrix.dart'; -enum ChatPopupMenuActions { details, mute, unmute, leave, search } +enum ChatPopupMenuActions { details, mute, unmute, emote, leave, search } class ChatSettingsPopupMenu extends StatefulWidget { final Room room; @@ -31,6 +31,20 @@ class ChatSettingsPopupMenuState extends State { super.dispose(); } + void goToEmoteSettings() async { + final room = widget.room; + // okay, we need to test if there are any emote state events other than the default one + // if so, we need to be directed to a selection screen for which pack we want to look at + // otherwise, we just open the normal one. + if ((room.states['im.ponies.room_emotes'] ?? {}) + .keys + .any((String s) => s.isNotEmpty)) { + context.push('/rooms/${room.id}/details/multiple_emotes'); + } else { + context.push('/rooms/${room.id}/details/emotes'); + } + } + @override Widget build(BuildContext context) { notificationChangeSub ??= Matrix.of(context) @@ -95,6 +109,8 @@ class ChatSettingsPopupMenuState extends State { case ChatPopupMenuActions.search: context.go('/rooms/${widget.room.id}/search'); break; + case ChatPopupMenuActions.emote: + goToEmoteSettings(); } }, itemBuilder: (BuildContext context) => [ @@ -141,6 +157,16 @@ class ChatSettingsPopupMenuState extends State { ], ), ), + PopupMenuItem( + value: ChatPopupMenuActions.emote, + child: Row( + children: [ + const Icon(Icons.emoji_emotions_outlined), + const SizedBox(width: 12), + Text(L10n.of(context).emoteSettings), + ], + ), + ), PopupMenuItem( value: ChatPopupMenuActions.leave, child: Row(