From 668b9dd65f9ca83ce5921e8161a12148d41ed929 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 26 Jul 2024 13:34:56 -0400 Subject: [PATCH 01/38] Center toolbar overlay --- lib/pangea/utils/overlay.dart | 19 ++- lib/pangea/widgets/chat/message_toolbar.dart | 165 +++++-------------- lib/pangea/widgets/chat/overlay_message.dart | 146 ++++++++-------- 3 files changed, 126 insertions(+), 204 deletions(-) diff --git a/lib/pangea/utils/overlay.dart b/lib/pangea/utils/overlay.dart index ce8c63d99..44d179b8d 100644 --- a/lib/pangea/utils/overlay.dart +++ b/lib/pangea/utils/overlay.dart @@ -26,6 +26,7 @@ class OverlayUtil { Alignment? targetAnchor, Alignment? followerAnchor, bool closePrevOverlay = true, + bool targetScreen = false, }) { try { if (closePrevOverlay) { @@ -47,14 +48,16 @@ class OverlayUtil { Positioned( width: width, height: height, - child: CompositedTransformFollower( - targetAnchor: targetAnchor ?? Alignment.topLeft, - followerAnchor: followerAnchor ?? Alignment.topLeft, - link: layerLinkAndKey.link, - showWhenUnlinked: false, - offset: offset ?? Offset.zero, - child: child, - ), + child: targetScreen + ? Center(child: child) + : CompositedTransformFollower( + targetAnchor: targetAnchor ?? Alignment.topLeft, + followerAnchor: followerAnchor ?? Alignment.topLeft, + link: layerLinkAndKey.link, + showWhenUnlinked: false, + offset: offset ?? Offset.zero, + child: child, + ), ), ], ), diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index a638640e7..c6f3cd4f2 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -5,7 +5,6 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/enum/message_mode_enum.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/utils/any_state_holder.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/overlay.dart'; import 'package:fluffychat/pangea/widgets/chat/message_audio_card.dart'; @@ -20,7 +19,6 @@ import 'package:fluffychat/pangea/widgets/user_settings/p_language_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; class ToolbarDisplayController { @@ -61,73 +59,13 @@ class ToolbarDisplayController { void showToolbar(BuildContext context, {MessageMode? mode}) { // Close keyboard, if open FocusManager.instance.primaryFocus?.unfocus(); - bool toolbarUp = true; if (highlighted) return; - if (controller.selectMode) { - controller.clearSelectedEvents(); - } if (!MatrixState.pangeaController.languageController.languagesSet) { pLanguageDialog(context, () {}); return; } focusNode.requestFocus(); - final LayerLinkAndKey layerLinkAndKey = - MatrixState.pAnyState.layerLinkAndKey(targetId); - final targetRenderBox = - layerLinkAndKey.key.currentContext?.findRenderObject(); - if (targetRenderBox != null) { - final Size transformTargetSize = (targetRenderBox as RenderBox).size; - messageWidth = transformTargetSize.width; - final Offset targetOffset = (targetRenderBox).localToGlobal(Offset.zero); - - // If there is enough space above, procede as normal - // Else if there is enough space below, show toolbar underneath - if (targetOffset.dy < 320) { - final spaceBeneath = MediaQuery.of(context).size.height - - (targetOffset.dy + transformTargetSize.height); - // If toolbar is open, opening toolbar beneath without scrolling can cause issues - // if (spaceBeneath >= 320) { - // toolbarUp = false; - // } - - // See if it's possible to scroll up to make space - if (controller.scrollController.offset - targetOffset.dy + 320 >= - controller.scrollController.position.minScrollExtent && - controller.scrollController.offset - targetOffset.dy + 320 <= - controller.scrollController.position.maxScrollExtent) { - controller.scrollController.animateTo( - controller.scrollController.offset - targetOffset.dy + 320, - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - ); - } - - // See if it's possible to scroll down to make space - else if (controller.scrollController.offset + spaceBeneath - 320 >= - controller.scrollController.position.minScrollExtent && - controller.scrollController.offset + spaceBeneath - 320 <= - controller.scrollController.position.maxScrollExtent) { - controller.scrollController.animateTo( - controller.scrollController.offset + spaceBeneath - 320, - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - ); - toolbarUp = false; - } - - // If message is too big and can't scroll either way - // Scroll up as much as possible, and show toolbar above - else { - controller.scrollController.animateTo( - controller.scrollController.position.minScrollExtent, - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - ); - } - } - } - final Widget overlayMessage = OverlayMessage( pangeaMessageEvent.event, timeline: pangeaMessageEvent.timeline, @@ -146,16 +84,18 @@ class ToolbarDisplayController { Widget overlayEntry; if (toolbar == null) return; try { - overlayEntry = Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: pangeaMessageEvent.ownMessage - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, - children: [ - toolbarUp ? toolbar! : overlayMessage, - const SizedBox(height: 6), - toolbarUp ? overlayMessage : toolbar!, - ], + overlayEntry = Container( + constraints: + BoxConstraints(maxHeight: MediaQuery.sizeOf(context).height * .72), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + toolbar!, + const SizedBox(height: 6), + overlayMessage, + ], + ), ); } catch (err) { debugger(when: kDebugMode); @@ -167,25 +107,16 @@ class ToolbarDisplayController { context: context, child: overlayEntry, transformTargetId: targetId, - targetAnchor: pangeaMessageEvent.ownMessage - ? toolbarUp - ? Alignment.bottomRight - : Alignment.topRight - : toolbarUp - ? Alignment.bottomLeft - : Alignment.topLeft, - followerAnchor: pangeaMessageEvent.ownMessage - ? toolbarUp - ? Alignment.bottomRight - : Alignment.topRight - : toolbarUp - ? Alignment.bottomLeft - : Alignment.topLeft, + targetAnchor: Alignment.center, + followerAnchor: Alignment.center, backgroundColor: const Color.fromRGBO(0, 0, 0, 1).withAlpha(100), closePrevOverlay: MatrixState.pangeaController.subscriptionController.isSubscribed, + targetScreen: true, ); + // controller.onSelectMessage(pangeaMessageEvent.event); + if (MatrixState.pAnyState.entries.isNotEmpty) { overlayId = MatrixState.pAnyState.entries.last.hashCode.toString(); } @@ -361,11 +292,6 @@ class MessageToolbarState extends State { void spellCheck() {} - void showMore() { - MatrixState.pAnyState.closeOverlay(); - widget.controller.onSelectMessage(widget.pangeaMessageEvent.event); - } - @override void initState() { super.initState(); @@ -454,40 +380,31 @@ class MessageToolbarState extends State { Row( mainAxisSize: MainAxisSize.min, children: MessageMode.values.map((mode) { - if ([ - MessageMode.definition, - MessageMode.textToSpeech, - MessageMode.translation, - ].contains(mode) && - widget.pangeaMessageEvent.isAudioMessage) { - return const SizedBox.shrink(); - } - if (mode == MessageMode.speechToText && - !widget.pangeaMessageEvent.isAudioMessage) { - return const SizedBox.shrink(); - } - return Tooltip( - message: mode.tooltip(context), - child: IconButton( - icon: Icon(mode.icon), - color: mode.iconColor( - widget.pangeaMessageEvent, - currentMode, - context, - ), - onPressed: () => updateMode(mode), - ), - ); - }).toList() + - [ - Tooltip( - message: L10n.of(context)!.more, - child: IconButton( - icon: const Icon(Icons.add_reaction_outlined), - onPressed: showMore, - ), + if ([ + MessageMode.definition, + MessageMode.textToSpeech, + MessageMode.translation, + ].contains(mode) && + widget.pangeaMessageEvent.isAudioMessage) { + return const SizedBox.shrink(); + } + if (mode == MessageMode.speechToText && + !widget.pangeaMessageEvent.isAudioMessage) { + return const SizedBox.shrink(); + } + return Tooltip( + message: mode.tooltip(context), + child: IconButton( + icon: Icon(mode.icon), + color: mode.iconColor( + widget.pangeaMessageEvent, + currentMode, + context, ), - ], + onPressed: () => updateMode(mode), + ), + ); + }).toList(), ), ], ), diff --git a/lib/pangea/widgets/chat/overlay_message.dart b/lib/pangea/widgets/chat/overlay_message.dart index 31e2b10b1..66937d9b0 100644 --- a/lib/pangea/widgets/chat/overlay_message.dart +++ b/lib/pangea/widgets/chat/overlay_message.dart @@ -119,83 +119,85 @@ class OverlayMessage extends StatelessWidget { ); return Flexible( - child: Material( - color: noBubble ? Colors.transparent : color, - clipBehavior: Clip.antiAlias, - shape: RoundedRectangleBorder( - borderRadius: borderRadius, - ), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, - ), + child: SingleChildScrollView( + child: Material( + color: noBubble ? Colors.transparent : color, + clipBehavior: Clip.antiAlias, + shape: RoundedRectangleBorder( + borderRadius: borderRadius, ), - padding: noBubble || noPadding - ? EdgeInsets.zero - : const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - constraints: BoxConstraints( - maxWidth: width ?? FluffyThemes.columnWidth * 1.25, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Flexible( - child: MessageContent( - event.getDisplayEvent(timeline), - textColor: textColor, - borderRadius: borderRadius, - selected: selected, - pangeaMessageEvent: pangeaMessageEvent, - immersionMode: immersionMode, - toolbarController: toolbarController, - isOverlay: true, - ), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, ), - if (event.hasAggregatedEvents( - timeline, - RelationshipTypes.edit, - ) || - (pangeaMessageEvent.showUseType)) - Padding( - padding: const EdgeInsets.only( - top: 4.0, + ), + padding: noBubble || noPadding + ? EdgeInsets.zero + : const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (pangeaMessageEvent.showUseType) ...[ - pangeaMessageEvent.msgUseType.iconView( - context, - textColor.withAlpha(164), - ), - const SizedBox(width: 4), - ], - if (event.hasAggregatedEvents( - timeline, - RelationshipTypes.edit, - )) ...[ - Icon( - Icons.edit_outlined, - color: textColor.withAlpha(164), - size: 14, - ), - Text( - ' - ${event.getDisplayEvent(timeline).originServerTs.localizedTimeShort(context)}', - style: TextStyle( - color: textColor.withAlpha(164), - fontSize: 12, - ), - ), - ], - ], + constraints: BoxConstraints( + maxWidth: width ?? FluffyThemes.columnWidth * 1.25, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + child: MessageContent( + event.getDisplayEvent(timeline), + textColor: textColor, + borderRadius: borderRadius, + selected: selected, + pangeaMessageEvent: pangeaMessageEvent, + immersionMode: immersionMode, + toolbarController: toolbarController, + isOverlay: true, ), ), - ], + if (event.hasAggregatedEvents( + timeline, + RelationshipTypes.edit, + ) || + (pangeaMessageEvent.showUseType)) + Padding( + padding: const EdgeInsets.only( + top: 4.0, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (pangeaMessageEvent.showUseType) ...[ + pangeaMessageEvent.msgUseType.iconView( + context, + textColor.withAlpha(164), + ), + const SizedBox(width: 4), + ], + if (event.hasAggregatedEvents( + timeline, + RelationshipTypes.edit, + )) ...[ + Icon( + Icons.edit_outlined, + color: textColor.withAlpha(164), + size: 14, + ), + Text( + ' - ${event.getDisplayEvent(timeline).originServerTs.localizedTimeShort(context)}', + style: TextStyle( + color: textColor.withAlpha(164), + fontSize: 12, + ), + ), + ], + ], + ), + ), + ], + ), ), ), ), From 622384036b3f66c4da3dd510f289bfc8c98ed93e Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 26 Jul 2024 15:28:05 -0400 Subject: [PATCH 02/38] Added selection features to toolbar overlay --- lib/pages/chat/chat_event_list.dart | 4 +- lib/pages/chat/chat_view.dart | 60 ++++++---- lib/pages/chat/events/message.dart | 50 +++++---- lib/pages/chat/events/message_content.dart | 8 +- lib/pangea/utils/overlay.dart | 7 ++ lib/pangea/widgets/chat/message_toolbar.dart | 46 +++++--- lib/pangea/widgets/chat/overlay_footer.dart | 57 ++++++++++ lib/pangea/widgets/chat/overlay_header.dart | 112 +++++++++++++++++++ lib/pangea/widgets/igc/pangea_rich_text.dart | 6 +- 9 files changed, 287 insertions(+), 63 deletions(-) create mode 100644 lib/pangea/widgets/chat/overlay_footer.dart create mode 100644 lib/pangea/widgets/chat/overlay_header.dart diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 9bca32169..d64f5fbef 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -165,7 +165,9 @@ class ChatEventList extends StatelessWidget { ), highlightMarker: controller.scrollToEventIdMarker == event.eventId, - onSelect: controller.onSelectMessage, + // #Pangea + // onSelect: controller.onSelectMessage, + // Pangea# scrollToEventId: (String eventId) => controller.scrollToEventId(eventId), longPressSelect: controller.selectedEvents.isNotEmpty, diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 9b12f9904..6456ebace 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -7,7 +7,6 @@ import 'package:fluffychat/pages/chat/chat_emoji_picker.dart'; import 'package:fluffychat/pages/chat/chat_event_list.dart'; import 'package:fluffychat/pages/chat/chat_input_row.dart'; import 'package:fluffychat/pages/chat/pinned_events.dart'; -import 'package:fluffychat/pages/chat/reactions_picker.dart'; import 'package:fluffychat/pages/chat/reply_display.dart'; import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart'; import 'package:fluffychat/pangea/choreographer/widgets/start_igc_button.dart'; @@ -35,6 +34,21 @@ class ChatView extends StatelessWidget { const ChatView(this.controller, {super.key}); + // #Pangea + List _editedAppBarActions(BuildContext context) { + if (!controller.selectMode) { + return [ + ChatSettingsPopupMenu( + controller.room, + (!controller.room.isDirectChat && !controller.room.isArchived), + ), + ]; + } else { + return []; + } + } + // Pangea# + List _appBarActions(BuildContext context) { if (controller.selectMode) { return [ @@ -197,26 +211,33 @@ class ChatView extends StatelessWidget { ? null : Theme.of(context).colorScheme.primary, ), - leading: controller.selectMode - ? IconButton( - icon: const Icon(Icons.close), - onPressed: controller.clearSelectedEvents, - tooltip: L10n.of(context)!.close, - color: Theme.of(context).colorScheme.primary, - ) - : UnreadRoomsBadge( - filter: (r) => - r.id != controller.roomId - // #Pangea - && - !r.isAnalyticsRoom, - // Pangea# - badgePosition: BadgePosition.topEnd(end: 8, top: 4), - child: const Center(child: BackButton()), - ), + leading: + // #Pangea + // controller.selectMode + // ? IconButton( + // icon: const Icon(Icons.close), + // onPressed: controller.clearSelectedEvents, + // tooltip: L10n.of(context)!.close, + // color: Theme.of(context).colorScheme.primary, + // ) + // : + // Pangea# + UnreadRoomsBadge( + filter: (r) => + r.id != controller.roomId + // #Pangea + && + !r.isAnalyticsRoom, + // Pangea# + badgePosition: BadgePosition.topEnd(end: 8, top: 4), + child: const Center(child: BackButton()), + ), titleSpacing: 0, title: ChatAppBarTitle(controller), - actions: _appBarActions(context), + // #Pangea + // actions: _appBarActions(context), + actions: _editedAppBarActions(context), + // Pangea# bottom: PreferredSize( preferredSize: Size.fromHeight(appbarBottomHeight), child: Column( @@ -497,7 +518,6 @@ class ChatView extends StatelessWidget { ITBar( choreographer: controller.choreographer, ), - ReactionsPicker(controller), ReplyDisplay(controller), ChatInputRow(controller), ChatEmojiPicker(controller), diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index 3a6b7030c..345d74ac1 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -9,7 +9,6 @@ import 'package:fluffychat/utils/string_color.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; import 'package:swipe_to_action/swipe_to_action.dart'; @@ -26,7 +25,6 @@ class Message extends StatelessWidget { final Event? nextEvent; final Event? previousEvent; final bool displayReadMarker; - final void Function(Event) onSelect; final void Function(Event) onAvatarTab; final void Function(Event) onInfoTab; final void Function(String) scrollToEventId; @@ -38,6 +36,7 @@ class Message extends StatelessWidget { final bool animateIn; final void Function()? resetAnimateIn; // #Pangea + // final void Function(Event) onSelect; final bool immersionMode; final bool definitions; final ChatController controller; @@ -50,7 +49,9 @@ class Message extends StatelessWidget { this.previousEvent, this.displayReadMarker = false, this.longPressSelect = false, - required this.onSelect, + // #Pangea + // required this.onSelect, + // Pangea# required this.onInfoTab, required this.onAvatarTab, required this.scrollToEventId, @@ -203,8 +204,10 @@ class Message extends StatelessWidget { left: 0, right: 0, child: InkWell( - onTap: () => onSelect(event), - onLongPress: () => onSelect(event), + // #Pangea + // onTap: () => onSelect(event), + // onLongPress: () => onSelect(event), + // Pangea# borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), child: Material( @@ -228,17 +231,20 @@ class Message extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: rowMainAxisAlignment, children: [ - if (longPressSelect) - SizedBox( - height: 32, - width: Avatar.defaultSize, - child: Checkbox.adaptive( - value: selected, - shape: const CircleBorder(), - onChanged: (_) => onSelect(event), - ), - ) - else if (nextEventSameSender || ownMessage) + // #Pangea + // if (longPressSelect) + // SizedBox( + // height: 32, + // width: Avatar.defaultSize, + // child: Checkbox.adaptive( + // value: selected, + // shape: const CircleBorder(), + // onChanged: (_) => onSelect(event), + // ), + // ) + // else + // Pangea# + if (nextEventSameSender || ownMessage) SizedBox( width: Avatar.defaultSize, child: Center( @@ -319,13 +325,13 @@ class Message extends StatelessWidget { ), onDoubleTap: () => toolbarController?.showToolbar(context), + // onLongPress: longPressSelect + // ? null + // : () { + // HapticFeedback.heavyImpact(); + // onSelect(event); + // }, // Pangea# - onLongPress: longPressSelect - ? null - : () { - HapticFeedback.heavyImpact(); - onSelect(event); - }, child: AnimatedOpacity( opacity: animateIn ? 0 diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index 5468b59e8..5da553366 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -317,9 +317,11 @@ class MessageContent extends StatelessWidget { pangeaMessageEvent != null && !(toolbarController!.highlighted) && !selected) { - toolbarController!.controller.onSelectMessage( - pangeaMessageEvent!.event, - ); + // #Pangea + // toolbarController!.controller.onSelectMessage( + // pangeaMessageEvent!.event, + // ); + // Pangea# return; } toolbarController?.toolbar?.textSelection diff --git a/lib/pangea/utils/overlay.dart b/lib/pangea/utils/overlay.dart index 44d179b8d..4add2e80a 100644 --- a/lib/pangea/utils/overlay.dart +++ b/lib/pangea/utils/overlay.dart @@ -27,6 +27,7 @@ class OverlayUtil { Alignment? followerAnchor, bool closePrevOverlay = true, bool targetScreen = false, + Function? onDismiss, }) { try { if (closePrevOverlay) { @@ -44,6 +45,7 @@ class OverlayUtil { if (backDropToDismiss) TransparentBackdrop( backgroundColor: backgroundColor, + onDismiss: onDismiss, ), Positioned( width: width, @@ -194,8 +196,10 @@ class OverlayUtil { class TransparentBackdrop extends StatelessWidget { final Color? backgroundColor; + final Function? onDismiss; const TransparentBackdrop({ super.key, + this.onDismiss, this.backgroundColor, }); @@ -211,6 +215,9 @@ class TransparentBackdrop extends StatelessWidget { focusColor: Colors.transparent, highlightColor: Colors.transparent, onTap: () { + if (onDismiss != null) { + onDismiss!(); + } MatrixState.pAnyState.closeOverlay(); }, child: Container( diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index c6f3cd4f2..c798e01f9 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -12,6 +12,8 @@ import 'package:fluffychat/pangea/widgets/chat/message_speech_to_text_card.dart' import 'package:fluffychat/pangea/widgets/chat/message_text_selection.dart'; import 'package:fluffychat/pangea/widgets/chat/message_translation_card.dart'; import 'package:fluffychat/pangea/widgets/chat/message_unsubscribed_card.dart'; +import 'package:fluffychat/pangea/widgets/chat/overlay_footer.dart'; +import 'package:fluffychat/pangea/widgets/chat/overlay_header.dart'; import 'package:fluffychat/pangea/widgets/chat/overlay_message.dart'; import 'package:fluffychat/pangea/widgets/igc/word_data_card.dart'; import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity_card.dart'; @@ -72,7 +74,7 @@ class ToolbarDisplayController { immersionMode: immersionMode, ownMessage: pangeaMessageEvent.ownMessage, toolbarController: this, - width: messageWidth, + width: 300, nextEvent: nextEvent, previousEvent: previousEvent, ); @@ -84,18 +86,33 @@ class ToolbarDisplayController { Widget overlayEntry; if (toolbar == null) return; try { - overlayEntry = Container( - constraints: - BoxConstraints(maxHeight: MediaQuery.sizeOf(context).height * .72), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - toolbar!, - const SizedBox(height: 6), - overlayMessage, - ], - ), + overlayEntry = Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + OverlayHeader(controller: controller), + const SizedBox( + height: 7, + ), + Container( + constraints: BoxConstraints( + maxHeight: MediaQuery.sizeOf(context).height * .72, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + toolbar!, + const SizedBox(height: 9), + overlayMessage, + ], + ), + ), + const SizedBox( + height: 7, + ), + OverlayFooter(controller: controller), + ], ); } catch (err) { debugger(when: kDebugMode); @@ -113,9 +130,10 @@ class ToolbarDisplayController { closePrevOverlay: MatrixState.pangeaController.subscriptionController.isSubscribed, targetScreen: true, + onDismiss: controller.clearSelectedEvents, ); - // controller.onSelectMessage(pangeaMessageEvent.event); + controller.onSelectMessage(pangeaMessageEvent.event); if (MatrixState.pAnyState.entries.isNotEmpty) { overlayId = MatrixState.pAnyState.entries.last.hashCode.toString(); diff --git a/lib/pangea/widgets/chat/overlay_footer.dart b/lib/pangea/widgets/chat/overlay_footer.dart new file mode 100644 index 000000000..c886bc54d --- /dev/null +++ b/lib/pangea/widgets/chat/overlay_footer.dart @@ -0,0 +1,57 @@ +import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pages/chat/chat.dart'; +import 'package:fluffychat/pages/chat/chat_input_row.dart'; +import 'package:fluffychat/pages/chat/reactions_picker.dart'; +import 'package:fluffychat/pages/chat/reply_display.dart'; +import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart'; +import 'package:fluffychat/widgets/connection_status_header.dart'; +import 'package:flutter/material.dart'; + +enum _EventContextAction { info, report } + +class OverlayFooter extends StatelessWidget { + ChatController controller; + + OverlayFooter({ + required this.controller, + super.key, + }); + + @override + Widget build(BuildContext context) { + final bottomSheetPadding = FluffyThemes.isColumnMode(context) ? 18.0 : 10.0; + + return Container( + margin: EdgeInsets.only( + bottom: bottomSheetPadding, + left: bottomSheetPadding, + right: bottomSheetPadding, + ), + constraints: const BoxConstraints( + maxWidth: FluffyThemes.columnWidth * 2.5, + ), + alignment: Alignment.center, + child: Material( + clipBehavior: Clip.hardEdge, + color: Theme.of(context).colorScheme.surfaceContainerHighest, + borderRadius: const BorderRadius.all( + Radius.circular(24), + ), + child: Column( + children: [ + const ConnectionStatusHeader(), + ITBar( + choreographer: controller.choreographer, + ), + ReactionsPicker(controller), + ReplyDisplay(controller), + ChatInputRow(controller), + SizedBox( + height: (FluffyThemes.isColumnMode(context) ? 16.0 : 8.0), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pangea/widgets/chat/overlay_header.dart b/lib/pangea/widgets/chat/overlay_header.dart new file mode 100644 index 000000000..3bac468c1 --- /dev/null +++ b/lib/pangea/widgets/chat/overlay_header.dart @@ -0,0 +1,112 @@ +import 'package:fluffychat/pages/chat/chat.dart'; +import 'package:fluffychat/pages/chat/chat_app_bar_title.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:matrix/matrix.dart'; + +enum _EventContextAction { info, report } + +class OverlayHeader extends StatelessWidget { + ChatController controller; + + OverlayHeader({ + required this.controller, + super.key, + }); + + @override + Widget build(BuildContext context) { + final Event selectedEvent = controller.selectedEvents.single; + + return AppBar( + actionsIconTheme: IconThemeData( + color: Theme.of(context).colorScheme.primary, + ), + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: MatrixState.pAnyState.closeOverlay, + tooltip: L10n.of(context)!.close, + color: Theme.of(context).colorScheme.primary, + ), + titleSpacing: 0, + title: ChatAppBarTitle(controller), + actions: [ + if (controller.canEditSelectedEvents) + IconButton( + icon: const Icon(Icons.edit_outlined), + tooltip: L10n.of(context)!.edit, + onPressed: controller.editSelectedEventAction, + ), + if (selectedEvent.messageType == MessageTypes.Text) + IconButton( + icon: const Icon(Icons.copy_outlined), + tooltip: L10n.of(context)!.copy, + onPressed: controller.copyEventsAction, + ), + if (controller.canSaveSelectedEvent) + // Use builder context to correctly position the share dialog on iPad + Builder( + builder: (context) => IconButton( + icon: Icon(Icons.adaptive.share), + tooltip: L10n.of(context)!.share, + onPressed: () => controller.saveSelectedEvent(context), + ), + ), + if (controller.canPinSelectedEvents) + IconButton( + icon: const Icon(Icons.push_pin_outlined), + onPressed: controller.pinEvent, + tooltip: L10n.of(context)!.pinMessage, + ), + if (controller.canRedactSelectedEvents) + IconButton( + icon: const Icon(Icons.delete_outlined), + tooltip: L10n.of(context)!.redactMessage, + onPressed: controller.redactEventsAction, + ), + PopupMenuButton<_EventContextAction>( + onSelected: (action) { + switch (action) { + case _EventContextAction.info: + controller.showEventInfo(); + controller.clearSelectedEvents(); + break; + case _EventContextAction.report: + controller.reportEventAction(); + break; + } + }, + itemBuilder: (context) => [ + PopupMenuItem( + value: _EventContextAction.info, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.info_outlined), + const SizedBox(width: 12), + Text(L10n.of(context)!.messageInfo), + ], + ), + ), + if (selectedEvent.status.isSent) + PopupMenuItem( + value: _EventContextAction.report, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.shield_outlined, + color: Colors.red, + ), + const SizedBox(width: 12), + Text(L10n.of(context)!.reportMessage), + ], + ), + ), + ], + ), + ], + ); + } +} diff --git a/lib/pangea/widgets/igc/pangea_rich_text.dart b/lib/pangea/widgets/igc/pangea_rich_text.dart index abf583b3b..7ab5ba61e 100644 --- a/lib/pangea/widgets/igc/pangea_rich_text.dart +++ b/lib/pangea/widgets/igc/pangea_rich_text.dart @@ -143,9 +143,9 @@ class PangeaRichTextState extends State { (e) => e.eventId == widget.pangeaMessageEvent.eventId, ) ?? false)) { - widget.toolbarController?.controller.onSelectMessage( - widget.pangeaMessageEvent.event, - ); + // widget.toolbarController?.controller.onSelectMessage( + // widget.pangeaMessageEvent.event, + // ); return; } widget.toolbarController?.toolbar?.textSelection From 0c37d88ef2eaf9bd688e5b7267b038b68ce9d805 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Mon, 29 Jul 2024 09:50:37 -0400 Subject: [PATCH 03/38] Fix overlay not closing in some situations --- lib/pages/chat/chat.dart | 4 ++++ lib/pages/image_viewer/image_viewer.dart | 9 ++++----- lib/pangea/widgets/chat/message_toolbar.dart | 2 +- lib/pangea/widgets/chat/overlay_footer.dart | 2 +- lib/pangea/widgets/chat/overlay_header.dart | 1 + 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index aa2b78d4d..63704f953 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -1080,6 +1080,9 @@ class ChatController extends State void forwardEventsAction() async { if (selectedEvents.length == 1) { + // #Pangea + MatrixState.pAnyState.closeOverlay(); + // Pangea# Matrix.of(context).shareContent = selectedEvents.first.getDisplayEvent(timeline!).content; } else { @@ -1111,6 +1114,7 @@ class ChatController extends State replyEvent = replyTo ?? selectedEvents.first; selectedEvents.clear(); }); + MatrixState.pAnyState.closeOverlay(); inputFocus.requestFocus(); } diff --git a/lib/pages/image_viewer/image_viewer.dart b/lib/pages/image_viewer/image_viewer.dart index 94ab19dd4..f7d3ea544 100644 --- a/lib/pages/image_viewer/image_viewer.dart +++ b/lib/pages/image_viewer/image_viewer.dart @@ -1,11 +1,10 @@ -import 'package:flutter/material.dart'; - -import 'package:go_router/go_router.dart'; -import 'package:matrix/matrix.dart'; - import 'package:fluffychat/pages/image_viewer/image_viewer_view.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:matrix/matrix.dart'; + import '../../utils/matrix_sdk_extensions/event_extension.dart'; class ImageViewer extends StatefulWidget { diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index c798e01f9..bac8e58eb 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -126,7 +126,7 @@ class ToolbarDisplayController { transformTargetId: targetId, targetAnchor: Alignment.center, followerAnchor: Alignment.center, - backgroundColor: const Color.fromRGBO(0, 0, 0, 1).withAlpha(100), + backgroundColor: const Color.fromRGBO(0, 0, 0, 1).withAlpha(200), closePrevOverlay: MatrixState.pangeaController.subscriptionController.isSubscribed, targetScreen: true, diff --git a/lib/pangea/widgets/chat/overlay_footer.dart b/lib/pangea/widgets/chat/overlay_footer.dart index c886bc54d..a571155ab 100644 --- a/lib/pangea/widgets/chat/overlay_footer.dart +++ b/lib/pangea/widgets/chat/overlay_footer.dart @@ -47,7 +47,7 @@ class OverlayFooter extends StatelessWidget { ReplyDisplay(controller), ChatInputRow(controller), SizedBox( - height: (FluffyThemes.isColumnMode(context) ? 16.0 : 8.0), + height: (FluffyThemes.isColumnMode(context) ? 18.0 : 10.0), ), ], ), diff --git a/lib/pangea/widgets/chat/overlay_header.dart b/lib/pangea/widgets/chat/overlay_header.dart index 3bac468c1..f6b686c6b 100644 --- a/lib/pangea/widgets/chat/overlay_header.dart +++ b/lib/pangea/widgets/chat/overlay_header.dart @@ -20,6 +20,7 @@ class OverlayHeader extends StatelessWidget { final Event selectedEvent = controller.selectedEvents.single; return AppBar( + backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest, actionsIconTheme: IconThemeData( color: Theme.of(context).colorScheme.primary, ), From 5aeea56cc1b875a7d0f23700eab9bd1d3c9146d9 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Wed, 31 Jul 2024 12:00:08 -0400 Subject: [PATCH 04/38] Change overlay message color/shape --- lib/pangea/widgets/chat/overlay_message.dart | 45 ++++---------------- 1 file changed, 8 insertions(+), 37 deletions(-) diff --git a/lib/pangea/widgets/chat/overlay_message.dart b/lib/pangea/widgets/chat/overlay_message.dart index 66937d9b0..8d2764c7a 100644 --- a/lib/pangea/widgets/chat/overlay_message.dart +++ b/lib/pangea/widgets/chat/overlay_message.dart @@ -42,45 +42,16 @@ class OverlayMessage extends StatelessWidget { return const SizedBox.shrink(); } - var color = Theme.of(context).colorScheme.surfaceContainerHighest; + var color = Theme.of(context).colorScheme.surfaceContainer; final isLight = Theme.of(context).brightness == Brightness.light; - var lightness = isLight ? .05 : .85; + var lightness = isLight ? .05 : .2; final textColor = ownMessage ? Theme.of(context).colorScheme.onPrimary : Theme.of(context).colorScheme.onSurface; - const hardCorner = Radius.circular(4); - - final displayTime = event.type == EventTypes.RoomCreate || - nextEvent == null || - !event.originServerTs.sameEnvironment(nextEvent!.originServerTs); - - final nextEventSameSender = nextEvent != null && - { - EventTypes.Message, - EventTypes.Sticker, - EventTypes.Encrypted, - }.contains(nextEvent!.type) && - nextEvent!.senderId == event.senderId && - !displayTime; - - final previousEventSameSender = previousEvent != null && - { - EventTypes.Message, - EventTypes.Sticker, - EventTypes.Encrypted, - }.contains(previousEvent!.type) && - previousEvent!.senderId == event.senderId && - previousEvent!.originServerTs.sameEnvironment(event.originServerTs); - const roundedCorner = Radius.circular(AppConfig.borderRadius); - final borderRadius = BorderRadius.only( - topLeft: !ownMessage && nextEventSameSender ? hardCorner : roundedCorner, - topRight: ownMessage && nextEventSameSender ? hardCorner : roundedCorner, - bottomLeft: - !ownMessage && previousEventSameSender ? hardCorner : roundedCorner, - bottomRight: - ownMessage && previousEventSameSender ? hardCorner : roundedCorner, + const borderRadius = BorderRadius.all( + roundedCorner, ); final noBubble = { @@ -101,13 +72,13 @@ class OverlayMessage extends StatelessWidget { // Make overlay a little darker/lighter than the message color = Color.fromARGB( color.alpha, - isLight + isLight || !ownMessage ? (color.red + lightness * (255 - color.red)).round() : (color.red * lightness).round(), - isLight + isLight || !ownMessage ? (color.green + lightness * (255 - color.green)).round() : (color.green * lightness).round(), - isLight + isLight || !ownMessage ? (color.blue + lightness * (255 - color.blue)).round() : (color.blue * lightness).round(), ); @@ -123,7 +94,7 @@ class OverlayMessage extends StatelessWidget { child: Material( color: noBubble ? Colors.transparent : color, clipBehavior: Clip.antiAlias, - shape: RoundedRectangleBorder( + shape: const RoundedRectangleBorder( borderRadius: borderRadius, ), child: Container( From d96298080143d8226d8d9a86ced8961d5aa64d74 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Wed, 31 Jul 2024 12:34:18 -0400 Subject: [PATCH 05/38] Fix toolbar not closing in all situations --- lib/pages/chat/chat.dart | 3 +++ lib/pangea/widgets/chat/message_toolbar.dart | 12 ++++++++++-- lib/pangea/widgets/chat/overlay_header.dart | 5 +++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 989c1a088..93e7f80ec 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -885,6 +885,9 @@ class ChatController extends State } void copyEventsAction() { + // #Pangea + MatrixState.pAnyState.closeOverlay(); + // Pangea# Clipboard.setData(ClipboardData(text: _getSelectedEventString())); setState(() { showEmojiPicker = false; diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index bac8e58eb..75a4c660d 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -47,6 +47,11 @@ class ToolbarDisplayController { this.previousEvent, }); + void closeToolbar() { + controller.clearSelectedEvents(); + MatrixState.pAnyState.closeOverlay(); + } + void setToolbar() { toolbar ??= MessageToolbar( textSelection: MessageTextSelection(), @@ -90,13 +95,16 @@ class ToolbarDisplayController { mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.max, children: [ - OverlayHeader(controller: controller), + OverlayHeader( + controller: controller, + closeToolbar: closeToolbar, + ), const SizedBox( height: 7, ), Container( constraints: BoxConstraints( - maxHeight: MediaQuery.sizeOf(context).height * .72, + maxHeight: MediaQuery.sizeOf(context).height * .7, ), child: Column( mainAxisSize: MainAxisSize.min, diff --git a/lib/pangea/widgets/chat/overlay_header.dart b/lib/pangea/widgets/chat/overlay_header.dart index f6b686c6b..cc90f218b 100644 --- a/lib/pangea/widgets/chat/overlay_header.dart +++ b/lib/pangea/widgets/chat/overlay_header.dart @@ -1,6 +1,5 @@ import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pages/chat/chat_app_bar_title.dart'; -import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; @@ -9,9 +8,11 @@ enum _EventContextAction { info, report } class OverlayHeader extends StatelessWidget { ChatController controller; + Function closeToolbar; OverlayHeader({ required this.controller, + required this.closeToolbar, super.key, }); @@ -26,7 +27,7 @@ class OverlayHeader extends StatelessWidget { ), leading: IconButton( icon: const Icon(Icons.close), - onPressed: MatrixState.pAnyState.closeOverlay, + onPressed: () => closeToolbar(), tooltip: L10n.of(context)!.close, color: Theme.of(context).colorScheme.primary, ), From 6f07eed8ed025aeea04ed50fb54fdbe26042179e Mon Sep 17 00:00:00 2001 From: Kelrap Date: Wed, 31 Jul 2024 13:03:19 -0400 Subject: [PATCH 06/38] Fix more closure fails --- lib/pages/chat/chat.dart | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 93e7f80ec..c4abcbac2 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -986,6 +986,9 @@ class ChatController extends State } void redactEventsAction() async { + // #Pangea + MatrixState.pAnyState.closeOverlay(); + // Pangea# final reasonInput = selectedEvents.any((event) => event.status.isSent) ? await showTextInputDialog( context: context, @@ -1001,7 +1004,12 @@ class ChatController extends State cancelLabel: L10n.of(context)!.cancel, ) : []; - if (reasonInput == null) return; + if (reasonInput == null) { + // #Pangea + clearSelectedEvents(); + // Pangea# + return; + } final reason = reasonInput.single.isEmpty ? null : reasonInput.single; for (final event in selectedEvents) { await showFutureLoadingDialog( @@ -1076,10 +1084,10 @@ class ChatController extends State } void forwardEventsAction() async { + // #Pangea + MatrixState.pAnyState.closeOverlay(); + // Pangea# if (selectedEvents.length == 1) { - // #Pangea - MatrixState.pAnyState.closeOverlay(); - // Pangea# Matrix.of(context).shareContent = selectedEvents.first.getDisplayEvent(timeline!).content; } else { From 03678cbba3aeedd2f561381893eb69200f0843f3 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Wed, 31 Jul 2024 16:30:41 -0400 Subject: [PATCH 07/38] Input bar should be same height in overlay --- lib/pangea/widgets/chat/overlay_footer.dart | 40 +++++++++++---------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/lib/pangea/widgets/chat/overlay_footer.dart b/lib/pangea/widgets/chat/overlay_footer.dart index a571155ab..cfbd77415 100644 --- a/lib/pangea/widgets/chat/overlay_footer.dart +++ b/lib/pangea/widgets/chat/overlay_footer.dart @@ -31,26 +31,30 @@ class OverlayFooter extends StatelessWidget { maxWidth: FluffyThemes.columnWidth * 2.5, ), alignment: Alignment.center, - child: Material( - clipBehavior: Clip.hardEdge, - color: Theme.of(context).colorScheme.surfaceContainerHighest, - borderRadius: const BorderRadius.all( - Radius.circular(24), - ), - child: Column( - children: [ - const ConnectionStatusHeader(), - ITBar( - choreographer: controller.choreographer, + child: Column( + children: [ + Material( + clipBehavior: Clip.hardEdge, + color: Theme.of(context).colorScheme.surfaceContainerHighest, + borderRadius: const BorderRadius.all( + Radius.circular(24), ), - ReactionsPicker(controller), - ReplyDisplay(controller), - ChatInputRow(controller), - SizedBox( - height: (FluffyThemes.isColumnMode(context) ? 18.0 : 10.0), + child: Column( + children: [ + const ConnectionStatusHeader(), + ITBar( + choreographer: controller.choreographer, + ), + ReactionsPicker(controller), + ReplyDisplay(controller), + ChatInputRow(controller), + ], ), - ], - ), + ), + SizedBox( + height: FluffyThemes.isColumnMode(context) ? 16.0 : 8.0, + ), + ], ), ); } From badb2888f233636c107be2d4d429bcd63682c320 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Thu, 1 Aug 2024 13:31:32 -0400 Subject: [PATCH 08/38] message info/report message menu work with overlay --- lib/pages/chat/chat.dart | 22 +++- lib/pangea/utils/overlay.dart | 21 +++- lib/pangea/widgets/chat/overlay_header.dart | 110 ++++++++++++++------ 3 files changed, 117 insertions(+), 36 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index c4abcbac2..b26ba605b 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -115,6 +115,7 @@ class ChatController extends State late Choreographer choreographer = Choreographer(pangeaController, this); // Pangea# + Room get room => sendingClient.getRoomById(roomId) ?? widget.room; late Client sendingClient; @@ -896,7 +897,14 @@ class ChatController extends State } void reportEventAction() async { + // #Pangea + MatrixState.pAnyState.closeOverlay(); + MatrixState.pAnyState.closeOverlay(); + // Pangea# final event = selectedEvents.single; + // #Pangea + clearSelectedEvents(); + // Pangea# final score = await showConfirmationDialog( context: context, title: L10n.of(context)!.reportMessage, @@ -1476,8 +1484,18 @@ class ChatController extends State bool get isArchived => {Membership.leave, Membership.ban}.contains(room.membership); - void showEventInfo([Event? event]) => - (event ?? selectedEvents.single).showInfoDialog(context); + void showEventInfo([Event? event]) + // #Pangea + // => + { + MatrixState.pAnyState.closeOverlay(); + MatrixState.pAnyState.closeOverlay(); + // Pangea# + (event ?? selectedEvents.single).showInfoDialog(context); + // #Pangea + clearSelectedEvents(); + // Pangea# + } void onPhoneButtonTap() async { // VoIP required Android SDK 21 diff --git a/lib/pangea/utils/overlay.dart b/lib/pangea/utils/overlay.dart index 4add2e80a..cf1cd881e 100644 --- a/lib/pangea/utils/overlay.dart +++ b/lib/pangea/utils/overlay.dart @@ -28,13 +28,12 @@ class OverlayUtil { bool closePrevOverlay = true, bool targetScreen = false, Function? onDismiss, + bool centered = true, }) { try { if (closePrevOverlay) { MatrixState.pAnyState.closeOverlay(); } - final LayerLinkAndKey layerLinkAndKey = - MatrixState.pAnyState.layerLinkAndKey(transformTargetId); final OverlayEntry entry = OverlayEntry( builder: (context) => AnimatedContainer( @@ -48,14 +47,28 @@ class OverlayUtil { onDismiss: onDismiss, ), Positioned( + top: (targetScreen && !centered) + ? FluffyThemes.isColumnMode(context) + ? 20 + : 65 + : null, + right: (targetScreen && !centered) + ? FluffyThemes.isColumnMode(context) + ? 20 + : 15 + : null, width: width, height: height, child: targetScreen - ? Center(child: child) + ? centered + ? Center(child: child) + : child : CompositedTransformFollower( targetAnchor: targetAnchor ?? Alignment.topLeft, followerAnchor: followerAnchor ?? Alignment.topLeft, - link: layerLinkAndKey.link, + link: MatrixState.pAnyState + .layerLinkAndKey(transformTargetId) + .link, showWhenUnlinked: false, offset: offset ?? Offset.zero, child: child, diff --git a/lib/pangea/widgets/chat/overlay_header.dart b/lib/pangea/widgets/chat/overlay_header.dart index cc90f218b..d8c04b47c 100644 --- a/lib/pangea/widgets/chat/overlay_header.dart +++ b/lib/pangea/widgets/chat/overlay_header.dart @@ -1,5 +1,6 @@ import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pages/chat/chat_app_bar_title.dart'; +import 'package:fluffychat/pangea/utils/overlay.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; @@ -67,21 +68,67 @@ class OverlayHeader extends StatelessWidget { tooltip: L10n.of(context)!.redactMessage, onPressed: controller.redactEventsAction, ), - PopupMenuButton<_EventContextAction>( - onSelected: (action) { - switch (action) { - case _EventContextAction.info: - controller.showEventInfo(); - controller.clearSelectedEvents(); - break; - case _EventContextAction.report: - controller.reportEventAction(); - break; - } - }, - itemBuilder: (context) => [ - PopupMenuItem( - value: _EventContextAction.info, + IconButton( + padding: const EdgeInsets.only(bottom: 6), + icon: Icon( + Icons.more_horiz, + color: Theme.of(context).colorScheme.onSurface, + ), + onPressed: () => showPopup(context), + ), + ], + ); + } + + void showPopup(BuildContext context) { + OverlayUtil.showOverlay( + context: context, + child: SelectionPopup(controller: controller), + transformTargetId: "", + targetAnchor: Alignment.center, + followerAnchor: Alignment.center, + backgroundColor: Colors.transparent, + closePrevOverlay: false, + targetScreen: true, + onDismiss: closeToolbar, + centered: false, + ); + } +} + +class SelectionPopup extends StatelessWidget { + ChatController controller; + + SelectionPopup({ + required this.controller, + super.key, + }); + + @override + Widget build(BuildContext context) { + return Material( + type: MaterialType.transparency, + child: Container( + // padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainer, + border: Border.all( + width: 3, + color: Theme.of(context).colorScheme.surfaceContainerHighest, + ), + borderRadius: const BorderRadius.all( + Radius.circular(20), + ), + ), + constraints: const BoxConstraints( + maxWidth: 200, + maxHeight: 200, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextButton( + onPressed: controller.showEventInfo, child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -91,24 +138,27 @@ class OverlayHeader extends StatelessWidget { ], ), ), - if (selectedEvent.status.isSent) - PopupMenuItem( - value: _EventContextAction.report, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon( - Icons.shield_outlined, - color: Colors.red, - ), - const SizedBox(width: 12), - Text(L10n.of(context)!.reportMessage), - ], - ), + Divider( + color: Theme.of(context).colorScheme.surfaceContainerHighest, + height: 5, + ), + TextButton( + onPressed: controller.reportEventAction, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.shield_outlined, + color: Colors.red, + ), + const SizedBox(width: 12), + Text(L10n.of(context)!.reportMessage), + ], ), + ), ], ), - ], + ), ); } } From 5ed0f59db244c65db3a002000ce9b352f0fe3e01 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Thu, 1 Aug 2024 14:31:18 -0400 Subject: [PATCH 09/38] Get rid of unneeded chat footer elements --- lib/pangea/widgets/chat/overlay_footer.dart | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/pangea/widgets/chat/overlay_footer.dart b/lib/pangea/widgets/chat/overlay_footer.dart index cfbd77415..df7ac2920 100644 --- a/lib/pangea/widgets/chat/overlay_footer.dart +++ b/lib/pangea/widgets/chat/overlay_footer.dart @@ -2,9 +2,6 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pages/chat/chat_input_row.dart'; import 'package:fluffychat/pages/chat/reactions_picker.dart'; -import 'package:fluffychat/pages/chat/reply_display.dart'; -import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart'; -import 'package:fluffychat/widgets/connection_status_header.dart'; import 'package:flutter/material.dart'; enum _EventContextAction { info, report } @@ -41,18 +38,13 @@ class OverlayFooter extends StatelessWidget { ), child: Column( children: [ - const ConnectionStatusHeader(), - ITBar( - choreographer: controller.choreographer, - ), ReactionsPicker(controller), - ReplyDisplay(controller), ChatInputRow(controller), ], ), ), SizedBox( - height: FluffyThemes.isColumnMode(context) ? 16.0 : 8.0, + height: FluffyThemes.isColumnMode(context) ? 23.0 : 15.0, ), ], ), From 23ac51c7663f5fd792d795e3851682677895a89f Mon Sep 17 00:00:00 2001 From: Kelrap Date: Thu, 1 Aug 2024 17:02:47 -0400 Subject: [PATCH 10/38] Emoji picker works now --- lib/pages/chat/chat.dart | 25 +++ lib/pages/chat/chat_emoji_picker.dart | 169 ++++++++++--------- lib/pangea/utils/overlay.dart | 13 +- lib/pangea/widgets/chat/message_toolbar.dart | 2 + 4 files changed, 124 insertions(+), 85 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index b26ba605b..75941a8c8 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -10,6 +10,7 @@ import 'package:file_picker/file_picker.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pages/chat/chat_emoji_picker.dart'; import 'package:fluffychat/pages/chat/chat_view.dart'; import 'package:fluffychat/pages/chat/event_info_dialog.dart'; import 'package:fluffychat/pages/chat/recording_dialog.dart'; @@ -23,6 +24,7 @@ import 'package:fluffychat/pangea/models/representation_content_model.dart'; import 'package:fluffychat/pangea/models/tokens_event_content_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/firebase_analytics.dart'; +import 'package:fluffychat/pangea/utils/overlay.dart'; import 'package:fluffychat/pangea/utils/report_message.dart'; import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart'; import 'package:fluffychat/pangea/widgets/igc/pangea_text_controller.dart'; @@ -841,6 +843,11 @@ class ChatController extends State } void hideEmojiPicker() { + // #Pangea + MatrixState.pAnyState.closeOverlay(); + MatrixState.pAnyState.closeOverlay(); + clearSelectedEvents(); + // Pangea setState(() => showEmojiPicker = false); } @@ -1234,11 +1241,29 @@ class ChatController extends State _allReactionEvents = allReactionEvents; emojiPickerType = EmojiPickerType.reaction; setState(() => showEmojiPicker = true); + // #Pangea + OverlayUtil.showOverlay( + context: context, + child: ChatEmojiPicker(this), + transformTargetId: selectedEvents.first.eventId, + targetAnchor: Alignment.center, + followerAnchor: Alignment.center, + backgroundColor: const Color.fromRGBO(0, 0, 0, 1).withAlpha(100), + closePrevOverlay: false, + targetScreen: true, + onDismiss: hideEmojiPicker, + centered: false, + bottom: true, + ); + // Pangea# } void sendEmojiAction(String? emoji) async { final events = List.from(selectedEvents); setState(() => selectedEvents.clear()); + // #Pangea + MatrixState.pAnyState.closeOverlay(); + // Pangea# for (final event in events) { await room.sendReaction( event.eventId, diff --git a/lib/pages/chat/chat_emoji_picker.dart b/lib/pages/chat/chat_emoji_picker.dart index f38243b84..088bb74a7 100644 --- a/lib/pages/chat/chat_emoji_picker.dart +++ b/lib/pages/chat/chat_emoji_picker.dart @@ -14,90 +14,97 @@ class ChatEmojiPicker extends StatelessWidget { @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); - return AnimatedContainer( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - clipBehavior: Clip.hardEdge, - decoration: const BoxDecoration(), - height: controller.showEmojiPicker - ? MediaQuery.of(context).size.height / 2 - : 0, - child: controller.showEmojiPicker - ? DefaultTabController( - length: 2, - child: Column( - children: [ - TabBar( - tabs: [ - Tab(text: L10n.of(context)!.emojis), - Tab(text: L10n.of(context)!.stickers), - ], - ), - Expanded( - child: TabBarView( - children: [ - EmojiPicker( - onEmojiSelected: controller.onEmojiSelected, - onBackspacePressed: controller.emojiPickerBackspace, - config: Config( - emojiViewConfig: EmojiViewConfig( - noRecents: const NoRecent(), - backgroundColor: Theme.of(context) - .colorScheme - .onInverseSurface, - ), - bottomActionBarConfig: const BottomActionBarConfig( - enabled: false, - ), - categoryViewConfig: CategoryViewConfig( - backspaceColor: theme.colorScheme.primary, - iconColor: - theme.colorScheme.primary.withOpacity(0.5), - iconColorSelected: theme.colorScheme.primary, - indicatorColor: theme.colorScheme.primary, - ), - skinToneConfig: SkinToneConfig( - dialogBackgroundColor: Color.lerp( - theme.colorScheme.surface, - theme.colorScheme.primaryContainer, - 0.75, - )!, - indicatorColor: theme.colorScheme.onSurface, - ), - ), - ), - StickerPickerDialog( - room: controller.room, - onSelected: (sticker) { - controller.room.sendEvent( - { - 'body': sticker.body, - 'info': sticker.info ?? {}, - 'url': sticker.url.toString(), - }, - type: EventTypes.Sticker, - ); - controller.hideEmojiPicker(); - }, - ), + return + // #Pangea + Material( + child: + // Pangea# + AnimatedContainer( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + clipBehavior: Clip.hardEdge, + decoration: const BoxDecoration(), + height: controller.showEmojiPicker + ? MediaQuery.of(context).size.height / 2 + : 0, + child: controller.showEmojiPicker + ? DefaultTabController( + length: 2, + child: Column( + children: [ + TabBar( + tabs: [ + Tab(text: L10n.of(context)!.emojis), + Tab(text: L10n.of(context)!.stickers), ], ), - ), - // #Pangea - Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: FloatingActionButton( - onPressed: controller.hideEmojiPicker, - shape: const CircleBorder(), - mini: true, - child: const Icon(Icons.close), + Expanded( + child: TabBarView( + children: [ + EmojiPicker( + onEmojiSelected: controller.onEmojiSelected, + onBackspacePressed: controller.emojiPickerBackspace, + config: Config( + emojiViewConfig: EmojiViewConfig( + noRecents: const NoRecent(), + backgroundColor: Theme.of(context) + .colorScheme + .onInverseSurface, + ), + bottomActionBarConfig: + const BottomActionBarConfig( + enabled: false, + ), + categoryViewConfig: CategoryViewConfig( + backspaceColor: theme.colorScheme.primary, + iconColor: + theme.colorScheme.primary.withOpacity(0.5), + iconColorSelected: theme.colorScheme.primary, + indicatorColor: theme.colorScheme.primary, + ), + skinToneConfig: SkinToneConfig( + dialogBackgroundColor: Color.lerp( + theme.colorScheme.surface, + theme.colorScheme.primaryContainer, + 0.75, + )!, + indicatorColor: theme.colorScheme.onSurface, + ), + ), + ), + StickerPickerDialog( + room: controller.room, + onSelected: (sticker) { + controller.room.sendEvent( + { + 'body': sticker.body, + 'info': sticker.info ?? {}, + 'url': sticker.url.toString(), + }, + type: EventTypes.Sticker, + ); + controller.hideEmojiPicker(); + }, + ), + ], + ), ), - ), - // Pangea# - ], - ), - ) - : null, + // #Pangea + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: FloatingActionButton( + onPressed: controller.hideEmojiPicker, + shape: const CircleBorder(), + mini: true, + child: const Icon(Icons.close), + ), + ), + // Pangea# + ], + ), + ) + : null, + ), ); } } diff --git a/lib/pangea/utils/overlay.dart b/lib/pangea/utils/overlay.dart index cf1cd881e..b8bfbc935 100644 --- a/lib/pangea/utils/overlay.dart +++ b/lib/pangea/utils/overlay.dart @@ -29,6 +29,7 @@ class OverlayUtil { bool targetScreen = false, Function? onDismiss, bool centered = true, + bool bottom = false, }) { try { if (closePrevOverlay) { @@ -47,16 +48,20 @@ class OverlayUtil { onDismiss: onDismiss, ), Positioned( - top: (targetScreen && !centered) + top: (targetScreen && !centered && !bottom) ? FluffyThemes.isColumnMode(context) ? 20 : 65 : null, right: (targetScreen && !centered) - ? FluffyThemes.isColumnMode(context) - ? 20 - : 15 + ? bottom + ? 0 + : FluffyThemes.isColumnMode(context) + ? 20 + : 15 : null, + left: bottom ? 0 : null, + bottom: bottom ? 0 : null, width: width, height: height, child: targetScreen diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 75a4c660d..68f119c49 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -66,6 +66,8 @@ class ToolbarDisplayController { void showToolbar(BuildContext context, {MessageMode? mode}) { // Close keyboard, if open FocusManager.instance.primaryFocus?.unfocus(); + // Close emoji picker, if open + controller.showEmojiPicker = false; if (highlighted) return; if (!MatrixState.pangeaController.languageController.languagesSet) { pLanguageDialog(context, () {}); From 21ab01dbcb2510ac7865f7bc20d44c48b3464684 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 2 Aug 2024 09:22:53 -0400 Subject: [PATCH 11/38] Make popup look like previous appearance --- lib/pangea/widgets/chat/overlay_header.dart | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/pangea/widgets/chat/overlay_header.dart b/lib/pangea/widgets/chat/overlay_header.dart index d8c04b47c..468e0e36c 100644 --- a/lib/pangea/widgets/chat/overlay_header.dart +++ b/lib/pangea/widgets/chat/overlay_header.dart @@ -109,13 +109,9 @@ class SelectionPopup extends StatelessWidget { return Material( type: MaterialType.transparency, child: Container( - // padding: const EdgeInsets.all(5), + padding: const EdgeInsets.all(5), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceContainer, - border: Border.all( - width: 3, - color: Theme.of(context).colorScheme.surfaceContainerHighest, - ), borderRadius: const BorderRadius.all( Radius.circular(20), ), @@ -126,6 +122,7 @@ class SelectionPopup extends StatelessWidget { ), child: Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ TextButton( onPressed: controller.showEventInfo, @@ -138,10 +135,6 @@ class SelectionPopup extends StatelessWidget { ], ), ), - Divider( - color: Theme.of(context).colorScheme.surfaceContainerHighest, - height: 5, - ), TextButton( onPressed: controller.reportEventAction, child: Row( From 37fa0e1d5ebf017737650ba63b5df2437287ca83 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 2 Aug 2024 10:03:22 -0400 Subject: [PATCH 12/38] Simpler overlay closing --- lib/pages/chat/chat.dart | 19 ++++++++----------- lib/pangea/utils/any_state_holder.dart | 17 +++++++++++++++++ lib/pangea/widgets/chat/message_toolbar.dart | 2 +- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 75941a8c8..632863df3 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -844,8 +844,7 @@ class ChatController extends State void hideEmojiPicker() { // #Pangea - MatrixState.pAnyState.closeOverlay(); - MatrixState.pAnyState.closeOverlay(); + MatrixState.pAnyState.closeAllOverlays(); clearSelectedEvents(); // Pangea setState(() => showEmojiPicker = false); @@ -894,7 +893,7 @@ class ChatController extends State void copyEventsAction() { // #Pangea - MatrixState.pAnyState.closeOverlay(); + MatrixState.pAnyState.closeAllOverlays(); // Pangea# Clipboard.setData(ClipboardData(text: _getSelectedEventString())); setState(() { @@ -905,8 +904,7 @@ class ChatController extends State void reportEventAction() async { // #Pangea - MatrixState.pAnyState.closeOverlay(); - MatrixState.pAnyState.closeOverlay(); + MatrixState.pAnyState.closeAllOverlays(); // Pangea# final event = selectedEvents.single; // #Pangea @@ -1002,7 +1000,7 @@ class ChatController extends State void redactEventsAction() async { // #Pangea - MatrixState.pAnyState.closeOverlay(); + MatrixState.pAnyState.closeAllOverlays(); // Pangea# final reasonInput = selectedEvents.any((event) => event.status.isSent) ? await showTextInputDialog( @@ -1100,7 +1098,7 @@ class ChatController extends State void forwardEventsAction() async { // #Pangea - MatrixState.pAnyState.closeOverlay(); + MatrixState.pAnyState.closeAllOverlays(); // Pangea# if (selectedEvents.length == 1) { Matrix.of(context).shareContent = @@ -1134,7 +1132,7 @@ class ChatController extends State replyEvent = replyTo ?? selectedEvents.first; selectedEvents.clear(); }); - MatrixState.pAnyState.closeOverlay(); + MatrixState.pAnyState.closeAllOverlays(); inputFocus.requestFocus(); } @@ -1262,7 +1260,7 @@ class ChatController extends State final events = List.from(selectedEvents); setState(() => selectedEvents.clear()); // #Pangea - MatrixState.pAnyState.closeOverlay(); + MatrixState.pAnyState.closeAllOverlays(); // Pangea# for (final event in events) { await room.sendReaction( @@ -1513,8 +1511,7 @@ class ChatController extends State // #Pangea // => { - MatrixState.pAnyState.closeOverlay(); - MatrixState.pAnyState.closeOverlay(); + MatrixState.pAnyState.closeAllOverlays(); // Pangea# (event ?? selectedEvents.single).showInfoDialog(context); // #Pangea diff --git a/lib/pangea/utils/any_state_holder.dart b/lib/pangea/utils/any_state_holder.dart index 9705a9ca1..67517bf22 100644 --- a/lib/pangea/utils/any_state_holder.dart +++ b/lib/pangea/utils/any_state_holder.dart @@ -61,6 +61,23 @@ class PangeaAnyState { } } + void closeAllOverlays() { + while (entries.isNotEmpty) { + try { + entries.last.remove(); + } catch (err, s) { + ErrorHandler.logError( + e: err, + s: s, + data: { + "overlay": entries.last, + }, + ); + } + entries.removeLast(); + } + } + LayerLinkAndKey messageLinkAndKey(String eventId) => layerLinkAndKey(eventId); // String chatViewTargetKey(String? roomId) => "chatViewKey$roomId"; diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 68f119c49..1c8a76877 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -49,7 +49,7 @@ class ToolbarDisplayController { void closeToolbar() { controller.clearSelectedEvents(); - MatrixState.pAnyState.closeOverlay(); + MatrixState.pAnyState.closeAllOverlays(); } void setToolbar() { From 6761f97b61fdfd1045d3832c14a56005d3a9e229 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 2 Aug 2024 10:17:17 -0400 Subject: [PATCH 13/38] Fix emoji picker exit button --- lib/pages/chat/chat_emoji_picker.dart | 121 +++++++++++++++----------- 1 file changed, 69 insertions(+), 52 deletions(-) diff --git a/lib/pages/chat/chat_emoji_picker.dart b/lib/pages/chat/chat_emoji_picker.dart index 088bb74a7..818ad4285 100644 --- a/lib/pages/chat/chat_emoji_picker.dart +++ b/lib/pages/chat/chat_emoji_picker.dart @@ -39,66 +39,83 @@ class ChatEmojiPicker extends StatelessWidget { ], ), Expanded( - child: TabBarView( + child: + // #Pangea + Stack( children: [ - EmojiPicker( - onEmojiSelected: controller.onEmojiSelected, - onBackspacePressed: controller.emojiPickerBackspace, - config: Config( - emojiViewConfig: EmojiViewConfig( - noRecents: const NoRecent(), - backgroundColor: Theme.of(context) - .colorScheme - .onInverseSurface, + // Pangea# + TabBarView( + children: [ + EmojiPicker( + onEmojiSelected: controller.onEmojiSelected, + onBackspacePressed: + controller.emojiPickerBackspace, + config: Config( + emojiViewConfig: EmojiViewConfig( + noRecents: const NoRecent(), + backgroundColor: Theme.of(context) + .colorScheme + .onInverseSurface, + ), + bottomActionBarConfig: + const BottomActionBarConfig( + enabled: false, + ), + categoryViewConfig: CategoryViewConfig( + backspaceColor: theme.colorScheme.primary, + iconColor: theme.colorScheme.primary + .withOpacity(0.5), + iconColorSelected: + theme.colorScheme.primary, + indicatorColor: theme.colorScheme.primary, + ), + skinToneConfig: SkinToneConfig( + dialogBackgroundColor: Color.lerp( + theme.colorScheme.surface, + theme.colorScheme.primaryContainer, + 0.75, + )!, + indicatorColor: theme.colorScheme.onSurface, + ), + ), ), - bottomActionBarConfig: - const BottomActionBarConfig( - enabled: false, - ), - categoryViewConfig: CategoryViewConfig( - backspaceColor: theme.colorScheme.primary, - iconColor: - theme.colorScheme.primary.withOpacity(0.5), - iconColorSelected: theme.colorScheme.primary, - indicatorColor: theme.colorScheme.primary, - ), - skinToneConfig: SkinToneConfig( - dialogBackgroundColor: Color.lerp( - theme.colorScheme.surface, - theme.colorScheme.primaryContainer, - 0.75, - )!, - indicatorColor: theme.colorScheme.onSurface, + StickerPickerDialog( + room: controller.room, + onSelected: (sticker) { + controller.room.sendEvent( + { + 'body': sticker.body, + 'info': sticker.info ?? {}, + 'url': sticker.url.toString(), + }, + type: EventTypes.Sticker, + ); + controller.hideEmojiPicker(); + }, ), + ], + ), + + // #Pangea + Positioned( + bottom: 20, + left: 0, + right: 0, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FloatingActionButton( + onPressed: controller.hideEmojiPicker, + shape: const CircleBorder(), + mini: true, + child: const Icon(Icons.close), + ), + ], ), ), - StickerPickerDialog( - room: controller.room, - onSelected: (sticker) { - controller.room.sendEvent( - { - 'body': sticker.body, - 'info': sticker.info ?? {}, - 'url': sticker.url.toString(), - }, - type: EventTypes.Sticker, - ); - controller.hideEmojiPicker(); - }, - ), ], ), ), - // #Pangea - Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: FloatingActionButton( - onPressed: controller.hideEmojiPicker, - shape: const CircleBorder(), - mini: true, - child: const Icon(Icons.close), - ), - ), // Pangea# ], ), From 61ffd096c9af7d0042e744ddb285e57a6fad11e9 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 2 Aug 2024 10:22:40 -0400 Subject: [PATCH 14/38] Fix overflow from long messages --- lib/pangea/widgets/chat/message_toolbar.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 1c8a76877..c79d1ad5b 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -104,10 +104,7 @@ class ToolbarDisplayController { const SizedBox( height: 7, ), - Container( - constraints: BoxConstraints( - maxHeight: MediaQuery.sizeOf(context).height * .7, - ), + Flexible( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, From 02dd22a45f4dd6231ba75a6b53db0e4dd45bf864 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 2 Aug 2024 11:01:38 -0400 Subject: [PATCH 15/38] Change message info/report message menu color --- lib/pangea/widgets/chat/overlay_header.dart | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/pangea/widgets/chat/overlay_header.dart b/lib/pangea/widgets/chat/overlay_header.dart index 468e0e36c..5b072b3ff 100644 --- a/lib/pangea/widgets/chat/overlay_header.dart +++ b/lib/pangea/widgets/chat/overlay_header.dart @@ -111,15 +111,11 @@ class SelectionPopup extends StatelessWidget { child: Container( padding: const EdgeInsets.all(5), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainer, + color: Theme.of(context).colorScheme.surfaceContainerHigh, borderRadius: const BorderRadius.all( Radius.circular(20), ), ), - constraints: const BoxConstraints( - maxWidth: 200, - maxHeight: 200, - ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, From 3d9000d2ae78a57ff2187988fc1a193cc4a55e7a Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 2 Aug 2024 12:12:34 -0400 Subject: [PATCH 16/38] Code cleanup --- lib/pages/chat/chat.dart | 6 ++-- lib/pangea/utils/overlay.dart | 33 +++++++++++--------- lib/pangea/widgets/chat/message_toolbar.dart | 2 +- lib/pangea/widgets/chat/overlay_header.dart | 5 +-- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 632863df3..65dc88ad2 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -1132,7 +1132,9 @@ class ChatController extends State replyEvent = replyTo ?? selectedEvents.first; selectedEvents.clear(); }); + // #Pangea MatrixState.pAnyState.closeAllOverlays(); + // Pangea inputFocus.requestFocus(); } @@ -1248,10 +1250,8 @@ class ChatController extends State followerAnchor: Alignment.center, backgroundColor: const Color.fromRGBO(0, 0, 0, 1).withAlpha(100), closePrevOverlay: false, - targetScreen: true, onDismiss: hideEmojiPicker, - centered: false, - bottom: true, + position: OverlayEnum.bottom, ); // Pangea# } diff --git a/lib/pangea/utils/overlay.dart b/lib/pangea/utils/overlay.dart index b8bfbc935..3ac41f4ba 100644 --- a/lib/pangea/utils/overlay.dart +++ b/lib/pangea/utils/overlay.dart @@ -11,6 +11,13 @@ import '../../config/themes.dart'; import '../../widgets/matrix.dart'; import 'error_handler.dart'; +enum OverlayEnum { + transform, + centered, + topRight, + bottom, +} + class OverlayUtil { static showOverlay({ required BuildContext context, @@ -26,10 +33,8 @@ class OverlayUtil { Alignment? targetAnchor, Alignment? followerAnchor, bool closePrevOverlay = true, - bool targetScreen = false, Function? onDismiss, - bool centered = true, - bool bottom = false, + OverlayEnum position = OverlayEnum.transform, }) { try { if (closePrevOverlay) { @@ -48,24 +53,24 @@ class OverlayUtil { onDismiss: onDismiss, ), Positioned( - top: (targetScreen && !centered && !bottom) + top: (position == OverlayEnum.topRight) ? FluffyThemes.isColumnMode(context) ? 20 : 65 : null, - right: (targetScreen && !centered) - ? bottom + right: (position == OverlayEnum.topRight) + ? FluffyThemes.isColumnMode(context) + ? 20 + : 15 + : (position == OverlayEnum.bottom) ? 0 - : FluffyThemes.isColumnMode(context) - ? 20 - : 15 - : null, - left: bottom ? 0 : null, - bottom: bottom ? 0 : null, + : null, + left: (position == OverlayEnum.bottom) ? 0 : null, + bottom: (position == OverlayEnum.bottom) ? 0 : null, width: width, height: height, - child: targetScreen - ? centered + child: (position != OverlayEnum.transform) + ? (position == OverlayEnum.centered) ? Center(child: child) : child : CompositedTransformFollower( diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index c79d1ad5b..7323f361c 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -136,7 +136,7 @@ class ToolbarDisplayController { backgroundColor: const Color.fromRGBO(0, 0, 0, 1).withAlpha(200), closePrevOverlay: MatrixState.pangeaController.subscriptionController.isSubscribed, - targetScreen: true, + position: OverlayEnum.centered, onDismiss: controller.clearSelectedEvents, ); diff --git a/lib/pangea/widgets/chat/overlay_header.dart b/lib/pangea/widgets/chat/overlay_header.dart index 5b072b3ff..f6da14281 100644 --- a/lib/pangea/widgets/chat/overlay_header.dart +++ b/lib/pangea/widgets/chat/overlay_header.dart @@ -87,11 +87,8 @@ class OverlayHeader extends StatelessWidget { transformTargetId: "", targetAnchor: Alignment.center, followerAnchor: Alignment.center, - backgroundColor: Colors.transparent, closePrevOverlay: false, - targetScreen: true, - onDismiss: closeToolbar, - centered: false, + position: OverlayEnum.topRight, ); } } From 19b20efc530ad3673806865e691ab7fa25a078a4 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Tue, 6 Aug 2024 13:19:40 -0400 Subject: [PATCH 17/38] PR edits, minus positioning changes --- lib/pages/chat/chat_emoji_picker.dart | 3 ++ lib/pages/chat/chat_view.dart | 20 +------- lib/pages/chat/events/message_content.dart | 5 -- lib/pages/chat/reactions_picker.dart | 16 +++--- lib/pangea/utils/any_state_holder.dart | 2 +- .../chat/message_selection_overlay.dart | 51 +++++++++++++++++++ lib/pangea/widgets/chat/message_toolbar.dart | 35 +++---------- lib/pangea/widgets/chat/overlay_footer.dart | 2 - lib/pangea/widgets/chat/overlay_header.dart | 2 - lib/pangea/widgets/chat/overlay_message.dart | 1 + lib/pangea/widgets/igc/pangea_rich_text.dart | 3 -- 11 files changed, 72 insertions(+), 68 deletions(-) create mode 100644 lib/pangea/widgets/chat/message_selection_overlay.dart diff --git a/lib/pages/chat/chat_emoji_picker.dart b/lib/pages/chat/chat_emoji_picker.dart index 818ad4285..fb11f01d6 100644 --- a/lib/pages/chat/chat_emoji_picker.dart +++ b/lib/pages/chat/chat_emoji_picker.dart @@ -16,6 +16,7 @@ class ChatEmojiPicker extends StatelessWidget { final ThemeData theme = Theme.of(context); return // #Pangea + // Emoji picker can't display without a material parent Material( child: // Pangea# @@ -41,6 +42,8 @@ class ChatEmojiPicker extends StatelessWidget { Expanded( child: // #Pangea + // Display the button to close the emoji picker + // over the emoji picker Stack( children: [ // Pangea# diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 6456ebace..4e29a31b4 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -34,21 +34,6 @@ class ChatView extends StatelessWidget { const ChatView(this.controller, {super.key}); - // #Pangea - List _editedAppBarActions(BuildContext context) { - if (!controller.selectMode) { - return [ - ChatSettingsPopupMenu( - controller.room, - (!controller.room.isDirectChat && !controller.room.isArchived), - ), - ]; - } else { - return []; - } - } - // Pangea# - List _appBarActions(BuildContext context) { if (controller.selectMode) { return [ @@ -234,10 +219,7 @@ class ChatView extends StatelessWidget { ), titleSpacing: 0, title: ChatAppBarTitle(controller), - // #Pangea - // actions: _appBarActions(context), - actions: _editedAppBarActions(context), - // Pangea# + actions: _appBarActions(context), bottom: PreferredSize( preferredSize: Size.fromHeight(appbarBottomHeight), child: Column( diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index 5da553366..3fd28cd6c 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -317,11 +317,6 @@ class MessageContent extends StatelessWidget { pangeaMessageEvent != null && !(toolbarController!.highlighted) && !selected) { - // #Pangea - // toolbarController!.controller.onSelectMessage( - // pangeaMessageEvent!.event, - // ); - // Pangea# return; } toolbarController?.toolbar?.textSelection diff --git a/lib/pages/chat/reactions_picker.dart b/lib/pages/chat/reactions_picker.dart index 7256e00bc..333daa049 100644 --- a/lib/pages/chat/reactions_picker.dart +++ b/lib/pages/chat/reactions_picker.dart @@ -1,11 +1,10 @@ -import 'package:flutter/material.dart'; - import 'package:emoji_proposal/emoji_proposal.dart'; -import 'package:matrix/matrix.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/app_emojis.dart'; import 'package:fluffychat/pages/chat/chat.dart'; +import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; + import '../../config/themes.dart'; class ReactionsPicker extends StatelessWidget { @@ -16,10 +15,13 @@ class ReactionsPicker extends StatelessWidget { @override Widget build(BuildContext context) { if (controller.showEmojiPicker) return const SizedBox.shrink(); - final display = controller.editEvent == null && - controller.replyEvent == null && + final display = + // #Pangea + // controller.editEvent == null && + // controller.replyEvent == null && + // Pangea# controller.room.canSendDefaultMessages && - controller.selectedEvents.isNotEmpty; + controller.selectedEvents.isNotEmpty; return AnimatedContainer( duration: FluffyThemes.animationDuration, curve: FluffyThemes.animationCurve, diff --git a/lib/pangea/utils/any_state_holder.dart b/lib/pangea/utils/any_state_holder.dart index 67517bf22..d7f0b073e 100644 --- a/lib/pangea/utils/any_state_holder.dart +++ b/lib/pangea/utils/any_state_holder.dart @@ -62,7 +62,7 @@ class PangeaAnyState { } void closeAllOverlays() { - while (entries.isNotEmpty) { + for (int i = 0; i < entries.length; i++) { try { entries.last.remove(); } catch (err, s) { diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart new file mode 100644 index 000000000..250e394a1 --- /dev/null +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -0,0 +1,51 @@ +import 'package:fluffychat/pages/chat/chat.dart'; +import 'package:fluffychat/pangea/widgets/chat/overlay_footer.dart'; +import 'package:fluffychat/pangea/widgets/chat/overlay_header.dart'; +import 'package:flutter/material.dart'; + +class MessageSelectionOverlay extends StatelessWidget { + final ChatController controller; + final Function closeToolbar; + final Widget toolbar; + final Widget overlayMessage; + + const MessageSelectionOverlay({ + required this.controller, + required this.closeToolbar, + required this.toolbar, + required this.overlayMessage, + super.key, + }); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + OverlayHeader( + controller: controller, + closeToolbar: closeToolbar, + ), + const SizedBox( + height: 7, + ), + Flexible( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + toolbar, + const SizedBox(height: 9), + overlayMessage, + ], + ), + ), + const SizedBox( + height: 7, + ), + OverlayFooter(controller: controller), + ], + ); + } +} diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 7323f361c..4c1d18256 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -8,12 +8,11 @@ import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dar import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/overlay.dart'; import 'package:fluffychat/pangea/widgets/chat/message_audio_card.dart'; +import 'package:fluffychat/pangea/widgets/chat/message_selection_overlay.dart'; import 'package:fluffychat/pangea/widgets/chat/message_speech_to_text_card.dart'; import 'package:fluffychat/pangea/widgets/chat/message_text_selection.dart'; import 'package:fluffychat/pangea/widgets/chat/message_translation_card.dart'; import 'package:fluffychat/pangea/widgets/chat/message_unsubscribed_card.dart'; -import 'package:fluffychat/pangea/widgets/chat/overlay_footer.dart'; -import 'package:fluffychat/pangea/widgets/chat/overlay_header.dart'; import 'package:fluffychat/pangea/widgets/chat/overlay_message.dart'; import 'package:fluffychat/pangea/widgets/igc/word_data_card.dart'; import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity_card.dart'; @@ -93,33 +92,11 @@ class ToolbarDisplayController { Widget overlayEntry; if (toolbar == null) return; try { - overlayEntry = Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.max, - children: [ - OverlayHeader( - controller: controller, - closeToolbar: closeToolbar, - ), - const SizedBox( - height: 7, - ), - Flexible( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - toolbar!, - const SizedBox(height: 9), - overlayMessage, - ], - ), - ), - const SizedBox( - height: 7, - ), - OverlayFooter(controller: controller), - ], + overlayEntry = MessageSelectionOverlay( + controller: controller, + closeToolbar: closeToolbar, + toolbar: toolbar!, + overlayMessage: overlayMessage, ); } catch (err) { debugger(when: kDebugMode); diff --git a/lib/pangea/widgets/chat/overlay_footer.dart b/lib/pangea/widgets/chat/overlay_footer.dart index df7ac2920..2fdb515a3 100644 --- a/lib/pangea/widgets/chat/overlay_footer.dart +++ b/lib/pangea/widgets/chat/overlay_footer.dart @@ -4,8 +4,6 @@ import 'package:fluffychat/pages/chat/chat_input_row.dart'; import 'package:fluffychat/pages/chat/reactions_picker.dart'; import 'package:flutter/material.dart'; -enum _EventContextAction { info, report } - class OverlayFooter extends StatelessWidget { ChatController controller; diff --git a/lib/pangea/widgets/chat/overlay_header.dart b/lib/pangea/widgets/chat/overlay_header.dart index f6da14281..b9a073125 100644 --- a/lib/pangea/widgets/chat/overlay_header.dart +++ b/lib/pangea/widgets/chat/overlay_header.dart @@ -5,8 +5,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; -enum _EventContextAction { info, report } - class OverlayHeader extends StatelessWidget { ChatController controller; Function closeToolbar; diff --git a/lib/pangea/widgets/chat/overlay_message.dart b/lib/pangea/widgets/chat/overlay_message.dart index 8d2764c7a..92f6252dd 100644 --- a/lib/pangea/widgets/chat/overlay_message.dart +++ b/lib/pangea/widgets/chat/overlay_message.dart @@ -90,6 +90,7 @@ class OverlayMessage extends StatelessWidget { ); return Flexible( + // Make overlay message scrollable so long messages don't run offscreen child: SingleChildScrollView( child: Material( color: noBubble ? Colors.transparent : color, diff --git a/lib/pangea/widgets/igc/pangea_rich_text.dart b/lib/pangea/widgets/igc/pangea_rich_text.dart index 7ab5ba61e..1ad2e6e08 100644 --- a/lib/pangea/widgets/igc/pangea_rich_text.dart +++ b/lib/pangea/widgets/igc/pangea_rich_text.dart @@ -143,9 +143,6 @@ class PangeaRichTextState extends State { (e) => e.eventId == widget.pangeaMessageEvent.eventId, ) ?? false)) { - // widget.toolbarController?.controller.onSelectMessage( - // widget.pangeaMessageEvent.event, - // ); return; } widget.toolbarController?.toolbar?.textSelection From 4b8087192215e96965d5efbd0c3c71bd1af5f44b Mon Sep 17 00:00:00 2001 From: Kelrap Date: Tue, 6 Aug 2024 13:32:09 -0400 Subject: [PATCH 18/38] Fix edit overflow --- lib/pages/chat/chat_view.dart | 2 -- lib/pages/chat/reply_display.dart | 26 +++++++++++++-------- lib/pangea/widgets/chat/overlay_footer.dart | 2 +- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 96ac67129..b70c1c8be 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -7,7 +7,6 @@ import 'package:fluffychat/pages/chat/chat_emoji_picker.dart'; import 'package:fluffychat/pages/chat/chat_event_list.dart'; import 'package:fluffychat/pages/chat/chat_input_row.dart'; import 'package:fluffychat/pages/chat/pinned_events.dart'; -import 'package:fluffychat/pages/chat/reactions_picker.dart'; import 'package:fluffychat/pages/chat/reply_display.dart'; import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart'; import 'package:fluffychat/pangea/choreographer/widgets/start_igc_button.dart'; @@ -500,7 +499,6 @@ class ChatView extends StatelessWidget { ITBar( choreographer: controller.choreographer, ), - ReactionsPicker(controller), ReplyDisplay(controller), ChatInputRow(controller), ChatEmojiPicker(controller), diff --git a/lib/pages/chat/reply_display.dart b/lib/pages/chat/reply_display.dart index e2bacac83..b25cbf0f3 100644 --- a/lib/pages/chat/reply_display.dart +++ b/lib/pages/chat/reply_display.dart @@ -65,16 +65,22 @@ class _EditContent extends StatelessWidget { color: Theme.of(context).colorScheme.primary, ), Container(width: 15.0), - Text( - event.calcLocalizedBodyFallback( - MatrixLocals(L10n.of(context)!), - withSenderNamePrefix: false, - hideReply: true, - ), - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: TextStyle( - color: Theme.of(context).textTheme.bodyMedium!.color, + + // #Pangea + Flexible( + child: + // Pangea# + Text( + event.calcLocalizedBodyFallback( + MatrixLocals(L10n.of(context)!), + withSenderNamePrefix: false, + hideReply: true, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: TextStyle( + color: Theme.of(context).textTheme.bodyMedium!.color, + ), ), ), ], diff --git a/lib/pangea/widgets/chat/overlay_footer.dart b/lib/pangea/widgets/chat/overlay_footer.dart index 2fdb515a3..a2a0567df 100644 --- a/lib/pangea/widgets/chat/overlay_footer.dart +++ b/lib/pangea/widgets/chat/overlay_footer.dart @@ -42,7 +42,7 @@ class OverlayFooter extends StatelessWidget { ), ), SizedBox( - height: FluffyThemes.isColumnMode(context) ? 23.0 : 15.0, + height: FluffyThemes.isColumnMode(context) ? 25.0 : 18.0, ), ], ), From a911b9f852141472778ec0d3a2d1f8a6a5f69256 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Tue, 6 Aug 2024 15:11:45 -0400 Subject: [PATCH 19/38] Attempt 1 of repositioning toolbar --- .../chat/message_selection_overlay.dart | 7 +++- lib/pangea/widgets/chat/message_toolbar.dart | 33 ++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index 250e394a1..4bc4c21aa 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -8,12 +8,14 @@ class MessageSelectionOverlay extends StatelessWidget { final Function closeToolbar; final Widget toolbar; final Widget overlayMessage; + final bool ownMessage; const MessageSelectionOverlay({ required this.controller, required this.closeToolbar, required this.toolbar, required this.overlayMessage, + required this.ownMessage, super.key, }); @@ -22,6 +24,8 @@ class MessageSelectionOverlay extends StatelessWidget { return Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.max, + crossAxisAlignment: + ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ OverlayHeader( controller: controller, @@ -33,7 +37,8 @@ class MessageSelectionOverlay extends StatelessWidget { Flexible( child: Column( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, + crossAxisAlignment: + ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ toolbar, const SizedBox(height: 9), diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 8d64dc01d..29e587e85 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -5,6 +5,7 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/enum/message_mode_enum.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; +import 'package:fluffychat/pangea/utils/any_state_holder.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/overlay.dart'; import 'package:fluffychat/pangea/widgets/chat/message_audio_card.dart'; @@ -74,6 +75,36 @@ class ToolbarDisplayController { } focusNode.requestFocus(); + final LayerLinkAndKey layerLinkAndKey = + MatrixState.pAnyState.layerLinkAndKey(targetId); + final targetRenderBox = + layerLinkAndKey.key.currentContext?.findRenderObject(); + if (targetRenderBox != null) { + final Size transformTargetSize = (targetRenderBox as RenderBox).size; + messageWidth = transformTargetSize.width; + final Offset targetOffset = (targetRenderBox).localToGlobal(Offset.zero); + + final double minValue = + controller.scrollController.position.minScrollExtent; + final double maxValue = + controller.scrollController.position.maxScrollExtent; + final double middlePoint = controller.scrollController.offset - + targetOffset.dy + + MediaQuery.of(context).size.height / 2 + + 37; + + // Scroll so message is right under half point of screen + controller.scrollController.animateTo( + middlePoint < minValue + ? minValue + : middlePoint > maxValue + ? maxValue + : middlePoint, + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + ); + } + final Widget overlayMessage = OverlayMessage( pangeaMessageEvent.event, timeline: pangeaMessageEvent.timeline, @@ -97,6 +128,7 @@ class ToolbarDisplayController { closeToolbar: closeToolbar, toolbar: toolbar!, overlayMessage: overlayMessage, + ownMessage: pangeaMessageEvent.ownMessage, ); } catch (err) { debugger(when: kDebugMode); @@ -129,7 +161,6 @@ class ToolbarDisplayController { () => toolbarModeStream.add(mode), ); } - // }); } bool get highlighted { From bb263c71c2a2887a470a11cb9cc105d3752c99c5 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Wed, 7 Aug 2024 17:10:44 -0400 Subject: [PATCH 20/38] Toolbar placed over selected message --- .../chat/message_selection_overlay.dart | 105 ++++++++++- lib/pangea/widgets/chat/message_toolbar.dart | 67 ++----- lib/pangea/widgets/chat/overlay_message.dart | 169 +++++++++--------- 3 files changed, 199 insertions(+), 142 deletions(-) diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index 4bc4c21aa..606f65b2f 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -1,26 +1,99 @@ import 'package:fluffychat/pages/chat/chat.dart'; +import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; +import 'package:fluffychat/pangea/utils/any_state_holder.dart'; +import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart'; import 'package:fluffychat/pangea/widgets/chat/overlay_footer.dart'; import 'package:fluffychat/pangea/widgets/chat/overlay_header.dart'; +import 'package:fluffychat/pangea/widgets/chat/overlay_message.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; class MessageSelectionOverlay extends StatelessWidget { final ChatController controller; + final ToolbarDisplayController toolbarController; final Function closeToolbar; final Widget toolbar; - final Widget overlayMessage; + final PangeaMessageEvent pangeaMessageEvent; final bool ownMessage; + final bool immersionMode; + final String targetId; const MessageSelectionOverlay({ required this.controller, required this.closeToolbar, required this.toolbar, - required this.overlayMessage, + required this.pangeaMessageEvent, + required this.immersionMode, required this.ownMessage, + required this.targetId, + required this.toolbarController, super.key, }); @override Widget build(BuildContext context) { + final LayerLinkAndKey layerLinkAndKey = + MatrixState.pAnyState.layerLinkAndKey(targetId); + final targetRenderBox = + layerLinkAndKey.key.currentContext?.findRenderObject(); + + double center = 290; + double left = 0; + bool showDown = false; + final double footerSize = PlatformInfos.isMobile + ? PlatformInfos.isIOS + ? 138 + : 140 + : 154; + final double headerSize = PlatformInfos.isMobile + ? PlatformInfos.isIOS + ? 122 + : 86 + : 80; + final double stackSize = + MediaQuery.of(context).size.height - footerSize - headerSize; + + if (targetRenderBox != null) { + final Size transformTargetSize = (targetRenderBox as RenderBox).size; + final Offset targetOffset = (targetRenderBox).localToGlobal(Offset.zero); + left = targetOffset.dx; + showDown = targetOffset.dy + transformTargetSize.height <= + headerSize + stackSize / 2; + + center = targetOffset.dy - + headerSize + + (showDown ? transformTargetSize.height + 3 : (-3)); + // If top of selected message extends below header + if (targetOffset.dy <= headerSize) { + center = transformTargetSize.height + 3; + showDown = true; + } + // If bottom of selected message extends below footer + else if (targetOffset.dy + transformTargetSize.height >= + headerSize + stackSize) { + center = stackSize - transformTargetSize.height - 3; + } + // If message is too long, or awkwardly positioned, + // center to avoid hitting edges of stack + if (transformTargetSize.height >= stackSize / 2 - 3 || + (targetOffset.dy < headerSize + stackSize / 2 && + targetOffset.dy + transformTargetSize.height > + headerSize + stackSize / 2)) { + center = stackSize / 2; + } + } + + final Widget overlayMessage = OverlayMessage( + pangeaMessageEvent.event, + timeline: pangeaMessageEvent.timeline, + immersionMode: immersionMode, + ownMessage: pangeaMessageEvent.ownMessage, + toolbarController: toolbarController, + width: 290, + showDown: showDown, + ); + return Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.max, @@ -35,14 +108,28 @@ class MessageSelectionOverlay extends StatelessWidget { height: 7, ), Flexible( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: - ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, + child: Stack( children: [ - toolbar, - const SizedBox(height: 9), - overlayMessage, + Positioned( + left: ownMessage ? null : left, + right: ownMessage + ? PlatformInfos.isMobile + ? 8 + : 16 + : null, + bottom: stackSize - center + 3, + child: showDown ? overlayMessage : toolbar, + ), + Positioned( + left: ownMessage ? null : left, + right: ownMessage + ? PlatformInfos.isMobile + ? 8 + : 16 + : null, + top: center + 3, + child: showDown ? toolbar : overlayMessage, + ), ], ), ), diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 29e587e85..f6431ef74 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -5,7 +5,6 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/enum/message_mode_enum.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/utils/any_state_holder.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/overlay.dart'; import 'package:fluffychat/pangea/widgets/chat/message_audio_card.dart'; @@ -14,10 +13,10 @@ import 'package:fluffychat/pangea/widgets/chat/message_speech_to_text_card.dart' import 'package:fluffychat/pangea/widgets/chat/message_text_selection.dart'; import 'package:fluffychat/pangea/widgets/chat/message_translation_card.dart'; import 'package:fluffychat/pangea/widgets/chat/message_unsubscribed_card.dart'; -import 'package:fluffychat/pangea/widgets/chat/overlay_message.dart'; import 'package:fluffychat/pangea/widgets/igc/word_data_card.dart'; import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity_card.dart'; import 'package:fluffychat/pangea/widgets/user_settings/p_language_dialog.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -75,47 +74,6 @@ class ToolbarDisplayController { } focusNode.requestFocus(); - final LayerLinkAndKey layerLinkAndKey = - MatrixState.pAnyState.layerLinkAndKey(targetId); - final targetRenderBox = - layerLinkAndKey.key.currentContext?.findRenderObject(); - if (targetRenderBox != null) { - final Size transformTargetSize = (targetRenderBox as RenderBox).size; - messageWidth = transformTargetSize.width; - final Offset targetOffset = (targetRenderBox).localToGlobal(Offset.zero); - - final double minValue = - controller.scrollController.position.minScrollExtent; - final double maxValue = - controller.scrollController.position.maxScrollExtent; - final double middlePoint = controller.scrollController.offset - - targetOffset.dy + - MediaQuery.of(context).size.height / 2 + - 37; - - // Scroll so message is right under half point of screen - controller.scrollController.animateTo( - middlePoint < minValue - ? minValue - : middlePoint > maxValue - ? maxValue - : middlePoint, - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - ); - } - - final Widget overlayMessage = OverlayMessage( - pangeaMessageEvent.event, - timeline: pangeaMessageEvent.timeline, - immersionMode: immersionMode, - ownMessage: pangeaMessageEvent.ownMessage, - toolbarController: this, - width: 300, - nextEvent: nextEvent, - previousEvent: previousEvent, - ); - // I'm not sure why I put this here, but it causes the toolbar // not to open immediately after clicking (user has to scroll or move their cursor) // so I'm commenting it out for now @@ -127,8 +85,11 @@ class ToolbarDisplayController { controller: controller, closeToolbar: closeToolbar, toolbar: toolbar!, - overlayMessage: overlayMessage, + pangeaMessageEvent: pangeaMessageEvent, + immersionMode: immersionMode, ownMessage: pangeaMessageEvent.ownMessage, + targetId: targetId, + toolbarController: this, ); } catch (err) { debugger(when: kDebugMode); @@ -372,6 +333,10 @@ class MessageToolbarState extends State { @override Widget build(BuildContext context) { + final double maxHeight = (MediaQuery.of(context).size.height - + (PlatformInfos.isIOS ? 254 : 232)) / + 2; + return Material( type: MaterialType.transparency, child: Container( @@ -386,18 +351,18 @@ class MessageToolbarState extends State { Radius.circular(25), ), ), - constraints: const BoxConstraints( - maxWidth: 300, - minWidth: 300, - maxHeight: 300, + constraints: BoxConstraints( + maxWidth: 290, + minWidth: 290, + maxHeight: maxHeight, ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( - constraints: const BoxConstraints( - minWidth: 300, - maxHeight: 228, + constraints: BoxConstraints( + minWidth: 290, + maxHeight: maxHeight - 72, ), child: SingleChildScrollView( child: AnimatedSize( diff --git a/lib/pangea/widgets/chat/overlay_message.dart b/lib/pangea/widgets/chat/overlay_message.dart index 92f6252dd..5afcd1f8b 100644 --- a/lib/pangea/widgets/chat/overlay_message.dart +++ b/lib/pangea/widgets/chat/overlay_message.dart @@ -4,6 +4,7 @@ import 'package:fluffychat/pangea/enum/use_type.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; @@ -11,8 +12,6 @@ import '../../../config/app_config.dart'; class OverlayMessage extends StatelessWidget { final Event event; - final Event? nextEvent; - final Event? previousEvent; final bool selected; final Timeline timeline; // final LanguageModel? selectedDisplayLang; @@ -21,16 +20,16 @@ class OverlayMessage extends StatelessWidget { final bool ownMessage; final ToolbarDisplayController toolbarController; final double? width; + final bool showDown; const OverlayMessage( this.event, { - this.nextEvent, - this.previousEvent, this.selected = false, required this.timeline, required this.immersionMode, required this.ownMessage, required this.toolbarController, + required this.showDown, this.width, super.key, }); @@ -49,9 +48,13 @@ class OverlayMessage extends StatelessWidget { ? Theme.of(context).colorScheme.onPrimary : Theme.of(context).colorScheme.onSurface; + const hardCorner = Radius.circular(4); const roundedCorner = Radius.circular(AppConfig.borderRadius); - const borderRadius = BorderRadius.all( - roundedCorner, + final borderRadius = BorderRadius.only( + topLeft: !showDown && !ownMessage ? hardCorner : roundedCorner, + topRight: !showDown && ownMessage ? hardCorner : roundedCorner, + bottomLeft: showDown && !ownMessage ? hardCorner : roundedCorner, + bottomRight: showDown && ownMessage ? hardCorner : roundedCorner, ); final noBubble = { @@ -83,93 +86,95 @@ class OverlayMessage extends StatelessWidget { : (color.blue * lightness).round(), ); + final double maxHeight = (MediaQuery.of(context).size.height - + (PlatformInfos.isIOS ? 254 : 232)) / + 2; + final pangeaMessageEvent = PangeaMessageEvent( event: event, timeline: timeline, ownMessage: ownMessage, ); - return Flexible( - // Make overlay message scrollable so long messages don't run offscreen - child: SingleChildScrollView( - child: Material( - color: noBubble ? Colors.transparent : color, - clipBehavior: Clip.antiAlias, - shape: const RoundedRectangleBorder( - borderRadius: borderRadius, + return SingleChildScrollView( + child: Material( + color: noBubble ? Colors.transparent : color, + clipBehavior: Clip.antiAlias, + shape: RoundedRectangleBorder( + borderRadius: borderRadius, + ), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, + ), ), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, + padding: noBubble || noPadding + ? EdgeInsets.zero + : const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + constraints: BoxConstraints( + maxWidth: width ?? FluffyThemes.columnWidth * 1.25, + maxHeight: maxHeight, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + child: MessageContent( + event.getDisplayEvent(timeline), + textColor: textColor, + borderRadius: borderRadius, + selected: selected, + pangeaMessageEvent: pangeaMessageEvent, + immersionMode: immersionMode, + toolbarController: toolbarController, + isOverlay: true, + ), ), - ), - padding: noBubble || noPadding - ? EdgeInsets.zero - : const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, + if (event.hasAggregatedEvents( + timeline, + RelationshipTypes.edit, + ) || + (pangeaMessageEvent.showUseType)) + Padding( + padding: const EdgeInsets.only( + top: 4.0, ), - constraints: BoxConstraints( - maxWidth: width ?? FluffyThemes.columnWidth * 1.25, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Flexible( - child: MessageContent( - event.getDisplayEvent(timeline), - textColor: textColor, - borderRadius: borderRadius, - selected: selected, - pangeaMessageEvent: pangeaMessageEvent, - immersionMode: immersionMode, - toolbarController: toolbarController, - isOverlay: true, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (pangeaMessageEvent.showUseType) ...[ + pangeaMessageEvent.msgUseType.iconView( + context, + textColor.withAlpha(164), + ), + const SizedBox(width: 4), + ], + if (event.hasAggregatedEvents( + timeline, + RelationshipTypes.edit, + )) ...[ + Icon( + Icons.edit_outlined, + color: textColor.withAlpha(164), + size: 14, + ), + Text( + ' - ${event.getDisplayEvent(timeline).originServerTs.localizedTimeShort(context)}', + style: TextStyle( + color: textColor.withAlpha(164), + fontSize: 12, + ), + ), + ], + ], ), ), - if (event.hasAggregatedEvents( - timeline, - RelationshipTypes.edit, - ) || - (pangeaMessageEvent.showUseType)) - Padding( - padding: const EdgeInsets.only( - top: 4.0, - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (pangeaMessageEvent.showUseType) ...[ - pangeaMessageEvent.msgUseType.iconView( - context, - textColor.withAlpha(164), - ), - const SizedBox(width: 4), - ], - if (event.hasAggregatedEvents( - timeline, - RelationshipTypes.edit, - )) ...[ - Icon( - Icons.edit_outlined, - color: textColor.withAlpha(164), - size: 14, - ), - Text( - ' - ${event.getDisplayEvent(timeline).originServerTs.localizedTimeShort(context)}', - style: TextStyle( - color: textColor.withAlpha(164), - fontSize: 12, - ), - ), - ], - ], - ), - ), - ], - ), + ], ), ), ), From f46a5bac7ff6908ff995644cf79ed8c9d9c8fbb9 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Thu, 8 Aug 2024 09:41:44 -0400 Subject: [PATCH 21/38] Shift footer down --- lib/pangea/widgets/chat/message_selection_overlay.dart | 6 +++--- lib/pangea/widgets/chat/message_toolbar.dart | 2 +- lib/pangea/widgets/chat/overlay_footer.dart | 2 +- lib/pangea/widgets/chat/overlay_message.dart | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index 606f65b2f..d40745d8e 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -43,9 +43,9 @@ class MessageSelectionOverlay extends StatelessWidget { bool showDown = false; final double footerSize = PlatformInfos.isMobile ? PlatformInfos.isIOS - ? 138 - : 140 - : 154; + ? 128 + : 130 + : 144; final double headerSize = PlatformInfos.isMobile ? PlatformInfos.isIOS ? 122 diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index f6431ef74..df1d040db 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -334,7 +334,7 @@ class MessageToolbarState extends State { @override Widget build(BuildContext context) { final double maxHeight = (MediaQuery.of(context).size.height - - (PlatformInfos.isIOS ? 254 : 232)) / + (PlatformInfos.isIOS ? 254 : 222)) / 2; return Material( diff --git a/lib/pangea/widgets/chat/overlay_footer.dart b/lib/pangea/widgets/chat/overlay_footer.dart index a2a0567df..340732f36 100644 --- a/lib/pangea/widgets/chat/overlay_footer.dart +++ b/lib/pangea/widgets/chat/overlay_footer.dart @@ -42,7 +42,7 @@ class OverlayFooter extends StatelessWidget { ), ), SizedBox( - height: FluffyThemes.isColumnMode(context) ? 25.0 : 18.0, + height: FluffyThemes.isColumnMode(context) ? 15.0 : 8.0, ), ], ), diff --git a/lib/pangea/widgets/chat/overlay_message.dart b/lib/pangea/widgets/chat/overlay_message.dart index 5afcd1f8b..d14697f01 100644 --- a/lib/pangea/widgets/chat/overlay_message.dart +++ b/lib/pangea/widgets/chat/overlay_message.dart @@ -87,7 +87,7 @@ class OverlayMessage extends StatelessWidget { ); final double maxHeight = (MediaQuery.of(context).size.height - - (PlatformInfos.isIOS ? 254 : 232)) / + (PlatformInfos.isIOS ? 244 : 222)) / 2; final pangeaMessageEvent = PangeaMessageEvent( From 43d99c526a7653bd446dfb53bbf8249f1ffb7e3a Mon Sep 17 00:00:00 2001 From: Kelrap Date: Thu, 8 Aug 2024 13:51:27 -0400 Subject: [PATCH 22/38] Tweaks --- .../chat/message_selection_overlay.dart | 26 +++++++++---------- lib/pangea/widgets/chat/message_toolbar.dart | 5 ++-- lib/pangea/widgets/chat/overlay_footer.dart | 9 +++++-- lib/pangea/widgets/chat/overlay_message.dart | 5 ++-- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index d40745d8e..ecb78eca5 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -43,14 +43,14 @@ class MessageSelectionOverlay extends StatelessWidget { bool showDown = false; final double footerSize = PlatformInfos.isMobile ? PlatformInfos.isIOS - ? 128 - : 130 - : 144; + ? 127 + : 106 + : 143; final double headerSize = PlatformInfos.isMobile ? PlatformInfos.isIOS - ? 122 - : 86 - : 80; + ? 121 + : 84 + : 79; final double stackSize = MediaQuery.of(context).size.height - footerSize - headerSize; @@ -77,10 +77,10 @@ class MessageSelectionOverlay extends StatelessWidget { // If message is too long, or awkwardly positioned, // center to avoid hitting edges of stack if (transformTargetSize.height >= stackSize / 2 - 3 || - (targetOffset.dy < headerSize + stackSize / 2 && + (targetOffset.dy < headerSize + stackSize / 2 + 30 && targetOffset.dy + transformTargetSize.height > - headerSize + stackSize / 2)) { - center = stackSize / 2; + headerSize + stackSize / 2 - 30)) { + center = stackSize / 2 + (showDown ? -30 : 30); } } @@ -104,8 +104,8 @@ class MessageSelectionOverlay extends StatelessWidget { controller: controller, closeToolbar: closeToolbar, ), - const SizedBox( - height: 7, + SizedBox( + height: PlatformInfos.isAndroid ? 3 : 6, ), Flexible( child: Stack( @@ -133,8 +133,8 @@ class MessageSelectionOverlay extends StatelessWidget { ], ), ), - const SizedBox( - height: 7, + SizedBox( + height: PlatformInfos.isAndroid ? 3 : 6, ), OverlayFooter(controller: controller), ], diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index df1d040db..9c493358c 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -334,8 +334,9 @@ class MessageToolbarState extends State { @override Widget build(BuildContext context) { final double maxHeight = (MediaQuery.of(context).size.height - - (PlatformInfos.isIOS ? 254 : 222)) / - 2; + (PlatformInfos.isIOS ? 256 : 198)) / + 2 + + 30; return Material( type: MaterialType.transparency, diff --git a/lib/pangea/widgets/chat/overlay_footer.dart b/lib/pangea/widgets/chat/overlay_footer.dart index 340732f36..a15045750 100644 --- a/lib/pangea/widgets/chat/overlay_footer.dart +++ b/lib/pangea/widgets/chat/overlay_footer.dart @@ -2,6 +2,7 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pages/chat/chat_input_row.dart'; import 'package:fluffychat/pages/chat/reactions_picker.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; import 'package:flutter/material.dart'; class OverlayFooter extends StatelessWidget { @@ -18,7 +19,7 @@ class OverlayFooter extends StatelessWidget { return Container( margin: EdgeInsets.only( - bottom: bottomSheetPadding, + bottom: PlatformInfos.isAndroid ? 0 : bottomSheetPadding, left: bottomSheetPadding, right: bottomSheetPadding, ), @@ -42,7 +43,11 @@ class OverlayFooter extends StatelessWidget { ), ), SizedBox( - height: FluffyThemes.isColumnMode(context) ? 15.0 : 8.0, + height: FluffyThemes.isColumnMode(context) + ? 15.0 + : PlatformInfos.isAndroid + ? 0 + : 8.0, ), ], ), diff --git a/lib/pangea/widgets/chat/overlay_message.dart b/lib/pangea/widgets/chat/overlay_message.dart index d14697f01..f45793eeb 100644 --- a/lib/pangea/widgets/chat/overlay_message.dart +++ b/lib/pangea/widgets/chat/overlay_message.dart @@ -87,8 +87,9 @@ class OverlayMessage extends StatelessWidget { ); final double maxHeight = (MediaQuery.of(context).size.height - - (PlatformInfos.isIOS ? 244 : 222)) / - 2; + (PlatformInfos.isIOS ? 256 : 198)) / + 2 - + 30; final pangeaMessageEvent = PangeaMessageEvent( event: event, From ea8e4497f328539dc64c839b2661ddbcb0e08fc9 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Thu, 8 Aug 2024 14:37:23 -0400 Subject: [PATCH 23/38] Close keyboard if open --- .../widgets/chat/message_selection_overlay.dart | 3 ++- lib/pangea/widgets/chat/message_toolbar.dart | 11 +++++++++-- lib/pangea/widgets/chat/overlay_message.dart | 6 +++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index ecb78eca5..f5bb9a800 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -1,3 +1,4 @@ +import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/utils/any_state_holder.dart'; @@ -57,7 +58,7 @@ class MessageSelectionOverlay extends StatelessWidget { if (targetRenderBox != null) { final Size transformTargetSize = (targetRenderBox as RenderBox).size; final Offset targetOffset = (targetRenderBox).localToGlobal(Offset.zero); - left = targetOffset.dx; + left = targetOffset.dx - (FluffyThemes.isColumnMode(context) ? 424 : 0); showDown = targetOffset.dy + transformTargetSize.height <= headerSize + stackSize / 2; diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 9c493358c..93eda8cee 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -64,7 +64,10 @@ class ToolbarDisplayController { void showToolbar(BuildContext context, {MessageMode? mode}) { // Close keyboard, if open - FocusManager.instance.primaryFocus?.unfocus(); + if (controller.inputFocus.hasFocus) { + FocusManager.instance.primaryFocus?.unfocus(); + return; + } // Close emoji picker, if open controller.showEmojiPicker = false; if (highlighted) return; @@ -334,7 +337,11 @@ class MessageToolbarState extends State { @override Widget build(BuildContext context) { final double maxHeight = (MediaQuery.of(context).size.height - - (PlatformInfos.isIOS ? 256 : 198)) / + (PlatformInfos.isWeb + ? 211 + : PlatformInfos.isIOS + ? 256 + : 198)) / 2 + 30; diff --git a/lib/pangea/widgets/chat/overlay_message.dart b/lib/pangea/widgets/chat/overlay_message.dart index f45793eeb..ccc0286fa 100644 --- a/lib/pangea/widgets/chat/overlay_message.dart +++ b/lib/pangea/widgets/chat/overlay_message.dart @@ -87,7 +87,11 @@ class OverlayMessage extends StatelessWidget { ); final double maxHeight = (MediaQuery.of(context).size.height - - (PlatformInfos.isIOS ? 256 : 198)) / + (PlatformInfos.isWeb + ? 225 + : PlatformInfos.isIOS + ? 256 + : 198)) / 2 - 30; From ecb1df70dc428efa6fb265daed6efa5a69a631ac Mon Sep 17 00:00:00 2001 From: Kelrap Date: Thu, 8 Aug 2024 17:19:16 -0400 Subject: [PATCH 24/38] Add explanation for reaction picker logic --- lib/pages/chat/reactions_picker.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pages/chat/reactions_picker.dart b/lib/pages/chat/reactions_picker.dart index 333daa049..6e57d57b2 100644 --- a/lib/pages/chat/reactions_picker.dart +++ b/lib/pages/chat/reactions_picker.dart @@ -17,6 +17,8 @@ class ReactionsPicker extends StatelessWidget { if (controller.showEmojiPicker) return const SizedBox.shrink(); final display = // #Pangea + // Even if a message is being edited/replied to, + // the reactions picker should show in selection overlay // controller.editEvent == null && // controller.replyEvent == null && // Pangea# From c42f44a7981a2d104dedde0bbcf88d042a279830 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 9 Aug 2024 10:23:09 -0400 Subject: [PATCH 25/38] Programmatically determine left/right --- .../chat/message_selection_overlay.dart | 27 +++++++++---------- lib/pangea/widgets/chat/message_toolbar.dart | 2 +- lib/pangea/widgets/chat/overlay_message.dart | 2 +- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index f5bb9a800..351b30c1e 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -40,7 +40,8 @@ class MessageSelectionOverlay extends StatelessWidget { layerLinkAndKey.key.currentContext?.findRenderObject(); double center = 290; - double left = 0; + double? left; + double? right; bool showDown = false; final double footerSize = PlatformInfos.isMobile ? PlatformInfos.isIOS @@ -58,7 +59,13 @@ class MessageSelectionOverlay extends StatelessWidget { if (targetRenderBox != null) { final Size transformTargetSize = (targetRenderBox as RenderBox).size; final Offset targetOffset = (targetRenderBox).localToGlobal(Offset.zero); - left = targetOffset.dx - (FluffyThemes.isColumnMode(context) ? 424 : 0); + if (ownMessage) { + right = MediaQuery.of(context).size.width - + targetOffset.dx - + transformTargetSize.width; + } else { + left = targetOffset.dx - (FluffyThemes.isColumnMode(context) ? 425 : 1); + } showDown = targetOffset.dy + transformTargetSize.height <= headerSize + stackSize / 2; @@ -112,22 +119,14 @@ class MessageSelectionOverlay extends StatelessWidget { child: Stack( children: [ Positioned( - left: ownMessage ? null : left, - right: ownMessage - ? PlatformInfos.isMobile - ? 8 - : 16 - : null, + left: left, + right: right, bottom: stackSize - center + 3, child: showDown ? overlayMessage : toolbar, ), Positioned( - left: ownMessage ? null : left, - right: ownMessage - ? PlatformInfos.isMobile - ? 8 - : 16 - : null, + left: left, + right: right, top: center + 3, child: showDown ? toolbar : overlayMessage, ), diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 93eda8cee..e854c2967 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -338,7 +338,7 @@ class MessageToolbarState extends State { Widget build(BuildContext context) { final double maxHeight = (MediaQuery.of(context).size.height - (PlatformInfos.isWeb - ? 211 + ? 214 : PlatformInfos.isIOS ? 256 : 198)) / diff --git a/lib/pangea/widgets/chat/overlay_message.dart b/lib/pangea/widgets/chat/overlay_message.dart index ccc0286fa..0d7115d39 100644 --- a/lib/pangea/widgets/chat/overlay_message.dart +++ b/lib/pangea/widgets/chat/overlay_message.dart @@ -88,7 +88,7 @@ class OverlayMessage extends StatelessWidget { final double maxHeight = (MediaQuery.of(context).size.height - (PlatformInfos.isWeb - ? 225 + ? 228 : PlatformInfos.isIOS ? 256 : 198)) / From baa2e17aadaa2847ec1a07d5620d7bacb1e4ca73 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 9 Aug 2024 10:43:58 -0400 Subject: [PATCH 26/38] Revert emoji picker close button --- lib/pages/chat/chat_emoji_picker.dart | 123 +++++++++++--------------- 1 file changed, 52 insertions(+), 71 deletions(-) diff --git a/lib/pages/chat/chat_emoji_picker.dart b/lib/pages/chat/chat_emoji_picker.dart index fb11f01d6..945979dc5 100644 --- a/lib/pages/chat/chat_emoji_picker.dart +++ b/lib/pages/chat/chat_emoji_picker.dart @@ -40,85 +40,66 @@ class ChatEmojiPicker extends StatelessWidget { ], ), Expanded( - child: - // #Pangea - // Display the button to close the emoji picker - // over the emoji picker - Stack( + child: TabBarView( children: [ - // Pangea# - TabBarView( - children: [ - EmojiPicker( - onEmojiSelected: controller.onEmojiSelected, - onBackspacePressed: - controller.emojiPickerBackspace, - config: Config( - emojiViewConfig: EmojiViewConfig( - noRecents: const NoRecent(), - backgroundColor: Theme.of(context) - .colorScheme - .onInverseSurface, - ), - bottomActionBarConfig: - const BottomActionBarConfig( - enabled: false, - ), - categoryViewConfig: CategoryViewConfig( - backspaceColor: theme.colorScheme.primary, - iconColor: theme.colorScheme.primary - .withOpacity(0.5), - iconColorSelected: - theme.colorScheme.primary, - indicatorColor: theme.colorScheme.primary, - ), - skinToneConfig: SkinToneConfig( - dialogBackgroundColor: Color.lerp( - theme.colorScheme.surface, - theme.colorScheme.primaryContainer, - 0.75, - )!, - indicatorColor: theme.colorScheme.onSurface, - ), - ), + EmojiPicker( + onEmojiSelected: controller.onEmojiSelected, + onBackspacePressed: controller.emojiPickerBackspace, + config: Config( + emojiViewConfig: EmojiViewConfig( + noRecents: const NoRecent(), + backgroundColor: Theme.of(context) + .colorScheme + .onInverseSurface, ), - StickerPickerDialog( - room: controller.room, - onSelected: (sticker) { - controller.room.sendEvent( - { - 'body': sticker.body, - 'info': sticker.info ?? {}, - 'url': sticker.url.toString(), - }, - type: EventTypes.Sticker, - ); - controller.hideEmojiPicker(); - }, + bottomActionBarConfig: + const BottomActionBarConfig( + enabled: false, + ), + categoryViewConfig: CategoryViewConfig( + backspaceColor: theme.colorScheme.primary, + iconColor: + theme.colorScheme.primary.withOpacity(0.5), + iconColorSelected: theme.colorScheme.primary, + indicatorColor: theme.colorScheme.primary, + ), + skinToneConfig: SkinToneConfig( + dialogBackgroundColor: Color.lerp( + theme.colorScheme.surface, + theme.colorScheme.primaryContainer, + 0.75, + )!, + indicatorColor: theme.colorScheme.onSurface, ), - ], - ), - - // #Pangea - Positioned( - bottom: 20, - left: 0, - right: 0, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - FloatingActionButton( - onPressed: controller.hideEmojiPicker, - shape: const CircleBorder(), - mini: true, - child: const Icon(Icons.close), - ), - ], ), ), + StickerPickerDialog( + room: controller.room, + onSelected: (sticker) { + controller.room.sendEvent( + { + 'body': sticker.body, + 'info': sticker.info ?? {}, + 'url': sticker.url.toString(), + }, + type: EventTypes.Sticker, + ); + controller.hideEmojiPicker(); + }, + ), ], ), ), + // #Pangea + Padding( + padding: const EdgeInsets.only(bottom: 14.0, top: 2.0), + child: FloatingActionButton( + onPressed: controller.hideEmojiPicker, + shape: const CircleBorder(), + mini: true, + child: const Icon(Icons.close), + ), + ), // Pangea# ], ), From 6acf829fe28a13fc47f64edde26937ecfb6d5f4d Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 9 Aug 2024 11:18:29 -0400 Subject: [PATCH 27/38] Emoji picker adjustments --- lib/pages/chat/chat.dart | 19 ++++++++++++++----- lib/pages/chat/chat_emoji_picker.dart | 11 +++++++++-- lib/pages/chat/chat_view.dart | 6 ++++-- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 6ec7f18e6..8db57a659 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -841,14 +841,23 @@ class ChatController extends State }); } - void hideEmojiPicker() { - // #Pangea - MatrixState.pAnyState.closeAllOverlays(); - clearSelectedEvents(); - // Pangea + // #Pangea + // void hideEmojiPicker() { + void hideEmojiPicker({bool closeOverlay = false}) { + if (closeOverlay) { + MatrixState.pAnyState.closeOverlay(); + } + // Pangea# setState(() => showEmojiPicker = false); } + // #Pangea + void hideOverlayEmojiPicker() { + MatrixState.pAnyState.closeOverlay(); + setState(() => showEmojiPicker = false); + } + // Pangea + void emojiPickerAction() { if (showEmojiPicker) { inputFocus.requestFocus(); diff --git a/lib/pages/chat/chat_emoji_picker.dart b/lib/pages/chat/chat_emoji_picker.dart index 945979dc5..36d5671b1 100644 --- a/lib/pages/chat/chat_emoji_picker.dart +++ b/lib/pages/chat/chat_emoji_picker.dart @@ -92,9 +92,16 @@ class ChatEmojiPicker extends StatelessWidget { ), // #Pangea Padding( - padding: const EdgeInsets.only(bottom: 14.0, top: 2.0), + padding: EdgeInsets.only( + bottom: (controller.emojiPickerType == + EmojiPickerType.reaction) + ? 14.0 + : 0, + top: 2.0, + ), child: FloatingActionButton( - onPressed: controller.hideEmojiPicker, + onPressed: () => + controller.hideEmojiPicker(closeOverlay: true), shape: const CircleBorder(), mini: true, child: const Icon(Icons.close), diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index b70c1c8be..648b5b780 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -447,7 +447,7 @@ class ChatView extends StatelessWidget { Positioned( left: 0, right: 0, - bottom: 16, + bottom: controller.showEmojiPicker ? 0 : 16, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, @@ -501,7 +501,9 @@ class ChatView extends StatelessWidget { ), ReplyDisplay(controller), ChatInputRow(controller), - ChatEmojiPicker(controller), + if (controller.emojiPickerType != + EmojiPickerType.reaction) + ChatEmojiPicker(controller), ], ), ), From a417f2f8cace6daf4c2b437a9ad5a0b175a8dce6 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 9 Aug 2024 14:59:19 -0400 Subject: [PATCH 28/38] Add scrolling to center messages --- .../chat/message_selection_overlay.dart | 58 ++++++++++++++++--- lib/pangea/widgets/chat/overlay_message.dart | 2 +- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index 351b30c1e..07c5bc771 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -45,14 +45,14 @@ class MessageSelectionOverlay extends StatelessWidget { bool showDown = false; final double footerSize = PlatformInfos.isMobile ? PlatformInfos.isIOS - ? 127 - : 106 + ? 128 + : 108 : 143; final double headerSize = PlatformInfos.isMobile ? PlatformInfos.isIOS ? 121 : 84 - : 79; + : 77; final double stackSize = MediaQuery.of(context).size.height - footerSize - headerSize; @@ -66,6 +66,7 @@ class MessageSelectionOverlay extends StatelessWidget { } else { left = targetOffset.dx - (FluffyThemes.isColumnMode(context) ? 425 : 1); } + showDown = targetOffset.dy + transformTargetSize.height <= headerSize + stackSize / 2; @@ -82,14 +83,53 @@ class MessageSelectionOverlay extends StatelessWidget { headerSize + stackSize) { center = stackSize - transformTargetSize.height - 3; } - // If message is too long, or awkwardly positioned, - // center to avoid hitting edges of stack - if (transformTargetSize.height >= stackSize / 2 - 3 || - (targetOffset.dy < headerSize + stackSize / 2 + 30 && - targetOffset.dy + transformTargetSize.height > - headerSize + stackSize / 2 - 30)) { + final double midpoint = headerSize + stackSize / 2; + // If message is too long, + // use default location to make full use of screen + if (transformTargetSize.height >= stackSize / 2 - 30) { center = stackSize / 2 + (showDown ? -30 : 30); } + // If message is normal size but too close + // to center of screen, scroll closer to edges + else if (targetOffset.dy > midpoint - 30 && + targetOffset.dy + transformTargetSize.height < midpoint + 30) { + final double scrollUp = + midpoint + 30 - (targetOffset.dy + transformTargetSize.height); + final double scrollDown = targetOffset.dy - (midpoint - 30); + final double minScroll = + controller.scrollController.position.minScrollExtent; + final double maxScroll = + controller.scrollController.position.maxScrollExtent; + final double currentOffset = controller.scrollController.offset; + + // If can scroll up, scroll up + if (currentOffset + scrollUp < maxScroll) { + controller.scrollController.animateTo( + currentOffset + scrollUp, + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + ); + showDown = false; + center = stackSize / 2 - 12; + } + + // Else if can scroll down, scroll down + else if (currentOffset - scrollDown > minScroll) { + controller.scrollController.animateTo( + currentOffset - scrollDown, + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + ); + showDown = true; + center = stackSize / 2 + 12; + } + + // Neither scrolling works; leave message as-is, + // and use default toolbar location + else { + center = stackSize / 2 + (showDown ? -30 : 30); + } + } } final Widget overlayMessage = OverlayMessage( diff --git a/lib/pangea/widgets/chat/overlay_message.dart b/lib/pangea/widgets/chat/overlay_message.dart index 0d7115d39..701bbf258 100644 --- a/lib/pangea/widgets/chat/overlay_message.dart +++ b/lib/pangea/widgets/chat/overlay_message.dart @@ -90,7 +90,7 @@ class OverlayMessage extends StatelessWidget { (PlatformInfos.isWeb ? 228 : PlatformInfos.isIOS - ? 256 + ? 258 : 198)) / 2 - 30; From e7fa9a35f1b78599052a09ae34e19d6af6b70d5f Mon Sep 17 00:00:00 2001 From: Kelrap Date: Mon, 12 Aug 2024 10:26:14 -0400 Subject: [PATCH 29/38] Adjust scrolling logic --- lib/pangea/utils/overlay.dart | 16 ++-- .../chat/message_selection_overlay.dart | 94 ++++++++++--------- lib/pangea/widgets/chat/message_toolbar.dart | 2 +- 3 files changed, 61 insertions(+), 51 deletions(-) diff --git a/lib/pangea/utils/overlay.dart b/lib/pangea/utils/overlay.dart index 3ac41f4ba..1fde65ff2 100644 --- a/lib/pangea/utils/overlay.dart +++ b/lib/pangea/utils/overlay.dart @@ -57,22 +57,26 @@ class OverlayUtil { ? FluffyThemes.isColumnMode(context) ? 20 : 65 - : null, + : (position == OverlayEnum.centered) + ? 0 + : null, right: (position == OverlayEnum.topRight) ? FluffyThemes.isColumnMode(context) ? 20 : 15 - : (position == OverlayEnum.bottom) + : (position == OverlayEnum.bottom || + position == OverlayEnum.centered) ? 0 : null, left: (position == OverlayEnum.bottom) ? 0 : null, - bottom: (position == OverlayEnum.bottom) ? 0 : null, + bottom: (position == OverlayEnum.bottom || + position == OverlayEnum.centered) + ? 0 + : null, width: width, height: height, child: (position != OverlayEnum.transform) - ? (position == OverlayEnum.centered) - ? Center(child: child) - : child + ? child : CompositedTransformFollower( targetAnchor: targetAnchor ?? Alignment.topLeft, followerAnchor: followerAnchor ?? Alignment.topLeft, diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index 07c5bc771..af74d88c2 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -89,13 +89,13 @@ class MessageSelectionOverlay extends StatelessWidget { if (transformTargetSize.height >= stackSize / 2 - 30) { center = stackSize / 2 + (showDown ? -30 : 30); } - // If message is normal size but too close + // If message is not too long, but too close // to center of screen, scroll closer to edges - else if (targetOffset.dy > midpoint - 30 && - targetOffset.dy + transformTargetSize.height < midpoint + 30) { - final double scrollUp = - midpoint + 30 - (targetOffset.dy + transformTargetSize.height); - final double scrollDown = targetOffset.dy - (midpoint - 30); + if (targetOffset.dy + transformTargetSize.height > midpoint - 30 && + targetOffset.dy < midpoint + 30) { + final double scrollUp = midpoint + 30 - targetOffset.dy; + final double scrollDown = + targetOffset.dy + transformTargetSize.height - (midpoint - 30); final double minScroll = controller.scrollController.position.minScrollExtent; final double maxScroll = @@ -110,7 +110,7 @@ class MessageSelectionOverlay extends StatelessWidget { curve: FluffyThemes.animationCurve, ); showDown = false; - center = stackSize / 2 - 12; + center = stackSize / 2 + 27; } // Else if can scroll down, scroll down @@ -121,11 +121,11 @@ class MessageSelectionOverlay extends StatelessWidget { curve: FluffyThemes.animationCurve, ); showDown = true; - center = stackSize / 2 + 12; + center = stackSize / 2 - 27; } // Neither scrolling works; leave message as-is, - // and use default toolbar location + // and use centered toolbar location else { center = stackSize / 2 + (showDown ? -30 : 30); } @@ -142,42 +142,48 @@ class MessageSelectionOverlay extends StatelessWidget { showDown: showDown, ); - return Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: - ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, - children: [ - OverlayHeader( - controller: controller, - closeToolbar: closeToolbar, - ), - SizedBox( - height: PlatformInfos.isAndroid ? 3 : 6, - ), - Flexible( - child: Stack( - children: [ - Positioned( - left: left, - right: right, - bottom: stackSize - center + 3, - child: showDown ? overlayMessage : toolbar, - ), - Positioned( - left: left, - right: right, - top: center + 3, - child: showDown ? toolbar : overlayMessage, - ), - ], + return Container( + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width - + (FluffyThemes.isColumnMode(context) ? 425 : 0), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: + ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, + children: [ + OverlayHeader( + controller: controller, + closeToolbar: closeToolbar, ), - ), - SizedBox( - height: PlatformInfos.isAndroid ? 3 : 6, - ), - OverlayFooter(controller: controller), - ], + SizedBox( + height: PlatformInfos.isAndroid ? 3 : 6, + ), + Flexible( + child: Stack( + children: [ + Positioned( + left: left, + right: right, + bottom: stackSize - center + 3, + child: showDown ? overlayMessage : toolbar, + ), + Positioned( + left: left, + right: right, + top: center + 3, + child: showDown ? toolbar : overlayMessage, + ), + ], + ), + ), + SizedBox( + height: PlatformInfos.isAndroid ? 3 : 6, + ), + OverlayFooter(controller: controller), + ], + ), ); } } diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index e854c2967..0cd7d8238 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -340,7 +340,7 @@ class MessageToolbarState extends State { (PlatformInfos.isWeb ? 214 : PlatformInfos.isIOS - ? 256 + ? 262 : 198)) / 2 + 30; From 6c5f4d0ece9c41e6fc4a61735c15411f6cdf37b9 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Mon, 12 Aug 2024 14:20:41 -0400 Subject: [PATCH 30/38] Try fix window resize problem --- lib/pages/chat/chat_emoji_picker.dart | 3 +- lib/pangea/utils/overlay.dart | 6 +- .../chat/message_selection_overlay.dart | 150 +++++++++--------- lib/pangea/widgets/chat/message_toolbar.dart | 4 +- 4 files changed, 87 insertions(+), 76 deletions(-) diff --git a/lib/pages/chat/chat_emoji_picker.dart b/lib/pages/chat/chat_emoji_picker.dart index 36d5671b1..9e389d8a5 100644 --- a/lib/pages/chat/chat_emoji_picker.dart +++ b/lib/pages/chat/chat_emoji_picker.dart @@ -1,6 +1,7 @@ import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/sticker_picker_dialog.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; @@ -97,7 +98,7 @@ class ChatEmojiPicker extends StatelessWidget { EmojiPickerType.reaction) ? 14.0 : 0, - top: 2.0, + top: PlatformInfos.isWeb ? 4 : 2.0, ), child: FloatingActionButton( onPressed: () => diff --git a/lib/pangea/utils/overlay.dart b/lib/pangea/utils/overlay.dart index 1fde65ff2..58846592d 100644 --- a/lib/pangea/utils/overlay.dart +++ b/lib/pangea/utils/overlay.dart @@ -68,7 +68,11 @@ class OverlayUtil { position == OverlayEnum.centered) ? 0 : null, - left: (position == OverlayEnum.bottom) ? 0 : null, + left: (position == OverlayEnum.bottom) + ? 0 + : (position == OverlayEnum.centered) + ? 0 + : null, bottom: (position == OverlayEnum.bottom || position == OverlayEnum.centered) ? 0 diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index af74d88c2..0f6a2670d 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -2,6 +2,7 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/utils/any_state_holder.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart'; import 'package:fluffychat/pangea/widgets/chat/overlay_footer.dart'; import 'package:fluffychat/pangea/widgets/chat/overlay_header.dart'; @@ -56,80 +57,89 @@ class MessageSelectionOverlay extends StatelessWidget { final double stackSize = MediaQuery.of(context).size.height - footerSize - headerSize; - if (targetRenderBox != null) { - final Size transformTargetSize = (targetRenderBox as RenderBox).size; - final Offset targetOffset = (targetRenderBox).localToGlobal(Offset.zero); - if (ownMessage) { - right = MediaQuery.of(context).size.width - - targetOffset.dx - - transformTargetSize.width; - } else { - left = targetOffset.dx - (FluffyThemes.isColumnMode(context) ? 425 : 1); - } - - showDown = targetOffset.dy + transformTargetSize.height <= - headerSize + stackSize / 2; - - center = targetOffset.dy - - headerSize + - (showDown ? transformTargetSize.height + 3 : (-3)); - // If top of selected message extends below header - if (targetOffset.dy <= headerSize) { - center = transformTargetSize.height + 3; - showDown = true; - } - // If bottom of selected message extends below footer - else if (targetOffset.dy + transformTargetSize.height >= - headerSize + stackSize) { - center = stackSize - transformTargetSize.height - 3; - } - final double midpoint = headerSize + stackSize / 2; - // If message is too long, - // use default location to make full use of screen - if (transformTargetSize.height >= stackSize / 2 - 30) { - center = stackSize / 2 + (showDown ? -30 : 30); - } - // If message is not too long, but too close - // to center of screen, scroll closer to edges - if (targetOffset.dy + transformTargetSize.height > midpoint - 30 && - targetOffset.dy < midpoint + 30) { - final double scrollUp = midpoint + 30 - targetOffset.dy; - final double scrollDown = - targetOffset.dy + transformTargetSize.height - (midpoint - 30); - final double minScroll = - controller.scrollController.position.minScrollExtent; - final double maxScroll = - controller.scrollController.position.maxScrollExtent; - final double currentOffset = controller.scrollController.offset; - - // If can scroll up, scroll up - if (currentOffset + scrollUp < maxScroll) { - controller.scrollController.animateTo( - currentOffset + scrollUp, - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - ); - showDown = false; - center = stackSize / 2 + 27; + try { + if (targetRenderBox != null) { + final Size transformTargetSize = (targetRenderBox as RenderBox).size; + final Offset targetOffset = + (targetRenderBox).localToGlobal(Offset.zero); + if (ownMessage) { + right = MediaQuery.of(context).size.width - + targetOffset.dx - + transformTargetSize.width; + } else { + left = + targetOffset.dx - (FluffyThemes.isColumnMode(context) ? 425 : 1); } - // Else if can scroll down, scroll down - else if (currentOffset - scrollDown > minScroll) { - controller.scrollController.animateTo( - currentOffset - scrollDown, - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - ); + showDown = targetOffset.dy + transformTargetSize.height / 2 <= + headerSize + stackSize / 2; + + center = targetOffset.dy - + headerSize + + (showDown ? transformTargetSize.height + 3 : (-3)); + // If top of selected message extends below header + if (targetOffset.dy <= headerSize) { + center = transformTargetSize.height + 3; showDown = true; - center = stackSize / 2 - 27; } - - // Neither scrolling works; leave message as-is, - // and use centered toolbar location - else { + // If bottom of selected message extends below footer + else if (targetOffset.dy + transformTargetSize.height >= + headerSize + stackSize) { + center = stackSize - transformTargetSize.height - 3; + } + final double midpoint = headerSize + stackSize / 2; + // If message is too long, + // use default location to make full use of screen + if (transformTargetSize.height >= stackSize / 2 - 30) { center = stackSize / 2 + (showDown ? -30 : 30); } + // If message is not too long, but too close + // to center of screen, scroll closer to edges + else if (targetOffset.dy + transformTargetSize.height > midpoint - 30 && + targetOffset.dy < midpoint + 30) { + final double scrollUp = midpoint + 30 - targetOffset.dy; + final double scrollDown = + targetOffset.dy + transformTargetSize.height - (midpoint - 30); + final double minScroll = + controller.scrollController.position.minScrollExtent; + final double maxScroll = + controller.scrollController.position.maxScrollExtent; + final double currentOffset = controller.scrollController.offset; + + // If can scroll up, scroll up + if (currentOffset + scrollUp < maxScroll) { + controller.scrollController.animateTo( + currentOffset + scrollUp, + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + ); + showDown = false; + center = stackSize / 2 + 27; + } + + // Else if can scroll down, scroll down + else if (currentOffset - scrollDown > minScroll) { + controller.scrollController.animateTo( + currentOffset - scrollDown, + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + ); + showDown = true; + center = stackSize / 2 - 27; + } + + // Neither scrolling works; leave message as-is, + // and use centered toolbar location + else { + center = stackSize / 2 + (showDown ? -30 : 30); + } + } } + } catch (err) { + MatrixState.pAnyState.closeAllOverlays(); + ErrorHandler.logError(e: err, s: StackTrace.current); + // throw L10n.of(context)!.toolbarError; + return const SizedBox(); } final Widget overlayMessage = OverlayMessage( @@ -142,11 +152,7 @@ class MessageSelectionOverlay extends StatelessWidget { showDown: showDown, ); - return Container( - constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width - - (FluffyThemes.isColumnMode(context) ? 425 : 0), - ), + return Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.max, diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 0cd7d8238..b89305b6d 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -81,7 +81,7 @@ class ToolbarDisplayController { // not to open immediately after clicking (user has to scroll or move their cursor) // so I'm commenting it out for now // WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - Widget overlayEntry; + Widget? overlayEntry; if (toolbar == null) return; try { overlayEntry = MessageSelectionOverlay( @@ -338,7 +338,7 @@ class MessageToolbarState extends State { Widget build(BuildContext context) { final double maxHeight = (MediaQuery.of(context).size.height - (PlatformInfos.isWeb - ? 214 + ? 217 : PlatformInfos.isIOS ? 262 : 198)) / From c99094f547a633f62ac683442453d521d31fd47f Mon Sep 17 00:00:00 2001 From: Kelrap Date: Tue, 13 Aug 2024 13:32:32 -0400 Subject: [PATCH 31/38] Fix window resize selection issue --- lib/pangea/widgets/chat/message_selection_overlay.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index 0f6a2670d..9fcc4f89c 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -136,6 +136,8 @@ class MessageSelectionOverlay extends StatelessWidget { } } } catch (err) { + controller.showEmojiPicker = false; + controller.selectedEvents.clear(); MatrixState.pAnyState.closeAllOverlays(); ErrorHandler.logError(e: err, s: StackTrace.current); // throw L10n.of(context)!.toolbarError; From 8e960f1b435eeb1d6950b098448bc6f16ca78321 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Wed, 14 Aug 2024 10:58:59 -0400 Subject: [PATCH 32/38] Make entirety of overlay message box scrollable --- lib/pangea/widgets/chat/overlay_message.dart | 44 ++++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/pangea/widgets/chat/overlay_message.dart b/lib/pangea/widgets/chat/overlay_message.dart index 701bbf258..3f8869399 100644 --- a/lib/pangea/widgets/chat/overlay_message.dart +++ b/lib/pangea/widgets/chat/overlay_message.dart @@ -101,29 +101,29 @@ class OverlayMessage extends StatelessWidget { ownMessage: ownMessage, ); - return SingleChildScrollView( - child: Material( - color: noBubble ? Colors.transparent : color, - clipBehavior: Clip.antiAlias, - shape: RoundedRectangleBorder( - borderRadius: borderRadius, + return Material( + color: noBubble ? Colors.transparent : color, + clipBehavior: Clip.antiAlias, + shape: RoundedRectangleBorder( + borderRadius: borderRadius, + ), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, + ), ), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, - ), - ), - padding: noBubble || noPadding - ? EdgeInsets.zero - : const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - constraints: BoxConstraints( - maxWidth: width ?? FluffyThemes.columnWidth * 1.25, - maxHeight: maxHeight, - ), + padding: noBubble || noPadding + ? EdgeInsets.zero + : const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + constraints: BoxConstraints( + maxWidth: width ?? FluffyThemes.columnWidth * 1.25, + maxHeight: maxHeight, + ), + child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, From 868e83709d43fa89e824ff0dbf2b21cdaad3abe5 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 21 Aug 2024 10:06:23 -0400 Subject: [PATCH 33/38] removed some excessive file changes to simplify PR --- lib/pages/chat/chat_emoji_picker.dart | 176 +++++++++---------- lib/pages/chat/chat_view.dart | 46 +++-- lib/pages/chat/reactions_picker.dart | 11 +- lib/pages/chat/reply_display.dart | 26 ++- lib/pangea/widgets/chat/message_toolbar.dart | 2 +- 5 files changed, 115 insertions(+), 146 deletions(-) diff --git a/lib/pages/chat/chat_emoji_picker.dart b/lib/pages/chat/chat_emoji_picker.dart index 9e389d8a5..f38243b84 100644 --- a/lib/pages/chat/chat_emoji_picker.dart +++ b/lib/pages/chat/chat_emoji_picker.dart @@ -1,7 +1,6 @@ import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/sticker_picker_dialog.dart'; -import 'package:fluffychat/utils/platform_infos.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; @@ -15,105 +14,90 @@ class ChatEmojiPicker extends StatelessWidget { @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); - return - // #Pangea - // Emoji picker can't display without a material parent - Material( - child: - // Pangea# - AnimatedContainer( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - clipBehavior: Clip.hardEdge, - decoration: const BoxDecoration(), - height: controller.showEmojiPicker - ? MediaQuery.of(context).size.height / 2 - : 0, - child: controller.showEmojiPicker - ? DefaultTabController( - length: 2, - child: Column( - children: [ - TabBar( - tabs: [ - Tab(text: L10n.of(context)!.emojis), - Tab(text: L10n.of(context)!.stickers), - ], - ), - Expanded( - child: TabBarView( - children: [ - EmojiPicker( - onEmojiSelected: controller.onEmojiSelected, - onBackspacePressed: controller.emojiPickerBackspace, - config: Config( - emojiViewConfig: EmojiViewConfig( - noRecents: const NoRecent(), - backgroundColor: Theme.of(context) - .colorScheme - .onInverseSurface, - ), - bottomActionBarConfig: - const BottomActionBarConfig( - enabled: false, - ), - categoryViewConfig: CategoryViewConfig( - backspaceColor: theme.colorScheme.primary, - iconColor: - theme.colorScheme.primary.withOpacity(0.5), - iconColorSelected: theme.colorScheme.primary, - indicatorColor: theme.colorScheme.primary, - ), - skinToneConfig: SkinToneConfig( - dialogBackgroundColor: Color.lerp( - theme.colorScheme.surface, - theme.colorScheme.primaryContainer, - 0.75, - )!, - indicatorColor: theme.colorScheme.onSurface, - ), + return AnimatedContainer( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + clipBehavior: Clip.hardEdge, + decoration: const BoxDecoration(), + height: controller.showEmojiPicker + ? MediaQuery.of(context).size.height / 2 + : 0, + child: controller.showEmojiPicker + ? DefaultTabController( + length: 2, + child: Column( + children: [ + TabBar( + tabs: [ + Tab(text: L10n.of(context)!.emojis), + Tab(text: L10n.of(context)!.stickers), + ], + ), + Expanded( + child: TabBarView( + children: [ + EmojiPicker( + onEmojiSelected: controller.onEmojiSelected, + onBackspacePressed: controller.emojiPickerBackspace, + config: Config( + emojiViewConfig: EmojiViewConfig( + noRecents: const NoRecent(), + backgroundColor: Theme.of(context) + .colorScheme + .onInverseSurface, + ), + bottomActionBarConfig: const BottomActionBarConfig( + enabled: false, + ), + categoryViewConfig: CategoryViewConfig( + backspaceColor: theme.colorScheme.primary, + iconColor: + theme.colorScheme.primary.withOpacity(0.5), + iconColorSelected: theme.colorScheme.primary, + indicatorColor: theme.colorScheme.primary, + ), + skinToneConfig: SkinToneConfig( + dialogBackgroundColor: Color.lerp( + theme.colorScheme.surface, + theme.colorScheme.primaryContainer, + 0.75, + )!, + indicatorColor: theme.colorScheme.onSurface, ), ), - StickerPickerDialog( - room: controller.room, - onSelected: (sticker) { - controller.room.sendEvent( - { - 'body': sticker.body, - 'info': sticker.info ?? {}, - 'url': sticker.url.toString(), - }, - type: EventTypes.Sticker, - ); - controller.hideEmojiPicker(); - }, - ), - ], - ), + ), + StickerPickerDialog( + room: controller.room, + onSelected: (sticker) { + controller.room.sendEvent( + { + 'body': sticker.body, + 'info': sticker.info ?? {}, + 'url': sticker.url.toString(), + }, + type: EventTypes.Sticker, + ); + controller.hideEmojiPicker(); + }, + ), + ], ), - // #Pangea - Padding( - padding: EdgeInsets.only( - bottom: (controller.emojiPickerType == - EmojiPickerType.reaction) - ? 14.0 - : 0, - top: PlatformInfos.isWeb ? 4 : 2.0, - ), - child: FloatingActionButton( - onPressed: () => - controller.hideEmojiPicker(closeOverlay: true), - shape: const CircleBorder(), - mini: true, - child: const Icon(Icons.close), - ), + ), + // #Pangea + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: FloatingActionButton( + onPressed: controller.hideEmojiPicker, + shape: const CircleBorder(), + mini: true, + child: const Icon(Icons.close), ), - // Pangea# - ], - ), - ) - : null, - ), + ), + // Pangea# + ], + ), + ) + : null, ); } } diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 1fb8c2b66..876db6e68 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -7,6 +7,7 @@ import 'package:fluffychat/pages/chat/chat_emoji_picker.dart'; import 'package:fluffychat/pages/chat/chat_event_list.dart'; import 'package:fluffychat/pages/chat/chat_input_row.dart'; import 'package:fluffychat/pages/chat/pinned_events.dart'; +import 'package:fluffychat/pages/chat/reactions_picker.dart'; import 'package:fluffychat/pages/chat/reply_display.dart'; import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart'; import 'package:fluffychat/pangea/choreographer/widgets/start_igc_button.dart'; @@ -200,27 +201,23 @@ class ChatView extends StatelessWidget { ? null : Theme.of(context).colorScheme.primary, ), - leading: - // #Pangea - // controller.selectMode - // ? IconButton( - // icon: const Icon(Icons.close), - // onPressed: controller.clearSelectedEvents, - // tooltip: L10n.of(context)!.close, - // color: Theme.of(context).colorScheme.primary, - // ) - // : - // Pangea# - UnreadRoomsBadge( - filter: (r) => - r.id != controller.roomId - // #Pangea - && - !r.isAnalyticsRoom, - // Pangea# - badgePosition: BadgePosition.topEnd(end: 8, top: 4), - child: const Center(child: BackButton()), - ), + leading: controller.selectMode + ? IconButton( + icon: const Icon(Icons.close), + onPressed: controller.clearSelectedEvents, + tooltip: L10n.of(context)!.close, + color: Theme.of(context).colorScheme.primary, + ) + : UnreadRoomsBadge( + filter: (r) => + r.id != controller.roomId + // #Pangea + && + !r.isAnalyticsRoom, + // Pangea# + badgePosition: BadgePosition.topEnd(end: 8, top: 4), + child: const Center(child: BackButton()), + ), titleSpacing: 0, title: ChatAppBarTitle(controller), actions: _appBarActions(context), @@ -452,7 +449,7 @@ class ChatView extends StatelessWidget { Positioned( left: 0, right: 0, - bottom: controller.showEmojiPicker ? 0 : 16, + bottom: 16, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, @@ -504,11 +501,10 @@ class ChatView extends StatelessWidget { ITBar( choreographer: controller.choreographer, ), + ReactionsPicker(controller), ReplyDisplay(controller), ChatInputRow(controller), - if (controller.emojiPickerType != - EmojiPickerType.reaction) - ChatEmojiPicker(controller), + ChatEmojiPicker(controller), ], ), ), diff --git a/lib/pages/chat/reactions_picker.dart b/lib/pages/chat/reactions_picker.dart index 6e57d57b2..c61dc4f48 100644 --- a/lib/pages/chat/reactions_picker.dart +++ b/lib/pages/chat/reactions_picker.dart @@ -15,15 +15,10 @@ class ReactionsPicker extends StatelessWidget { @override Widget build(BuildContext context) { if (controller.showEmojiPicker) return const SizedBox.shrink(); - final display = - // #Pangea - // Even if a message is being edited/replied to, - // the reactions picker should show in selection overlay - // controller.editEvent == null && - // controller.replyEvent == null && - // Pangea# + final display = controller.editEvent == null && + controller.replyEvent == null && controller.room.canSendDefaultMessages && - controller.selectedEvents.isNotEmpty; + controller.selectedEvents.isNotEmpty; return AnimatedContainer( duration: FluffyThemes.animationDuration, curve: FluffyThemes.animationCurve, diff --git a/lib/pages/chat/reply_display.dart b/lib/pages/chat/reply_display.dart index b25cbf0f3..e2bacac83 100644 --- a/lib/pages/chat/reply_display.dart +++ b/lib/pages/chat/reply_display.dart @@ -65,22 +65,16 @@ class _EditContent extends StatelessWidget { color: Theme.of(context).colorScheme.primary, ), Container(width: 15.0), - - // #Pangea - Flexible( - child: - // Pangea# - Text( - event.calcLocalizedBodyFallback( - MatrixLocals(L10n.of(context)!), - withSenderNamePrefix: false, - hideReply: true, - ), - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: TextStyle( - color: Theme.of(context).textTheme.bodyMedium!.color, - ), + Text( + event.calcLocalizedBodyFallback( + MatrixLocals(L10n.of(context)!), + withSenderNamePrefix: false, + hideReply: true, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: TextStyle( + color: Theme.of(context).textTheme.bodyMedium!.color, ), ), ], diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index b89305b6d..238d6b36e 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -65,7 +65,7 @@ class ToolbarDisplayController { void showToolbar(BuildContext context, {MessageMode? mode}) { // Close keyboard, if open if (controller.inputFocus.hasFocus) { - FocusManager.instance.primaryFocus?.unfocus(); + controller.inputFocus.unfocus(); return; } // Close emoji picker, if open From 0373d01f1b824c222e31c031375e342a540bdeb9 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 27 Aug 2024 13:41:36 -0400 Subject: [PATCH 34/38] simplified positioning of toolbar --- lib/pages/chat/chat.dart | 162 ++-- lib/pages/chat/chat_emoji_picker.dart | 167 ++-- lib/pages/chat/chat_event_list.dart | 1 - lib/pages/chat/chat_view.dart | 236 +++--- lib/pages/chat/events/message.dart | 751 ++++++++++-------- lib/pages/chat/events/message_content.dart | 58 +- lib/pangea/utils/any_state_holder.dart | 3 + lib/pangea/widgets/chat/message_buttons.dart | 16 +- .../chat/message_selection_overlay.dart | 345 ++++---- lib/pangea/widgets/chat/message_toolbar.dart | 254 ++---- .../chat/message_unsubscribed_card.dart | 11 +- lib/pangea/widgets/chat/overlay_footer.dart | 16 +- lib/pangea/widgets/chat/overlay_header.dart | 198 ++--- lib/pangea/widgets/chat/overlay_message.dart | 188 ----- .../toolbar_content_loading_indicator.dart | 8 +- lib/pangea/widgets/igc/pangea_rich_text.dart | 52 +- 16 files changed, 1041 insertions(+), 1425 deletions(-) delete mode 100644 lib/pangea/widgets/chat/overlay_message.dart diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 5f319d48b..08db1a1bf 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -3,14 +3,12 @@ import 'dart:developer'; import 'dart:io'; import 'package:adaptive_dialog/adaptive_dialog.dart'; -import 'package:collection/collection.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; import 'package:file_picker/file_picker.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/config/themes.dart'; -import 'package:fluffychat/pages/chat/chat_emoji_picker.dart'; import 'package:fluffychat/pages/chat/chat_view.dart'; import 'package:fluffychat/pages/chat/event_info_dialog.dart'; import 'package:fluffychat/pages/chat/recording_dialog.dart'; @@ -18,6 +16,7 @@ import 'package:fluffychat/pages/chat_details/chat_details.dart'; import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/enum/message_mode_enum.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/models/choreo_record.dart'; @@ -29,8 +28,10 @@ import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/firebase_analytics.dart'; import 'package:fluffychat/pangea/utils/overlay.dart'; import 'package:fluffychat/pangea/utils/report_message.dart'; -import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart'; +import 'package:fluffychat/pangea/widgets/chat/message_selection_overlay.dart'; +import 'package:fluffychat/pangea/widgets/chat/message_text_selection.dart'; import 'package:fluffychat/pangea/widgets/igc/pangea_text_controller.dart'; +import 'package:fluffychat/pangea/widgets/user_settings/p_language_dialog.dart'; import 'package:fluffychat/utils/error_reporter.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart'; @@ -928,20 +929,17 @@ class ChatController extends State } void copyEventsAction() { - // #Pangea - MatrixState.pAnyState.closeAllOverlays(); - // Pangea# Clipboard.setData(ClipboardData(text: _getSelectedEventString())); setState(() { showEmojiPicker = false; - selectedEvents.clear(); + // #Pangea + // selectedEvents.clear(); + clearSelectedEvents(); + // Pangea# }); } void reportEventAction() async { - // #Pangea - MatrixState.pAnyState.closeAllOverlays(); - // Pangea# final event = selectedEvents.single; // #Pangea clearSelectedEvents(); @@ -1035,9 +1033,6 @@ class ChatController extends State } void redactEventsAction() async { - // #Pangea - MatrixState.pAnyState.closeAllOverlays(); - // Pangea# final reasonInput = selectedEvents.any((event) => event.status.isSent) ? await showTextInputDialog( context: context, @@ -1086,6 +1081,9 @@ class ChatController extends State }, ); } + // #Pangea + clearSelectedEvents(); + // Pangea# setState(() { showEmojiPicker = false; selectedEvents.clear(); @@ -1133,9 +1131,6 @@ class ChatController extends State } void forwardEventsAction() async { - // #Pangea - MatrixState.pAnyState.closeAllOverlays(); - // Pangea# if (selectedEvents.length == 1) { Matrix.of(context).shareContent = selectedEvents.first.getDisplayEvent(timeline!).content; @@ -1169,7 +1164,7 @@ class ChatController extends State selectedEvents.clear(); }); // #Pangea - MatrixState.pAnyState.closeAllOverlays(); + clearSelectedEvents(); // Pangea inputFocus.requestFocus(); } @@ -1283,39 +1278,32 @@ class ChatController extends State } void pickEmojiReactionAction(Iterable allReactionEvents) async { + // #Pangea + MatrixState.pAnyState.closeAllOverlays(); + // Pangea# _allReactionEvents = allReactionEvents; emojiPickerType = EmojiPickerType.reaction; setState(() => showEmojiPicker = true); - // #Pangea - OverlayUtil.showOverlay( - context: context, - child: ChatEmojiPicker(this), - transformTargetId: selectedEvents.first.eventId, - targetAnchor: Alignment.center, - followerAnchor: Alignment.center, - backgroundColor: const Color.fromRGBO(0, 0, 0, 1).withAlpha(100), - closePrevOverlay: false, - onDismiss: hideEmojiPicker, - position: OverlayEnum.bottom, - ); - // Pangea# } void sendEmojiAction(String? emoji) async { final events = List.from(selectedEvents); setState(() => selectedEvents.clear()); - // #Pangea - MatrixState.pAnyState.closeAllOverlays(); - // Pangea# for (final event in events) { await room.sendReaction( event.eventId, emoji!, ); } + // #Pangea + clearSelectedEvents(); + // Pangea# } void clearSelectedEvents() => setState(() { + // #Pangea + MatrixState.pAnyState.closeAllOverlays(); + // Pangea# selectedEvents.clear(); showEmojiPicker = false; }); @@ -1552,12 +1540,7 @@ class ChatController extends State bool get isArchived => {Membership.leave, Membership.ban}.contains(room.membership); - void showEventInfo([Event? event]) - // #Pangea - // => - { - MatrixState.pAnyState.closeAllOverlays(); - // Pangea# + void showEventInfo([Event? event]) { (event ?? selectedEvents.single).showInfoDialog(context); // #Pangea clearSelectedEvents(); @@ -1618,80 +1601,51 @@ class ChatController extends State editEvent = null; }); - // #Pangea - final Map _pangeaMessageEvents = {}; - final Map _toolbarDisplayControllers = {}; +// #Pangea + MessageTextSelection textSelection = MessageTextSelection(); - void setPangeaMessageEvent(String eventId) { - final Event? event = timeline!.events.firstWhereOrNull( - (e) => e.eventId == eventId, - ); - if (event == null || timeline == null) return; - _pangeaMessageEvents[eventId] = PangeaMessageEvent( - event: event, - timeline: timeline!, - ownMessage: event.senderId == room.client.userID, - ); - } - - void setToolbarDisplayController( - String eventId, { - Event? nextEvent, - Event? previousEvent, + void showToolbar( + PangeaMessageEvent pangeaMessageEvent, { + MessageMode? mode, }) { - final Event? event = timeline!.events.firstWhereOrNull( - (e) => e.eventId == eventId, - ); - if (event == null || timeline == null) return; - if (_pangeaMessageEvents[eventId] == null) { - setPangeaMessageEvent(eventId); - if (_pangeaMessageEvents[eventId] == null) return; + // Close keyboard, if open + if (inputFocus.hasFocus && PlatformInfos.isMobile) { + inputFocus.unfocus(); + return; + } + // Close emoji picker, if open + showEmojiPicker = false; + + // Check if the user has set their languages. If not, prompt them to do so. + if (!MatrixState.pangeaController.languageController.languagesSet) { + pLanguageDialog(context, () {}); + return; } + Widget? overlayEntry; try { - _toolbarDisplayControllers[eventId] = ToolbarDisplayController( - targetId: event.eventId, - pangeaMessageEvent: _pangeaMessageEvents[eventId]!, - immersionMode: choreographer.immersionMode, + overlayEntry = MessageSelectionOverlay( controller: this, - nextEvent: nextEvent, - previousEvent: previousEvent, - ); - _toolbarDisplayControllers[eventId]!.setToolbar(); - } catch (e, s) { - ErrorHandler.logError( - e: e, - s: s, - m: "Failed to set toolbar display controller", - data: { - "eventId": eventId, - "event": event.toJson(), - "pangeaMessageEvent": _pangeaMessageEvents[eventId]?.toString(), - }, + event: pangeaMessageEvent.event, + pangeaMessageEvent: pangeaMessageEvent, + textSelection: textSelection, ); + } catch (err) { + debugger(when: kDebugMode); + ErrorHandler.logError(e: err, s: StackTrace.current); + return; } - } - PangeaMessageEvent? getPangeaMessageEvent(String eventId) { - if (_pangeaMessageEvents[eventId] == null) { - setPangeaMessageEvent(eventId); - } - return _pangeaMessageEvents[eventId]; - } - - ToolbarDisplayController? getToolbarDisplayController( - String eventId, { - Event? nextEvent, - Event? previousEvent, - }) { - if (_toolbarDisplayControllers[eventId] == null) { - setToolbarDisplayController( - eventId, - nextEvent: nextEvent, - previousEvent: previousEvent, - ); - } - return _toolbarDisplayControllers[eventId]; + OverlayUtil.showOverlay( + context: context, + child: overlayEntry, + transformTargetId: "", + backgroundColor: const Color.fromRGBO(0, 0, 0, 1).withAlpha(200), + closePrevOverlay: + MatrixState.pangeaController.subscriptionController.isSubscribed, + position: OverlayEnum.centered, + onDismiss: clearSelectedEvents, + ); } // Pangea# diff --git a/lib/pages/chat/chat_emoji_picker.dart b/lib/pages/chat/chat_emoji_picker.dart index f38243b84..e7bf9c744 100644 --- a/lib/pages/chat/chat_emoji_picker.dart +++ b/lib/pages/chat/chat_emoji_picker.dart @@ -14,90 +14,95 @@ class ChatEmojiPicker extends StatelessWidget { @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); - return AnimatedContainer( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - clipBehavior: Clip.hardEdge, - decoration: const BoxDecoration(), - height: controller.showEmojiPicker - ? MediaQuery.of(context).size.height / 2 - : 0, - child: controller.showEmojiPicker - ? DefaultTabController( - length: 2, - child: Column( - children: [ - TabBar( - tabs: [ - Tab(text: L10n.of(context)!.emojis), - Tab(text: L10n.of(context)!.stickers), - ], - ), - Expanded( - child: TabBarView( - children: [ - EmojiPicker( - onEmojiSelected: controller.onEmojiSelected, - onBackspacePressed: controller.emojiPickerBackspace, - config: Config( - emojiViewConfig: EmojiViewConfig( - noRecents: const NoRecent(), - backgroundColor: Theme.of(context) - .colorScheme - .onInverseSurface, - ), - bottomActionBarConfig: const BottomActionBarConfig( - enabled: false, - ), - categoryViewConfig: CategoryViewConfig( - backspaceColor: theme.colorScheme.primary, - iconColor: - theme.colorScheme.primary.withOpacity(0.5), - iconColorSelected: theme.colorScheme.primary, - indicatorColor: theme.colorScheme.primary, - ), - skinToneConfig: SkinToneConfig( - dialogBackgroundColor: Color.lerp( - theme.colorScheme.surface, - theme.colorScheme.primaryContainer, - 0.75, - )!, - indicatorColor: theme.colorScheme.onSurface, - ), - ), - ), - StickerPickerDialog( - room: controller.room, - onSelected: (sticker) { - controller.room.sendEvent( - { - 'body': sticker.body, - 'info': sticker.info ?? {}, - 'url': sticker.url.toString(), - }, - type: EventTypes.Sticker, - ); - controller.hideEmojiPicker(); - }, - ), + // #Pangea + return Material( + // Pangea# + child: AnimatedContainer( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + clipBehavior: Clip.hardEdge, + decoration: const BoxDecoration(), + height: controller.showEmojiPicker + ? MediaQuery.of(context).size.height / 2 + : 0, + child: controller.showEmojiPicker + ? DefaultTabController( + length: 2, + child: Column( + children: [ + TabBar( + tabs: [ + Tab(text: L10n.of(context)!.emojis), + Tab(text: L10n.of(context)!.stickers), ], ), - ), - // #Pangea - Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: FloatingActionButton( - onPressed: controller.hideEmojiPicker, - shape: const CircleBorder(), - mini: true, - child: const Icon(Icons.close), + Expanded( + child: TabBarView( + children: [ + EmojiPicker( + onEmojiSelected: controller.onEmojiSelected, + onBackspacePressed: controller.emojiPickerBackspace, + config: Config( + emojiViewConfig: EmojiViewConfig( + noRecents: const NoRecent(), + backgroundColor: Theme.of(context) + .colorScheme + .onInverseSurface, + ), + bottomActionBarConfig: + const BottomActionBarConfig( + enabled: false, + ), + categoryViewConfig: CategoryViewConfig( + backspaceColor: theme.colorScheme.primary, + iconColor: + theme.colorScheme.primary.withOpacity(0.5), + iconColorSelected: theme.colorScheme.primary, + indicatorColor: theme.colorScheme.primary, + ), + skinToneConfig: SkinToneConfig( + dialogBackgroundColor: Color.lerp( + theme.colorScheme.surface, + theme.colorScheme.primaryContainer, + 0.75, + )!, + indicatorColor: theme.colorScheme.onSurface, + ), + ), + ), + StickerPickerDialog( + room: controller.room, + onSelected: (sticker) { + controller.room.sendEvent( + { + 'body': sticker.body, + 'info': sticker.info ?? {}, + 'url': sticker.url.toString(), + }, + type: EventTypes.Sticker, + ); + controller.hideEmojiPicker(); + }, + ), + ], + ), ), - ), - // Pangea# - ], - ), - ) - : null, + // #Pangea + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: FloatingActionButton( + onPressed: controller.hideEmojiPicker, + shape: const CircleBorder(), + mini: true, + child: const Icon(Icons.close), + ), + ), + // Pangea# + ], + ), + ) + : null, + ), ); } } diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 4813823c1..ccb8bf471 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -188,7 +188,6 @@ class ChatEventList extends StatelessWidget { longPressSelect: controller.selectedEvents.isNotEmpty, // #Pangea immersionMode: controller.choreographer.immersionMode, - definitions: controller.choreographer.definitionsEnabled, controller: controller, // Pangea# selected: controller.selectedEvents diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 876db6e68..737b29534 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -7,7 +7,6 @@ import 'package:fluffychat/pages/chat/chat_emoji_picker.dart'; import 'package:fluffychat/pages/chat/chat_event_list.dart'; import 'package:fluffychat/pages/chat/chat_input_row.dart'; import 'package:fluffychat/pages/chat/pinned_events.dart'; -import 'package:fluffychat/pages/chat/reactions_picker.dart'; import 'package:fluffychat/pages/chat/reply_display.dart'; import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart'; import 'package:fluffychat/pangea/choreographer/widgets/start_igc_button.dart'; @@ -36,100 +35,98 @@ class ChatView extends StatelessWidget { const ChatView(this.controller, {super.key}); List _appBarActions(BuildContext context) { - if (controller.selectMode) { - return [ - if (controller.canEditSelectedEvents) - IconButton( - icon: const Icon(Icons.edit_outlined), - tooltip: L10n.of(context)!.edit, - onPressed: controller.editSelectedEventAction, - ), - // #Pangea - if (controller.selectedEvents.length == 1 && - controller.selectedEvents.single.messageType == MessageTypes.Text) - // Pangea# - IconButton( - icon: const Icon(Icons.copy_outlined), - tooltip: L10n.of(context)!.copy, - onPressed: controller.copyEventsAction, - ), - if (controller.canSaveSelectedEvent) - // Use builder context to correctly position the share dialog on iPad - Builder( - builder: (context) => IconButton( - icon: Icon(Icons.adaptive.share), - tooltip: L10n.of(context)!.share, - onPressed: () => controller.saveSelectedEvent(context), - ), - ), - if (controller.canPinSelectedEvents) - IconButton( - icon: const Icon(Icons.push_pin_outlined), - onPressed: controller.pinEvent, - tooltip: L10n.of(context)!.pinMessage, - ), - if (controller.canRedactSelectedEvents) - IconButton( - icon: const Icon(Icons.delete_outlined), - tooltip: L10n.of(context)!.redactMessage, - onPressed: controller.redactEventsAction, - ), - if (controller.selectedEvents.length == 1) - PopupMenuButton<_EventContextAction>( - onSelected: (action) { - switch (action) { - case _EventContextAction.info: - controller.showEventInfo(); - controller.clearSelectedEvents(); - break; - case _EventContextAction.report: - controller.reportEventAction(); - break; - } - }, - itemBuilder: (context) => [ - PopupMenuItem( - value: _EventContextAction.info, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.info_outlined), - const SizedBox(width: 12), - Text(L10n.of(context)!.messageInfo), - ], - ), - ), - if (controller.selectedEvents.single.status.isSent) - PopupMenuItem( - value: _EventContextAction.report, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon( - Icons.shield_outlined, - color: Colors.red, - ), - const SizedBox(width: 12), - Text(L10n.of(context)!.reportMessage), - ], - ), - ), - ], - ), - ]; - // #Pangea - } else { - return [ - RoundTimer(controller: controller), - const SizedBox( - width: 10, - ), - ChatSettingsPopupMenu( - controller.room, - (!controller.room.isDirectChat && !controller.room.isArchived), - ), - ]; - } + // #Pangea + // if (controller.selectMode) { + // return [ + // if (controller.canEditSelectedEvents) + // IconButton( + // icon: const Icon(Icons.edit_outlined), + // tooltip: L10n.of(context)!.edit, + // onPressed: controller.editSelectedEventAction, + // ), + // // #Pangea + // if (controller.selectedEvents.length == 1 && + // controller.selectedEvents.single.messageType == MessageTypes.Text) + // // Pangea# + // IconButton( + // icon: const Icon(Icons.copy_outlined), + // tooltip: L10n.of(context)!.copy, + // onPressed: controller.copyEventsAction, + // ), + // if (controller.canSaveSelectedEvent) + // // Use builder context to correctly position the share dialog on iPad + // Builder( + // builder: (context) => IconButton( + // icon: Icon(Icons.adaptive.share), + // tooltip: L10n.of(context)!.share, + // onPressed: () => controller.saveSelectedEvent(context), + // ), + // ), + // if (controller.canPinSelectedEvents) + // IconButton( + // icon: const Icon(Icons.push_pin_outlined), + // onPressed: controller.pinEvent, + // tooltip: L10n.of(context)!.pinMessage, + // ), + // if (controller.canRedactSelectedEvents) + // IconButton( + // icon: const Icon(Icons.delete_outlined), + // tooltip: L10n.of(context)!.redactMessage, + // onPressed: controller.redactEventsAction, + // ), + // if (controller.selectedEvents.length == 1) + // PopupMenuButton<_EventContextAction>( + // onSelected: (action) { + // switch (action) { + // case _EventContextAction.info: + // controller.showEventInfo(); + // controller.clearSelectedEvents(); + // break; + // case _EventContextAction.report: + // controller.reportEventAction(); + // break; + // } + // }, + // itemBuilder: (context) => [ + // PopupMenuItem( + // value: _EventContextAction.info, + // child: Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // const Icon(Icons.info_outlined), + // const SizedBox(width: 12), + // Text(L10n.of(context)!.messageInfo), + // ], + // ), + // ), + // if (controller.selectedEvents.single.status.isSent) + // PopupMenuItem( + // value: _EventContextAction.report, + // child: Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // const Icon( + // Icons.shield_outlined, + // color: Colors.red, + // ), + // const SizedBox(width: 12), + // Text(L10n.of(context)!.reportMessage), + // ], + // ), + // ), + // ], + // ), + // ]; + return [ + RoundTimer(controller: controller), + const SizedBox( + width: 10, + ), + ChatSettingsPopupMenu( + controller.room, + (!controller.room.isDirectChat && !controller.room.isArchived), + ), + ]; // else if (!controller.room.isArchived) { // return [ // if (Matrix.of(context).voipPlugin != null && @@ -196,28 +193,34 @@ class ChatView extends StatelessWidget { } return Scaffold( appBar: AppBar( - actionsIconTheme: IconThemeData( - color: controller.selectedEvents.isEmpty - ? null - : Theme.of(context).colorScheme.primary, + actionsIconTheme: const IconThemeData( + // #Pangea + // color: controller.selectedEvents.isEmpty + // ? null + // : Theme.of(context).colorScheme.primary, + // Pangea# + ), + leading: + // #Pangea + // controller.selectMode + // ? IconButton( + // icon: const Icon(Icons.close), + // onPressed: controller.clearSelectedEvents, + // tooltip: L10n.of(context)!.close, + // color: Theme.of(context).colorScheme.primary, + // ) + // : + // Pangea# + UnreadRoomsBadge( + filter: (r) => + r.id != controller.roomId + // #Pangea + && + !r.isAnalyticsRoom, + // Pangea# + badgePosition: BadgePosition.topEnd(end: 8, top: 4), + child: const Center(child: BackButton()), ), - leading: controller.selectMode - ? IconButton( - icon: const Icon(Icons.close), - onPressed: controller.clearSelectedEvents, - tooltip: L10n.of(context)!.close, - color: Theme.of(context).colorScheme.primary, - ) - : UnreadRoomsBadge( - filter: (r) => - r.id != controller.roomId - // #Pangea - && - !r.isAnalyticsRoom, - // Pangea# - badgePosition: BadgePosition.topEnd(end: 8, top: 4), - child: const Center(child: BackButton()), - ), titleSpacing: 0, title: ChatAppBarTitle(controller), actions: _appBarActions(context), @@ -501,7 +504,6 @@ class ChatView extends StatelessWidget { ITBar( choreographer: controller.choreographer, ), - ReactionsPicker(controller), ReplyDisplay(controller), ChatInputRow(controller), ChatEmojiPicker(controller), diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index 280ea3082..cedd9d3c8 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -2,13 +2,14 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/enum/use_type.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; +import 'package:fluffychat/pangea/utils/any_state_holder.dart'; import 'package:fluffychat/pangea/widgets/chat/message_buttons.dart'; -import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/string_color.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; import 'package:swipe_to_action/swipe_to_action.dart'; @@ -38,8 +39,8 @@ class Message extends StatelessWidget { // #Pangea // final void Function(Event) onSelect; final bool immersionMode; - final bool definitions; final ChatController controller; + final bool isOverlay; // Pangea# final Color? avatarPresenceBackgroundColor; @@ -64,21 +65,33 @@ class Message extends StatelessWidget { this.avatarPresenceBackgroundColor, // #Pangea required this.immersionMode, - required this.definitions, required this.controller, + this.isOverlay = false, // Pangea# super.key, }); // #Pangea - PangeaMessageEvent? get pangeaMessageEvent => - controller.getPangeaMessageEvent(event.eventId); + void showToolbar(PangeaMessageEvent? pangeaMessageEvent) { + if (pangeaMessageEvent != null && !isOverlay) { + HapticFeedback.mediumImpact(); + controller.showToolbar(pangeaMessageEvent); + } + } // Pangea# @override Widget build(BuildContext context) { // #Pangea debugPrint('Message.build()'); + PangeaMessageEvent? pangeaMessageEvent; + if (event.type == EventTypes.Message) { + pangeaMessageEvent = PangeaMessageEvent( + event: event, + timeline: timeline, + ownMessage: event.senderId == Matrix.of(context).client.userID, + ); + } WidgetsBinding.instance.addPostFrameCallback((_) { if (controller.pangeaEditingEvent?.eventId == event.eventId) { pangeaMessageEvent?.updateLatestEdit(); @@ -109,8 +122,13 @@ class Message extends StatelessWidget { // ignore: deprecated_member_use var color = Theme.of(context).colorScheme.surfaceVariant; final displayTime = event.type == EventTypes.RoomCreate || - nextEvent == null || - !event.originServerTs.sameEnvironment(nextEvent!.originServerTs); + nextEvent == null || + !event.originServerTs.sameEnvironment(nextEvent!.originServerTs) + // #Pangea + && + !isOverlay + // Pangea# + ; final nextEventSameSender = nextEvent != null && { EventTypes.Message, @@ -163,370 +181,405 @@ class Message extends StatelessWidget { : Theme.of(context).colorScheme.primary; } - // #Pangea - ToolbarDisplayController? toolbarController; - if (event.type == EventTypes.Message && - !event.redacted && - (event.messageType == MessageTypes.Text || - event.messageType == MessageTypes.Notice || - event.messageType == MessageTypes.Audio)) { - toolbarController = controller.getToolbarDisplayController( - event.eventId, - nextEvent: nextEvent, - previousEvent: previousEvent, - ); - } - // Pangea# - final resetAnimateIn = this.resetAnimateIn; var animateIn = this.animateIn; - final row = StatefulBuilder( - builder: (context, setState) { - if (animateIn && resetAnimateIn != null) { - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - animateIn = false; - setState(resetAnimateIn); - }); - } - return AnimatedSize( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - clipBehavior: Clip.none, - alignment: ownMessage ? Alignment.bottomRight : Alignment.bottomLeft, - child: animateIn - ? const SizedBox(height: 0, width: double.infinity) - : Stack( - children: [ - Positioned( - top: 0, - bottom: 0, - left: 0, - right: 0, - child: InkWell( - // #Pangea - // onTap: () => onSelect(event), - // onLongPress: () => onSelect(event), - // Pangea# - borderRadius: - BorderRadius.circular(AppConfig.borderRadius / 2), - child: Material( + final row = + // #Pangea + Material( + color: Colors.transparent, + child: + // Pangea# + StatefulBuilder( + builder: (context, setState) { + if (animateIn && resetAnimateIn != null) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + animateIn = false; + setState(resetAnimateIn); + }); + } + return AnimatedSize( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + clipBehavior: Clip.none, + alignment: + ownMessage ? Alignment.bottomRight : Alignment.bottomLeft, + child: animateIn + ? const SizedBox(height: 0, width: double.infinity) + : Stack( + children: [ + Positioned( + top: 0, + bottom: 0, + left: 0, + right: 0, + child: InkWell( + // #Pangea + onTap: () => MatrixState.pAnyState.closeOverlay(), + // onTap: () => onSelect(event), + // onLongPress: () => onSelect(event), + // Pangea# borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), - color: selected - ? Theme.of(context) - .colorScheme - .secondaryContainer - .withAlpha(100) - : highlightMarker - ? Theme.of(context) - .colorScheme - .tertiaryContainer - .withAlpha(100) - : Colors.transparent, + child: Material( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius / 2, + ), + color: selected + ? Theme.of(context) + .colorScheme + .secondaryContainer + .withAlpha(100) + : highlightMarker + ? Theme.of(context) + .colorScheme + .tertiaryContainer + .withAlpha(100) + : Colors.transparent, + ), ), ), - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: rowMainAxisAlignment, - children: [ - // #Pangea - // if (longPressSelect) - // SizedBox( - // height: 32, - // width: Avatar.defaultSize, - // child: Checkbox.adaptive( - // value: selected, - // shape: const CircleBorder(), - // onChanged: (_) => onSelect(event), - // ), - // ) - // else - // Pangea# - if (nextEventSameSender || ownMessage) - SizedBox( - width: Avatar.defaultSize, - child: Center( - child: SizedBox( - width: 16, - height: 16, - child: event.status == EventStatus.error - ? const Icon(Icons.error, color: Colors.red) - : event.fileSendingStatus != null - ? const CircularProgressIndicator - .adaptive( - strokeWidth: 1, - ) - : null, - ), - ), - ) - else - FutureBuilder( - future: event.fetchSenderUser(), - builder: (context, snapshot) { - final user = snapshot.data ?? - event.senderFromMemoryOrFallback; - return Avatar( - // mxContent: user.avatarUrl, - // name: user.calcDisplayname(), - // presenceUserId: user.stateKey, - name: "?", - presenceBackgroundColor: - avatarPresenceBackgroundColor, - onTap: () => onAvatarTab(event), - ); - }, - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (!nextEventSameSender) - Padding( - padding: const EdgeInsets.only( - left: 8.0, - bottom: 4, - ), - child: ownMessage || event.room.isDirectChat - ? const SizedBox(height: 12) - : FutureBuilder( - future: event.fetchSenderUser(), - builder: (context, snapshot) { - // final displayname = snapshot.data - // ?.calcDisplayname() ?? - // event.senderFromMemoryOrFallback - // .calcDisplayname(); - const displayname = "?"; - return Text( - displayname, - style: TextStyle( - fontSize: 12, - color: (Theme.of(context) - .brightness == - Brightness.light - ? displayname.color - : displayname - .lightColorText), - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ); - }, - ), - ), - Container( - alignment: alignment, - padding: const EdgeInsets.only(left: 8), - child: GestureDetector( + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: rowMainAxisAlignment, + children: [ + // #Pangea + // if (longPressSelect) + // SizedBox( + // height: 32, + // width: Avatar.defaultSize, + // child: Checkbox.adaptive( + // value: selected, + // shape: const CircleBorder(), + // onChanged: (_) => onSelect(event), + // ), + // ) + // else + // Pangea# + if (nextEventSameSender || + ownMessage // #Pangea - onTap: () => toolbarController?.showToolbar( - context, - ), - onDoubleTap: () => - toolbarController?.showToolbar(context), - // onLongPress: longPressSelect - // ? null - // : () { - // HapticFeedback.heavyImpact(); - // onSelect(event); - // }, - // Pangea# - child: AnimatedOpacity( - opacity: animateIn - ? 0 - : event.redacted || - event.messageType == - MessageTypes.BadEncrypted || - event.status.isSending - ? 0.5 - : 1, - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - child: Material( - color: - noBubble ? Colors.transparent : color, - clipBehavior: Clip.antiAlias, - shape: RoundedRectangleBorder( - borderRadius: borderRadius, - ), - // #Pangea - child: CompositedTransformTarget( - link: MatrixState.pAnyState - .layerLinkAndKey(event.eventId) - .link, - child: Container( - key: MatrixState.pAnyState - .layerLinkAndKey(event.eventId) - .key, - // Pangea# - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, - ), - ), - padding: noBubble || noPadding - ? EdgeInsets.zero - : const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, + || + isOverlay + // Pangea# + ) + SizedBox( + width: Avatar.defaultSize, + child: Center( + child: SizedBox( + width: 16, + height: 16, + child: event.status == EventStatus.error + ? const Icon( + Icons.error, + color: Colors.red, + ) + : event.fileSendingStatus != null + ? const CircularProgressIndicator + .adaptive( + strokeWidth: 1, + ) + : null, + ), + ), + ) + else + FutureBuilder( + future: event.fetchSenderUser(), + builder: (context, snapshot) { + final user = snapshot.data ?? + event.senderFromMemoryOrFallback; + return Avatar( + // mxContent: user.avatarUrl, + // name: user.calcDisplayname(), + // presenceUserId: user.stateKey, + name: "?", + presenceBackgroundColor: + avatarPresenceBackgroundColor, + onTap: () => onAvatarTab(event), + ); + }, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if (!nextEventSameSender + // #Pangea + && + !isOverlay + // Pangea# + ) + Padding( + padding: const EdgeInsets.only( + left: 8.0, + bottom: 4, + ), + child: ownMessage || event.room.isDirectChat + ? const SizedBox(height: 12) + : FutureBuilder( + future: event.fetchSenderUser(), + builder: (context, snapshot) { + // final displayname = snapshot.data + // ?.calcDisplayname() ?? + // event.senderFromMemoryOrFallback + // .calcDisplayname(); + const displayname = "?"; + return Text( + displayname, + style: TextStyle( + fontSize: 12, + color: (Theme.of(context) + .brightness == + Brightness.light + ? displayname.color + : displayname + .lightColorText), ), - constraints: const BoxConstraints( - maxWidth: - FluffyThemes.columnWidth * 1.5, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + }, ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - if (event.relationshipType == - RelationshipTypes.reply) - FutureBuilder( - future: event - .getReplyEvent(timeline), - builder: ( - BuildContext context, - snapshot, - ) { - final replyEvent = snapshot - .hasData - ? snapshot.data! - : Event( - eventId: event - .relationshipEventId!, - content: { - 'msgtype': - 'm.text', - 'body': '...', - }, - senderId: - event.senderId, - type: - 'm.room.message', - room: event.room, - status: EventStatus - .sent, - originServerTs: - DateTime.now(), - ); - return Padding( - padding: - const EdgeInsets.only( - bottom: 4.0, - ), - child: InkWell( - borderRadius: - ReplyContent - .borderRadius, - onTap: () => - scrollToEventId( - replyEvent.eventId, + ), + Container( + alignment: alignment, + padding: const EdgeInsets.only(left: 8), + child: GestureDetector( + // #Pangea + onTap: () => + showToolbar(pangeaMessageEvent), + onDoubleTap: () => + showToolbar(pangeaMessageEvent), + onLongPress: () => + showToolbar(pangeaMessageEvent), + // onLongPress: longPressSelect + // ? null + // : () { + // HapticFeedback.heavyImpact(); + // onSelect(event); + // }, + // Pangea# + child: AnimatedOpacity( + opacity: animateIn + ? 0 + : event.redacted || + event.messageType == + MessageTypes + .BadEncrypted || + event.status.isSending + ? 0.5 + : 1, + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + child: Material( + color: noBubble + ? Colors.transparent + : color, + clipBehavior: Clip.antiAlias, + shape: RoundedRectangleBorder( + borderRadius: borderRadius, + ), + // #Pangea + child: CompositedTransformTarget( + link: isOverlay + ? LayerLinkAndKey('overlay_msg') + .link + : MatrixState.pAnyState + .layerLinkAndKey( + event.eventId, + ) + .link, + child: Container( + key: isOverlay + ? LayerLinkAndKey('overlay_msg') + .key + : MatrixState.pAnyState + .layerLinkAndKey( + event.eventId, + ) + .key, + // Pangea# + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular( + AppConfig.borderRadius, + ), + ), + padding: noBubble || noPadding + ? EdgeInsets.zero + : const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + constraints: const BoxConstraints( + maxWidth: + FluffyThemes.columnWidth * + 1.5, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + if (event.relationshipType == + RelationshipTypes.reply) + FutureBuilder( + future: event.getReplyEvent( + timeline, + ), + builder: ( + BuildContext context, + snapshot, + ) { + final replyEvent = + snapshot.hasData + ? snapshot.data! + : Event( + eventId: event + .relationshipEventId!, + content: { + 'msgtype': + 'm.text', + 'body': + '...', + }, + senderId: event + .senderId, + type: + 'm.room.message', + room: event + .room, + status: + EventStatus + .sent, + originServerTs: + DateTime + .now(), + ); + return Padding( + padding: + const EdgeInsets + .only( + bottom: 4.0, ), - child: AbsorbPointer( - child: ReplyContent( - replyEvent, - ownMessage: - ownMessage, - timeline: timeline, + child: InkWell( + borderRadius: + ReplyContent + .borderRadius, + onTap: () => + scrollToEventId( + replyEvent.eventId, + ), + child: AbsorbPointer( + child: ReplyContent( + replyEvent, + ownMessage: + ownMessage, + timeline: + timeline, + ), ), ), - ), - ); - }, - ), - MessageContent( - displayEvent, - textColor: textColor, - onInfoTab: onInfoTab, - borderRadius: borderRadius, - // #Pangea - selected: selected, - pangeaMessageEvent: - pangeaMessageEvent, - immersionMode: immersionMode, - toolbarController: - toolbarController, - // Pangea# - ), - if (event.hasAggregatedEvents( - timeline, - RelationshipTypes.edit, - ) - // #Pangea - || - (pangeaMessageEvent - ?.showUseType ?? - false) - // Pangea# - ) - Padding( - padding: - const EdgeInsets.only( - top: 4.0, + ); + }, ), - child: Row( - mainAxisSize: - MainAxisSize.min, - children: [ - // #Pangea - if (pangeaMessageEvent - ?.showUseType ?? - false) ...[ - pangeaMessageEvent! - .msgUseType - .iconView( - context, - textColor - .withAlpha(164), - ), - const SizedBox( - width: 4, - ), - ], - if (event - .hasAggregatedEvents( - timeline, - RelationshipTypes.edit, - )) ...[ - // Pangea# - Icon( - Icons.edit_outlined, - color: textColor - .withAlpha(164), - size: 14, - ), - Text( - ' - ${displayEvent.originServerTs.localizedTimeShort(context)}', - style: TextStyle( + MessageContent( + displayEvent, + textColor: textColor, + onInfoTab: onInfoTab, + borderRadius: borderRadius, + // #Pangea + selected: selected, + pangeaMessageEvent: + pangeaMessageEvent, + immersionMode: immersionMode, + isOverlay: isOverlay, + controller: controller, + // Pangea# + ), + if (event.hasAggregatedEvents( + timeline, + RelationshipTypes + .edit, + ) + // #Pangea + || + (pangeaMessageEvent + ?.showUseType ?? + false) + // Pangea# + ) + Padding( + padding: + const EdgeInsets.only( + top: 4.0, + ), + child: Row( + mainAxisSize: + MainAxisSize.min, + children: [ + // #Pangea + if (pangeaMessageEvent + ?.showUseType ?? + false) ...[ + pangeaMessageEvent! + .msgUseType + .iconView( + context, + textColor + .withAlpha(164), + ), + const SizedBox( + width: 4, + ), + ], + if (event + .hasAggregatedEvents( + timeline, + RelationshipTypes + .edit, + )) ...[ + // Pangea# + Icon( + Icons.edit_outlined, color: textColor .withAlpha(164), - fontSize: 12, + size: 14, ), - ), + Text( + ' - ${displayEvent.originServerTs.localizedTimeShort(context)}', + style: TextStyle( + color: textColor + .withAlpha( + 164, + ), + fontSize: 12, + ), + ), + ], ], - ], + ), ), - ), - ], + ], + ), ), ), ), ), ), ), - ), - ], + ], + ), ), - ), - ], - ), - ], - ), - ); - }, + ], + ), + ], + ), + ); + }, + ), ); Widget container; final showReceiptsRow = @@ -544,7 +597,10 @@ class Message extends StatelessWidget { crossAxisAlignment: ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ - if (displayTime || selected) + // #Pangea + // if (displayTime || selected) + if ((displayTime || selected) && !isOverlay) + // Pangea# Padding( padding: displayTime ? const EdgeInsets.symmetric(vertical: 8.0) @@ -595,7 +651,8 @@ class Message extends StatelessWidget { children: [ if (pangeaMessageEvent?.showMessageButtons ?? false) MessageButtons( - toolbarController: toolbarController, + controller: controller, + pangeaMessageEvent: pangeaMessageEvent!, ), MessageReactions(event, timeline), ], diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index 6431f8efb..6a3b2078c 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -1,16 +1,15 @@ import 'dart:math'; +import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pages/chat/events/video_player.dart'; -import 'package:fluffychat/pangea/enum/message_mode_enum.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/widgets/chat/message_context_menu.dart'; -import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart'; import 'package:fluffychat/pangea/widgets/igc/pangea_rich_text.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:matrix/matrix.dart'; @@ -37,8 +36,8 @@ class MessageContent extends StatelessWidget { //here rather than passing the choreographer? pangea rich text, a widget //further down in the chain is also using pangeaController so its not constant final bool immersionMode; - final ToolbarDisplayController? toolbarController; final bool isOverlay; + final ChatController controller; // Pangea# const MessageContent( @@ -50,8 +49,8 @@ class MessageContent extends StatelessWidget { required this.selected, this.pangeaMessageEvent, required this.immersionMode, - required this.toolbarController, this.isOverlay = false, + required this.controller, // Pangea# required this.borderRadius, }); @@ -306,45 +305,34 @@ class MessageContent extends StatelessWidget { style: messageTextStyle, pangeaMessageEvent: pangeaMessageEvent!, immersionMode: immersionMode, - toolbarController: toolbarController, + isOverlay: isOverlay, + controller: controller, ), ); - } else if (pangeaMessageEvent != null) { - toolbarController?.toolbar?.textSelection.setMessageText( - (event.getDisplayEvent(pangeaMessageEvent!.timeline).body), + } + + if (isOverlay) { + controller.textSelection.setMessageText( + event.calcLocalizedBodyFallback( + MatrixLocals(L10n.of(context)!), + hideReply: true, + ), ); } return SelectableLinkify( onSelectionChanged: (selection, cause) { - if (cause == SelectionChangedCause.longPress && - toolbarController != null && - pangeaMessageEvent != null && - !(toolbarController!.highlighted) && - !selected) { - return; + if (isOverlay) { + controller.textSelection.onTextSelection(selection); } - toolbarController?.toolbar?.textSelection - .onTextSelection(selection); }, - onTap: () => toolbarController?.showToolbar(context), - contextMenuBuilder: (context, state) => - (toolbarController?.highlighted ?? false) - ? const SizedBox.shrink() - : MessageContextMenu.contextMenuOverride( - context: context, - textSelection: state, - onDefine: () => toolbarController?.showToolbar( - context, - mode: MessageMode.definition, - ), - onListen: () => toolbarController?.showToolbar( - context, - mode: MessageMode.textToSpeech, - ), - ), - enableInteractiveSelection: - toolbarController?.highlighted ?? false, + onTap: () { + if (pangeaMessageEvent != null && !isOverlay) { + HapticFeedback.mediumImpact(); + controller.showToolbar(pangeaMessageEvent!); + } + }, + enableInteractiveSelection: isOverlay, // Pangea# text: event.calcLocalizedBodyFallback( MatrixLocals(L10n.of(context)!), diff --git a/lib/pangea/utils/any_state_holder.dart b/lib/pangea/utils/any_state_holder.dart index d7f0b073e..00800a74b 100644 --- a/lib/pangea/utils/any_state_holder.dart +++ b/lib/pangea/utils/any_state_holder.dart @@ -83,6 +83,9 @@ class PangeaAnyState { // String chatViewTargetKey(String? roomId) => "chatViewKey$roomId"; // LayerLinkAndKey chatViewLinkAndKey(String? roomId) => // layerLinkAndKey(chatViewTargetKey(roomId)); + + RenderBox? getRenderBox(String key) => + layerLinkAndKey(key).key.currentContext?.findRenderObject() as RenderBox?; } class LayerLinkAndKey { diff --git a/lib/pangea/widgets/chat/message_buttons.dart b/lib/pangea/widgets/chat/message_buttons.dart index f7748675f..43dbfc95a 100644 --- a/lib/pangea/widgets/chat/message_buttons.dart +++ b/lib/pangea/widgets/chat/message_buttons.dart @@ -1,27 +1,27 @@ +import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/enum/message_mode_enum.dart'; -import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart'; +import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:flutter/material.dart'; class MessageButtons extends StatelessWidget { - final ToolbarDisplayController? toolbarController; + final ChatController controller; + final PangeaMessageEvent pangeaMessageEvent; const MessageButtons({ super.key, - this.toolbarController, + required this.controller, + required this.pangeaMessageEvent, }); void showActivity(BuildContext context) { - toolbarController?.showToolbar( - context, + controller.showToolbar( + pangeaMessageEvent, mode: MessageMode.practiceActivity, ); } @override Widget build(BuildContext context) { - if (toolbarController == null) { - return const SizedBox.shrink(); - } return Padding( padding: const EdgeInsets.only(right: 8.0), child: Row( diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index 9fcc4f89c..6ef6cb39b 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -1,195 +1,220 @@ +import 'dart:async'; + import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; +import 'package:fluffychat/pages/chat/events/message.dart'; +import 'package:fluffychat/pangea/enum/message_mode_enum.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/utils/any_state_holder.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:fluffychat/pangea/widgets/chat/message_text_selection.dart'; import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart'; import 'package:fluffychat/pangea/widgets/chat/overlay_footer.dart'; import 'package:fluffychat/pangea/widgets/chat/overlay_header.dart'; -import 'package:fluffychat/pangea/widgets/chat/overlay_message.dart'; -import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; -class MessageSelectionOverlay extends StatelessWidget { +class MessageSelectionOverlay extends StatefulWidget { final ChatController controller; - final ToolbarDisplayController toolbarController; - final Function closeToolbar; - final Widget toolbar; + final Event event; final PangeaMessageEvent pangeaMessageEvent; - final bool ownMessage; - final bool immersionMode; - final String targetId; + final MessageMode? initialMode; + final MessageTextSelection textSelection; const MessageSelectionOverlay({ required this.controller, - required this.closeToolbar, - required this.toolbar, + required this.event, required this.pangeaMessageEvent, - required this.immersionMode, - required this.ownMessage, - required this.targetId, - required this.toolbarController, + required this.textSelection, + this.initialMode, super.key, }); @override - Widget build(BuildContext context) { - final LayerLinkAndKey layerLinkAndKey = - MatrixState.pAnyState.layerLinkAndKey(targetId); - final targetRenderBox = - layerLinkAndKey.key.currentContext?.findRenderObject(); + MessageSelectionOverlayState createState() => MessageSelectionOverlayState(); +} - double center = 290; - double? left; - double? right; - bool showDown = false; - final double footerSize = PlatformInfos.isMobile - ? PlatformInfos.isIOS - ? 128 - : 108 - : 143; - final double headerSize = PlatformInfos.isMobile - ? PlatformInfos.isIOS - ? 121 - : 84 - : 77; - final double stackSize = - MediaQuery.of(context).size.height - footerSize - headerSize; +class MessageSelectionOverlayState extends State { + double overlayBottomOffset = -1; + double adjustedOverlayBottomOffset = -1; + Size? messageSize; + Offset? messageOffset; + final StreamController _completeAnimationStream = + StreamController.broadcast(); + + @override + void initState() { + super.initState(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + // position the overlay directly over the underlying message + setOverlayBottomOffset(); + + // wait for the toolbar to animate to full height + _completeAnimationStream.stream.first.then((_) { + if (toolbarHeight == null || + messageSize == null || + messageOffset == null) { + return; + } + + // Once the toolbar has fully expanded, adjust + // the overlay's position if there's an overflow + final overlayTopOffset = messageOffset!.dy - toolbarHeight!; + + final bool hasHeaderOverflow = overlayTopOffset < headerHeight; + final bool hasFooterOverflow = overlayBottomOffset < footerHeight; + + if (hasHeaderOverflow) { + final overlayHeight = toolbarHeight! + messageSize!.height; + adjustedOverlayBottomOffset = screenHeight - + overlayHeight - + footerHeight - + MediaQuery.of(context).padding.bottom; + } else if (hasFooterOverflow) { + adjustedOverlayBottomOffset = footerHeight; + } + + setState(() {}); + }); + } + + @override + void dispose() { + _completeAnimationStream.close(); + super.dispose(); + } + + void setOverlayBottomOffset() { + // Try to get the offset and size of the original message bubble. + // If it fails, return an empty SizedBox. For instance, this can fail if + // you change the screen size while the overlay is open. try { - if (targetRenderBox != null) { - final Size transformTargetSize = (targetRenderBox as RenderBox).size; - final Offset targetOffset = - (targetRenderBox).localToGlobal(Offset.zero); - if (ownMessage) { - right = MediaQuery.of(context).size.width - - targetOffset.dx - - transformTargetSize.width; - } else { - left = - targetOffset.dx - (FluffyThemes.isColumnMode(context) ? 425 : 1); - } - - showDown = targetOffset.dy + transformTargetSize.height / 2 <= - headerSize + stackSize / 2; - - center = targetOffset.dy - - headerSize + - (showDown ? transformTargetSize.height + 3 : (-3)); - // If top of selected message extends below header - if (targetOffset.dy <= headerSize) { - center = transformTargetSize.height + 3; - showDown = true; - } - // If bottom of selected message extends below footer - else if (targetOffset.dy + transformTargetSize.height >= - headerSize + stackSize) { - center = stackSize - transformTargetSize.height - 3; - } - final double midpoint = headerSize + stackSize / 2; - // If message is too long, - // use default location to make full use of screen - if (transformTargetSize.height >= stackSize / 2 - 30) { - center = stackSize / 2 + (showDown ? -30 : 30); - } - // If message is not too long, but too close - // to center of screen, scroll closer to edges - else if (targetOffset.dy + transformTargetSize.height > midpoint - 30 && - targetOffset.dy < midpoint + 30) { - final double scrollUp = midpoint + 30 - targetOffset.dy; - final double scrollDown = - targetOffset.dy + transformTargetSize.height - (midpoint - 30); - final double minScroll = - controller.scrollController.position.minScrollExtent; - final double maxScroll = - controller.scrollController.position.maxScrollExtent; - final double currentOffset = controller.scrollController.offset; - - // If can scroll up, scroll up - if (currentOffset + scrollUp < maxScroll) { - controller.scrollController.animateTo( - currentOffset + scrollUp, - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - ); - showDown = false; - center = stackSize / 2 + 27; - } - - // Else if can scroll down, scroll down - else if (currentOffset - scrollDown > minScroll) { - controller.scrollController.animateTo( - currentOffset - scrollDown, - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - ); - showDown = true; - center = stackSize / 2 - 27; - } - - // Neither scrolling works; leave message as-is, - // and use centered toolbar location - else { - center = stackSize / 2 + (showDown ? -30 : 30); - } - } + final messageRenderBox = MatrixState.pAnyState.getRenderBox( + widget.event.eventId, + ); + if (messageRenderBox != null && messageRenderBox.hasSize) { + messageSize = messageRenderBox.size; + messageOffset = messageRenderBox.localToGlobal(Offset.zero); + final messageTopOffset = messageOffset!.dy; + overlayBottomOffset = + screenHeight - messageTopOffset - messageSize!.height; } } catch (err) { - controller.showEmojiPicker = false; - controller.selectedEvents.clear(); - MatrixState.pAnyState.closeAllOverlays(); - ErrorHandler.logError(e: err, s: StackTrace.current); - // throw L10n.of(context)!.toolbarError; - return const SizedBox(); + overlayBottomOffset = adjustedOverlayBottomOffset = -1; + } finally { + setState(() {}); + } + } + + // height of the reply/forward bar + the reaction picker + contextual padding + double get footerHeight => + 48 + 56 + (FluffyThemes.isColumnMode(context) ? 16.0 : 8.0); + + double get headerHeight => + (Theme.of(context).appBarTheme.toolbarHeight ?? 56) + + MediaQuery.of(context).padding.top; + + double get screenHeight => MediaQuery.of(context).size.height; + + double? get toolbarHeight { + try { + final toolbarRenderBox = MatrixState.pAnyState.getRenderBox( + '${widget.pangeaMessageEvent.eventId}-toolbar', + ); + + return toolbarRenderBox?.size.height; + } catch (e) { + return null; + } + } + + @override + Widget build(BuildContext context) { + if (overlayBottomOffset == -1) { + return const SizedBox.shrink(); } - final Widget overlayMessage = OverlayMessage( - pangeaMessageEvent.event, - timeline: pangeaMessageEvent.timeline, - immersionMode: immersionMode, - ownMessage: pangeaMessageEvent.ownMessage, - toolbarController: toolbarController, - width: 290, - showDown: showDown, - ); - - return Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: - ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, - children: [ - OverlayHeader( - controller: controller, - closeToolbar: closeToolbar, - ), - SizedBox( - height: PlatformInfos.isAndroid ? 3 : 6, - ), - Flexible( - child: Stack( + final overlayMessage = ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: FluffyThemes.columnWidth * 2.5, + ), + child: Material( + type: MaterialType.transparency, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: widget.pangeaMessageEvent.ownMessage + ? MainAxisAlignment.end + : MainAxisAlignment.start, children: [ - Positioned( - left: left, - right: right, - bottom: stackSize - center + 3, - child: showDown ? overlayMessage : toolbar, - ), - Positioned( - left: left, - right: right, - top: center + 3, - child: showDown ? toolbar : overlayMessage, + Padding( + padding: EdgeInsets.only( + left: widget.pangeaMessageEvent.ownMessage + ? 0 + : Avatar.defaultSize + 16, + right: widget.pangeaMessageEvent.ownMessage ? 8 : 0, + ), + child: MessageToolbar( + pangeaMessageEvent: widget.pangeaMessageEvent, + controller: widget.controller, + textSelection: widget.textSelection, + completeAnimationStream: _completeAnimationStream, + initialMode: widget.initialMode, + ), ), ], ), + Message( + widget.event, + onSwipe: () => {}, + onInfoTab: (_) => {}, + onAvatarTab: (_) => {}, + scrollToEventId: (_) => {}, + immersionMode: widget.controller.choreographer.immersionMode, + controller: widget.controller, + timeline: widget.controller.timeline!, + isOverlay: true, + animateIn: false, + ), + ], + ), + ), + ); + + return Expanded( + child: Stack( + children: [ + AnimatedPositioned( + duration: FluffyThemes.animationDuration, + left: 0, + right: 0, + bottom: adjustedOverlayBottomOffset == -1 + ? overlayBottomOffset + : adjustedOverlayBottomOffset, + child: Align( + alignment: Alignment.center, + child: overlayMessage, + ), ), - SizedBox( - height: PlatformInfos.isAndroid ? 3 : 6, + Align( + alignment: Alignment.bottomCenter, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + OverlayFooter(controller: widget.controller), + ], + ), + ), + Material( + child: OverlayHeader(controller: widget.controller), ), - OverlayFooter(controller: controller), ], ), ); diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 238d6b36e..943269bdb 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -1,158 +1,35 @@ import 'dart:async'; -import 'dart:developer'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/enum/message_mode_enum.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:fluffychat/pangea/utils/overlay.dart'; import 'package:fluffychat/pangea/widgets/chat/message_audio_card.dart'; -import 'package:fluffychat/pangea/widgets/chat/message_selection_overlay.dart'; import 'package:fluffychat/pangea/widgets/chat/message_speech_to_text_card.dart'; import 'package:fluffychat/pangea/widgets/chat/message_text_selection.dart'; import 'package:fluffychat/pangea/widgets/chat/message_translation_card.dart'; import 'package:fluffychat/pangea/widgets/chat/message_unsubscribed_card.dart'; import 'package:fluffychat/pangea/widgets/igc/word_data_card.dart'; import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity_card.dart'; -import 'package:fluffychat/pangea/widgets/user_settings/p_language_dialog.dart'; -import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:matrix/matrix.dart'; - -class ToolbarDisplayController { - final PangeaMessageEvent pangeaMessageEvent; - final String targetId; - final bool immersionMode; - final ChatController controller; - final FocusNode focusNode = FocusNode(); - Event? nextEvent; - Event? previousEvent; - - MessageToolbar? toolbar; - String? overlayId; - double? messageWidth; - - final toolbarModeStream = StreamController.broadcast(); - - ToolbarDisplayController({ - required this.pangeaMessageEvent, - required this.targetId, - required this.immersionMode, - required this.controller, - this.nextEvent, - this.previousEvent, - }); - - void closeToolbar() { - controller.clearSelectedEvents(); - MatrixState.pAnyState.closeAllOverlays(); - } - - void setToolbar() { - toolbar ??= MessageToolbar( - textSelection: MessageTextSelection(), - room: pangeaMessageEvent.room, - toolbarModeStream: toolbarModeStream, - pangeaMessageEvent: pangeaMessageEvent, - immersionMode: immersionMode, - controller: controller, - ); - } - - void showToolbar(BuildContext context, {MessageMode? mode}) { - // Close keyboard, if open - if (controller.inputFocus.hasFocus) { - controller.inputFocus.unfocus(); - return; - } - // Close emoji picker, if open - controller.showEmojiPicker = false; - if (highlighted) return; - if (!MatrixState.pangeaController.languageController.languagesSet) { - pLanguageDialog(context, () {}); - return; - } - focusNode.requestFocus(); - - // I'm not sure why I put this here, but it causes the toolbar - // not to open immediately after clicking (user has to scroll or move their cursor) - // so I'm commenting it out for now - // WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - Widget? overlayEntry; - if (toolbar == null) return; - try { - overlayEntry = MessageSelectionOverlay( - controller: controller, - closeToolbar: closeToolbar, - toolbar: toolbar!, - pangeaMessageEvent: pangeaMessageEvent, - immersionMode: immersionMode, - ownMessage: pangeaMessageEvent.ownMessage, - targetId: targetId, - toolbarController: this, - ); - } catch (err) { - debugger(when: kDebugMode); - ErrorHandler.logError(e: err, s: StackTrace.current); - return; - } - - OverlayUtil.showOverlay( - context: context, - child: overlayEntry, - transformTargetId: targetId, - targetAnchor: Alignment.center, - followerAnchor: Alignment.center, - backgroundColor: const Color.fromRGBO(0, 0, 0, 1).withAlpha(200), - closePrevOverlay: - MatrixState.pangeaController.subscriptionController.isSubscribed, - position: OverlayEnum.centered, - onDismiss: controller.clearSelectedEvents, - ); - - controller.onSelectMessage(pangeaMessageEvent.event); - - if (MatrixState.pAnyState.entries.isNotEmpty) { - overlayId = MatrixState.pAnyState.entries.last.hashCode.toString(); - } - - if (mode != null) { - Future.delayed( - const Duration(milliseconds: 100), - () => toolbarModeStream.add(mode), - ); - } - } - - bool get highlighted { - if (overlayId == null) return false; - if (MatrixState.pAnyState.entries.isEmpty) { - overlayId = null; - return false; - } - return MatrixState.pAnyState.entries.last.hashCode.toString() == overlayId; - } -} class MessageToolbar extends StatefulWidget { final MessageTextSelection textSelection; - final Room room; final PangeaMessageEvent pangeaMessageEvent; - final StreamController toolbarModeStream; - final bool immersionMode; final ChatController controller; + final MessageMode? initialMode; + + final StreamController completeAnimationStream; const MessageToolbar({ super.key, required this.textSelection, - required this.room, required this.pangeaMessageEvent, - required this.toolbarModeStream, - required this.immersionMode, required this.controller, + required this.completeAnimationStream, + this.initialMode, }); @override @@ -164,7 +41,6 @@ class MessageToolbarState extends State { MessageMode? currentMode; bool updatingMode = false; late StreamSubscription selectionStream; - late StreamSubscription toolbarModeStream; void updateMode(MessageMode newMode) { //Early exit from the function if the widget has been unmounted to prevent updates on an inactive widget. @@ -203,7 +79,7 @@ class MessageToolbarState extends State { toolbarContent = MessageUnsubscribedCard( languageTool: newMode.title(context), mode: newMode, - toolbarModeStream: widget.toolbarModeStream, + controller: this, ); } else { switch (currentMode) { @@ -242,7 +118,7 @@ class MessageToolbarState extends State { debugPrint("show translation"); toolbarContent = MessageTranslationCard( messageEvent: widget.pangeaMessageEvent, - immersionMode: widget.immersionMode, + immersionMode: widget.controller.choreographer.immersionMode, selection: widget.textSelection, ); } @@ -275,7 +151,7 @@ class MessageToolbarState extends State { fullText: widget.textSelection.messageText, fullTextLang: widget.pangeaMessageEvent.messageDisplayLangCode, hasInfo: true, - room: widget.room, + room: widget.controller.room, ); } @@ -294,20 +170,20 @@ class MessageToolbarState extends State { super.initState(); widget.textSelection.selectedText = null; - toolbarModeStream = widget.toolbarModeStream.stream.listen((mode) { - updateMode(mode); - }); - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { if (widget.pangeaMessageEvent.isAudioMessage) { updateMode(MessageMode.speechToText); return; } - MatrixState.pangeaController.userController.profile.userSettings - .autoPlayMessages - ? updateMode(MessageMode.textToSpeech) - : updateMode(MessageMode.translation); + if (widget.initialMode != null) { + updateMode(widget.initialMode!); + } else { + MatrixState.pangeaController.userController.profile.userSettings + .autoPlayMessages + ? updateMode(MessageMode.textToSpeech) + : updateMode(MessageMode.translation); + } }); Timer? timer; @@ -330,22 +206,37 @@ class MessageToolbarState extends State { @override void dispose() { selectionStream.cancel(); - toolbarModeStream.cancel(); super.dispose(); } @override Widget build(BuildContext context) { - final double maxHeight = (MediaQuery.of(context).size.height - - (PlatformInfos.isWeb - ? 217 - : PlatformInfos.isIOS - ? 262 - : 198)) / - 2 + - 30; + final buttonRow = Row( + mainAxisSize: MainAxisSize.min, + children: MessageMode.values + .map( + (mode) => mode.isValidMode(widget.pangeaMessageEvent.event) + ? Tooltip( + message: mode.tooltip(context), + child: IconButton( + icon: Icon(mode.icon), + color: mode.iconColor( + widget.pangeaMessageEvent, + currentMode, + context, + ), + onPressed: () => updateMode(mode), + ), + ) + : const SizedBox.shrink(), + ) + .toList(), + ); return Material( + key: MatrixState.pAnyState + .layerLinkAndKey('${widget.pangeaMessageEvent.eventId}-toolbar') + .key, type: MaterialType.transparency, child: Container( padding: const EdgeInsets.all(10), @@ -359,63 +250,26 @@ class MessageToolbarState extends State { Radius.circular(25), ), ), - constraints: BoxConstraints( - maxWidth: 290, - minWidth: 290, - maxHeight: maxHeight, - ), child: Column( mainAxisSize: MainAxisSize.min, children: [ - Container( - constraints: BoxConstraints( - minWidth: 290, - maxHeight: maxHeight - 72, - ), - child: SingleChildScrollView( - child: AnimatedSize( - duration: FluffyThemes.animationDuration, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: toolbarContent ?? const SizedBox(), - ), - SizedBox(height: toolbarContent == null ? 0 : 20), - ], + if (toolbarContent != null) + Container( + padding: const EdgeInsets.fromLTRB(8, 8, 8, 16), + constraints: const BoxConstraints( + maxWidth: 275, + minWidth: 275, + maxHeight: 250, + ), + child: SingleChildScrollView( + child: AnimatedSize( + duration: FluffyThemes.animationDuration, + child: toolbarContent, + onEnd: () => widget.completeAnimationStream.add(null), ), ), ), - ), - Row( - mainAxisSize: MainAxisSize.min, - children: MessageMode.values.map((mode) { - if ([ - MessageMode.definition, - MessageMode.textToSpeech, - MessageMode.translation, - ].contains(mode) && - widget.pangeaMessageEvent.isAudioMessage) { - return const SizedBox.shrink(); - } - if (mode == MessageMode.speechToText && - !widget.pangeaMessageEvent.isAudioMessage) { - return const SizedBox.shrink(); - } - return Tooltip( - message: mode.tooltip(context), - child: IconButton( - icon: Icon(mode.icon), - color: mode.iconColor( - widget.pangeaMessageEvent, - currentMode, - context, - ), - onPressed: () => updateMode(mode), - ), - ); - }).toList(), - ), + buttonRow, ], ), ), diff --git a/lib/pangea/widgets/chat/message_unsubscribed_card.dart b/lib/pangea/widgets/chat/message_unsubscribed_card.dart index 720b25748..d1ff5c343 100644 --- a/lib/pangea/widgets/chat/message_unsubscribed_card.dart +++ b/lib/pangea/widgets/chat/message_unsubscribed_card.dart @@ -1,7 +1,6 @@ -import 'dart:async'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/utils/bot_style.dart'; +import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -11,13 +10,13 @@ import '../../enum/message_mode_enum.dart'; class MessageUnsubscribedCard extends StatelessWidget { final String languageTool; final MessageMode mode; - final StreamController toolbarModeStream; + final MessageToolbarState controller; const MessageUnsubscribedCard({ super.key, required this.languageTool, required this.mode, - required this.toolbarModeStream, + required this.controller, }); @override @@ -29,7 +28,7 @@ class MessageUnsubscribedCard extends StatelessWidget { if (inTrialWindow) { MatrixState.pangeaController.subscriptionController .activateNewUserTrial(); - toolbarModeStream.add(mode); + controller.updateMode(mode); } else { MatrixState.pangeaController.subscriptionController .showPaywall(context); @@ -49,7 +48,7 @@ class MessageUnsubscribedCard extends StatelessWidget { child: TextButton( onPressed: onButtonPress, style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( + backgroundColor: WidgetStateProperty.all( (AppConfig.primaryColor).withOpacity(0.1), ), ), diff --git a/lib/pangea/widgets/chat/overlay_footer.dart b/lib/pangea/widgets/chat/overlay_footer.dart index a15045750..b4c51d07c 100644 --- a/lib/pangea/widgets/chat/overlay_footer.dart +++ b/lib/pangea/widgets/chat/overlay_footer.dart @@ -2,24 +2,23 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pages/chat/chat_input_row.dart'; import 'package:fluffychat/pages/chat/reactions_picker.dart'; -import 'package:fluffychat/utils/platform_infos.dart'; import 'package:flutter/material.dart'; class OverlayFooter extends StatelessWidget { - ChatController controller; + final ChatController controller; - OverlayFooter({ + const OverlayFooter({ required this.controller, super.key, }); @override Widget build(BuildContext context) { - final bottomSheetPadding = FluffyThemes.isColumnMode(context) ? 18.0 : 10.0; + final bottomSheetPadding = FluffyThemes.isColumnMode(context) ? 16.0 : 8.0; return Container( margin: EdgeInsets.only( - bottom: PlatformInfos.isAndroid ? 0 : bottomSheetPadding, + bottom: bottomSheetPadding, left: bottomSheetPadding, right: bottomSheetPadding, ), @@ -42,13 +41,6 @@ class OverlayFooter extends StatelessWidget { ], ), ), - SizedBox( - height: FluffyThemes.isColumnMode(context) - ? 15.0 - : PlatformInfos.isAndroid - ? 0 - : 8.0, - ), ], ), ); diff --git a/lib/pangea/widgets/chat/overlay_header.dart b/lib/pangea/widgets/chat/overlay_header.dart index b9a073125..c8f5b2a62 100644 --- a/lib/pangea/widgets/chat/overlay_header.dart +++ b/lib/pangea/widgets/chat/overlay_header.dart @@ -1,148 +1,92 @@ import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pages/chat/chat_app_bar_title.dart'; -import 'package:fluffychat/pangea/utils/overlay.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; class OverlayHeader extends StatelessWidget { - ChatController controller; - Function closeToolbar; + final ChatController controller; - OverlayHeader({ + const OverlayHeader({ required this.controller, - required this.closeToolbar, super.key, }); @override Widget build(BuildContext context) { - final Event selectedEvent = controller.selectedEvents.single; - - return AppBar( - backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest, - actionsIconTheme: IconThemeData( - color: Theme.of(context).colorScheme.primary, - ), - leading: IconButton( - icon: const Icon(Icons.close), - onPressed: () => closeToolbar(), - tooltip: L10n.of(context)!.close, - color: Theme.of(context).colorScheme.primary, - ), - titleSpacing: 0, - title: ChatAppBarTitle(controller), - actions: [ - if (controller.canEditSelectedEvents) - IconButton( - icon: const Icon(Icons.edit_outlined), - tooltip: L10n.of(context)!.edit, - onPressed: controller.editSelectedEventAction, + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + AppBar( + actionsIconTheme: IconThemeData( + color: Theme.of(context).colorScheme.primary, ), - if (selectedEvent.messageType == MessageTypes.Text) - IconButton( - icon: const Icon(Icons.copy_outlined), - tooltip: L10n.of(context)!.copy, - onPressed: controller.copyEventsAction, + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: () { + controller.clearSelectedEvents(); + MatrixState.pAnyState.closeAllOverlays(); + }, + tooltip: L10n.of(context)!.close, + color: Theme.of(context).colorScheme.primary, ), - if (controller.canSaveSelectedEvent) - // Use builder context to correctly position the share dialog on iPad - Builder( - builder: (context) => IconButton( - icon: Icon(Icons.adaptive.share), - tooltip: L10n.of(context)!.share, - onPressed: () => controller.saveSelectedEvent(context), - ), - ), - if (controller.canPinSelectedEvents) - IconButton( - icon: const Icon(Icons.push_pin_outlined), - onPressed: controller.pinEvent, - tooltip: L10n.of(context)!.pinMessage, - ), - if (controller.canRedactSelectedEvents) - IconButton( - icon: const Icon(Icons.delete_outlined), - tooltip: L10n.of(context)!.redactMessage, - onPressed: controller.redactEventsAction, - ), - IconButton( - padding: const EdgeInsets.only(bottom: 6), - icon: Icon( - Icons.more_horiz, - color: Theme.of(context).colorScheme.onSurface, - ), - onPressed: () => showPopup(context), + titleSpacing: 0, + title: ChatAppBarTitle(controller), + actions: [ + if (controller.canEditSelectedEvents) + IconButton( + icon: const Icon(Icons.edit_outlined), + tooltip: L10n.of(context)!.edit, + onPressed: controller.editSelectedEventAction, + ), + if (controller.selectedEvents.length == 1 && + controller.selectedEvents.single.messageType == + MessageTypes.Text) + IconButton( + icon: const Icon(Icons.copy_outlined), + tooltip: L10n.of(context)!.copy, + onPressed: controller.copyEventsAction, + ), + if (controller.canSaveSelectedEvent) + // Use builder context to correctly position the share dialog on iPad + Builder( + builder: (context) => IconButton( + icon: Icon(Icons.adaptive.share), + tooltip: L10n.of(context)!.share, + onPressed: () => controller.saveSelectedEvent(context), + ), + ), + if (controller.canPinSelectedEvents) + IconButton( + icon: const Icon(Icons.push_pin_outlined), + onPressed: controller.pinEvent, + tooltip: L10n.of(context)!.pinMessage, + ), + if (controller.canRedactSelectedEvents) + IconButton( + icon: const Icon(Icons.delete_outlined), + tooltip: L10n.of(context)!.redactMessage, + onPressed: controller.redactEventsAction, + ), + if (controller.selectedEvents.length == 1) + IconButton( + icon: const Icon(Icons.info_outlined), + tooltip: L10n.of(context)!.messageInfo, + onPressed: () { + controller.showEventInfo(); + controller.clearSelectedEvents(); + }, + ), + if (controller.selectedEvents.length == 1) + IconButton( + icon: const Icon(Icons.shield_outlined), + tooltip: L10n.of(context)!.reportMessage, + onPressed: controller.reportEventAction, + ), + ], ), ], ); } - - void showPopup(BuildContext context) { - OverlayUtil.showOverlay( - context: context, - child: SelectionPopup(controller: controller), - transformTargetId: "", - targetAnchor: Alignment.center, - followerAnchor: Alignment.center, - closePrevOverlay: false, - position: OverlayEnum.topRight, - ); - } -} - -class SelectionPopup extends StatelessWidget { - ChatController controller; - - SelectionPopup({ - required this.controller, - super.key, - }); - - @override - Widget build(BuildContext context) { - return Material( - type: MaterialType.transparency, - child: Container( - padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainerHigh, - borderRadius: const BorderRadius.all( - Radius.circular(20), - ), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextButton( - onPressed: controller.showEventInfo, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.info_outlined), - const SizedBox(width: 12), - Text(L10n.of(context)!.messageInfo), - ], - ), - ), - TextButton( - onPressed: controller.reportEventAction, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon( - Icons.shield_outlined, - color: Colors.red, - ), - const SizedBox(width: 12), - Text(L10n.of(context)!.reportMessage), - ], - ), - ), - ], - ), - ), - ); - } } diff --git a/lib/pangea/widgets/chat/overlay_message.dart b/lib/pangea/widgets/chat/overlay_message.dart deleted file mode 100644 index 3f8869399..000000000 --- a/lib/pangea/widgets/chat/overlay_message.dart +++ /dev/null @@ -1,188 +0,0 @@ -import 'package:fluffychat/config/themes.dart'; -import 'package:fluffychat/pages/chat/events/message_content.dart'; -import 'package:fluffychat/pangea/enum/use_type.dart'; -import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart'; -import 'package:fluffychat/utils/date_time_extension.dart'; -import 'package:fluffychat/utils/platform_infos.dart'; -import 'package:flutter/material.dart'; -import 'package:matrix/matrix.dart'; - -import '../../../config/app_config.dart'; - -class OverlayMessage extends StatelessWidget { - final Event event; - final bool selected; - final Timeline timeline; - // final LanguageModel? selectedDisplayLang; - final bool immersionMode; - // final bool definitions; - final bool ownMessage; - final ToolbarDisplayController toolbarController; - final double? width; - final bool showDown; - - const OverlayMessage( - this.event, { - this.selected = false, - required this.timeline, - required this.immersionMode, - required this.ownMessage, - required this.toolbarController, - required this.showDown, - this.width, - super.key, - }); - - @override - Widget build(BuildContext context) { - if (event.type != EventTypes.Message || - event.messageType == EventTypes.KeyVerificationRequest) { - return const SizedBox.shrink(); - } - - var color = Theme.of(context).colorScheme.surfaceContainer; - final isLight = Theme.of(context).brightness == Brightness.light; - var lightness = isLight ? .05 : .2; - final textColor = ownMessage - ? Theme.of(context).colorScheme.onPrimary - : Theme.of(context).colorScheme.onSurface; - - const hardCorner = Radius.circular(4); - const roundedCorner = Radius.circular(AppConfig.borderRadius); - final borderRadius = BorderRadius.only( - topLeft: !showDown && !ownMessage ? hardCorner : roundedCorner, - topRight: !showDown && ownMessage ? hardCorner : roundedCorner, - bottomLeft: showDown && !ownMessage ? hardCorner : roundedCorner, - bottomRight: showDown && ownMessage ? hardCorner : roundedCorner, - ); - - final noBubble = { - MessageTypes.Video, - MessageTypes.Image, - MessageTypes.Sticker, - }.contains(event.messageType) && - !event.redacted; - final noPadding = { - MessageTypes.File, - MessageTypes.Audio, - }.contains(event.messageType); - - if (ownMessage) { - color = Theme.of(context).colorScheme.primary; - lightness = isLight ? .15 : .85; - } - // Make overlay a little darker/lighter than the message - color = Color.fromARGB( - color.alpha, - isLight || !ownMessage - ? (color.red + lightness * (255 - color.red)).round() - : (color.red * lightness).round(), - isLight || !ownMessage - ? (color.green + lightness * (255 - color.green)).round() - : (color.green * lightness).round(), - isLight || !ownMessage - ? (color.blue + lightness * (255 - color.blue)).round() - : (color.blue * lightness).round(), - ); - - final double maxHeight = (MediaQuery.of(context).size.height - - (PlatformInfos.isWeb - ? 228 - : PlatformInfos.isIOS - ? 258 - : 198)) / - 2 - - 30; - - final pangeaMessageEvent = PangeaMessageEvent( - event: event, - timeline: timeline, - ownMessage: ownMessage, - ); - - return Material( - color: noBubble ? Colors.transparent : color, - clipBehavior: Clip.antiAlias, - shape: RoundedRectangleBorder( - borderRadius: borderRadius, - ), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, - ), - ), - padding: noBubble || noPadding - ? EdgeInsets.zero - : const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - constraints: BoxConstraints( - maxWidth: width ?? FluffyThemes.columnWidth * 1.25, - maxHeight: maxHeight, - ), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Flexible( - child: MessageContent( - event.getDisplayEvent(timeline), - textColor: textColor, - borderRadius: borderRadius, - selected: selected, - pangeaMessageEvent: pangeaMessageEvent, - immersionMode: immersionMode, - toolbarController: toolbarController, - isOverlay: true, - ), - ), - if (event.hasAggregatedEvents( - timeline, - RelationshipTypes.edit, - ) || - (pangeaMessageEvent.showUseType)) - Padding( - padding: const EdgeInsets.only( - top: 4.0, - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (pangeaMessageEvent.showUseType) ...[ - pangeaMessageEvent.msgUseType.iconView( - context, - textColor.withAlpha(164), - ), - const SizedBox(width: 4), - ], - if (event.hasAggregatedEvents( - timeline, - RelationshipTypes.edit, - )) ...[ - Icon( - Icons.edit_outlined, - color: textColor.withAlpha(164), - size: 14, - ), - Text( - ' - ${event.getDisplayEvent(timeline).originServerTs.localizedTimeShort(context)}', - style: TextStyle( - color: textColor.withAlpha(164), - fontSize: 12, - ), - ), - ], - ], - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/pangea/widgets/chat/toolbar_content_loading_indicator.dart b/lib/pangea/widgets/chat/toolbar_content_loading_indicator.dart index 021f4ee44..9edc9971d 100644 --- a/lib/pangea/widgets/chat/toolbar_content_loading_indicator.dart +++ b/lib/pangea/widgets/chat/toolbar_content_loading_indicator.dart @@ -7,13 +7,15 @@ class ToolbarContentLoadingIndicator extends StatelessWidget { @override Widget build(BuildContext context) { - return SizedBox( + return Center( + child: SizedBox( height: 14, width: 14, child: CircularProgressIndicator( strokeWidth: 2.0, color: Theme.of(context).colorScheme.primary, ), - ); + ), + ); } -} \ No newline at end of file +} diff --git a/lib/pangea/widgets/igc/pangea_rich_text.dart b/lib/pangea/widgets/igc/pangea_rich_text.dart index 1ad2e6e08..3ff10d4c7 100644 --- a/lib/pangea/widgets/igc/pangea_rich_text.dart +++ b/lib/pangea/widgets/igc/pangea_rich_text.dart @@ -2,31 +2,31 @@ import 'dart:developer'; import 'dart:ui'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/enum/instructions_enum.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/models/representation_content_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:fluffychat/pangea/widgets/chat/message_context_menu.dart'; -import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import '../../enum/message_mode_enum.dart'; import '../../models/pangea_match_model.dart'; class PangeaRichText extends StatefulWidget { final PangeaMessageEvent pangeaMessageEvent; final bool immersionMode; - final ToolbarDisplayController? toolbarController; final TextStyle? style; + final bool isOverlay; + final ChatController controller; const PangeaRichText({ super.key, required this.pangeaMessageEvent, required this.immersionMode, - required this.toolbarController, + required this.isOverlay, + required this.controller, this.style, }); @@ -59,12 +59,11 @@ class PangeaRichTextState extends State { void _setTextSpan(String newTextSpan) { try { if (!mounted) return; // Early exit if the widget is no longer in the tree - - widget.toolbarController?.toolbar?.textSelection.setMessageText( - newTextSpan, - ); setState(() { textSpan = newTextSpan; + if (widget.isOverlay) { + widget.controller.textSelection.setMessageText(textSpan); + } }); } catch (error, stackTrace) { ErrorHandler.logError( @@ -137,35 +136,16 @@ class PangeaRichTextState extends State { //TODO - take out of build function of every message final Widget richText = SelectableText.rich( onSelectionChanged: (selection, cause) { - if (cause == SelectionChangedCause.longPress && - !(widget.toolbarController?.highlighted ?? false) && - !(widget.toolbarController?.controller.selectedEvents.any( - (e) => e.eventId == widget.pangeaMessageEvent.eventId, - ) ?? - false)) { - return; + if (widget.isOverlay) { + widget.controller.textSelection.onTextSelection(selection); } - widget.toolbarController?.toolbar?.textSelection - .onTextSelection(selection); }, - onTap: () => widget.toolbarController?.showToolbar(context), - enableInteractiveSelection: - widget.toolbarController?.highlighted ?? false, - contextMenuBuilder: (context, state) => - widget.toolbarController?.highlighted ?? true - ? const SizedBox.shrink() - : MessageContextMenu.contextMenuOverride( - context: context, - textSelection: state, - onDefine: () => widget.toolbarController?.showToolbar( - context, - mode: MessageMode.definition, - ), - onListen: () => widget.toolbarController?.showToolbar( - context, - mode: MessageMode.textToSpeech, - ), - ), + onTap: () { + if (!widget.isOverlay) { + widget.controller.showToolbar(widget.pangeaMessageEvent); + } + }, + enableInteractiveSelection: widget.isOverlay, TextSpan( text: textSpan, style: widget.style, From a181ace577b195d7736bb90ac571f7f3236fee46 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 27 Aug 2024 14:09:06 -0400 Subject: [PATCH 35/38] select message on showToolbar and ensure message is deselected on dismiss --- lib/pages/chat/chat.dart | 3 +++ lib/pages/chat/events/message.dart | 2 +- lib/pangea/widgets/chat/overlay_header.dart | 6 +----- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index d16957134..bd449ade5 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -1579,6 +1579,9 @@ class ChatController extends State PangeaMessageEvent pangeaMessageEvent, { MessageMode? mode, }) { + // select the message + onSelectMessage(pangeaMessageEvent.event); + // Close keyboard, if open if (inputFocus.hasFocus && PlatformInfos.isMobile) { inputFocus.unfocus(); diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index cedd9d3c8..a4c7aa283 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -215,7 +215,7 @@ class Message extends StatelessWidget { right: 0, child: InkWell( // #Pangea - onTap: () => MatrixState.pAnyState.closeOverlay(), + onTap: controller.clearSelectedEvents, // onTap: () => onSelect(event), // onLongPress: () => onSelect(event), // Pangea# diff --git a/lib/pangea/widgets/chat/overlay_header.dart b/lib/pangea/widgets/chat/overlay_header.dart index c8f5b2a62..cce47adcc 100644 --- a/lib/pangea/widgets/chat/overlay_header.dart +++ b/lib/pangea/widgets/chat/overlay_header.dart @@ -1,6 +1,5 @@ import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pages/chat/chat_app_bar_title.dart'; -import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; @@ -24,10 +23,7 @@ class OverlayHeader extends StatelessWidget { ), leading: IconButton( icon: const Icon(Icons.close), - onPressed: () { - controller.clearSelectedEvents(); - MatrixState.pAnyState.closeAllOverlays(); - }, + onPressed: controller.clearSelectedEvents, tooltip: L10n.of(context)!.close, color: Theme.of(context).colorScheme.primary, ), From bf29a324e41718b51a7d8e4b6a4e3e152050f192 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 27 Aug 2024 15:00:56 -0400 Subject: [PATCH 36/38] removed unnecessary pangea comments --- lib/pages/chat/chat_event_list.dart | 4 +- lib/pages/chat/chat_view.dart | 226 +++++++++--------- lib/pages/chat/events/message.dart | 6 +- .../chat/message_selection_overlay.dart | 1 + 4 files changed, 115 insertions(+), 122 deletions(-) diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 3510a4989..f243485e5 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -165,9 +165,7 @@ class ChatEventList extends StatelessWidget { ), highlightMarker: controller.scrollToEventIdMarker == event.eventId, - // #Pangea - // onSelect: controller.onSelectMessage, - // Pangea# + onSelect: controller.onSelectMessage, scrollToEventId: (String eventId) => controller.scrollToEventId(eventId), longPressSelect: controller.selectedEvents.isNotEmpty, diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 27b68d4dc..061a3b3d5 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -34,94 +34,96 @@ class ChatView extends StatelessWidget { const ChatView(this.controller, {super.key}); List _appBarActions(BuildContext context) { - // #Pangea - // if (controller.selectMode) { - // return [ - // if (controller.canEditSelectedEvents) - // IconButton( - // icon: const Icon(Icons.edit_outlined), - // tooltip: L10n.of(context)!.edit, - // onPressed: controller.editSelectedEventAction, - // ), - // // #Pangea - // if (controller.selectedEvents.length == 1 && - // controller.selectedEvents.single.messageType == MessageTypes.Text) - // // Pangea# - // IconButton( - // icon: const Icon(Icons.copy_outlined), - // tooltip: L10n.of(context)!.copy, - // onPressed: controller.copyEventsAction, - // ), - // if (controller.canSaveSelectedEvent) - // // Use builder context to correctly position the share dialog on iPad - // Builder( - // builder: (context) => IconButton( - // icon: Icon(Icons.adaptive.share), - // tooltip: L10n.of(context)!.share, - // onPressed: () => controller.saveSelectedEvent(context), - // ), - // ), - // if (controller.canPinSelectedEvents) - // IconButton( - // icon: const Icon(Icons.push_pin_outlined), - // onPressed: controller.pinEvent, - // tooltip: L10n.of(context)!.pinMessage, - // ), - // if (controller.canRedactSelectedEvents) - // IconButton( - // icon: const Icon(Icons.delete_outlined), - // tooltip: L10n.of(context)!.redactMessage, - // onPressed: controller.redactEventsAction, - // ), - // if (controller.selectedEvents.length == 1) - // PopupMenuButton<_EventContextAction>( - // onSelected: (action) { - // switch (action) { - // case _EventContextAction.info: - // controller.showEventInfo(); - // controller.clearSelectedEvents(); - // break; - // case _EventContextAction.report: - // controller.reportEventAction(); - // break; - // } - // }, - // itemBuilder: (context) => [ - // PopupMenuItem( - // value: _EventContextAction.info, - // child: Row( - // mainAxisSize: MainAxisSize.min, - // children: [ - // const Icon(Icons.info_outlined), - // const SizedBox(width: 12), - // Text(L10n.of(context)!.messageInfo), - // ], - // ), - // ), - // if (controller.selectedEvents.single.status.isSent) - // PopupMenuItem( - // value: _EventContextAction.report, - // child: Row( - // mainAxisSize: MainAxisSize.min, - // children: [ - // const Icon( - // Icons.shield_outlined, - // color: Colors.red, - // ), - // const SizedBox(width: 12), - // Text(L10n.of(context)!.reportMessage), - // ], - // ), - // ), - // ], - // ), - // ]; - return [ - ChatSettingsPopupMenu( - controller.room, - (!controller.room.isDirectChat && !controller.room.isArchived), - ), - ]; + if (controller.selectMode) { + return [ + if (controller.canEditSelectedEvents) + IconButton( + icon: const Icon(Icons.edit_outlined), + tooltip: L10n.of(context)!.edit, + onPressed: controller.editSelectedEventAction, + ), + // #Pangea + if (controller.selectedEvents.length == 1 && + controller.selectedEvents.single.messageType == MessageTypes.Text) + // Pangea# + IconButton( + icon: const Icon(Icons.copy_outlined), + tooltip: L10n.of(context)!.copy, + onPressed: controller.copyEventsAction, + ), + if (controller.canSaveSelectedEvent) + // Use builder context to correctly position the share dialog on iPad + Builder( + builder: (context) => IconButton( + icon: Icon(Icons.adaptive.share), + tooltip: L10n.of(context)!.share, + onPressed: () => controller.saveSelectedEvent(context), + ), + ), + if (controller.canPinSelectedEvents) + IconButton( + icon: const Icon(Icons.push_pin_outlined), + onPressed: controller.pinEvent, + tooltip: L10n.of(context)!.pinMessage, + ), + if (controller.canRedactSelectedEvents) + IconButton( + icon: const Icon(Icons.delete_outlined), + tooltip: L10n.of(context)!.redactMessage, + onPressed: controller.redactEventsAction, + ), + if (controller.selectedEvents.length == 1) + PopupMenuButton<_EventContextAction>( + onSelected: (action) { + switch (action) { + case _EventContextAction.info: + controller.showEventInfo(); + controller.clearSelectedEvents(); + break; + case _EventContextAction.report: + controller.reportEventAction(); + break; + } + }, + itemBuilder: (context) => [ + PopupMenuItem( + value: _EventContextAction.info, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.info_outlined), + const SizedBox(width: 12), + Text(L10n.of(context)!.messageInfo), + ], + ), + ), + if (controller.selectedEvents.single.status.isSent) + PopupMenuItem( + value: _EventContextAction.report, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.shield_outlined, + color: Colors.red, + ), + const SizedBox(width: 12), + Text(L10n.of(context)!.reportMessage), + ], + ), + ), + ], + ), + ]; + // #Pangea + } else { + return [ + ChatSettingsPopupMenu( + controller.room, + (!controller.room.isDirectChat && !controller.room.isArchived), + ), + ]; + } // else if (!controller.room.isArchived) { // return [ // if (Matrix.of(context).voipPlugin != null && @@ -188,34 +190,28 @@ class ChatView extends StatelessWidget { } return Scaffold( appBar: AppBar( - actionsIconTheme: const IconThemeData( - // #Pangea - // color: controller.selectedEvents.isEmpty - // ? null - // : Theme.of(context).colorScheme.primary, - // Pangea# - ), - leading: - // #Pangea - // controller.selectMode - // ? IconButton( - // icon: const Icon(Icons.close), - // onPressed: controller.clearSelectedEvents, - // tooltip: L10n.of(context)!.close, - // color: Theme.of(context).colorScheme.primary, - // ) - // : - // Pangea# - UnreadRoomsBadge( - filter: (r) => - r.id != controller.roomId - // #Pangea - && - !r.isAnalyticsRoom, - // Pangea# - badgePosition: BadgePosition.topEnd(end: 8, top: 4), - child: const Center(child: BackButton()), + actionsIconTheme: IconThemeData( + color: controller.selectedEvents.isEmpty + ? null + : Theme.of(context).colorScheme.primary, ), + leading: controller.selectMode + ? IconButton( + icon: const Icon(Icons.close), + onPressed: controller.clearSelectedEvents, + tooltip: L10n.of(context)!.close, + color: Theme.of(context).colorScheme.primary, + ) + : UnreadRoomsBadge( + filter: (r) => + r.id != controller.roomId + // #Pangea + && + !r.isAnalyticsRoom, + // Pangea# + badgePosition: BadgePosition.topEnd(end: 8, top: 4), + child: const Center(child: BackButton()), + ), titleSpacing: 0, title: ChatAppBarTitle(controller), actions: _appBarActions(context), diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index a4c7aa283..f05555df5 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -36,8 +36,8 @@ class Message extends StatelessWidget { final bool highlightMarker; final bool animateIn; final void Function()? resetAnimateIn; + final void Function(Event) onSelect; // #Pangea - // final void Function(Event) onSelect; final bool immersionMode; final ChatController controller; final bool isOverlay; @@ -50,9 +50,7 @@ class Message extends StatelessWidget { this.previousEvent, this.displayReadMarker = false, this.longPressSelect = false, - // #Pangea - // required this.onSelect, - // Pangea# + required this.onSelect, required this.onInfoTab, required this.onAvatarTab, required this.scrollToEventId, diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index 6ef6cb39b..a62b8e930 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -177,6 +177,7 @@ class MessageSelectionOverlayState extends State { onInfoTab: (_) => {}, onAvatarTab: (_) => {}, scrollToEventId: (_) => {}, + onSelect: (_) => {}, immersionMode: widget.controller.choreographer.immersionMode, controller: widget.controller, timeline: widget.controller.timeline!, From 6976c0575dc74a2631a2d824722423c64841c54c Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 29 Aug 2024 14:36:42 -0400 Subject: [PATCH 37/38] removed unused overlay position options and renamed OverlayEnum to OverlayPositionEnum --- lib/pages/chat/chat.dart | 2 +- lib/pangea/utils/overlay.dart | 36 +++++++---------------------------- 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index bd449ade5..d008032fc 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -1617,7 +1617,7 @@ class ChatController extends State backgroundColor: const Color.fromRGBO(0, 0, 0, 1).withAlpha(200), closePrevOverlay: MatrixState.pangeaController.subscriptionController.isSubscribed, - position: OverlayEnum.centered, + position: OverlayPositionEnum.centered, onDismiss: clearSelectedEvents, ); } diff --git a/lib/pangea/utils/overlay.dart b/lib/pangea/utils/overlay.dart index 58846592d..c83c39af4 100644 --- a/lib/pangea/utils/overlay.dart +++ b/lib/pangea/utils/overlay.dart @@ -11,11 +11,9 @@ import '../../config/themes.dart'; import '../../widgets/matrix.dart'; import 'error_handler.dart'; -enum OverlayEnum { +enum OverlayPositionEnum { transform, centered, - topRight, - bottom, } class OverlayUtil { @@ -34,7 +32,7 @@ class OverlayUtil { Alignment? followerAnchor, bool closePrevOverlay = true, Function? onDismiss, - OverlayEnum position = OverlayEnum.transform, + OverlayPositionEnum position = OverlayPositionEnum.transform, }) { try { if (closePrevOverlay) { @@ -53,33 +51,13 @@ class OverlayUtil { onDismiss: onDismiss, ), Positioned( - top: (position == OverlayEnum.topRight) - ? FluffyThemes.isColumnMode(context) - ? 20 - : 65 - : (position == OverlayEnum.centered) - ? 0 - : null, - right: (position == OverlayEnum.topRight) - ? FluffyThemes.isColumnMode(context) - ? 20 - : 15 - : (position == OverlayEnum.bottom || - position == OverlayEnum.centered) - ? 0 - : null, - left: (position == OverlayEnum.bottom) - ? 0 - : (position == OverlayEnum.centered) - ? 0 - : null, - bottom: (position == OverlayEnum.bottom || - position == OverlayEnum.centered) - ? 0 - : null, + top: (position == OverlayPositionEnum.centered) ? 0 : null, + right: (position == OverlayPositionEnum.centered) ? 0 : null, + left: (position == OverlayPositionEnum.centered) ? 0 : null, + bottom: (position == OverlayPositionEnum.centered) ? 0 : null, width: width, height: height, - child: (position != OverlayEnum.transform) + child: (position != OverlayPositionEnum.transform) ? child : CompositedTransformFollower( targetAnchor: targetAnchor ?? Alignment.topLeft, From 5f3326b843af3fa585ac41ebe131ef5f9d3d823a Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 29 Aug 2024 16:24:36 -0400 Subject: [PATCH 38/38] cleaned up message.dart --- lib/pages/chat/chat.dart | 1 + lib/pages/chat/chat_event_list.dart | 5 +- lib/pages/chat/events/message.dart | 711 +++++++++++++--------------- 3 files changed, 339 insertions(+), 378 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index d008032fc..69f37136c 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -1581,6 +1581,7 @@ class ChatController extends State }) { // select the message onSelectMessage(pangeaMessageEvent.event); + HapticFeedback.mediumImpact(); // Close keyboard, if open if (inputFocus.hasFocus && PlatformInfos.isMobile) { diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index f243485e5..003d0d47d 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -165,7 +165,10 @@ class ChatEventList extends StatelessWidget { ), highlightMarker: controller.scrollToEventIdMarker == event.eventId, - onSelect: controller.onSelectMessage, + // #Pangea + // onSelect: controller.onSelectMessage, + onSelect: (_) {}, + // Pangea# scrollToEventId: (String eventId) => controller.scrollToEventId(eventId), longPressSelect: controller.selectedEvents.isNotEmpty, diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index f05555df5..0ac258f18 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -9,7 +9,6 @@ import 'package:fluffychat/utils/string_color.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; import 'package:swipe_to_action/swipe_to_action.dart'; @@ -26,6 +25,7 @@ class Message extends StatelessWidget { final Event? nextEvent; final Event? previousEvent; final bool displayReadMarker; + final void Function(Event) onSelect; final void Function(Event) onAvatarTab; final void Function(Event) onInfoTab; final void Function(String) scrollToEventId; @@ -36,7 +36,6 @@ class Message extends StatelessWidget { final bool highlightMarker; final bool animateIn; final void Function()? resetAnimateIn; - final void Function(Event) onSelect; // #Pangea final bool immersionMode; final ChatController controller; @@ -72,7 +71,6 @@ class Message extends StatelessWidget { // #Pangea void showToolbar(PangeaMessageEvent? pangeaMessageEvent) { if (pangeaMessageEvent != null && !isOverlay) { - HapticFeedback.mediumImpact(); controller.showToolbar(pangeaMessageEvent); } } @@ -120,13 +118,8 @@ class Message extends StatelessWidget { // ignore: deprecated_member_use var color = Theme.of(context).colorScheme.surfaceVariant; final displayTime = event.type == EventTypes.RoomCreate || - nextEvent == null || - !event.originServerTs.sameEnvironment(nextEvent!.originServerTs) - // #Pangea - && - !isOverlay - // Pangea# - ; + nextEvent == null || + !event.originServerTs.sameEnvironment(nextEvent!.originServerTs); final nextEventSameSender = nextEvent != null && { EventTypes.Message, @@ -182,402 +175,362 @@ class Message extends StatelessWidget { final resetAnimateIn = this.resetAnimateIn; var animateIn = this.animateIn; - final row = - // #Pangea - Material( - color: Colors.transparent, - child: - // Pangea# - StatefulBuilder( - builder: (context, setState) { - if (animateIn && resetAnimateIn != null) { - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - animateIn = false; - setState(resetAnimateIn); - }); - } - return AnimatedSize( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - clipBehavior: Clip.none, - alignment: - ownMessage ? Alignment.bottomRight : Alignment.bottomLeft, - child: animateIn - ? const SizedBox(height: 0, width: double.infinity) - : Stack( - children: [ - Positioned( - top: 0, - bottom: 0, - left: 0, - right: 0, - child: InkWell( - // #Pangea - onTap: controller.clearSelectedEvents, - // onTap: () => onSelect(event), - // onLongPress: () => onSelect(event), - // Pangea# + final row = StatefulBuilder( + builder: (context, setState) { + if (animateIn && resetAnimateIn != null) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + animateIn = false; + setState(resetAnimateIn); + }); + } + return AnimatedSize( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + clipBehavior: Clip.none, + alignment: ownMessage ? Alignment.bottomRight : Alignment.bottomLeft, + child: animateIn + ? const SizedBox(height: 0, width: double.infinity) + : Stack( + children: [ + Positioned( + top: 0, + bottom: 0, + left: 0, + right: 0, + child: InkWell( + // #Pangea + onTap: controller.clearSelectedEvents, + // onTap: () => onSelect(event), + // onLongPress: () => onSelect(event), + // Pangea# + borderRadius: + BorderRadius.circular(AppConfig.borderRadius / 2), + child: Material( borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), - child: Material( - borderRadius: BorderRadius.circular( - AppConfig.borderRadius / 2, - ), - color: selected - ? Theme.of(context) - .colorScheme - .secondaryContainer - .withAlpha(100) - : highlightMarker - ? Theme.of(context) - .colorScheme - .tertiaryContainer - .withAlpha(100) - : Colors.transparent, - ), + color: selected + ? Theme.of(context) + .colorScheme + .secondaryContainer + .withAlpha(100) + : highlightMarker + ? Theme.of(context) + .colorScheme + .tertiaryContainer + .withAlpha(100) + : Colors.transparent, ), ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: rowMainAxisAlignment, - children: [ - // #Pangea - // if (longPressSelect) - // SizedBox( - // height: 32, - // width: Avatar.defaultSize, - // child: Checkbox.adaptive( - // value: selected, - // shape: const CircleBorder(), - // onChanged: (_) => onSelect(event), - // ), - // ) - // else + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: rowMainAxisAlignment, + children: [ + // #Pangea + // if (longPressSelect) + // SizedBox( + // height: 32, + // width: Avatar.defaultSize, + // child: Checkbox.adaptive( + // value: selected, + // shape: const CircleBorder(), + // onChanged: (_) => onSelect(event), + // ), + // ) + // else if (nextEventSameSender || ownMessage) + if (nextEventSameSender || ownMessage || isOverlay) // Pangea# - if (nextEventSameSender || - ownMessage - // #Pangea - || - isOverlay - // Pangea# - ) - SizedBox( - width: Avatar.defaultSize, - child: Center( - child: SizedBox( - width: 16, - height: 16, - child: event.status == EventStatus.error - ? const Icon( - Icons.error, - color: Colors.red, - ) - : event.fileSendingStatus != null - ? const CircularProgressIndicator - .adaptive( - strokeWidth: 1, - ) - : null, - ), + SizedBox( + width: Avatar.defaultSize, + child: Center( + child: SizedBox( + width: 16, + height: 16, + child: event.status == EventStatus.error + ? const Icon(Icons.error, color: Colors.red) + : event.fileSendingStatus != null + ? const CircularProgressIndicator + .adaptive( + strokeWidth: 1, + ) + : null, ), - ) - else - FutureBuilder( - future: event.fetchSenderUser(), - builder: (context, snapshot) { - final user = snapshot.data ?? - event.senderFromMemoryOrFallback; - return Avatar( - // mxContent: user.avatarUrl, - // name: user.calcDisplayname(), - // presenceUserId: user.stateKey, - name: "?", - presenceBackgroundColor: - avatarPresenceBackgroundColor, - onTap: () => onAvatarTab(event), - ); - }, ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (!nextEventSameSender - // #Pangea - && - !isOverlay - // Pangea# - ) - Padding( - padding: const EdgeInsets.only( - left: 8.0, - bottom: 4, - ), - child: ownMessage || event.room.isDirectChat - ? const SizedBox(height: 12) - : FutureBuilder( - future: event.fetchSenderUser(), - builder: (context, snapshot) { - // final displayname = snapshot.data - // ?.calcDisplayname() ?? - // event.senderFromMemoryOrFallback - // .calcDisplayname(); - const displayname = "?"; - return Text( - displayname, - style: TextStyle( - fontSize: 12, - color: (Theme.of(context) - .brightness == - Brightness.light - ? displayname.color - : displayname - .lightColorText), - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ); - }, - ), + ) + else + FutureBuilder( + future: event.fetchSenderUser(), + builder: (context, snapshot) { + final user = snapshot.data ?? + event.senderFromMemoryOrFallback; + return Avatar( + mxContent: user.avatarUrl, + name: user.calcDisplayname(), + presenceUserId: user.stateKey, + presenceBackgroundColor: + avatarPresenceBackgroundColor, + onTap: () => onAvatarTab(event), + ); + }, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // #Pangea + // if (!nextEventSameSender) + if (!nextEventSameSender && !isOverlay) + // Pangea# + Padding( + padding: const EdgeInsets.only( + left: 8.0, + bottom: 4, ), - Container( - alignment: alignment, - padding: const EdgeInsets.only(left: 8), - child: GestureDetector( - // #Pangea - onTap: () => - showToolbar(pangeaMessageEvent), - onDoubleTap: () => - showToolbar(pangeaMessageEvent), - onLongPress: () => - showToolbar(pangeaMessageEvent), - // onLongPress: longPressSelect - // ? null - // : () { - // HapticFeedback.heavyImpact(); - // onSelect(event); - // }, - // Pangea# - child: AnimatedOpacity( - opacity: animateIn - ? 0 - : event.redacted || - event.messageType == - MessageTypes - .BadEncrypted || - event.status.isSending - ? 0.5 - : 1, - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - child: Material( - color: noBubble - ? Colors.transparent - : color, - clipBehavior: Clip.antiAlias, - shape: RoundedRectangleBorder( - borderRadius: borderRadius, + child: ownMessage || event.room.isDirectChat + ? const SizedBox(height: 12) + : FutureBuilder( + future: event.fetchSenderUser(), + builder: (context, snapshot) { + final displayname = snapshot.data + ?.calcDisplayname() ?? + event.senderFromMemoryOrFallback + .calcDisplayname(); + return Text( + displayname, + style: TextStyle( + fontSize: 12, + color: (Theme.of(context) + .brightness == + Brightness.light + ? displayname.color + : displayname + .lightColorText), + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + }, ), - // #Pangea - child: CompositedTransformTarget( - link: isOverlay + ), + Container( + alignment: alignment, + padding: const EdgeInsets.only(left: 8), + child: GestureDetector( + // #Pangea + onTap: () => showToolbar(pangeaMessageEvent), + onDoubleTap: () => + showToolbar(pangeaMessageEvent), + onLongPress: () => + showToolbar(pangeaMessageEvent), + // onLongPress: longPressSelect + // ? null + // : () { + // HapticFeedback.heavyImpact(); + // onSelect(event); + // }, + // Pangea# + child: AnimatedOpacity( + opacity: animateIn + ? 0 + : event.redacted || + event.messageType == + MessageTypes.BadEncrypted || + event.status.isSending + ? 0.5 + : 1, + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + child: Material( + color: + noBubble ? Colors.transparent : color, + clipBehavior: Clip.antiAlias, + shape: RoundedRectangleBorder( + borderRadius: borderRadius, + ), + // #Pangea + child: CompositedTransformTarget( + link: isOverlay + ? LayerLinkAndKey('overlay_msg') + .link + : MatrixState.pAnyState + .layerLinkAndKey(event.eventId) + .link, + child: Container( + key: isOverlay ? LayerLinkAndKey('overlay_msg') - .link + .key : MatrixState.pAnyState .layerLinkAndKey( event.eventId, ) - .link, - child: Container( - key: isOverlay - ? LayerLinkAndKey('overlay_msg') - .key - : MatrixState.pAnyState - .layerLinkAndKey( - event.eventId, - ) - .key, - // Pangea# - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular( - AppConfig.borderRadius, - ), + .key, + // Pangea# + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, ), - padding: noBubble || noPadding - ? EdgeInsets.zero - : const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - constraints: const BoxConstraints( - maxWidth: - FluffyThemes.columnWidth * - 1.5, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - if (event.relationshipType == - RelationshipTypes.reply) - FutureBuilder( - future: event.getReplyEvent( - timeline, - ), - builder: ( - BuildContext context, - snapshot, - ) { - final replyEvent = - snapshot.hasData - ? snapshot.data! - : Event( - eventId: event - .relationshipEventId!, - content: { - 'msgtype': - 'm.text', - 'body': - '...', - }, - senderId: event - .senderId, - type: - 'm.room.message', - room: event - .room, - status: - EventStatus - .sent, - originServerTs: - DateTime - .now(), - ); - return Padding( - padding: - const EdgeInsets - .only( - bottom: 4.0, - ), - child: InkWell( - borderRadius: - ReplyContent - .borderRadius, - onTap: () => - scrollToEventId( - replyEvent.eventId, - ), - child: AbsorbPointer( - child: ReplyContent( - replyEvent, - ownMessage: - ownMessage, - timeline: - timeline, - ), - ), - ), - ); - }, - ), - MessageContent( - displayEvent, - textColor: textColor, - onInfoTab: onInfoTab, - borderRadius: borderRadius, - // #Pangea - selected: selected, - pangeaMessageEvent: - pangeaMessageEvent, - immersionMode: immersionMode, - isOverlay: isOverlay, - controller: controller, - // Pangea# + ), + padding: noBubble || noPadding + ? EdgeInsets.zero + : const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, ), - if (event.hasAggregatedEvents( - timeline, - RelationshipTypes - .edit, - ) - // #Pangea - || - (pangeaMessageEvent - ?.showUseType ?? - false) - // Pangea# - ) - Padding( - padding: - const EdgeInsets.only( - top: 4.0, - ), - child: Row( - mainAxisSize: - MainAxisSize.min, - children: [ - // #Pangea - if (pangeaMessageEvent - ?.showUseType ?? - false) ...[ - pangeaMessageEvent! - .msgUseType - .iconView( - context, - textColor - .withAlpha(164), + constraints: const BoxConstraints( + maxWidth: + FluffyThemes.columnWidth * 1.5, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + if (event.relationshipType == + RelationshipTypes.reply) + FutureBuilder( + future: event + .getReplyEvent(timeline), + builder: ( + BuildContext context, + snapshot, + ) { + final replyEvent = snapshot + .hasData + ? snapshot.data! + : Event( + eventId: event + .relationshipEventId!, + content: { + 'msgtype': + 'm.text', + 'body': '...', + }, + senderId: + event.senderId, + type: + 'm.room.message', + room: event.room, + status: EventStatus + .sent, + originServerTs: + DateTime.now(), + ); + return Padding( + padding: + const EdgeInsets.only( + bottom: 4.0, + ), + child: InkWell( + borderRadius: + ReplyContent + .borderRadius, + onTap: () => + scrollToEventId( + replyEvent.eventId, + ), + child: AbsorbPointer( + child: ReplyContent( + replyEvent, + ownMessage: + ownMessage, + timeline: timeline, ), - const SizedBox( - width: 4, - ), - ], - if (event - .hasAggregatedEvents( - timeline, - RelationshipTypes - .edit, - )) ...[ - // Pangea# - Icon( - Icons.edit_outlined, + ), + ), + ); + }, + ), + MessageContent( + displayEvent, + textColor: textColor, + onInfoTab: onInfoTab, + borderRadius: borderRadius, + // #Pangea + selected: selected, + pangeaMessageEvent: + pangeaMessageEvent, + immersionMode: immersionMode, + isOverlay: isOverlay, + controller: controller, + // Pangea# + ), + if (event.hasAggregatedEvents( + timeline, + RelationshipTypes.edit, + ) + // #Pangea + || + (pangeaMessageEvent + ?.showUseType ?? + false) + // Pangea# + ) + Padding( + padding: + const EdgeInsets.only( + top: 4.0, + ), + child: Row( + mainAxisSize: + MainAxisSize.min, + children: [ + // #Pangea + if (pangeaMessageEvent + ?.showUseType ?? + false) ...[ + pangeaMessageEvent! + .msgUseType + .iconView( + context, + textColor + .withAlpha(164), + ), + const SizedBox( + width: 4, + ), + ], + if (event + .hasAggregatedEvents( + timeline, + RelationshipTypes.edit, + )) ...[ + // Pangea# + Icon( + Icons.edit_outlined, + color: textColor + .withAlpha(164), + size: 14, + ), + Text( + ' - ${displayEvent.originServerTs.localizedTimeShort(context)}', + style: TextStyle( color: textColor .withAlpha(164), - size: 14, + fontSize: 12, ), - Text( - ' - ${displayEvent.originServerTs.localizedTimeShort(context)}', - style: TextStyle( - color: textColor - .withAlpha( - 164, - ), - fontSize: 12, - ), - ), - ], + ), ], - ), + ], ), - ], - ), + ), + ], ), ), ), ), ), ), - ], - ), + ), + ], ), - ], - ), - ], - ), - ); - }, - ), + ), + ], + ), + ], + ), + ); + }, ); Widget container; final showReceiptsRow = @@ -694,6 +647,10 @@ class Message extends StatelessWidget { container = row; } + // #Pangea + container = Material(type: MaterialType.transparency, child: container); + // Pangea# + return Center( child: Swipeable( key: ValueKey(event.eventId),