From bb263c71c2a2887a470a11cb9cc105d3752c99c5 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Wed, 7 Aug 2024 17:10:44 -0400 Subject: [PATCH] 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, - ), - ), - ], - ], - ), - ), - ], - ), + ], ), ), ),