diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 734869ecc..2106db95a 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -44,7 +44,6 @@ abstract class AppConfig { toolbarButtonsHeight + (chatInputRowOverlayPadding * 2) + toolbarSpacing; - static const double audioTranscriptionMaxHeight = 150.0; static TextStyle messageTextStyle( Event? event, diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart index 83aaf2275..ee9f64546 100644 --- a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart @@ -22,6 +22,8 @@ class PhoneticTranscriptionWidget extends StatefulWidget { final bool enabled; + final VoidCallback? onTranscriptionFetched; + const PhoneticTranscriptionWidget({ super.key, required this.text, @@ -30,6 +32,7 @@ class PhoneticTranscriptionWidget extends StatefulWidget { this.iconSize, this.iconColor, this.enabled = true, + this.onTranscriptionFetched, }); @override @@ -103,7 +106,12 @@ class _PhoneticTranscriptionWidgetState }, ); } finally { - if (mounted) setState(() => _isLoading = false); + if (mounted) { + setState(() { + _isLoading = false; + widget.onTranscriptionFetched?.call(); + }); + } } } diff --git a/lib/pangea/toolbar/widgets/message_selection_overlay.dart b/lib/pangea/toolbar/widgets/message_selection_overlay.dart index daaecd2ae..14da26467 100644 --- a/lib/pangea/toolbar/widgets/message_selection_overlay.dart +++ b/lib/pangea/toolbar/widgets/message_selection_overlay.dart @@ -101,6 +101,8 @@ class MessageOverlayController extends State bool showSpeechTranslation = false; String? speechTranslation; + final StreamController contentChangedStream = StreamController.broadcast(); + double maxWidth = AppConfig.toolbarMinWidth; ///////////////////////////////////// @@ -121,6 +123,7 @@ class MessageOverlayController extends State WidgetsBinding.instance.addPostFrameCallback( (_) => widget.chatController.clearSelectedEvents(), ); + contentChangedStream.close(); super.dispose(); } @@ -587,7 +590,10 @@ class MessageOverlayController extends State void setTranslation(String value) { if (mounted) { - setState(() => translation = value); + setState(() { + translation = value; + contentChangedStream.add(true); + }); } } @@ -598,12 +604,18 @@ class MessageOverlayController extends State } if (showTranslation == show) return; - setState(() => showTranslation = show); + setState(() { + showTranslation = show; + contentChangedStream.add(true); + }); } void setSpeechTranslation(String value) { if (mounted) { - setState(() => speechTranslation = value); + setState(() { + speechTranslation = value; + contentChangedStream.add(true); + }); } } @@ -614,7 +626,10 @@ class MessageOverlayController extends State } if (showSpeechTranslation == show) return; - setState(() => showSpeechTranslation = show); + setState(() { + showSpeechTranslation = show; + contentChangedStream.add(true); + }); } void setTranscription(SpeechToTextModel value) { @@ -622,13 +637,17 @@ class MessageOverlayController extends State setState(() { transcriptionError = null; transcription = value; + contentChangedStream.add(true); }); } } void setTranscriptionError(String value) { if (mounted) { - setState(() => transcriptionError = value); + setState(() { + transcriptionError = value; + contentChangedStream.add(true); + }); } } diff --git a/lib/pangea/toolbar/widgets/message_selection_positioner.dart b/lib/pangea/toolbar/widgets/message_selection_positioner.dart index f32f4d3ba..bbfad506d 100644 --- a/lib/pangea/toolbar/widgets/message_selection_positioner.dart +++ b/lib/pangea/toolbar/widgets/message_selection_positioner.dart @@ -71,6 +71,7 @@ class MessageSelectionPositionerState extends State Offset? _currentOffset; StreamSubscription? _reactionSubscription; + StreamSubscription? _contentChangedSubscription; final _animationDuration = const Duration( milliseconds: AppConfig.overlayAnimationDuration, @@ -106,6 +107,10 @@ class MessageSelectionPositionerState extends State }, ).listen((_) => setState(() {})); + _contentChangedSubscription = widget + .overlayController.contentChangedStream.stream + .listen(_onContentSizeChanged); + WidgetsBinding.instance.addPostFrameCallback((_) async { await _centeredMessageCompleter.future; if (!mounted) return; @@ -138,6 +143,7 @@ class MessageSelectionPositionerState extends State void dispose() { _animationController.dispose(); _reactionSubscription?.cancel(); + _contentChangedSubscription?.cancel(); MatrixState.pangeaController.matrixState.audioPlayer ?..stop() ..dispose(); @@ -196,34 +202,9 @@ class MessageSelectionPositionerState extends State } if (mode == ReadingAssistanceMode.selectMode) { - _overlayOffsetAnimation = Tween( - begin: _currentOffset, - end: _adjustedOriginalMessageOffset, - ).animate( - CurvedAnimation( - parent: _animationController, - curve: FluffyThemes.animationCurve, - ), - )..addListener(() { - if (mounted) { - setState(() => _currentOffset = _overlayOffsetAnimation?.value); - } - }); + _resetOffsetAnimation(_adjustedOriginalMessageOffset); } else if (mode == ReadingAssistanceMode.practiceMode) { - _overlayOffsetAnimation = Tween( - begin: _currentOffset, - end: _centeredMessageOffset!, - ).animate( - CurvedAnimation( - parent: _animationController, - curve: FluffyThemes.animationCurve, - ), - )..addListener(() { - if (mounted) { - setState(() => _currentOffset = _overlayOffsetAnimation?.value); - } - }); - + _resetOffsetAnimation(_centeredMessageOffset!); _messageSizeAnimation = Tween( begin: Size( _originalMessageSize.width, @@ -244,6 +225,40 @@ class MessageSelectionPositionerState extends State } } + void _onContentSizeChanged(_) { + Future.delayed(FluffyThemes.animationDuration, () { + final offset = _overlayMessageRenderBox?.localToGlobal(Offset.zero); + if (offset == null || !_overlayMessageRenderBox!.hasSize) { + return null; + } + + final newOffset = _adjustedMessageOffset( + _overlayMessageRenderBox!.size, + offset, + ); + + if (newOffset == _currentOffset) return; + _resetOffsetAnimation(newOffset); + _animationController.forward(from: 0); + }); + } + + void _resetOffsetAnimation(Offset offset) { + _overlayOffsetAnimation = Tween( + begin: _currentOffset, + end: offset, + ).animate( + CurvedAnimation( + parent: _animationController, + curve: FluffyThemes.animationCurve, + ), + )..addListener(() { + if (mounted) { + setState(() => _currentOffset = _overlayOffsetAnimation?.value); + } + }); + } + T _runWithLogging( Function runner, String errorMessage, @@ -326,6 +341,14 @@ class MessageSelectionPositionerState extends State null, ); + RenderBox? get _overlayMessageRenderBox => _runWithLogging( + () => MatrixState.pAnyState.getRenderBox( + 'overlay_message_${widget.event.eventId}', + ), + "Error getting overlay message render box", + null, + ); + Size get _defaultMessageSize => const Size(FluffyThemes.columnWidth / 2, 100); /// The size of the message in the chat list (as opposed to the expanded size in the center overlay) @@ -394,17 +417,28 @@ class MessageSelectionPositionerState extends State } Offset get _adjustedOriginalMessageOffset { + return _adjustedMessageOffset( + _originalMessageSize, + _originalMessageOffset, + ); + } + + Offset _adjustedMessageOffset( + Size messageSize, + Offset messageOffset, + ) { if (_messageRenderBox == null || !_messageRenderBox!.hasSize) { return _defaultMessageOffset; } - final topOffset = _originalMessageOffset.dy; - final bottomOffset = _originalMessageBottomOffset - - _reactionsHeight - - _selectionButtonsHeight; + final topOffset = messageOffset.dy; + final bottomOffset = + (_mediaQuery!.size.height - topOffset - messageSize.height) - + _reactionsHeight - + _selectionButtonsHeight; - final hasHeaderOverflow = topOffset < - (_headerHeight + AppConfig.toolbarSpacing + _audioTranscriptionHeight); + final hasHeaderOverflow = + topOffset < (_headerHeight + AppConfig.toolbarSpacing); final hasFooterOverflow = bottomOffset < (_footerHeight + AppConfig.toolbarSpacing); @@ -416,15 +450,12 @@ class MessageSelectionPositionerState extends State } if (hasHeaderOverflow) { - final difference = topOffset - - (_headerHeight + - AppConfig.toolbarSpacing + - _audioTranscriptionHeight); + final difference = topOffset - (_headerHeight + AppConfig.toolbarSpacing); double newBottomOffset = _mediaQuery!.size.height - - _originalMessageOffset.dy + + topOffset + difference - - _originalMessageSize.height - + messageSize.height - _selectionButtonsHeight; if (newBottomOffset < _footerHeight + AppConfig.toolbarSpacing) { @@ -524,12 +555,6 @@ class MessageSelectionPositionerState extends State return showSelectionButtons ? AppConfig.toolbarButtonsHeight : 0; } - double get _audioTranscriptionHeight { - return widget.pangeaMessageEvent?.isAudioMessage ?? false - ? AppConfig.audioTranscriptionMaxHeight - : 0; - } - bool get _hasReactions { final reactionsEvents = widget.event.aggregatedEvents( widget.chatController.timeline!, diff --git a/lib/pangea/toolbar/widgets/overlay_center_content.dart b/lib/pangea/toolbar/widgets/overlay_center_content.dart index 835177fc5..c4d2810d9 100644 --- a/lib/pangea/toolbar/widgets/overlay_center_content.dart +++ b/lib/pangea/toolbar/widgets/overlay_center_content.dart @@ -9,6 +9,7 @@ import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dar import 'package:fluffychat/pangea/toolbar/widgets/measure_render_box.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; import 'package:fluffychat/pangea/toolbar/widgets/overlay_message.dart'; +import 'package:fluffychat/widgets/matrix.dart'; class OverlayCenterContent extends StatelessWidget { final Event event; @@ -69,6 +70,9 @@ class OverlayCenterContent extends StatelessWidget { MeasureRenderBox( onChange: onChangeMessageSize, child: OverlayMessage( + key: MatrixState.pAnyState + .layerLinkAndKey('overlay_message_${event.eventId}') + .key, event, pangeaMessageEvent: pangeaMessageEvent, immersionMode: chatController.choreographer.immersionMode, diff --git a/lib/pangea/toolbar/widgets/overlay_message.dart b/lib/pangea/toolbar/widgets/overlay_message.dart index e83e856d3..a5dda244c 100644 --- a/lib/pangea/toolbar/widgets/overlay_message.dart +++ b/lib/pangea/toolbar/widgets/overlay_message.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pages/chat/events/message_content.dart'; @@ -149,9 +150,8 @@ class OverlayMessage extends StatelessWidget { final transcription = showTranscription ? Container( - width: messageWidth, constraints: const BoxConstraints( - maxHeight: AppConfig.audioTranscriptionMaxHeight, + maxWidth: FluffyThemes.columnWidth * 1.5, ), child: Padding( padding: const EdgeInsets.all(12.0), @@ -178,6 +178,7 @@ class OverlayMessage extends StatelessWidget { child: Column( spacing: 8.0, crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ SttTranscriptTokens( model: overlayController.transcription!, @@ -208,6 +209,9 @@ class OverlayMessage extends StatelessWidget { iconColor: textColor, enabled: event.senderId != BotName.byEnvironment, + onTranscriptionFetched: () => + overlayController.contentChangedStream + .add(true), ), ], ), @@ -226,9 +230,8 @@ class OverlayMessage extends StatelessWidget { final translation = showTranslation || showSpeechTranslation ? Container( - width: messageWidth, constraints: const BoxConstraints( - maxHeight: AppConfig.audioTranscriptionMaxHeight, + maxWidth: FluffyThemes.columnWidth * 1.5, ), child: Padding( padding: const EdgeInsets.fromLTRB( @@ -271,8 +274,6 @@ class OverlayMessage extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (readingAssistanceMode == ReadingAssistanceMode.transitionMode) - transcription, if (event.relationshipType == RelationshipTypes.reply) FutureBuilder( future: event.getReplyEvent( @@ -371,8 +372,6 @@ class OverlayMessage extends StatelessWidget { ], ), ), - if (readingAssistanceMode == ReadingAssistanceMode.transitionMode) - translation, ], ), ), @@ -386,26 +385,31 @@ class OverlayMessage extends StatelessWidget { color: noBubble ? Colors.transparent : color, borderRadius: borderRadius, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (readingAssistanceMode != ReadingAssistanceMode.transitionMode) + constraints: BoxConstraints( + maxWidth: FluffyThemes.columnWidth * 1.5, + maxHeight: maxHeight, + ), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ transcription, - sizeAnimation != null - ? AnimatedBuilder( - animation: sizeAnimation!, - builder: (context, child) { - return SizedBox( - height: sizeAnimation!.value.height, - width: sizeAnimation!.value.width, - child: content, - ); - }, - ) - : content, - if (readingAssistanceMode != ReadingAssistanceMode.transitionMode) + sizeAnimation != null + ? AnimatedBuilder( + animation: sizeAnimation!, + builder: (context, child) { + return SizedBox( + height: sizeAnimation!.value.height, + width: sizeAnimation!.value.width, + child: content, + ); + }, + ) + : content, translation, - ], + ], + ), ), ), );