diff --git a/lib/pages/chat/chat_input_row.dart b/lib/pages/chat/chat_input_row.dart index 5f2f8b3b2..b85deaf41 100644 --- a/lib/pages/chat/chat_input_row.dart +++ b/lib/pages/chat/chat_input_row.dart @@ -5,6 +5,7 @@ // import 'package:fluffychat/config/app_config.dart'; // import 'package:fluffychat/l10n/l10n.dart'; +// import 'package:fluffychat/pages/chat/encryption_button.dart'; // import 'package:fluffychat/utils/other_party_can_receive.dart'; // import 'package:fluffychat/utils/platform_infos.dart'; // import 'package:fluffychat/widgets/avatar.dart'; @@ -227,7 +228,6 @@ // ), // Container( // height: height, -// width: height, // alignment: Alignment.center, // child: IconButton( // tooltip: L10n.of(context).emojis, @@ -256,6 +256,11 @@ // onPressed: controller.emojiPickerAction, // ), // ), +// Container( +// height: height, +// alignment: Alignment.center, +// child: EncryptionButton(controller.room), +// ), // if (Matrix.of(context).isMultiAccount && // Matrix.of(context).hasComplexBundles && // Matrix.of(context).currentBundle!.length > 1) diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index c698dacbf..4727ccb6d 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -138,7 +138,6 @@ class ChatView extends StatelessWidget { // icon: const Icon(Icons.call_outlined), // tooltip: L10n.of(context).placeCall, // ), - // EncryptionButton(controller.room), // ChatSettingsPopupMenu(controller.room, true), // ]; // } @@ -488,67 +487,67 @@ class ChatView extends StatelessWidget { // #Pangea // else if (controller.room.canSendDefaultMessages && // controller.room.membership == Membership.join) - // Container( - // margin: EdgeInsets.all(bottomSheetPadding), - // constraints: const BoxConstraints( - // maxWidth: FluffyThemes.maxTimelineWidth, - // ), - // alignment: Alignment.center, - // child: Material( - // clipBehavior: Clip.hardEdge, - // color: controller.selectedEvents.isNotEmpty - // ? theme.colorScheme.tertiaryContainer - // : theme.colorScheme.surfaceContainerHigh, - // borderRadius: const BorderRadius.all( - // Radius.circular(24), + // Container( + // margin: EdgeInsets.all(bottomSheetPadding), + // constraints: const BoxConstraints( + // maxWidth: FluffyThemes.maxTimelineWidth, // ), - // child: controller.room.isAbandonedDMRoom == true - // ? Row( - // mainAxisAlignment: - // MainAxisAlignment.spaceEvenly, - // children: [ - // TextButton.icon( - // style: TextButton.styleFrom( - // padding: const EdgeInsets.all( - // 16, + // alignment: Alignment.center, + // child: Material( + // clipBehavior: Clip.hardEdge, + // color: controller.selectedEvents.isNotEmpty + // ? theme.colorScheme.tertiaryContainer + // : theme.colorScheme.surfaceContainerHigh, + // borderRadius: const BorderRadius.all( + // Radius.circular(24), + // ), + // child: controller.room.isAbandonedDMRoom == true + // ? Row( + // mainAxisAlignment: + // MainAxisAlignment.spaceEvenly, + // children: [ + // TextButton.icon( + // style: TextButton.styleFrom( + // padding: const EdgeInsets.all( + // 16, + // ), + // foregroundColor: + // theme.colorScheme.error, // ), - // foregroundColor: - // theme.colorScheme.error, - // ), - // icon: const Icon( - // Icons.archive_outlined, - // ), - // onPressed: controller.leaveChat, - // label: Text( - // L10n.of(context).leave, - // ), - // ), - // TextButton.icon( - // style: TextButton.styleFrom( - // padding: const EdgeInsets.all( - // 16, + // icon: const Icon( + // Icons.archive_outlined, + // ), + // onPressed: controller.leaveChat, + // label: Text( + // L10n.of(context).leave, // ), // ), - // icon: const Icon( - // Icons.forum_outlined, + // TextButton.icon( + // style: TextButton.styleFrom( + // padding: const EdgeInsets.all( + // 16, + // ), + // ), + // icon: const Icon( + // Icons.forum_outlined, + // ), + // onPressed: controller.recreateChat, + // label: Text( + // L10n.of(context).reopenChat, + // ), // ), - // onPressed: controller.recreateChat, - // label: Text( - // L10n.of(context).reopenChat, - // ), - // ), - // ], - // ) - // : Column( - // mainAxisSize: MainAxisSize.min, - // children: [ - // ReplyDisplay(controller), - // ChatInputRow(controller), - // ChatEmojiPicker(controller), - // ], - // ), - // ), - // ) + // ], + // ) + // : Column( + // mainAxisSize: MainAxisSize.min, + // children: [ + // ReplyDisplay(controller), + // ChatInputRow(controller), + // ChatEmojiPicker(controller), + // ], + // ), + // ), + // ) else if (controller.room.canSendDefaultMessages && controller.room.membership == Membership.join && (controller.room.activityPlan == null || diff --git a/lib/pages/chat/encryption_button.dart b/lib/pages/chat/encryption_button.dart index 652d9f1cc..3bc556b4a 100644 --- a/lib/pages/chat/encryption_button.dart +++ b/lib/pages/chat/encryption_button.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:badges/badges.dart' as b; import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; @@ -12,6 +13,7 @@ class EncryptionButton extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); return StreamBuilder( stream: Matrix.of(context) .client @@ -27,16 +29,27 @@ class EncryptionButton extends StatelessWidget { tooltip: room.encrypted ? L10n.of(context).encrypted : L10n.of(context).encryptionNotEnabled, - icon: Icon( - room.encrypted ? Icons.lock_outlined : Icons.lock_open_outlined, - size: 20, - color: room.joinRules != JoinRules.public && !room.encrypted - ? Colors.red - : room.joinRules != JoinRules.public && - snapshot.data == - EncryptionHealthState.unverifiedDevices - ? Colors.orange - : null, + icon: b.Badge( + badgeAnimation: const b.BadgeAnimation.fade(), + showBadge: + snapshot.data == EncryptionHealthState.unverifiedDevices, + badgeStyle: b.BadgeStyle( + badgeColor: theme.colorScheme.error, + elevation: 4, + ), + badgeContent: Text( + '!', + style: TextStyle( + fontSize: 9, + color: theme.colorScheme.onError, + fontWeight: FontWeight.bold, + ), + ), + child: Icon( + room.encrypted ? Icons.lock_outlined : Icons.lock_open_outlined, + size: 20, + color: theme.colorScheme.onSurface, + ), ), onPressed: () => context.go('/rooms/${room.id}/encryption'), ), diff --git a/lib/pages/chat_details/chat_details.dart b/lib/pages/chat_details/chat_details.dart index b1cac71cf..1c8aec15c 100644 --- a/lib/pages/chat_details/chat_details.dart +++ b/lib/pages/chat_details/chat_details.dart @@ -3,7 +3,6 @@ import 'dart:async'; 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' as sdk; import 'package:matrix/matrix.dart'; @@ -166,20 +165,6 @@ class ChatDetailsController extends State // Pangea# } - 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 e124d0ffb..0e1502733 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -210,68 +210,59 @@ class ChatDetailsView extends StatelessWidget { ), ], ), - Divider(color: theme.dividerColor), - ListTile( - title: Text( - L10n.of(context).chatDescription, - style: TextStyle( - color: theme.colorScheme.secondary, - fontWeight: FontWeight.bold, + if (room.canChangeStateEvent(EventTypes.RoomTopic) || + room.topic.isNotEmpty) ...[ + Divider(color: theme.dividerColor), + ListTile( + title: Text( + L10n.of(context).chatDescription, + style: TextStyle( + color: theme.colorScheme.secondary, + fontWeight: FontWeight.bold, + ), + ), + trailing: + room.canChangeStateEvent(EventTypes.RoomTopic) + ? IconButton( + onPressed: controller.setTopicAction, + tooltip: + L10n.of(context).setChatDescription, + icon: const Icon(Icons.edit_outlined), + ) + : null, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + ), + child: SelectableLinkify( + text: room.topic.isEmpty + ? L10n.of(context).noChatDescriptionYet + : room.topic, + textScaleFactor: + MediaQuery.textScalerOf(context).scale(1), + options: const LinkifyOptions(humanize: false), + linkStyle: const TextStyle( + color: Colors.blueAccent, + decorationColor: Colors.blueAccent, + ), + style: TextStyle( + fontSize: 14, + fontStyle: room.topic.isEmpty + ? FontStyle.italic + : FontStyle.normal, + color: theme.textTheme.bodyMedium!.color, + decorationColor: + theme.textTheme.bodyMedium!.color, + ), + onOpen: (url) => + UrlLauncher(context, url.url).launchUrl(), ), ), - trailing: room - .canChangeStateEvent(EventTypes.RoomTopic) - ? IconButton( - onPressed: controller.setTopicAction, - tooltip: L10n.of(context).setChatDescription, - icon: const Icon(Icons.edit_outlined), - ) - : null, - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - ), - child: SelectableLinkify( - text: room.topic.isEmpty - ? L10n.of(context).noChatDescriptionYet - : room.topic, - textScaleFactor: - MediaQuery.textScalerOf(context).scale(1), - options: const LinkifyOptions(humanize: false), - linkStyle: const TextStyle( - color: Colors.blueAccent, - decorationColor: Colors.blueAccent, - ), - style: TextStyle( - fontSize: 14, - fontStyle: room.topic.isEmpty - ? FontStyle.italic - : FontStyle.normal, - color: theme.textTheme.bodyMedium!.color, - decorationColor: - theme.textTheme.bodyMedium!.color, - ), - onOpen: (url) => - UrlLauncher(context, url.url).launchUrl(), - ), - ), - 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) + const SizedBox(height: 16), + ], + if (!room.isDirectChat) ...[ + Divider(color: theme.dividerColor), ListTile( leading: CircleAvatar( backgroundColor: theme.scaffoldBackgroundColor, @@ -288,7 +279,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( @@ -305,6 +295,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 b82b571b5..586312508 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) @@ -98,6 +112,8 @@ class ChatSettingsPopupMenuState extends State { case ChatPopupMenuActions.search: context.go('/rooms/${widget.room.id}/search'); break; + case ChatPopupMenuActions.emote: + goToEmoteSettings(); } }, itemBuilder: (BuildContext context) => [ @@ -144,6 +160,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(