diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index 5b4dce09b..43d9e59e3 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -11,7 +11,6 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat/chat.dart'; -import 'package:fluffychat/pangea/common/utils/any_state_holder.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/message_token_text/message_token_button.dart'; @@ -21,7 +20,6 @@ import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart import 'package:fluffychat/utils/event_checkbox_extension.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; -import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/mxc_image.dart'; import '../../../utils/url_launcher.dart'; @@ -412,68 +410,57 @@ class HtmlMessage extends StatelessWidget { alignment: readingAssistanceMode == ReadingAssistanceMode.practiceMode ? PlaceholderAlignment.bottom : PlaceholderAlignment.middle, - child: CompositedTransformTarget( - link: token != null && renderer.assignTokenKey - ? MatrixState.pAnyState - .layerLinkAndKey(token.text.uniqueKey) - .link - : LayerLinkAndKey(token.hashCode.toString()).link, - child: Column( - key: token != null && renderer.assignTokenKey - ? MatrixState.pAnyState - .layerLinkAndKey(token.text.uniqueKey) - .key - : null, - children: [ - if (renderer.showCenterStyling && token != null) - MessageTokenButton( - token: token, - overlayController: overlayController, - textStyle: renderer.style( + child: Column( + children: [ + if (renderer.showCenterStyling && token != null) + MessageTokenButton( + token: token, + overlayController: overlayController, + textStyle: renderer.style( + context, + color: renderer.backgroundColor( context, - color: renderer.backgroundColor( - context, - selected, - highlighted, - isNew, - ), + selected, + highlighted, + isNew, ), - width: tokenWidth, - animateIn: isTransitionAnimation, ), - MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: onClick != null && token != null - ? () => onClick?.call(token) - : null, - child: RichText( - textDirection: pangeaMessageEvent?.textDirection, - text: TextSpan( - children: [ - LinkifySpan( - text: node.text, - style: renderer.style( + width: tokenWidth, + animateIn: isTransitionAnimation, + ), + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: onClick != null && token != null + ? () => onClick?.call(token) + : null, + child: RichText( + textDirection: pangeaMessageEvent?.textDirection, + text: TextSpan( + children: [ + LinkifySpan( + text: node.text, + style: renderer.style( + context, + color: renderer.backgroundColor( context, - color: renderer.backgroundColor( - context, - selected, - highlighted, - isNew, - ), + selected, + highlighted, + isNew, ), - linkStyle: linkStyle, - onOpen: (url) => - UrlLauncher(context, url.url).launchUrl(), ), - ], - ), + linkStyle: linkStyle, + onOpen: (url) => + UrlLauncher(context, url.url).launchUrl(), + ), + ], ), ), ), - ], - ), + ), + ], + // ), ), ); // Pangea# diff --git a/lib/pangea/toolbar/reading_assistance_input_row/overlay_footer.dart b/lib/pangea/toolbar/reading_assistance_input_row/overlay_footer.dart deleted file mode 100644 index 3511f23ac..000000000 --- a/lib/pangea/toolbar/reading_assistance_input_row/overlay_footer.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/config/themes.dart'; -import 'package:fluffychat/pages/chat/chat.dart'; -import 'package:fluffychat/pangea/chat/widgets/pangea_chat_input_row.dart'; -import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/practice_mode_buttons.dart'; - -class OverlayFooter extends StatelessWidget { - final ChatController controller; - final MessageOverlayController overlayController; - final bool showToolbarButtons; - final ReadingAssistanceMode? readingAssistanceMode; - - const OverlayFooter({ - required this.controller, - required this.overlayController, - required this.showToolbarButtons, - required this.readingAssistanceMode, - super.key, - }); - - @override - Widget build(BuildContext context) { - //@ggurdin can we change this mobile padding to 0? seems a some extrea space on mobile - final bottomSheetPadding = FluffyThemes.isColumnMode(context) ? 16.0 : 8.0; - - return Container( - margin: EdgeInsets.only( - bottom: bottomSheetPadding, - left: bottomSheetPadding, - right: bottomSheetPadding, - ), - height: readingAssistanceMode == ReadingAssistanceMode.practiceMode || - readingAssistanceMode == ReadingAssistanceMode.transitionMode - ? AppConfig.practiceModeInputBarHeight - : AppConfig.selectModeInputBarHeight, - alignment: Alignment.center, - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (showToolbarButtons) - PracticeModeButtons(overlayController: overlayController), - Material( - clipBehavior: Clip.hardEdge, - color: Colors.transparent, - borderRadius: const BorderRadius.all( - Radius.circular(AppConfig.borderRadius), - ), - child: PangeaChatInputRow( - controller: controller, - ), - ), - ], - ), - ); - } -} diff --git a/lib/pangea/toolbar/reading_assistance_input_row/reading_assistance_input_bar.dart b/lib/pangea/toolbar/reading_assistance_input_row/reading_assistance_input_bar.dart index df9211e68..a597ea356 100644 --- a/lib/pangea/toolbar/reading_assistance_input_row/reading_assistance_input_bar.dart +++ b/lib/pangea/toolbar/reading_assistance_input_row/reading_assistance_input_bar.dart @@ -4,11 +4,11 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; -import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_mode_locked_card.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_translation_card.dart'; import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/practice_activity_card.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/practice_mode_buttons.dart'; const double minContentHeight = 120; @@ -23,12 +23,6 @@ class ReadingAssistanceInputBar extends StatelessWidget { }); Widget barContent(BuildContext context) { - if (overlayController.readingAssistanceMode != - ReadingAssistanceMode.practiceMode) { - return const SizedBox(); - // return ReactionsPicker(controller); - } - Widget? content; final target = overlayController.toolbarMode.associatedActivityType != null && @@ -57,6 +51,7 @@ class ReadingAssistanceInputBar extends StatelessWidget { .textTheme .bodyLarge ?.copyWith(fontStyle: FontStyle.italic), + textAlign: TextAlign.center, ); case MessageMode.messageTranslation: @@ -78,7 +73,10 @@ class ReadingAssistanceInputBar extends StatelessWidget { overlayController: overlayController, ); } else { - content = Text(L10n.of(context).allDone); + content = Text( + L10n.of(context).allDone, + textAlign: TextAlign.center, + ); } case MessageMode.wordMorph: if (target != null) { @@ -92,37 +90,43 @@ class ReadingAssistanceInputBar extends StatelessWidget { child: Text( L10n.of(context).selectForGrammar, style: Theme.of(context).textTheme.bodyLarge, + textAlign: TextAlign.center, ), ); } } } - return Container( - constraints: const BoxConstraints( - minHeight: minContentHeight, - ), - child: Center(child: content), - ); + return content; } @override Widget build(BuildContext context) { - return Expanded( - child: ConstrainedBox( - constraints: BoxConstraints( - maxHeight: AppConfig.readingAssistanceInputBarHeight, - maxWidth: overlayController.maxWidth, + return Column( + children: [ + PracticeModeButtons( + overlayController: overlayController, ), - child: AnimatedSize( - duration: const Duration( - milliseconds: AppConfig.overlayAnimationDuration, - ), - child: SingleChildScrollView( - child: barContent(context), + Material( + child: Container( + padding: const EdgeInsets.all(8.0), + alignment: Alignment.center, + constraints: BoxConstraints( + minHeight: minContentHeight, + maxHeight: AppConfig.readingAssistanceInputBarHeight, + maxWidth: overlayController.maxWidth, + ), + child: AnimatedSize( + duration: const Duration( + milliseconds: AppConfig.overlayAnimationDuration, + ), + child: SingleChildScrollView( + child: barContent(context), + ), + ), ), ), - ), + ], ); } } diff --git a/lib/pangea/toolbar/utils/token_rendering_util.dart b/lib/pangea/toolbar/utils/token_rendering_util.dart index 6e4304b10..20fd7225d 100644 --- a/lib/pangea/toolbar/utils/token_rendering_util.dart +++ b/lib/pangea/toolbar/utils/token_rendering_util.dart @@ -25,7 +25,7 @@ class TokenRenderingUtil { bool get showCenterStyling { if (overlayController == null) return false; if (!isTransitionAnimation) return true; - return readingAssistanceMode == ReadingAssistanceMode.transitionMode; + return readingAssistanceMode == ReadingAssistanceMode.practiceMode; } double? fontSize(BuildContext context) => showCenterStyling diff --git a/lib/pangea/toolbar/widgets/icon_number_widget.dart b/lib/pangea/toolbar/widgets/icon_number_widget.dart deleted file mode 100644 index 099baf3df..000000000 --- a/lib/pangea/toolbar/widgets/icon_number_widget.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:flutter/material.dart'; - -class IconNumberWidget extends StatelessWidget { - final IconData icon; - final String number; - final Color? iconColor; - final double? iconSize; - final String? toolTip; - final VoidCallback? onPressed; - - const IconNumberWidget({ - super.key, - required this.icon, - required this.number, - this.toolTip, - this.iconColor, - this.iconSize, - this.onPressed, - }); - - Widget _content(BuildContext context) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: Icon( - icon, - color: iconColor ?? Theme.of(context).iconTheme.color, - size: iconSize ?? Theme.of(context).iconTheme.size, - ), - onPressed: onPressed, - ), - const SizedBox(width: 5), - Text( - number.toString(), - style: TextStyle( - fontSize: - iconSize ?? Theme.of(context).textTheme.bodyMedium?.fontSize, - color: iconColor ?? Theme.of(context).textTheme.bodyMedium?.color, - ), - ), - ], - ); - } - - @override - Widget build(BuildContext context) { - return toolTip != null - ? Tooltip(message: toolTip!, child: _content(context)) - : _content(context); - } -} diff --git a/lib/pangea/toolbar/widgets/message_selection_positioner.dart b/lib/pangea/toolbar/widgets/message_selection_positioner.dart index 948109db4..12c728d48 100644 --- a/lib/pangea/toolbar/widgets/message_selection_positioner.dart +++ b/lib/pangea/toolbar/widgets/message_selection_positioner.dart @@ -14,10 +14,12 @@ import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; +import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart'; +import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/reading_assistance_input_bar.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/overlay_center_content.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/reading_assistance_content.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/select_mode_buttons.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/over_message_overlay.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/practice_mode_transition_animation.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/word_card_switcher.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -51,57 +53,32 @@ class MessageSelectionPositioner extends StatefulWidget { class MessageSelectionPositionerState extends State with TickerProviderStateMixin { - // late AnimationController _animationController; - - // Offset? _centeredMessageOffset; - // Size? _centeredMessageSize; - - // Size? _tooltipSize; - - // final Completer _centeredMessageCompleter = Completer(); - // final Completer _tooltipCompleter = Completer(); - - // MessageMode _currentMode = MessageMode.noneSelected; - - // Animation? _overlayOffsetAnimation; - // Animation? _messageSizeAnimation; - // Offset? _currentOffset; - StreamSubscription? _reactionSubscription; StreamSubscription? _contentChangedSubscription; - ScrollController? _scrollController; + ScrollController? scrollController; - // final _animationDuration = const Duration( - // milliseconds: AppConfig.overlayAnimationDuration, - // // seconds: 5, - // ); + bool finishedTransition = false; + bool startedTransition = false; + + ReadingAssistanceMode readingAssistanceMode = + ReadingAssistanceMode.selectMode; @override void initState() { super.initState(); - _scrollController = ScrollController(); - Future.delayed( - const Duration(milliseconds: 100), - () { - if (_scrollController == null || !_scrollController!.hasClients) { - return; - } - - // _scrollController!.animateTo( - // _scrollController!.position.maxScrollExtent, - // duration: FluffyThemes.animationDuration, - // curve: FluffyThemes.animationCurve, - // ); + scrollController = ScrollController( + onAttach: (position) { + Future.delayed(const Duration(milliseconds: 200), () { + if (mounted) { + scrollController?.jumpTo( + scrollController!.position.maxScrollExtent, + ); + } + }); }, ); - // // _currentMode = widget.overlayController.toolbarMode; - // _animationController = AnimationController( - // vsync: this, - // duration: _animationDuration, - // ); - _reactionSubscription = widget.chatController.room.client.onSync.stream.where( (update) { @@ -125,360 +102,19 @@ class MessageSelectionPositionerState extends State _contentChangedSubscription = widget .overlayController.contentChangedStream.stream .listen(_onContentSizeChanged); - - // WidgetsBinding.instance.addPostFrameCallback((_) async { - // await _centeredMessageCompleter.future; - // if (!mounted) return; - - // setState(() { - // _currentOffset = Offset( - // _ownMessage ? _messageRightOffset : _messageLeftOffset, - // _originalMessageBottomOffset - - // _reactionsHeight - - // _selectionButtonsHeight, - // ); - // }); - - // _setReadingAssistanceMode( - // ReadingAssistanceMode.selectMode, - // ); - // }); } - // @override - // void didUpdateWidget(MessageSelectionPositioner oldWidget) { - // super.didUpdateWidget(oldWidget); - // final mode = widget.overlayController.toolbarMode; - // if (mode != _currentMode) { - // setState(() => _currentMode = mode); - // } - // } - @override void dispose() { - // _animationController.dispose(); _reactionSubscription?.cancel(); _contentChangedSubscription?.cancel(); - _scrollController?.dispose(); + scrollController?.dispose(); MatrixState.pangeaController.matrixState.audioPlayer ?..stop() ..dispose(); super.dispose(); } - // void _setCenteredMessageSize(RenderBox renderBox) { - // if (_centeredMessageCompleter.isCompleted) return; - - // _centeredMessageSize = renderBox.size; - // final offset = renderBox.localToGlobal(Offset.zero); - // _centeredMessageOffset = Offset( - // offset.dx - _columnWidth - _horizontalPadding - 2.0, - // _mediaQuery!.size.height - - // (offset.dy - - // ((AppConfig.practiceModeInputBarHeight - - // AppConfig.selectModeInputBarHeight) * - // 0.75)) - - // renderBox.size.height - - // _reactionsHeight, - // ); - // setState(() {}); - - // if (!_centeredMessageCompleter.isCompleted) { - // _centeredMessageCompleter.complete(); - // } - // } - - // void _setTooltipSize(RenderBox renderBox) { - // setState(() { - // _tooltipSize = renderBox.size; - // }); - - // if (!_tooltipCompleter.isCompleted) { - // _tooltipCompleter.complete(); - // } - // } - - // Future _setReadingAssistanceMode(ReadingAssistanceMode mode) async { - // if (mode == _readingAssistanceMode) { - // return; - // } - - // await _centeredMessageCompleter.future; - - // if (mode == ReadingAssistanceMode.practiceMode) { - // setState( - // () => widget.overlayController.readingAssistanceMode = - // ReadingAssistanceMode.transitionMode, - // ); - // } else if (mode == ReadingAssistanceMode.selectMode) { - // setState( - // () => widget.overlayController.readingAssistanceMode = - // ReadingAssistanceMode.selectMode, - // ); - // } - - // if (mode == ReadingAssistanceMode.selectMode) { - // _resetOffsetAnimation(_adjustedOriginalMessageOffset); - // } else if (mode == ReadingAssistanceMode.practiceMode) { - // _resetOffsetAnimation(_centeredMessageOffset!); - // _messageSizeAnimation = Tween( - // begin: Size( - // _originalMessageSize.width, - // _originalMessageSize.height, - // ), - // end: _adjustedCenteredMessageSize, - // ).animate( - // CurvedAnimation( - // parent: _animationController, - // curve: FluffyThemes.animationCurve, - // ), - // ); - // } - - // await _animationController.forward(from: 0); - // if (mounted) { - // setState(() => widget.overlayController.readingAssistanceMode = mode); - // } - // } - - void _onContentSizeChanged(_) { - Future.delayed(FluffyThemes.animationDuration, () { - setState(() {}); - // 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); - // } - // }); - // } - - // double get _inputBarSize => - // _readingAssistanceMode == ReadingAssistanceMode.practiceMode || - // _readingAssistanceMode == ReadingAssistanceMode.transitionMode - // ? AppConfig.practiceModeInputBarHeight - // : AppConfig.selectModeInputBarHeight; - - // /// Available vertical space not taken up by the header and footer - // double? get _verticalSpace { - // if (_mediaQuery == null) return null; - // return _mediaQuery!.size.height - _headerHeight - _footerHeight; - // } - - // original message size and offset - - // Offset? get _overlayMessageOffset => - // _overlayMessageRenderBox?.localToGlobal(Offset.zero); - - // double? get _buttonsTopOffset { - // if (_overlayMessageOffset == null || - // _overlayMessageSize == null || - // _mediaQuery == null) { - // return null; - // } - - // const buttonsHeight = 300.0; - // final availableSpace = _mediaQuery!.size.height - - // _overlayMessageOffset!.dy - - // _overlayMessageSize!.height - - // _reactionsHeight - - // 4.0; - - // if (availableSpace >= buttonsHeight) { - // return _overlayMessageOffset!.dy + _overlayMessageSize!.height + 4.0; - // } - - // return _mediaQuery!.size.height - buttonsHeight - 4.0; - // } - - // Centered message size and offset - - // bool get _centeredMessageHasOverflow { - // if (_verticalSpace == null || - // _centeredMessageSize == null || - // _centeredMessageOffset == null) { - // return false; - // } - - // final finalMessageHeight = _centeredMessageSize!.height + _reactionsHeight; - // return finalMessageHeight > _verticalSpace!; - // } - - // /// Size of the centered overlay message adjusted for overflow - // Size? get _adjustedCenteredMessageSize { - // if (_centeredMessageHasOverflow) { - // return Size( - // _centeredMessageSize!.width, - // _verticalSpace! - (AppConfig.toolbarSpacing * 2), - // ); - // } - // return _centeredMessageSize; - // } - - // Offset? get _adjustedCenteredMessageOffset { - // if (_centeredMessageHasOverflow) { - // return Offset( - // _centeredMessageOffset!.dx, - // _footerHeight + AppConfig.toolbarSpacing, - // ); - // } - // return _centeredMessageOffset; - // } - - // message offset - - // Offset get _adjustedOriginalMessageOffset { - // return _adjustedMessageOffset( - // _originalMessageSize, - // _originalMessageOffset, - // ); - // } - - // Offset _adjustedMessageOffset( - // Size messageSize, - // Offset messageOffset, - // ) { - // if (_messageRenderBox == null || !_messageRenderBox!.hasSize) { - // return _defaultMessageOffset; - // } - - // final topOffset = messageOffset.dy; - // final bottomOffset = - // (_mediaQuery!.size.height - topOffset - messageSize.height) - - // _reactionsHeight - - // _selectionButtonsHeight; - - // final hasHeaderOverflow = - // topOffset < (_headerHeight + AppConfig.toolbarSpacing); - // final hasFooterOverflow = - // bottomOffset < (_footerHeight + AppConfig.toolbarSpacing); - - // if (!hasHeaderOverflow && !hasFooterOverflow) { - // return Offset( - // _ownMessage ? _messageRightOffset : _messageLeftOffset, - // bottomOffset, - // ); - // } - - // if (hasHeaderOverflow) { - // final difference = topOffset - (_headerHeight + AppConfig.toolbarSpacing); - - // double newBottomOffset = _mediaQuery!.size.height - - // topOffset + - // difference - - // messageSize.height - - // _selectionButtonsHeight; - - // if (newBottomOffset < _footerHeight + AppConfig.toolbarSpacing) { - // newBottomOffset = _footerHeight + AppConfig.toolbarSpacing; - // } - - // return Offset( - // _ownMessage ? _messageRightOffset : _messageLeftOffset, - // newBottomOffset, - // ); - // } else { - // return Offset( - // _ownMessage ? _messageRightOffset : _messageLeftOffset, - // _footerHeight + (AppConfig.toolbarSpacing * 2), - // ); - // } - // } - - // double get _originalMessageBottomOffset => - // _mediaQuery!.size.height - - // _originalMessageOffset.dy - - // _originalMessageSize.height; - - // double? get _centeredMessageTopOffset { - // if (_mediaQuery == null || - // _adjustedCenteredMessageOffset == null || - // _adjustedCenteredMessageSize == null) { - // return null; - // } - // return _mediaQuery!.size.height - - // _adjustedCenteredMessageOffset!.dy - - // _adjustedCenteredMessageSize!.height - - // _reactionsHeight; - // } - - // double get _headerHeight { - // return (Theme.of(context).appBarTheme.toolbarHeight ?? - // AppConfig.defaultHeaderHeight) + - // (_mediaQuery?.padding.top ?? 0); - // } - - // double get _footerHeight { - // return _inputBarSize + (_mediaQuery?.padding.bottom ?? 0); - // } - - // measurement for items in the toolbar - - // bool get _showButtons { - // if (!(widget.pangeaMessageEvent?.shouldShowToolbar ?? false)) { - // return false; - // } - - // final type = widget.pangeaMessageEvent?.event.messageType; - // if (![MessageTypes.Text, MessageTypes.Audio].contains(type)) { - // return false; - // } - - // if (type == MessageTypes.Text) { - // return widget.pangeaMessageEvent?.messageDisplayLangIsL2 ?? false; - // } - - // return true; - // } - - // bool get showPracticeButtons => - // _showButtons && - // widget.overlayController.readingAssistanceMode == - // ReadingAssistanceMode.practiceMode; - - // bool get showSelectionButtons => - // _showButtons && - // [ReadingAssistanceMode.selectMode, null] - // .contains(widget.overlayController.readingAssistanceMode); - - // double get _selectionButtonsHeight { - // return showSelectionButtons ? AppConfig.toolbarButtonsHeight : 0; - // } - - // double get _readingAssistanceModeOpacity { - // switch (_readingAssistanceMode) { - // case ReadingAssistanceMode.practiceMode: - // case ReadingAssistanceMode.transitionMode: - // return 0.8; - // case ReadingAssistanceMode.selectMode: - // case null: - // return 0.6; - // } - // } - T _runWithLogging( Function runner, String errorMessage, @@ -498,10 +134,16 @@ class MessageSelectionPositionerState extends State } } + final Duration transitionAnimationDuration = + const Duration(milliseconds: 300); + + final Offset _defaultMessageOffset = + const Offset(Avatar.defaultSize + 16 + 8, 300); + double get _horizontalPadding => FluffyThemes.isColumnMode(context) ? 8.0 : 0.0; - bool get _hasReactions { + bool get hasReactions { final reactionsEvents = widget.event.aggregatedEvents( widget.chatController.timeline!, RelationshipTypes.reaction, @@ -509,23 +151,23 @@ class MessageSelectionPositionerState extends State return reactionsEvents.where((e) => !e.redacted).isNotEmpty; } - double get _reactionsHeight => _hasReactions ? 32.0 : 0.0; + double get reactionsHeight => hasReactions ? 32.0 : 0.0; - bool get _ownMessage => + bool get ownMessage => widget.event.senderId == widget.event.room.client.userID; - bool get _showDetails => + bool get showDetails => AppSettings.displayChatDetailsColumn.getItem(Matrix.of(context).store) && FluffyThemes.isThreeColumnMode(context) && widget.chatController.room.membership == Membership.join; - MediaQueryData? get _mediaQuery => _runWithLogging( + MediaQueryData? get mediaQuery => _runWithLogging( () => MediaQuery.of(context), "Error getting media query", null, ); - double get _columnWidth => FluffyThemes.isColumnMode(context) + double get columnWidth => FluffyThemes.isColumnMode(context) ? (FluffyThemes.columnWidth + FluffyThemes.navRailWidth + 1.0) : 0; @@ -538,8 +180,8 @@ class MessageSelectionPositionerState extends State messageMargin; double? maxWidth; - if (_mediaQuery != null) { - final chatViewWidth = _mediaQuery!.size.width - _columnWidth; + if (mediaQuery != null) { + final chatViewWidth = mediaQuery!.size.width - columnWidth; maxWidth = chatViewWidth - (2 * _horizontalPadding) - messageMargin; } @@ -550,9 +192,6 @@ class MessageSelectionPositionerState extends State return maxWidth; } - static const Offset _defaultMessageOffset = - Offset(Avatar.defaultSize + 16 + 8, 300); - Size get _defaultMessageSize => const Size(FluffyThemes.columnWidth / 2, 100); RenderBox? get _overlayMessageRenderBox => _runWithLogging( @@ -565,6 +204,18 @@ class MessageSelectionPositionerState extends State Size? get _overlayMessageSize => _overlayMessageRenderBox?.size; + Offset? get overlayMessageOffset { + if (_overlayMessageRenderBox == null || + !_overlayMessageRenderBox!.hasSize) { + return null; + } + return _runWithLogging( + () => _overlayMessageRenderBox?.localToGlobal(Offset.zero), + "Error getting overlay message offset", + null, + ); + } + RenderBox? get _messageRenderBox => _runWithLogging( () => MatrixState.pAnyState.getRenderBox( widget.event.eventId, @@ -585,7 +236,7 @@ class MessageSelectionPositionerState extends State } /// The size of the message in the chat list (as opposed to the expanded size in the center overlay) - Size get _originalMessageSize { + Size get originalMessageSize { if (_messageRenderBox == null || !_messageRenderBox!.hasSize) { return _defaultMessageSize; } @@ -597,25 +248,23 @@ class MessageSelectionPositionerState extends State ); } - double? get _messageLeftOffset { - if (_ownMessage) return null; - return max(_originalMessageOffset.dx - _columnWidth, 0); + double? get messageLeftOffset { + if (ownMessage) return null; + return max(_originalMessageOffset.dx - columnWidth, 0); } - double? get _messageRightOffset { - if (_mediaQuery == null || !_ownMessage) return null; - return _mediaQuery!.size.width - + double? get messageRightOffset { + if (mediaQuery == null || !ownMessage) return null; + return mediaQuery!.size.width - _originalMessageOffset.dx - - _originalMessageSize.width - - (_showDetails ? FluffyThemes.columnWidth : 0); + originalMessageSize.width - + (showDetails ? FluffyThemes.columnWidth : 0); } - double? get _contentHeight { - if (_overlayMessageSize == null) return null; - return _overlayMessageSize!.height + - _reactionsHeight + - AppConfig.toolbarMenuHeight + - 4.0; + double get _contentHeight { + final messageHeight = + _overlayMessageSize?.height ?? originalMessageSize.height; + return messageHeight + reactionsHeight + AppConfig.toolbarMenuHeight + 4.0; } double get _overheadContentHeight { @@ -625,44 +274,86 @@ class MessageSelectionPositionerState extends State : 40.0; } - double? get _availableSpaceAboveContent { - if (_contentHeight == null || _mediaQuery == null) return null; - return max( - 0, - (_mediaQuery!.size.height - - _mediaQuery!.padding.top - - _mediaQuery!.padding.bottom - - _contentHeight!) / - 2, - ); - } - - double? get _wordCardTopOffset { - if (_contentHeight == null || _availableSpaceAboveContent == null) { - return null; - } - - if (_availableSpaceAboveContent! >= _overheadContentHeight) { - return _availableSpaceAboveContent! - _overheadContentHeight - 4.0; - } - - return 0; - } - double? get _wordCardLeftOffset { - if (_ownMessage) return null; + if (ownMessage) return null; if (widget.pangeaMessageEvent != null && widget.overlayController.selectedToken != null && - _mediaQuery != null && - (_mediaQuery!.size.width < _toolbarMaxWidth + _messageLeftOffset!)) { - return _mediaQuery!.size.width - _toolbarMaxWidth - 8.0; + mediaQuery != null && + (mediaQuery!.size.width < _toolbarMaxWidth + messageLeftOffset!)) { + return mediaQuery!.size.width - _toolbarMaxWidth - 8.0; + } + return messageLeftOffset; + } + + double get _fullContentHeight { + return _contentHeight + _overheadContentHeight; + } + + bool get shouldScroll { + if (mediaQuery == null) return false; + return _fullContentHeight > + (mediaQuery!.size.height - + mediaQuery!.padding.bottom - + mediaQuery!.padding.top); + } + + bool get _hasFooterOverflow { + if (mediaQuery == null || _overlayMessageSize == null) return false; + final bottomOffset = _originalMessageOffset.dy + + originalMessageSize.height + + reactionsHeight + + AppConfig.toolbarMenuHeight + + 4.0; + return bottomOffset > + (mediaQuery!.size.height - + mediaQuery!.padding.bottom - + mediaQuery!.padding.top); + } + + double get spaceAboveContent { + if (shouldScroll) return _overheadContentHeight; + if (_hasFooterOverflow) { + return mediaQuery!.size.height - + mediaQuery!.padding.top - + _fullContentHeight; + } + + return _originalMessageOffset.dy - + mediaQuery!.padding.top - + _overheadContentHeight; + } + + void _onContentSizeChanged(_) { + Future.delayed(FluffyThemes.animationDuration, () { + setState(() {}); + }); + } + + void onStartedTransition() { + if (mounted) { + setState(() { + startedTransition = true; + }); + } + } + + void onFinishedTransition() { + if (mounted) { + setState(() { + finishedTransition = true; + }); + } + } + + void setReadingAssistanceMode(ReadingAssistanceMode mode) { + if (mounted) { + setState(() => readingAssistanceMode = mode); } - return _messageLeftOffset; } @override Widget build(BuildContext context) { - if (_messageRenderBox == null || _mediaQuery == null) { + if (_messageRenderBox == null || mediaQuery == null) { return const SizedBox.shrink(); } @@ -672,119 +363,55 @@ class MessageSelectionPositionerState extends State children: [ Column( children: [ - Expanded( - child: SizedBox( - width: _mediaQuery!.size.width - - _columnWidth - - (_showDetails ? FluffyThemes.columnWidth : 0), - child: Stack( - alignment: _ownMessage - ? Alignment.centerRight - : Alignment.centerLeft, - children: [ - GestureDetector( - onTap: widget.chatController.clearSelectedEvents, - child: SingleChildScrollView( - controller: _scrollController, - padding: EdgeInsets.only( - left: _messageLeftOffset ?? 0.0, - right: _messageRightOffset ?? 0.0, - ), - child: Column( - crossAxisAlignment: _ownMessage - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (_contentHeight != null && - _mediaQuery != null && - _availableSpaceAboveContent != null && - _availableSpaceAboveContent! < - _overheadContentHeight) - AnimatedContainer( - duration: FluffyThemes.animationDuration, - height: _contentHeight! + - _overheadContentHeight > - _mediaQuery!.size.height - ? _overheadContentHeight - : (_overheadContentHeight - - _availableSpaceAboveContent!) * - 2, - ), - CompositedTransformTarget( - link: MatrixState.pAnyState - .layerLinkAndKey( - 'overlay_message_${widget.event.eventId}', - ) - .link, - child: OverlayCenterContent( - event: widget.event, - messageHeight: _originalMessageSize.height, - messageWidth: widget - .overlayController.showingExtraContent - ? max(_originalMessageSize.width, 150) - : _originalMessageSize.width, - overlayController: widget.overlayController, - chatController: widget.chatController, - nextEvent: widget.nextEvent, - prevEvent: widget.prevEvent, - hasReactions: _hasReactions, - // sizeAnimation: _messageSizeAnimation, - isTransitionAnimation: true, - readingAssistanceMode: widget - .overlayController.readingAssistanceMode, - ), - ), - const SizedBox(height: 4.0), - SelectModeButtons( - controller: widget.chatController, - overlayController: widget.overlayController, - lauchPractice: () {}, - // lauchPractice: () { - // _setReadingAssistanceMode( - // ReadingAssistanceMode.practiceMode, - // ); - // widget.overlayController - // .updateSelectedSpan(null); - // }, - ), - ], - ), + SizedBox( + width: mediaQuery!.size.width - + columnWidth - + (showDetails ? FluffyThemes.columnWidth : 0), + height: mediaQuery!.size.height - + mediaQuery!.padding.top - + mediaQuery!.padding.bottom, + child: Stack( + alignment: + ownMessage ? Alignment.centerRight : Alignment.centerLeft, + children: [ + if (!startedTransition) ...[ + OverMessageOverlay(controller: this), + if (shouldScroll) + Positioned( + top: 0, + left: _wordCardLeftOffset, + right: messageRightOffset, + child: WordCardSwitcher(controller: this), ), + ], + if (readingAssistanceMode == + ReadingAssistanceMode.practiceMode) ...[ + CenteredMessage( + targetId: + "overlay_center_message_${widget.event.eventId}", + controller: this, ), - AnimatedPositioned( - top: _wordCardTopOffset, - left: _wordCardLeftOffset, - right: _messageRightOffset, - duration: FluffyThemes.animationDuration, - child: AnimatedSize( - alignment: _ownMessage - ? Alignment.bottomRight - : Alignment.bottomLeft, - duration: FluffyThemes.animationDuration, - child: _wordCardTopOffset == null - ? const SizedBox() - : widget.pangeaMessageEvent != null && - widget.overlayController.selectedToken != - null - ? ReadingAssistanceContent( - pangeaMessageEvent: - widget.pangeaMessageEvent!, - overlayController: - widget.overlayController, - ) - : MessageReactionPicker( - chatController: widget.chatController, - ), + PracticeModeTransitionAnimation( + targetId: + "overlay_center_message_${widget.event.eventId}", + controller: this, + ), + Positioned( + left: 0, + right: 0, + bottom: 20, + child: ReadingAssistanceInputBar( + widget.chatController, + widget.overlayController, ), ), ], - ), + ], ), ), ], ), - if (_showDetails) + if (showDetails) const SizedBox( width: FluffyThemes.columnWidth, ), diff --git a/lib/pangea/toolbar/widgets/over_message_overlay.dart b/lib/pangea/toolbar/widgets/over_message_overlay.dart new file mode 100644 index 000000000..38e5b750c --- /dev/null +++ b/lib/pangea/toolbar/widgets/over_message_overlay.dart @@ -0,0 +1,87 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/message_selection_positioner.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/overlay_center_content.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/select_mode_buttons.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/word_card_switcher.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +class OverMessageOverlay extends StatelessWidget { + final MessageSelectionPositionerState controller; + const OverMessageOverlay({super.key, required this.controller}); + + @override + Widget build(BuildContext context) { + return Align( + alignment: controller.ownMessage ? Alignment.topRight : Alignment.topLeft, + child: Padding( + padding: EdgeInsets.only( + left: controller.messageLeftOffset ?? 0.0, + right: controller.messageRightOffset ?? 0.0, + ), + child: GestureDetector( + onTap: controller.widget.chatController.clearSelectedEvents, + child: SingleChildScrollView( + controller: controller.scrollController, + child: Column( + crossAxisAlignment: controller.ownMessage + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + AnimatedContainer( + duration: FluffyThemes.animationDuration, + height: max(0, controller.spaceAboveContent), + width: controller.mediaQuery!.size.width - + controller.columnWidth - + (controller.showDetails ? FluffyThemes.columnWidth : 0), + ), + if (!controller.shouldScroll) + WordCardSwitcher(controller: controller), + CompositedTransformTarget( + link: MatrixState.pAnyState + .layerLinkAndKey( + 'overlay_message_${controller.widget.event.eventId}', + ) + .link, + child: OverlayCenterContent( + event: controller.widget.event, + messageHeight: controller.originalMessageSize.height, + messageWidth: + controller.widget.overlayController.showingExtraContent + ? max(controller.originalMessageSize.width, 150) + : controller.originalMessageSize.width, + overlayController: controller.widget.overlayController, + chatController: controller.widget.chatController, + nextEvent: controller.widget.nextEvent, + prevEvent: controller.widget.prevEvent, + hasReactions: controller.hasReactions, + isTransitionAnimation: true, + readingAssistanceMode: controller.readingAssistanceMode, + overlayKey: MatrixState.pAnyState + .layerLinkAndKey( + 'overlay_message_${controller.widget.event.eventId}', + ) + .key, + ), + ), + const SizedBox(height: 4.0), + SelectModeButtons( + controller: controller.widget.chatController, + overlayController: controller.widget.overlayController, + lauchPractice: () => controller.setReadingAssistanceMode( + ReadingAssistanceMode.practiceMode, + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pangea/toolbar/widgets/overlay_center_content.dart b/lib/pangea/toolbar/widgets/overlay_center_content.dart index aa23be54f..3c28384a4 100644 --- a/lib/pangea/toolbar/widgets/overlay_center_content.dart +++ b/lib/pangea/toolbar/widgets/overlay_center_content.dart @@ -8,7 +8,6 @@ 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; @@ -29,10 +28,12 @@ class OverlayCenterContent extends StatelessWidget { final bool isTransitionAnimation; final ReadingAssistanceMode? readingAssistanceMode; + final LabeledGlobalKey? overlayKey; + const OverlayCenterContent({ required this.event, - required this.messageHeight, - required this.messageWidth, + this.messageHeight, + this.messageWidth, required this.overlayController, required this.chatController, required this.nextEvent, @@ -42,6 +43,7 @@ class OverlayCenterContent extends StatelessWidget { this.sizeAnimation, this.isTransitionAnimation = false, this.readingAssistanceMode, + this.overlayKey, super.key, }); @@ -63,11 +65,7 @@ class OverlayCenterContent extends StatelessWidget { MeasureRenderBox( onChange: onChangeMessageSize, child: OverlayMessage( - key: isTransitionAnimation - ? MatrixState.pAnyState - .layerLinkAndKey('overlay_message_${event.eventId}') - .key - : null, + key: overlayKey, event, immersionMode: chatController.choreographer.immersionMode, controller: chatController, @@ -78,13 +76,8 @@ class OverlayCenterContent extends StatelessWidget { sizeAnimation: sizeAnimation, // there's a split seconds between when the transition animation starts and // when the sizeAnimation is set when the original dimensions need to be enforced - messageWidth: (sizeAnimation == null && isTransitionAnimation) - ? messageWidth - : null, - messageHeight: - (sizeAnimation == null && isTransitionAnimation) - ? messageHeight - : null, + messageWidth: messageWidth, + messageHeight: messageHeight, isTransitionAnimation: isTransitionAnimation, readingAssistanceMode: readingAssistanceMode, ), diff --git a/lib/pangea/toolbar/widgets/overlay_header.dart b/lib/pangea/toolbar/widgets/overlay_header.dart deleted file mode 100644 index d748aac47..000000000 --- a/lib/pangea/toolbar/widgets/overlay_header.dart +++ /dev/null @@ -1,171 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:material_symbols_icons/symbols.dart'; -import 'package:matrix/matrix.dart'; - -import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pages/chat/chat.dart'; -import 'package:fluffychat/pangea/events/utils/report_message.dart'; - -class OverlayHeader extends StatefulWidget { - final ChatController controller; - - const OverlayHeader({ - required this.controller, - super.key, - }); - - @override - State createState() => OverlayHeaderState(); -} - -class OverlayHeaderState extends State { - ChatController get controller => widget.controller; - - final ScrollController _scrollController = ScrollController(); - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final l10n = L10n.of(context); - final theme = Theme.of(context); - final pinned = controller.selectedEvents.length == 1 && - controller.room.pinnedEventIds.contains( - controller.selectedEvents.first.eventId, - ); - return Container( - padding: const EdgeInsets.symmetric(horizontal: 10), - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(AppConfig.borderRadius), - bottomRight: Radius.circular(AppConfig.borderRadius), - ), - color: theme.appBarTheme.backgroundColor ?? - theme.colorScheme.surfaceContainerHighest, - ), - height: theme.appBarTheme.toolbarHeight ?? AppConfig.defaultHeaderHeight, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Expanded( - child: Scrollbar( - thumbVisibility: true, - controller: _scrollController, - child: Align( - alignment: Alignment.centerRight, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - controller: _scrollController, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Row( - children: [ - // #Pangea - // if (controller.selectedEvents.length == 1) - if (controller.selectedEvents.length == 1 && - controller.room.canSendDefaultMessages) - // Pangea# - IconButton( - icon: const Icon(Symbols.reply_all), - tooltip: l10n.reply, - onPressed: controller.replyAction, - color: theme.colorScheme.primary, - ), - IconButton( - icon: const Icon(Symbols.forward), - tooltip: l10n.forward, - onPressed: controller.forwardEventsAction, - color: theme.colorScheme.primary, - ), - if (controller.selectedEvents.length == 1 && - controller.selectedEvents.single.messageType == - MessageTypes.Text) - IconButton( - icon: const Icon(Icons.copy_outlined), - tooltip: l10n.copy, - onPressed: controller.copyEventsAction, - color: theme.colorScheme.primary, - ), - if (controller.canSaveSelectedEvent) - // Use builder context to correctly position the share dialog on iPad - Builder( - builder: (context) => IconButton( - icon: const Icon(Symbols.download), - tooltip: L10n.of(context).download, - onPressed: () => - controller.saveSelectedEvent(context), - color: theme.colorScheme.primary, - ), - ), - if (controller.canPinSelectedEvents) - IconButton( - icon: pinned - ? const Icon(Icons.push_pin) - : const Icon(Icons.push_pin_outlined), - onPressed: () { - controller - .pinEvent() - .then((_) => setState(() {})); - }, - tooltip: pinned ? l10n.unpin : l10n.pinMessage, - color: theme.colorScheme.primary, - ), - - // if (controller.canEditSelectedEvents && - // !controller.selectedEvents.first.isActivityMessage) - // IconButton( - // icon: const Icon(Icons.edit_outlined), - // tooltip: l10n.edit, - // onPressed: controller.editSelectedEventAction, - // color: theme.colorScheme.primary, - // ), - if (controller.canRedactSelectedEvents) - IconButton( - icon: const Icon(Icons.delete_outlined), - tooltip: l10n.redactMessage, - onPressed: controller.redactEventsAction, - color: theme.colorScheme.primary, - ), - if (controller.selectedEvents.length == 1) - IconButton( - icon: const Icon(Icons.shield_outlined), - tooltip: l10n.reportMessage, - onPressed: () { - final event = controller.selectedEvents.first; - controller.clearSelectedEvents(); - reportEvent( - event, - controller, - controller.context, - ); - }, - color: theme.colorScheme.primary, - ), - if (controller.selectedEvents.length == 1) - IconButton( - icon: const Icon(Icons.info_outlined), - tooltip: l10n.messageInfo, - color: theme.colorScheme.primary, - onPressed: () { - controller.showEventInfo(); - controller.clearSelectedEvents(); - }, - ), - ], - ), - ), - ), - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/pangea/toolbar/widgets/overlay_message.dart b/lib/pangea/toolbar/widgets/overlay_message.dart index e565be942..546ab1258 100644 --- a/lib/pangea/toolbar/widgets/overlay_message.dart +++ b/lib/pangea/toolbar/widgets/overlay_message.dart @@ -334,21 +334,23 @@ class OverlayMessage extends StatelessWidget { ); }, ), - MessageContent( - displayEvent, - textColor: textColor, - linkColor: linkColor, - borderRadius: borderRadius, - timeline: timeline, - pangeaMessageEvent: overlayController.pangeaMessageEvent, - immersionMode: immersionMode, - overlayController: overlayController, - controller: controller, - nextEvent: nextEvent, - prevEvent: previousEvent, - isTransitionAnimation: isTransitionAnimation, - readingAssistanceMode: readingAssistanceMode, - selected: true, + Flexible( + child: MessageContent( + displayEvent, + textColor: textColor, + linkColor: linkColor, + borderRadius: borderRadius, + timeline: timeline, + pangeaMessageEvent: overlayController.pangeaMessageEvent, + immersionMode: immersionMode, + overlayController: overlayController, + controller: controller, + nextEvent: nextEvent, + prevEvent: previousEvent, + isTransitionAnimation: isTransitionAnimation, + readingAssistanceMode: readingAssistanceMode, + selected: true, + ), ), if (event.hasAggregatedEvents( timeline, diff --git a/lib/pangea/toolbar/widgets/practice_activity/emoji_practice_button.dart b/lib/pangea/toolbar/widgets/practice_activity/emoji_practice_button.dart deleted file mode 100644 index 1ac4aeb10..000000000 --- a/lib/pangea/toolbar/widgets/practice_activity/emoji_practice_button.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:fluffychat/pangea/emojis/emoji_stack.dart'; -import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_zoom_activity_button.dart'; - -class EmojiPracticeButton extends StatelessWidget { - final PangeaToken token; - final VoidCallback onPressed; - final bool isSelected; - - const EmojiPracticeButton({ - required this.token, - required this.onPressed, - this.isSelected = false, - super.key, - }); - - @override - Widget build(BuildContext context) { - final emoji = token.getEmoji(); - return WordZoomActivityButton( - icon: emoji.isEmpty - ? const Icon(Icons.add_reaction_outlined) - : EmojiStack( - emoji: emoji, - style: const TextStyle(fontSize: 24), - ), - isSelected: isSelected, - onPressed: onPressed, - ); - } -} diff --git a/lib/pangea/toolbar/widgets/practice_mode_transition_animation.dart b/lib/pangea/toolbar/widgets/practice_mode_transition_animation.dart new file mode 100644 index 000000000..6f342e574 --- /dev/null +++ b/lib/pangea/toolbar/widgets/practice_mode_transition_animation.dart @@ -0,0 +1,200 @@ +import 'package:flutter/material.dart'; + +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/message_selection_positioner.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/overlay_center_content.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +class PracticeModeTransitionAnimation extends StatefulWidget { + final String targetId; + final MessageSelectionPositionerState controller; + const PracticeModeTransitionAnimation({ + super.key, + required this.targetId, + required this.controller, + }); + + @override + State createState() => + PracticeModeTransitionAnimationState(); +} + +class PracticeModeTransitionAnimationState + extends State + with SingleTickerProviderStateMixin { + AnimationController? _animationController; + Animation? _offsetAnimation; + Animation? _sizeAnimation; + + bool _finishedAnimation = false; + + RenderBox? get _centerMessageRenderBox { + try { + return MatrixState.pAnyState.getRenderBox(widget.targetId); + } catch (e) { + return null; + } + } + + Offset? get _centerMessageOffset { + final renderBox = _centerMessageRenderBox; + if (renderBox == null) { + return null; + } + return renderBox.localToGlobal(Offset.zero); + } + + Size? get _centerMessageSize { + final renderBox = _centerMessageRenderBox; + if (renderBox == null) { + return null; + } + return renderBox.size; + } + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + final startOffset = Offset( + widget.controller.ownMessage + ? widget.controller.messageRightOffset! + : widget.controller.messageLeftOffset!, + widget.controller.overlayMessageOffset!.dy, + ); + + final endOffset = Offset( + _centerMessageOffset!.dx - widget.controller.columnWidth, + _centerMessageOffset!.dy, + ); + + _animationController = AnimationController( + vsync: this, + duration: widget.controller.transitionAnimationDuration, + // duration: const Duration(seconds: 3), + ); + + _offsetAnimation = Tween( + begin: startOffset, + end: endOffset, + ).animate( + CurvedAnimation( + parent: _animationController!, + curve: FluffyThemes.animationCurve, + ), + ); + + final startSize = Size( + widget.controller.originalMessageSize.width, + widget.controller.originalMessageSize.height, + ); + + _sizeAnimation = Tween( + begin: startSize, + end: _centerMessageSize!, + ).animate( + CurvedAnimation( + parent: _animationController!, + curve: FluffyThemes.animationCurve, + ), + ); + + widget.controller.onStartedTransition(); + _animationController!.forward().then((_) { + widget.controller.onFinishedTransition(); + if (mounted) { + setState(() { + _finishedAnimation = true; + }); + } + }); + }); + } + + @override + void dispose() { + _animationController?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (_offsetAnimation == null || _finishedAnimation) { + return const SizedBox(); + } + + return AnimatedBuilder( + animation: _offsetAnimation!, + builder: (context, child) { + return Positioned( + top: _offsetAnimation!.value.dy, + left: + widget.controller.ownMessage ? null : _offsetAnimation!.value.dx, + right: + widget.controller.ownMessage ? _offsetAnimation!.value.dx : null, + child: OverlayCenterContent( + event: widget.controller.widget.event, + overlayController: widget.controller.widget.overlayController, + chatController: widget.controller.widget.chatController, + nextEvent: widget.controller.widget.nextEvent, + prevEvent: widget.controller.widget.prevEvent, + hasReactions: widget.controller.hasReactions, + sizeAnimation: _sizeAnimation, + readingAssistanceMode: widget.controller.readingAssistanceMode, + ), + ); + }, + ); + } +} + +class CenteredMessage extends StatelessWidget { + final String targetId; + final MessageSelectionPositionerState controller; + + const CenteredMessage({ + super.key, + required this.targetId, + required this.controller, + }); + + @override + Widget build(BuildContext context) { + return Opacity( + opacity: controller.finishedTransition ? 1.0 : 0.0, + child: GestureDetector( + onTap: controller.widget.chatController.clearSelectedEvents, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: + controller.mediaQuery!.size.width - controller.columnWidth, + height: 20.0, + ), + OverlayCenterContent( + event: controller.widget.event, + overlayController: controller.widget.overlayController, + chatController: controller.widget.chatController, + nextEvent: controller.widget.nextEvent, + prevEvent: controller.widget.prevEvent, + hasReactions: controller.hasReactions, + overlayKey: MatrixState.pAnyState + .layerLinkAndKey( + "overlay_center_message_${controller.widget.event.eventId}", + ) + .key, + readingAssistanceMode: controller.readingAssistanceMode, + ), + const SizedBox( + height: AppConfig.readingAssistanceInputBarHeight + 60.0, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pangea/toolbar/widgets/stt_transcript_tokens.dart b/lib/pangea/toolbar/widgets/stt_transcript_tokens.dart index 98777076b..fcea78ddf 100644 --- a/lib/pangea/toolbar/widgets/stt_transcript_tokens.dart +++ b/lib/pangea/toolbar/widgets/stt_transcript_tokens.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/message_token_text/token_position_model.dart'; import 'package:fluffychat/pangea/toolbar/models/speech_to_text_models.dart'; -import 'package:fluffychat/widgets/matrix.dart'; class SttTranscriptTokens extends StatelessWidget { final SpeechToTextModel model; @@ -55,29 +54,21 @@ class SttTranscriptTokens extends StatelessWidget { final selected = isSelected?.call(token) ?? false; return WidgetSpan( - child: CompositedTransformTarget( - link: MatrixState.pAnyState - .layerLinkAndKey(token.text.uniqueKey) - .link, - child: MouseRegion( - key: MatrixState.pAnyState - .layerLinkAndKey(token.text.uniqueKey) - .key, - cursor: SystemMouseCursors.click, - child: GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: onClick != null ? () => onClick?.call(token) : null, - child: RichText( - text: TextSpan( - text: text, - style: (style ?? DefaultTextStyle.of(context).style) - .copyWith( - decoration: TextDecoration.underline, - decorationThickness: 4, - decorationColor: selected - ? Theme.of(context).colorScheme.primary - : Colors.white.withAlpha(0), - ), + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: onClick != null ? () => onClick?.call(token) : null, + child: RichText( + text: TextSpan( + text: text, + style: + (style ?? DefaultTextStyle.of(context).style).copyWith( + decoration: TextDecoration.underline, + decorationThickness: 4, + decorationColor: selected + ? Theme.of(context).colorScheme.primary + : Colors.white.withAlpha(0), ), ), ), diff --git a/lib/pangea/toolbar/widgets/toolbar_button_and_progress_column.dart b/lib/pangea/toolbar/widgets/toolbar_button_and_progress_column.dart deleted file mode 100644 index 193b5ec9c..000000000 --- a/lib/pangea/toolbar/widgets/toolbar_button_and_progress_column.dart +++ /dev/null @@ -1,104 +0,0 @@ -import 'dart:math'; - -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/pangea/toolbar/enums/message_mode_enum.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; - -class ToolbarButtonAndProgressColumn extends StatelessWidget { - final Event event; - final MessageOverlayController overlayController; - final double height; - final double width; - - const ToolbarButtonAndProgressColumn({ - required this.event, - required this.overlayController, - required this.height, - required this.width, - super.key, - }); - - double? get proportionOfActivitiesCompleted => - overlayController.pangeaMessageEvent?.proportionOfActivitiesCompleted; - - static const double iconWidth = 36.0; - static const double buttonSize = 40.0; - static const barMargin = - EdgeInsets.symmetric(horizontal: iconWidth / 2, vertical: buttonSize / 2); - - @override - Widget build(BuildContext context) { - if (event.messageType == MessageTypes.Audio || - !(overlayController.pangeaMessageEvent?.messageDisplayLangIsL2 ?? - false)) { - return SizedBox(height: height, width: width); - } - - return SizedBox( - height: height, - width: width, - child: Stack( - alignment: Alignment.bottomCenter, - children: [ - Stack( - alignment: Alignment.bottomCenter, - children: [ - Container( - width: width, - height: height, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(AppConfig.borderRadius), - color: MessageModeExtension.barAndLockedButtonColor(context), - ), - margin: barMargin, - ), - AnimatedContainer( - duration: FluffyThemes.animationDuration, - width: width, - height: overlayController.isPracticeComplete - ? height - : min( - height, - height * proportionOfActivitiesCompleted!, - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(AppConfig.borderRadius), - color: AppConfig.gold, - ), - margin: barMargin, - ), - Positioned( - bottom: height * MessageMode.noneSelected.pointOnBar - - buttonSize / 2 - - barMargin.vertical / 2, - child: Container( - decoration: BoxDecoration( - color: overlayController.isPracticeComplete - ? AppConfig.gold - : MessageModeExtension.barAndLockedButtonColor(context), - shape: BoxShape.circle, - ), - height: buttonSize, - width: buttonSize, - alignment: Alignment.center, - child: Icon( - Icons.star_rounded, - color: overlayController.isPracticeComplete - ? Colors.white - : Theme.of(context).colorScheme.onSurface, - size: 30, - ), - ), - ), - ], - ), - ], - ), - ); - } -} diff --git a/lib/pangea/toolbar/widgets/word_card_switcher.dart b/lib/pangea/toolbar/widgets/word_card_switcher.dart new file mode 100644 index 000000000..cd2285927 --- /dev/null +++ b/lib/pangea/toolbar/widgets/word_card_switcher.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/message_selection_positioner.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/reading_assistance_content.dart'; + +class WordCardSwitcher extends StatelessWidget { + final MessageSelectionPositionerState controller; + const WordCardSwitcher({super.key, required this.controller}); + + @override + Widget build(BuildContext context) { + return AnimatedSize( + alignment: + controller.ownMessage ? Alignment.bottomRight : Alignment.bottomLeft, + duration: FluffyThemes.animationDuration, + child: controller.widget.pangeaMessageEvent != null && + controller.widget.overlayController.selectedToken != null + ? ReadingAssistanceContent( + pangeaMessageEvent: controller.widget.pangeaMessageEvent!, + overlayController: controller.widget.overlayController, + ) + : MessageReactionPicker( + chatController: controller.widget.chatController, + ), + ); + } +}