diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 599f5b821..f849231ca 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -10,6 +10,8 @@ import 'package:fluffychat/pages/chat/events/message.dart'; import 'package:fluffychat/pages/chat/seen_by_row.dart'; import 'package:fluffychat/pages/chat/typing_indicators.dart'; import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart'; +import 'package:fluffychat/pangea/activity_planner/activity_plan_message.dart'; +import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart'; import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; import 'package:fluffychat/pangea/instructions/instructions_show_popup.dart'; import 'package:fluffychat/utils/account_config.dart'; @@ -168,49 +170,64 @@ class ChatEventList extends StatelessWidget { key: ValueKey(event.eventId), index: i, controller: controller.scrollController, - child: Message( - event, - animateIn: animateIn, - resetAnimateIn: () { - controller.animateInEventIndex = null; - }, - onSwipe: () => controller.replyAction(replyTo: event), - // #Pangea - onInfoTab: (_) => {}, - // onInfoTab: controller.showEventInfo, - // Pangea# - onAvatarTab: (Event event) => showAdaptiveBottomSheet( - context: context, - builder: (c) => UserBottomSheet( - user: event.senderFromMemoryOrFallback, - outerContext: context, - onMention: () => controller.sendController.text += - '${event.senderFromMemoryOrFallback.mention} ', - ), - ), - highlightMarker: - controller.scrollToEventIdMarker == event.eventId, - // #Pangea - // onSelect: controller.onSelectMessage, - onSelect: (_) {}, - // Pangea# - scrollToEventId: (String eventId) => - controller.scrollToEventId(eventId), - longPressSelect: controller.selectedEvents.isNotEmpty, - // #Pangea - immersionMode: controller.choreographer.immersionMode, - controller: controller, - isButton: event.eventId == controller.buttonEventID, - // Pangea# - selected: controller.selectedEvents - .any((e) => e.eventId == event.eventId), - timeline: timeline, - displayReadMarker: - i > 0 && controller.readMarkerEventId == event.eventId, - nextEvent: i + 1 < events.length ? events[i + 1] : null, - previousEvent: i > 0 ? events[i - 1] : null, - wallpaperMode: hasWallpaper, - ), + child: + // #Pangea + event.isActivityMessage + ? ActivityPlanMessage( + event, + controller: controller, + timeline: timeline, + animateIn: animateIn, + resetAnimateIn: () { + controller.animateInEventIndex = null; + }, + ) + : + // Pangea# + Message( + event, + animateIn: animateIn, + resetAnimateIn: () { + controller.animateInEventIndex = null; + }, + onSwipe: () => controller.replyAction(replyTo: event), + // #Pangea + onInfoTab: (_) => {}, + // onInfoTab: controller.showEventInfo, + // Pangea# + onAvatarTab: (Event event) => showAdaptiveBottomSheet( + context: context, + builder: (c) => UserBottomSheet( + user: event.senderFromMemoryOrFallback, + outerContext: context, + onMention: () => controller.sendController.text += + '${event.senderFromMemoryOrFallback.mention} ', + ), + ), + highlightMarker: + controller.scrollToEventIdMarker == event.eventId, + // #Pangea + // onSelect: controller.onSelectMessage, + onSelect: (_) {}, + // Pangea# + scrollToEventId: (String eventId) => + controller.scrollToEventId(eventId), + longPressSelect: controller.selectedEvents.isNotEmpty, + // #Pangea + immersionMode: controller.choreographer.immersionMode, + controller: controller, + isButton: event.eventId == controller.buttonEventID, + // Pangea# + selected: controller.selectedEvents + .any((e) => e.eventId == event.eventId), + timeline: timeline, + displayReadMarker: i > 0 && + controller.readMarkerEventId == event.eventId, + nextEvent: + i + 1 < events.length ? events[i + 1] : null, + previousEvent: i > 0 ? events[i - 1] : null, + wallpaperMode: hasWallpaper, + ), ); }, childCount: events.length + 2, diff --git a/lib/pangea/activity_planner/activity_plan_message.dart b/lib/pangea/activity_planner/activity_plan_message.dart new file mode 100644 index 000000000..7ddac659f --- /dev/null +++ b/lib/pangea/activity_planner/activity_plan_message.dart @@ -0,0 +1,241 @@ +import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pages/chat/chat.dart'; +import 'package:fluffychat/pages/chat/events/message_content.dart'; +import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; +import 'package:fluffychat/utils/date_time_extension.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; +import 'package:swipe_to_action/swipe_to_action.dart'; + +import '../../../config/app_config.dart'; + +class ActivityPlanMessage extends StatelessWidget { + final Event event; + final Timeline timeline; + final bool animateIn; + final void Function()? resetAnimateIn; + final ChatController controller; + + const ActivityPlanMessage( + this.event, { + required this.timeline, + this.animateIn = false, + this.resetAnimateIn, + required this.controller, + super.key, + }); + + @override + Widget build(BuildContext context) { + 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(); + controller.clearEditingEvent(); + } + }); + + final theme = Theme.of(context); + final color = Color.alphaBlend( + Colors.white.withAlpha(180), + ThemeData.dark().colorScheme.primary, + ); + + final textColor = ThemeData.dark().colorScheme.onPrimary; + + final displayEvent = event.getDisplayEvent(timeline); + const roundedCorner = Radius.circular(AppConfig.borderRadius); + const borderRadius = BorderRadius.all(roundedCorner); + + final resetAnimateIn = this.resetAnimateIn; + var animateIn = this.animateIn; + + final row = StatefulBuilder( + builder: (context, setState) { + if (animateIn && resetAnimateIn != null) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + animateIn = false; + if (context.mounted) { + setState(resetAnimateIn); + } + }); + } + return AnimatedSize( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + clipBehavior: Clip.none, + alignment: Alignment.bottomLeft, + child: animateIn + ? const SizedBox(height: 0, width: double.infinity) + : Container( + alignment: Alignment.center, + child: AnimatedOpacity( + opacity: animateIn + ? 0 + : event.messageType == MessageTypes.BadEncrypted || + event.status.isSending + ? 0.5 + : 1, + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + child: Container( + decoration: BoxDecoration( + color: color, + borderRadius: borderRadius, + ), + clipBehavior: Clip.antiAlias, + child: CompositedTransformTarget( + link: MatrixState.pAnyState + .layerLinkAndKey( + event.eventId, + ) + .link, + child: Container( + key: MatrixState.pAnyState + .layerLinkAndKey( + event.eventId, + ) + .key, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, + ), + ), + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + constraints: const BoxConstraints( + maxWidth: FluffyThemes.columnWidth * 1.5, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MessageContent( + displayEvent, + textColor: textColor, + borderRadius: borderRadius, + pangeaMessageEvent: pangeaMessageEvent, + controller: controller, + immersionMode: false, + ), + if (event.hasAggregatedEvents( + timeline, + RelationshipTypes.edit, + )) + Padding( + padding: const EdgeInsets.only( + top: 4.0, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (event.hasAggregatedEvents( + timeline, + RelationshipTypes.edit, + )) ...[ + Icon( + Icons.edit_outlined, + color: textColor.withAlpha(164), + size: 14, + ), + Text( + ' - ${displayEvent.originServerTs.localizedTimeShort(context)}', + style: TextStyle( + color: textColor.withAlpha( + 164, + ), + fontSize: 12, + ), + ), + ], + ], + ), + ), + ], + ), + ), + ), + ), + ), + ), + ); + }, + ); + + Widget container; + + container = Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Center( + child: Padding( + padding: const EdgeInsets.only(top: 4.0), + child: Material( + borderRadius: BorderRadius.circular(AppConfig.borderRadius * 2), + color: theme.colorScheme.surface.withAlpha(128), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 2.0, + ), + child: Text( + event.originServerTs.localizedTime(context), + style: TextStyle( + fontSize: 12 * AppConfig.fontSizeFactor, + fontWeight: FontWeight.bold, + color: theme.colorScheme.secondary, + ), + ), + ), + ), + ), + ), + ), + row, + ], + ); + + container = Material(type: MaterialType.transparency, child: container); + + return Center( + child: Swipeable( + key: ValueKey(event.eventId), + background: const Padding( + padding: EdgeInsets.symmetric(horizontal: 12.0), + child: Center( + child: Icon(Icons.check_outlined), + ), + ), + direction: AppConfig.swipeRightToLeftToReply + ? SwipeDirection.endToStart + : SwipeDirection.startToEnd, + onSwipe: (_) {}, + child: Container( + constraints: const BoxConstraints( + maxWidth: FluffyThemes.columnWidth * 2.5, + ), + padding: const EdgeInsets.only( + left: 8.0, + right: 8.0, + top: 4.0, + bottom: 4.0, + ), + child: container, + ), + ), + ); + } +} diff --git a/lib/pangea/events/event_wrappers/pangea_message_event.dart b/lib/pangea/events/event_wrappers/pangea_message_event.dart index cf5288cba..971a4f77c 100644 --- a/lib/pangea/events/event_wrappers/pangea_message_event.dart +++ b/lib/pangea/events/event_wrappers/pangea_message_event.dart @@ -12,6 +12,7 @@ import 'package:fluffychat/pangea/choreographer/models/pangea_match_model.dart'; import 'package:fluffychat/pangea/choreographer/repo/full_text_translation_repo.dart'; import 'package:fluffychat/pangea/common/constants/model_keys.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_representation_event.dart'; +import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart'; import 'package:fluffychat/pangea/events/models/representation_content_model.dart'; import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart'; import 'package:fluffychat/pangea/spaces/models/space_model.dart'; @@ -689,4 +690,6 @@ class PangeaMessageEvent { /// Returns a list of [PracticeActivityEvent] for the user's active l2. List get practiceActivities => l2Code == null ? [] : practiceActivitiesByLangCode(l2Code!); + + bool get shouldShowToolbar => !event.isActivityMessage; } diff --git a/lib/pangea/events/extensions/pangea_event_extension.dart b/lib/pangea/events/extensions/pangea_event_extension.dart index f5884ddf6..bceebb3c1 100644 --- a/lib/pangea/events/extensions/pangea_event_extension.dart +++ b/lib/pangea/events/extensions/pangea_event_extension.dart @@ -88,4 +88,7 @@ extension PangeaEvent on Event { waveform: waveform, ); } + + bool get isActivityMessage => + content[ModelKey.messageTags] == ModelKey.messageTagActivityPlan; } diff --git a/lib/pangea/toolbar/widgets/message_selection_positioner.dart b/lib/pangea/toolbar/widgets/message_selection_positioner.dart index 5ff067c5d..fec92a74a 100644 --- a/lib/pangea/toolbar/widgets/message_selection_positioner.dart +++ b/lib/pangea/toolbar/widgets/message_selection_positioner.dart @@ -252,6 +252,7 @@ class MessageSelectionPositionerState extends State bool get showToolbarButtons => widget.pangeaMessageEvent != null && + widget.pangeaMessageEvent!.shouldShowToolbar && widget.pangeaMessageEvent!.event.messageType == MessageTypes.Text; double get _toolbarButtonsHeight => @@ -374,6 +375,7 @@ class MessageSelectionPositionerState extends State overlayController: widget.overlayController, chatController: widget.chatController, hasReactions: _hasReactions, + shouldShowToolbarButtons: showToolbarButtons, ), ); }, @@ -436,6 +438,8 @@ class ToolbarOverlay extends StatelessWidget { final Event? prevEvent; final bool hasReactions; + final bool shouldShowToolbarButtons; + final PangeaMessageEvent? pangeaMessageEvent; final MessageOverlayController overlayController; final ChatController chatController; @@ -449,6 +453,7 @@ class ToolbarOverlay extends StatelessWidget { required this.overlayController, required this.chatController, required this.hasReactions, + required this.shouldShowToolbarButtons, this.pangeaMessageEvent, this.nextEvent, this.prevEvent, @@ -466,7 +471,8 @@ class ToolbarOverlay extends StatelessWidget { ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ - if (pangeaMessageEvent != null) + if (pangeaMessageEvent != null && + pangeaMessageEvent!.shouldShowToolbar) MessageToolbar( pangeaMessageEvent: pangeaMessageEvent!, overlayController: overlayController, @@ -498,10 +504,11 @@ class ToolbarOverlay extends StatelessWidget { ), ), ), - ToolbarButtons( - event: event, - overlayController: overlayController, - ), + if (shouldShowToolbarButtons) + ToolbarButtons( + event: event, + overlayController: overlayController, + ), ], ), ),