diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 2106db95a..e4167ea7b 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -89,6 +89,8 @@ abstract class AppConfig { static String _privacyUrl = "https://www.pangeachat.com/privacy"; //Pangea# + static const Set defaultReactions = {'👍', '❤️', '😊'}; + static String get privacyUrl => _privacyUrl; // #Pangea // static const String website = 'https://fluffychat.im'; diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index c5c74d682..11d114939 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -3240,6 +3240,7 @@ "commandHint_logout": "Logout your current device", "commandHint_logoutall": "Logout all active devices", "displayNavigationRail": "Show navigation rail on mobile", + "customReaction": "Custom reaction", "accountInformation": "Account information", "addGroupDescription": "Add a chat description", "addNewFriend": "Add new friend", diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 3137ad6c5..017ef0228 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -1,9 +1,6 @@ -// ignore_for_file: depend_on_referenced_packages, implementation_imports - import 'dart:async'; import 'dart:developer'; import 'dart:io'; -import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -251,8 +248,6 @@ class ChatController extends State context.go('/rooms'); } - EmojiPickerType emojiPickerType = EmojiPickerType.keyboard; - // #Pangea // void requestHistory([_]) async { Future requestHistory() async { @@ -924,7 +919,6 @@ class ChatController extends State // editEventId: editEvent?.eventId, // parseCommands: parseCommands, // ); - // If the message and the sendController text don't match, it's possible // that there was a delay in tokenization before send, and the user started // typing a new message. We don't want to erase that, so only reset the input @@ -1031,7 +1025,6 @@ class ChatController extends State // text: pendingText, // selection: const TextSelection.collapsed(offset: 0), // ); - // Pangea# setState(() { // #Pangea @@ -1226,13 +1219,11 @@ class ChatController extends State } else { inputFocus.unfocus(); } - emojiPickerType = EmojiPickerType.keyboard; setState(() => showEmojiPicker = !showEmojiPicker); } void _inputFocusListener() { if (showEmojiPicker && inputFocus.hasFocus) { - emojiPickerType = EmojiPickerType.keyboard; setState(() => showEmojiPicker = false); } } @@ -1431,19 +1422,6 @@ class ChatController extends State return true; } - bool get canEditSelectedEvents { - if (isArchived || - selectedEvents.length != 1 || - // #Pangea - selectedEvents.single.messageType != MessageTypes.Text || - // Pangea# - !selectedEvents.first.status.isSent) { - return false; - } - return currentRoomBundle - .any((cl) => selectedEvents.first.senderId == cl!.userID); - } - void forwardEventsAction() async { if (selectedEvents.isEmpty) return; await showScaffoldDialog( @@ -1567,34 +1545,8 @@ class ChatController extends State } void onEmojiSelected(_, Emoji? emoji) { - switch (emojiPickerType) { - case EmojiPickerType.reaction: - senEmojiReaction(emoji); - break; - case EmojiPickerType.keyboard: - typeEmoji(emoji); - onInputBarChanged(sendController.text); - break; - } - } - - void senEmojiReaction(Emoji? emoji) { - setState(() => showEmojiPicker = false); - if (emoji == null) return; - // make sure we don't send the same emoji twice - if (_allReactionEvents.any( - (e) => e.content.tryGetMap('m.relates_to')?['key'] == emoji.emoji, - )) { - return; - } - // #Pangea - // return sendEmojiAction(emoji.emoji); - sendEmojiAction(emoji.emoji); - - // don't need to clear these when sending while in select mode, - // but do need to clear these when reacting from the large emoji picker - setState(() => selectedEvents.clear()); - // Pangea# + typeEmoji(emoji); + onInputBarChanged(sendController.text); } void typeEmoji(Emoji? emoji) { @@ -1609,7 +1561,6 @@ class ChatController extends State ); } // Pangea# - final selection = sendController.selection; final newText = sendController.text.isEmpty ? emoji.emoji @@ -1623,54 +1574,12 @@ class ChatController extends State ); } - late Iterable _allReactionEvents; - void emojiPickerBackspace() { - switch (emojiPickerType) { - case EmojiPickerType.reaction: - setState(() => showEmojiPicker = false); - break; - case EmojiPickerType.keyboard: - sendController - ..text = sendController.text.characters.skipLast(1).toString() - ..selection = TextSelection.fromPosition( - TextPosition(offset: sendController.text.length), - ); - break; - } - } - - void pickEmojiReactionAction(Iterable allReactionEvents) async { - // #Pangea - closeSelectionOverlay(); - // Pangea# - _allReactionEvents = allReactionEvents; - emojiPickerType = EmojiPickerType.reaction; - setState(() => showEmojiPicker = true); - } - - void sendEmojiAction(String? emoji) async { - final events = List.from(selectedEvents); - // #Pangea - // keep this event selected in case the user wants to send another emoji - // setState(() => selectedEvents.clear()); - // Pangea# - // if reaction already exists, don't send it again - if (timeline == null || - events.any( - (e) => e.aggregatedEvents(timeline!, RelationshipTypes.reaction).any( - (re) => re.content.tryGetMap('m.relates_to')?['key'] == emoji, - ), - )) { - return; - } - - for (final event in events) { - await room.sendReaction( - event.eventId, - emoji!, + sendController + ..text = sendController.text.characters.skipLast(1).toString() + ..selection = TextSelection.fromPosition( + TextPosition(offset: sendController.text.length), ); - } } // #Pangea @@ -1702,7 +1611,6 @@ class ChatController extends State selectedEvents.add(event); }); } - // Pangea# void clearSingleSelectedEvent() { if (selectedEvents.length <= 1) { @@ -1776,9 +1684,8 @@ class ChatController extends State final matches = selectedEvents.where((e) => e.eventId == event.eventId); if (matches.isNotEmpty) { setState(() => selectedEvents.remove(matches.first)); - } - // Pangea# - else { + // Pangea# + } else { setState( () => selectedEvents.add(event), ); @@ -1883,7 +1790,7 @@ class ChatController extends State } Timer? _storeInputTimeoutTimer; - Duration storeInputTimeout = const Duration(milliseconds: 500); + static const Duration _storeInputTimeout = Duration(milliseconds: 500); void onInputBarChanged(String text) { if (_inputTextIsEmpty != text.isEmpty) { @@ -1893,7 +1800,7 @@ class ChatController extends State } _storeInputTimeoutTimer?.cancel(); - _storeInputTimeoutTimer = Timer(storeInputTimeout, () async { + _storeInputTimeoutTimer = Timer(_storeInputTimeout, () async { final prefs = await SharedPreferences.getInstance(); await prefs.setString('draft_$roomId', text); }); @@ -1937,12 +1844,14 @@ class ChatController extends State bool get isArchived => {Membership.leave, Membership.ban}.contains(room.membership); + // #Pangea + // void showEventInfo([Event? event]) => + // (event ?? selectedEvents.single).showInfoDialog(context); void showEventInfo([Event? event]) { (event ?? selectedEvents.single).showInfoDialog(context); - // #Pangea clearSelectedEvents(); - // Pangea# } + // Pangea# void onPhoneButtonTap() async { // VoIP required Android SDK 21 @@ -2000,7 +1909,6 @@ class ChatController extends State replyEvent = null; editEvent = null; }); - // #Pangea String? get buttonEventID => timeline!.events .firstWhereOrNull( @@ -2261,5 +2169,3 @@ class ChatController extends State ); } } - -enum EmojiPickerType { reaction, keyboard } diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 948c0578b..63736b214 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -201,6 +201,10 @@ class ChatEventList extends StatelessWidget { // Pangea# selected: controller.selectedEvents .any((e) => e.eventId == event.eventId), + singleSelected: + controller.selectedEvents.singleOrNull?.eventId == + event.eventId, + onEdit: () => controller.editSelectedEventAction(), timeline: timeline, displayReadMarker: i > 0 && controller.readMarkerEventId == event.eventId, diff --git a/lib/pages/chat/chat_input_row.dart b/lib/pages/chat/chat_input_row.dart index a598d9b11..a17105b8d 100644 --- a/lib/pages/chat/chat_input_row.dart +++ b/lib/pages/chat/chat_input_row.dart @@ -21,10 +21,6 @@ class ChatInputRow extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - if (controller.showEmojiPicker && - controller.emojiPickerType == EmojiPickerType.reaction) { - return const SizedBox.shrink(); - } const height = 48.0; if (!controller.room.otherPartyCanReceiveMessages) { @@ -43,302 +39,238 @@ class ChatInputRow extends StatelessWidget { return Row( crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: controller.selectMode - ? [ - if (controller.selectedEvents - .every((event) => event.status == EventStatus.error)) - SizedBox( - height: height, - child: TextButton( - style: TextButton.styleFrom( - foregroundColor: theme.colorScheme.error, - ), - onPressed: controller.deleteErrorEventsAction, - child: Row( - children: [ - const Icon(Icons.delete), - Text(L10n.of(context).delete), - ], - ), - ), - ) - else - SizedBox( - height: height, - child: TextButton( - onPressed: controller.forwardEventsAction, - child: Row( - children: [ - const Icon(Icons.keyboard_arrow_left_outlined), - Text(L10n.of(context).forward), - ], - ), - ), - ), - controller.selectedEvents.length == 1 - ? controller.selectedEvents.first - .getDisplayEvent(controller.timeline!) - .status - .isSent - ? SizedBox( - height: height, - child: TextButton( - onPressed: controller.replyAction, - child: Row( - children: [ - Text(L10n.of(context).reply), - const Icon(Icons.keyboard_arrow_right), - ], - ), - ), - ) - : SizedBox( - height: height, - child: TextButton( - onPressed: controller.sendAgainAction, - child: Row( - children: [ - Text(L10n.of(context).tryToSendAgain), - const SizedBox(width: 4), - const Icon(Icons.send_outlined, size: 16), - ], - ), - ), - ) - : const SizedBox.shrink(), - ] - : [ - const SizedBox(width: 4), - AnimatedContainer( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - width: controller.sendController.text.isNotEmpty ? 0 : height, - height: height, - alignment: Alignment.center, - decoration: const BoxDecoration(), - clipBehavior: Clip.hardEdge, - child: PopupMenuButton( - useRootNavigator: true, - icon: const Icon(Icons.add_circle_outline), - iconColor: theme.colorScheme.onPrimaryContainer, - onSelected: controller.onAddPopupMenuButtonSelected, - itemBuilder: (BuildContext context) => - >[ - if (PlatformInfos.isMobile) - PopupMenuItem( - value: 'location', - child: ListTile( - leading: CircleAvatar( - backgroundColor: - theme.colorScheme.onPrimaryContainer, - foregroundColor: theme.colorScheme.primaryContainer, - child: const Icon(Icons.gps_fixed_outlined), - ), - title: Text(L10n.of(context).shareLocation), - contentPadding: const EdgeInsets.all(0), - ), - ), - PopupMenuItem( - value: 'image', - child: ListTile( - leading: CircleAvatar( - backgroundColor: theme.colorScheme.onPrimaryContainer, - foregroundColor: theme.colorScheme.primaryContainer, - child: const Icon(Icons.photo_outlined), - ), - title: Text(L10n.of(context).sendImage), - contentPadding: const EdgeInsets.all(0), - ), - ), - PopupMenuItem( - value: 'video', - child: ListTile( - leading: CircleAvatar( - backgroundColor: theme.colorScheme.onPrimaryContainer, - foregroundColor: theme.colorScheme.primaryContainer, - child: const Icon(Icons.video_camera_back_outlined), - ), - title: Text(L10n.of(context).sendVideo), - contentPadding: const EdgeInsets.all(0), - ), - ), - PopupMenuItem( - value: 'file', - child: ListTile( - leading: CircleAvatar( - backgroundColor: theme.colorScheme.onPrimaryContainer, - foregroundColor: theme.colorScheme.primaryContainer, - child: const Icon(Icons.attachment_outlined), - ), - title: Text(L10n.of(context).sendFile), - contentPadding: const EdgeInsets.all(0), - ), - ), - ], - ), - ), + children: [ + const SizedBox(width: 4), + AnimatedContainer( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + width: controller.sendController.text.isNotEmpty ? 0 : height, + height: height, + alignment: Alignment.center, + decoration: const BoxDecoration(), + clipBehavior: Clip.hardEdge, + child: PopupMenuButton( + useRootNavigator: true, + enabled: !controller.selectMode, + icon: const Icon(Icons.add_circle_outline), + iconColor: theme.colorScheme.onPrimaryContainer, + onSelected: controller.onAddPopupMenuButtonSelected, + itemBuilder: (BuildContext context) => >[ if (PlatformInfos.isMobile) - AnimatedContainer( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - width: controller.sendController.text.isNotEmpty ? 0 : height, - height: height, - alignment: Alignment.center, - decoration: const BoxDecoration(), - clipBehavior: Clip.hardEdge, - child: PopupMenuButton( - useRootNavigator: true, - icon: const Icon(Icons.camera_alt_outlined), - onSelected: controller.onAddPopupMenuButtonSelected, - iconColor: theme.colorScheme.onPrimaryContainer, - itemBuilder: (context) => [ - PopupMenuItem( - value: 'camera-video', - child: ListTile( - leading: CircleAvatar( - backgroundColor: - theme.colorScheme.onPrimaryContainer, - foregroundColor: theme.colorScheme.primaryContainer, - child: const Icon(Icons.videocam_outlined), - ), - title: Text(L10n.of(context).recordAVideo), - contentPadding: const EdgeInsets.all(0), - ), - ), - PopupMenuItem( - value: 'camera', - child: ListTile( - leading: CircleAvatar( - backgroundColor: - theme.colorScheme.onPrimaryContainer, - foregroundColor: theme.colorScheme.primaryContainer, - child: const Icon(Icons.camera_alt_outlined), - ), - title: Text(L10n.of(context).takeAPhoto), - contentPadding: const EdgeInsets.all(0), - ), - ), - ], + PopupMenuItem( + value: 'location', + child: ListTile( + leading: CircleAvatar( + backgroundColor: theme.colorScheme.onPrimaryContainer, + foregroundColor: theme.colorScheme.primaryContainer, + child: const Icon(Icons.gps_fixed_outlined), + ), + title: Text(L10n.of(context).shareLocation), + contentPadding: const EdgeInsets.all(0), ), ), - Container( - height: height, - width: height, - alignment: Alignment.center, - child: IconButton( - tooltip: L10n.of(context).emojis, - color: theme.colorScheme.onPrimaryContainer, - icon: PageTransitionSwitcher( - transitionBuilder: ( - Widget child, - Animation primaryAnimation, - Animation secondaryAnimation, - ) { - return SharedAxisTransition( - animation: primaryAnimation, - secondaryAnimation: secondaryAnimation, - transitionType: SharedAxisTransitionType.scaled, - fillColor: Colors.transparent, - child: child, - ); - }, - child: Icon( - controller.showEmojiPicker - ? Icons.keyboard - : Icons.add_reaction_outlined, - key: ValueKey(controller.showEmojiPicker), - ), + PopupMenuItem( + value: 'image', + child: ListTile( + leading: CircleAvatar( + backgroundColor: theme.colorScheme.onPrimaryContainer, + foregroundColor: theme.colorScheme.primaryContainer, + child: const Icon(Icons.photo_outlined), ), - onPressed: controller.emojiPickerAction, + title: Text(L10n.of(context).sendImage), + contentPadding: const EdgeInsets.all(0), ), ), - if (Matrix.of(context).isMultiAccount && - Matrix.of(context).hasComplexBundles && - Matrix.of(context).currentBundle!.length > 1) - Container( - width: height, - height: height, - alignment: Alignment.center, - child: _ChatAccountPicker(controller), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 0.0), - child: InputBar( - room: controller.room, - minLines: 1, - maxLines: 8, - autofocus: !PlatformInfos.isMobile, - keyboardType: TextInputType.multiline, - textInputAction: - AppConfig.sendOnEnter == true && PlatformInfos.isMobile - ? TextInputAction.send - : null, - // #Pangea - // onSubmitted: controller.onInputBarSubmitted, - onSubmitted: (value) => - controller.onInputBarSubmitted(value, context), - // Pangea# - onSubmitImage: controller.sendImageFromClipBoard, - focusNode: controller.inputFocus, - controller: controller.sendController, - decoration: InputDecoration( - contentPadding: const EdgeInsets.only( - left: 6.0, - right: 6.0, - bottom: 6.0, - top: 3.0, - ), - hintText: L10n.of(context).writeAMessage, - hintMaxLines: 1, - border: InputBorder.none, - enabledBorder: InputBorder.none, - filled: false, - ), - onChanged: controller.onInputBarChanged, - // #Pangea - hintText: "", - // Pangea# + PopupMenuItem( + value: 'video', + child: ListTile( + leading: CircleAvatar( + backgroundColor: theme.colorScheme.onPrimaryContainer, + foregroundColor: theme.colorScheme.primaryContainer, + child: const Icon(Icons.video_camera_back_outlined), ), + title: Text(L10n.of(context).sendVideo), + contentPadding: const EdgeInsets.all(0), ), ), - Container( - height: height, - width: height, - alignment: Alignment.center, - child: PlatformInfos.platformCanRecord && - controller.sendController.text.isEmpty - ? FloatingActionButton.small( - tooltip: L10n.of(context).voiceMessage, - onPressed: controller.voiceMessageAction, - elevation: 0, - heroTag: null, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(height), - ), - backgroundColor: theme.bubbleColor, - foregroundColor: theme.onBubbleColor, - child: const Icon(Icons.mic_none_outlined), - ) - : FloatingActionButton.small( - tooltip: L10n.of(context).send, - // #Pangea - // onPressed: controller.send, - onPressed: () => controller.send( - message: controller.sendController.text, - ), - // Pangea# - elevation: 0, - heroTag: null, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(height), - ), - backgroundColor: theme.bubbleColor, - foregroundColor: theme.onBubbleColor, - child: const Icon(Icons.send_outlined), - ), + PopupMenuItem( + value: 'file', + child: ListTile( + leading: CircleAvatar( + backgroundColor: theme.colorScheme.onPrimaryContainer, + foregroundColor: theme.colorScheme.primaryContainer, + child: const Icon(Icons.attachment_outlined), + ), + title: Text(L10n.of(context).sendFile), + contentPadding: const EdgeInsets.all(0), + ), ), ], + ), + ), + if (PlatformInfos.isMobile) + AnimatedContainer( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + width: controller.sendController.text.isNotEmpty ? 0 : height, + height: height, + alignment: Alignment.center, + decoration: const BoxDecoration(), + clipBehavior: Clip.hardEdge, + child: PopupMenuButton( + enabled: !controller.selectMode, + useRootNavigator: true, + icon: const Icon(Icons.camera_alt_outlined), + onSelected: controller.onAddPopupMenuButtonSelected, + iconColor: theme.colorScheme.onPrimaryContainer, + itemBuilder: (context) => [ + PopupMenuItem( + value: 'camera-video', + child: ListTile( + leading: CircleAvatar( + backgroundColor: theme.colorScheme.onPrimaryContainer, + foregroundColor: theme.colorScheme.primaryContainer, + child: const Icon(Icons.videocam_outlined), + ), + title: Text(L10n.of(context).recordAVideo), + contentPadding: const EdgeInsets.all(0), + ), + ), + PopupMenuItem( + value: 'camera', + child: ListTile( + leading: CircleAvatar( + backgroundColor: theme.colorScheme.onPrimaryContainer, + foregroundColor: theme.colorScheme.primaryContainer, + child: const Icon(Icons.camera_alt_outlined), + ), + title: Text(L10n.of(context).takeAPhoto), + contentPadding: const EdgeInsets.all(0), + ), + ), + ], + ), + ), + Container( + height: height, + width: height, + alignment: Alignment.center, + child: IconButton( + tooltip: L10n.of(context).emojis, + color: theme.colorScheme.onPrimaryContainer, + icon: PageTransitionSwitcher( + transitionBuilder: ( + Widget child, + Animation primaryAnimation, + Animation secondaryAnimation, + ) { + return SharedAxisTransition( + animation: primaryAnimation, + secondaryAnimation: secondaryAnimation, + transitionType: SharedAxisTransitionType.scaled, + fillColor: Colors.transparent, + child: child, + ); + }, + child: Icon( + controller.showEmojiPicker + ? Icons.keyboard + : Icons.add_reaction_outlined, + key: ValueKey(controller.showEmojiPicker), + ), + ), + onPressed: + controller.selectMode ? null : controller.emojiPickerAction, + ), + ), + if (Matrix.of(context).isMultiAccount && + Matrix.of(context).hasComplexBundles && + Matrix.of(context).currentBundle!.length > 1) + Container( + width: height, + height: height, + alignment: Alignment.center, + child: _ChatAccountPicker(controller), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 0.0), + child: InputBar( + room: controller.room, + minLines: 1, + readOnly: controller.selectMode, + maxLines: 8, + autofocus: !PlatformInfos.isMobile, + keyboardType: TextInputType.multiline, + textInputAction: + AppConfig.sendOnEnter == true && PlatformInfos.isMobile + ? TextInputAction.send + : null, + // #Pangea + // onSubmitted: controller.onInputBarSubmitted, + onSubmitted: (c) => controller.onInputBarSubmitted(c, context), + hintText: "", + // Pangea# + onSubmitImage: controller.sendImageFromClipBoard, + focusNode: controller.inputFocus, + controller: controller.sendController, + decoration: InputDecoration( + contentPadding: const EdgeInsets.only( + left: 6.0, + right: 6.0, + bottom: 6.0, + top: 3.0, + ), + hintText: L10n.of(context).writeAMessage, + hintMaxLines: 1, + border: InputBorder.none, + enabledBorder: InputBorder.none, + filled: false, + ), + onChanged: controller.onInputBarChanged, + ), + ), + ), + Opacity( + opacity: controller.selectMode ? 0.66 : 1, + child: Container( + height: height, + width: height, + alignment: Alignment.center, + child: PlatformInfos.platformCanRecord && + controller.sendController.text.isEmpty + ? FloatingActionButton.small( + tooltip: L10n.of(context).voiceMessage, + onPressed: controller.selectMode + ? null + : controller.voiceMessageAction, + elevation: 0, + heroTag: null, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(height), + ), + backgroundColor: theme.bubbleColor, + foregroundColor: theme.onBubbleColor, + child: const Icon(Icons.mic_none_outlined), + ) + : FloatingActionButton.small( + tooltip: L10n.of(context).send, + // #Pangea + // onPressed: controller.selectMode ? null : controller.send, + onPressed: () {}, + // Pangea# + elevation: 0, + heroTag: null, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(height), + ), + backgroundColor: theme.bubbleColor, + foregroundColor: theme.onBubbleColor, + child: const Icon(Icons.send_outlined), + ), + ), + ), + ], ); } } diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 1436cb00b..26f35c8c0 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -34,12 +34,6 @@ class ChatView extends StatelessWidget { List _appBarActions(BuildContext context) { if (controller.selectMode) { return [ - if (controller.canEditSelectedEvents) - IconButton( - icon: const Icon(Icons.edit_outlined), - tooltip: L10n.of(context).edit, - onPressed: controller.editSelectedEventAction, - ), IconButton( icon: const Icon(Icons.copy_outlined), tooltip: L10n.of(context).copy, @@ -310,13 +304,14 @@ class ChatView extends StatelessWidget { child: ChatEventList(controller: controller), ), ), + if (controller.showScrollDownButton) + Divider( + height: 1, + color: theme.dividerColor, + ), if (controller.room.isExtinct) Container( - margin: EdgeInsets.only( - bottom: bottomSheetPadding, - left: bottomSheetPadding, - right: bottomSheetPadding, - ), + margin: EdgeInsets.all(bottomSheetPadding), width: double.infinity, child: ElevatedButton.icon( icon: const Icon(Icons.chevron_right), @@ -327,11 +322,7 @@ class ChatView extends StatelessWidget { else if (controller.room.canSendDefaultMessages && controller.room.membership == Membership.join) Container( - margin: EdgeInsets.only( - bottom: bottomSheetPadding, - left: bottomSheetPadding, - right: bottomSheetPadding, - ), + margin: EdgeInsets.all(bottomSheetPadding), constraints: const BoxConstraints( maxWidth: FluffyThemes.columnWidth * 2.5, ), @@ -386,7 +377,6 @@ class ChatView extends StatelessWidget { // : Column( // mainAxisSize: MainAxisSize.min, // children: [ - // ReactionsPicker(controller), // ReplyDisplay(controller), // ChatInputRow(controller), // ChatEmojiPicker(controller), diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index ada6cceb4..47b7173fa 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -2,6 +2,7 @@ import 'dart:ui' as ui; import 'package:flutter/material.dart'; +import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; import 'package:matrix/matrix.dart'; import 'package:swipe_to_action/swipe_to_action.dart'; @@ -33,8 +34,10 @@ class Message extends StatelessWidget { final void Function(String) scrollToEventId; final void Function() onSwipe; final void Function() onMention; + final void Function() onEdit; final bool longPressSelect; final bool selected; + final bool singleSelected; final Timeline timeline; final bool highlightMarker; final bool animateIn; @@ -59,6 +62,8 @@ class Message extends StatelessWidget { required this.scrollToEventId, required this.onSwipe, this.selected = false, + required this.onEdit, + required this.singleSelected, required this.timeline, this.highlightMarker = false, this.animateIn = false, @@ -217,518 +222,30 @@ class Message extends StatelessWidget { final resetAnimateIn = this.resetAnimateIn; var animateIn = this.animateIn; - final row = StatefulBuilder( - builder: (context, setState) { - if (animateIn && resetAnimateIn != null) { - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - animateIn = false; - // #Pangea - // setState(resetAnimateIn); - if (context.mounted) setState(resetAnimateIn); - // Pangea# - }); - } - return AnimatedSize( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - clipBehavior: Clip.none, - alignment: ownMessage ? Alignment.bottomRight : Alignment.bottomLeft, - child: animateIn - ? const SizedBox(height: 0, width: double.infinity) - : Stack( - children: [ - Positioned( - top: 0, - bottom: 0, - left: 0, - right: 0, - child: InkWell( - // #Pangea - onTap: () => showToolbar(pangeaMessageEvent), - onLongPress: () => showToolbar(pangeaMessageEvent), - // onTap: () => onSelect(event), - // onLongPress: () => onSelect(event), - // Pangea# - borderRadius: - BorderRadius.circular(AppConfig.borderRadius / 2), - child: Material( - borderRadius: - BorderRadius.circular(AppConfig.borderRadius / 2), - color: selected || highlightMarker - ? theme.colorScheme.secondaryContainer - .withAlpha(128) - : Colors.transparent, - ), - ), - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: rowMainAxisAlignment, - children: [ - // #Pangea - // if (longPressSelect) - // SizedBox( - // height: 32, - // width: Avatar.defaultSize, - // child: Checkbox.adaptive( - // value: selected, - // shape: const CircleBorder(), - // onChanged: (_) => onSelect(event), - // ), - // ) - // else if (nextEventSameSender || ownMessage) - if (nextEventSameSender || ownMessage) - // Pangea# - SizedBox( - width: Avatar.defaultSize, - child: Center( - child: SizedBox( - width: 16, - height: 16, - child: event.status == EventStatus.error - ? const Icon(Icons.error, color: Colors.red) - : event.fileSendingStatus != null - ? const CircularProgressIndicator - .adaptive( - strokeWidth: 1, - ) - : null, - ), - ), - ) - else - FutureBuilder( - future: event.fetchSenderUser(), - builder: (context, snapshot) { - final user = snapshot.data ?? - event.senderFromMemoryOrFallback; - return Avatar( - mxContent: user.avatarUrl, - name: user.calcDisplayname(), - onTap: () => showMemberActionsPopupMenu( - context: context, - user: user, - onMention: onMention, - ), - presenceUserId: user.stateKey, - presenceBackgroundColor: - wallpaperMode ? Colors.transparent : null, - ); - }, - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (!nextEventSameSender) - Padding( - padding: const EdgeInsets.only( - left: 8.0, - bottom: 4, - ), - child: ownMessage || event.room.isDirectChat - ? const SizedBox(height: 12) - : FutureBuilder( - future: event.fetchSenderUser(), - builder: (context, snapshot) { - final displayname = snapshot.data - ?.calcDisplayname() ?? - event.senderFromMemoryOrFallback - .calcDisplayname(); - return Text( - displayname, - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.bold, - color: (theme.brightness == - Brightness.light - ? displayname.color - : displayname - .lightColorText), - shadows: !wallpaperMode - ? null - : [ - const Shadow( - offset: Offset( - 0.0, - 0.0, - ), - blurRadius: 3, - color: Colors.black, - ), - ], - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ); - }, - ), - ), - Container( - alignment: alignment, - padding: const EdgeInsets.only(left: 8), - child: GestureDetector( - // #Pangea - onTap: () => showToolbar(pangeaMessageEvent), - onLongPress: () => - showToolbar(pangeaMessageEvent), - // onLongPress: longPressSelect - // ? null - // : () { - // HapticFeedback.heavyImpact(); - // onSelect(event); - // }, - // Pangea# - child: AnimatedOpacity( - opacity: animateIn - ? 0 - : event.messageType == - MessageTypes.BadEncrypted || - event.status.isSending - ? 0.5 - : 1, - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - child: - // #Pangea - PressableButton( - triggerAnimation: controller - .showToolbarStream.stream - .where( - (eventID) => eventID == event.eventId, - ), - depressed: !isButton, - borderRadius: borderRadius, - onPressed: () { - showToolbar(pangeaMessageEvent); - }, - color: color, - visible: isButton && !noBubble, - child: - // Pangea# - Container( - decoration: BoxDecoration( - color: noBubble - ? Colors.transparent - : color, - borderRadius: borderRadius, - ), - clipBehavior: Clip.antiAlias, - // #Pangea - child: CompositedTransformTarget( - link: MatrixState.pAnyState - .layerLinkAndKey( - event.eventId, - ) - .link, - // child: BubbleBackground( - // colors: colors, - // ignore: noBubble || !ownMessage, - // scrollController: scrollController, - // Pangea# - child: Container( - // #Pangea - key: MatrixState.pAnyState - .layerLinkAndKey( - event.eventId, - ) - .key, - // Pangea# - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular( - AppConfig.borderRadius, - ), - ), - constraints: const BoxConstraints( - maxWidth: - FluffyThemes.columnWidth * - 1.5, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - if ({ - RelationshipTypes.reply, - RelationshipTypes.thread, - }.contains( - event.relationshipType, - )) - FutureBuilder( - future: event.getReplyEvent( - timeline, - ), - builder: ( - BuildContext context, - snapshot, - ) { - final replyEvent = - snapshot.hasData - ? snapshot.data! - : Event( - eventId: event - .relationshipEventId!, - content: { - 'msgtype': - 'm.text', - 'body': - '...', - }, - // #Pangea - // senderId: event - // .senderId, - senderId: "", - // Pangea# - type: - 'm.room.message', - room: event - .room, - status: - EventStatus - .sent, - originServerTs: - DateTime - .now(), - ); - return Padding( - padding: - const EdgeInsets - .only( - left: 16, - right: 16, - top: 8, - ), - child: Material( - color: Colors - .transparent, - borderRadius: - ReplyContent - .borderRadius, - child: InkWell( - borderRadius: - ReplyContent - .borderRadius, - onTap: () => - scrollToEventId( - replyEvent - .eventId, - ), - child: - AbsorbPointer( - child: - ReplyContent( - replyEvent, - ownMessage: - ownMessage, - timeline: - timeline, - ), - ), - ), - ), - ); - }, - ), - MessageContent( - displayEvent, - textColor: textColor, - linkColor: linkColor, - onInfoTab: onInfoTab, - borderRadius: borderRadius, - timeline: timeline, - selected: selected, - // #Pangea - pangeaMessageEvent: - pangeaMessageEvent, - immersionMode: immersionMode, - controller: controller, - nextEvent: nextEvent, - prevEvent: previousEvent, - // Pangea# - ), - if (event.hasAggregatedEvents( - timeline, - RelationshipTypes.edit, - )) - Padding( - padding: - const EdgeInsets.only( - bottom: 8.0, - left: 16.0, - right: 16.0, - ), - child: Row( - mainAxisSize: - MainAxisSize.min, - spacing: 4.0, - children: [ - Icon( - Icons.edit_outlined, - color: textColor - .withAlpha(164), - size: 14, - ), - Text( - displayEvent - .originServerTs - .localizedTimeShort( - context, - ), - style: TextStyle( - color: textColor - .withAlpha( - 164, - ), - fontSize: 11, - ), - ), - ], - ), - ), - ], - ), - ), - ), - ), - ), - ), - ), - ), - ], - ), - ), - ], - ), - ], - ), - ); - }, - ); - Widget container; - final showReceiptsRow = - event.hasAggregatedEvents(timeline, RelationshipTypes.reaction); - // #Pangea - // if (showReceiptsRow || displayTime || selected || displayReadMarker) { - if (showReceiptsRow || - displayTime || - displayReadMarker || - (pangeaMessageEvent?.showMessageButtons ?? false)) { - // Pangea# - container = Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: - ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, - children: [ - // #Pangea - if (displayTime) - // we don't need to display the time if the message is selected - // we have the background blotted out so you can't see the time anyway - // if (displayTime || selected) - // Pangea# - Padding( - padding: displayTime - ? const EdgeInsets.symmetric(vertical: 8.0) - : EdgeInsets.zero, - 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, - AnimatedSize( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - // #Pangea - child: !showReceiptsRow && - !(pangeaMessageEvent?.showMessageButtons ?? false) - // child: !showReceiptsRow - // Pangea# - ? const SizedBox.shrink() - : Padding( - padding: EdgeInsets.only( - top: 4.0, - left: (ownMessage ? 0 : Avatar.defaultSize) + 12.0, - right: ownMessage ? 0 : 12.0, - ), - // #Pangea - child: Row( - mainAxisAlignment: ownMessage - ? MainAxisAlignment.end - : MainAxisAlignment.start, - children: [ - MessageReactions(event, timeline), - ], - ), - // child: MessageReactions(event, timeline), - // Pangea# - ), - ), - if (displayReadMarker) - Row( - children: [ - Expanded( - child: - Divider(color: theme.colorScheme.surfaceContainerHighest), - ), - Container( - margin: const EdgeInsets.symmetric( - horizontal: 4, - vertical: 16.0, - ), - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 2, - ), - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(AppConfig.borderRadius / 3), - color: theme.colorScheme.surface.withAlpha(128), - ), - child: Text( - L10n.of(context).readUpToHere, - style: TextStyle( - fontSize: 12 * AppConfig.fontSizeFactor, - ), - ), - ), - Expanded( - child: - Divider(color: theme.colorScheme.surfaceContainerHighest), - ), - ], - ), - ], + final sentReactions = {}; + if (singleSelected) { + sentReactions.addAll( + event + .aggregatedEvents( + timeline, + RelationshipTypes.reaction, + ) + .where( + (event) => + event.senderId == event.room.client.userID && + event.type == 'm.reaction', + ) + .map( + (event) => event.content + .tryGetMap('m.relates_to') + ?.tryGet('key'), + ) + .whereType(), ); - } else { - container = row; } - // #Pangea - container = Material(type: MaterialType.transparency, child: container); - // Pangea# + final showReceiptsRow = + event.hasAggregatedEvents(timeline, RelationshipTypes.reaction); return Center( child: Swipeable( @@ -753,7 +270,706 @@ class Message extends StatelessWidget { top: nextEventSameSender ? 1.0 : 4.0, bottom: previousEventSameSender ? 1.0 : 4.0, ), - child: container, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, + children: [ + if (displayTime || selected) + Padding( + padding: displayTime + ? const EdgeInsets.symmetric(vertical: 8.0) + : EdgeInsets.zero, + 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, + ), + ), + ), + ), + ), + ), + ), + StatefulBuilder( + builder: (context, setState) { + if (animateIn && resetAnimateIn != null) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + animateIn = false; + // #Pangea + // setState(resetAnimateIn); + if (context.mounted) setState(resetAnimateIn); + // Pangea# + }); + } + return AnimatedSize( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + clipBehavior: Clip.none, + alignment: ownMessage + ? Alignment.bottomRight + : Alignment.bottomLeft, + child: animateIn + ? const SizedBox(height: 0, width: double.infinity) + : Stack( + children: [ + Positioned( + top: 0, + bottom: 0, + left: 0, + right: 0, + child: InkWell( + // #Pangea + onTap: () => showToolbar(pangeaMessageEvent), + onLongPress: () => + showToolbar(pangeaMessageEvent), + // onTap: () => onSelect(event), + // onLongPress: () => onSelect(event), + // Pangea# + borderRadius: BorderRadius.circular( + AppConfig.borderRadius / 2, + ), + child: Material( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius / 2, + ), + color: selected || highlightMarker + ? theme.colorScheme.secondaryContainer + .withAlpha(128) + : Colors.transparent, + ), + ), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: rowMainAxisAlignment, + children: [ + // #Pangea + // if (longPressSelect) + // SizedBox( + // height: 32, + // width: Avatar.defaultSize, + // child: IconButton( + // padding: EdgeInsets.zero, + // icon: Icon( + // selected + // ? Icons.check_circle + // : Icons.circle_outlined, + // ), + // onPressed: () => onSelect(event), + // ), + // ) + // else if (nextEventSameSender || ownMessage) + if (nextEventSameSender || ownMessage) + // Pangea# + SizedBox( + width: Avatar.defaultSize, + child: Center( + child: SizedBox( + width: 16, + height: 16, + child: event.status == + EventStatus.error + ? const Icon( + Icons.error, + color: Colors.red, + ) + : event.fileSendingStatus != null + ? const CircularProgressIndicator + .adaptive( + strokeWidth: 1, + ) + : null, + ), + ), + ) + else + FutureBuilder( + future: event.fetchSenderUser(), + builder: (context, snapshot) { + final user = snapshot.data ?? + event.senderFromMemoryOrFallback; + return Avatar( + mxContent: user.avatarUrl, + name: user.calcDisplayname(), + onTap: () => + showMemberActionsPopupMenu( + context: context, + user: user, + onMention: onMention, + ), + presenceUserId: user.stateKey, + presenceBackgroundColor: wallpaperMode + ? Colors.transparent + : null, + ); + }, + ), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if (!nextEventSameSender) + Padding( + padding: const EdgeInsets.only( + left: 8.0, + bottom: 4, + ), + child: ownMessage || + event.room.isDirectChat + ? const SizedBox(height: 12) + : FutureBuilder( + future: + event.fetchSenderUser(), + builder: + (context, snapshot) { + final displayname = snapshot + .data + ?.calcDisplayname() ?? + event + .senderFromMemoryOrFallback + .calcDisplayname(); + return Text( + displayname, + style: TextStyle( + fontSize: 11, + fontWeight: + FontWeight.bold, + color: (theme.brightness == + Brightness + .light + ? displayname + .color + : displayname + .lightColorText), + shadows: + !wallpaperMode + ? null + : [ + const Shadow( + offset: + Offset( + 0.0, + 0.0, + ), + blurRadius: + 3, + color: Colors + .black, + ), + ], + ), + maxLines: 1, + overflow: TextOverflow + .ellipsis, + ); + }, + ), + ), + Container( + alignment: alignment, + padding: + const EdgeInsets.only(left: 8), + child: GestureDetector( + // #Pangea + onTap: () => + showToolbar(pangeaMessageEvent), + onLongPress: () => + showToolbar(pangeaMessageEvent), + // onLongPress: longPressSelect + // ? null + // : () { + // HapticFeedback.heavyImpact(); + // onSelect(event); + // }, + // Pangea# + child: AnimatedOpacity( + opacity: animateIn + ? 0 + : event.messageType == + MessageTypes + .BadEncrypted || + event.status.isSending + ? 0.5 + : 1, + duration: FluffyThemes + .animationDuration, + curve: + FluffyThemes.animationCurve, + child: + // #Pangea + PressableButton( + triggerAnimation: controller + .showToolbarStream.stream + .where( + (eventID) => + eventID == event.eventId, + ), + depressed: !isButton, + borderRadius: borderRadius, + onPressed: () { + showToolbar( + pangeaMessageEvent, + ); + }, + color: color, + visible: isButton && !noBubble, + child: + // Pangea# + Container( + decoration: BoxDecoration( + color: noBubble + ? Colors.transparent + : color, + borderRadius: borderRadius, + ), + clipBehavior: Clip.antiAlias, + // #Pangea + child: + CompositedTransformTarget( + link: MatrixState.pAnyState + .layerLinkAndKey( + event.eventId, + ) + .link, + // child: BubbleBackground( + // colors: colors, + // ignore: noBubble || !ownMessage, + // scrollController: scrollController, + // Pangea# + child: Container( + // #Pangea + key: MatrixState.pAnyState + .layerLinkAndKey( + event.eventId, + ) + .key, + // Pangea# + decoration: BoxDecoration( + borderRadius: + BorderRadius + .circular( + AppConfig + .borderRadius, + ), + ), + constraints: + const BoxConstraints( + maxWidth: FluffyThemes + .columnWidth * + 1.5, + ), + child: Column( + mainAxisSize: + MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment + .start, + children: [ + if ({ + RelationshipTypes + .reply, + RelationshipTypes + .thread, + }.contains( + event + .relationshipType, + )) + FutureBuilder< + Event?>( + future: event + .getReplyEvent( + timeline, + ), + builder: ( + BuildContext + context, + snapshot, + ) { + final replyEvent = + snapshot + .hasData + ? snapshot + .data! + : Event( + eventId: + event.relationshipEventId!, + content: { + 'msgtype': 'm.text', + 'body': '...', + }, + // #Pangea + // senderId: event + // .senderId, + senderId: + "", + // Pangea# + type: + 'm.room.message', + room: + event.room, + status: + EventStatus.sent, + originServerTs: + DateTime.now(), + ); + return Padding( + padding: + const EdgeInsets + .only( + left: 16, + right: 16, + top: 8, + ), + child: + Material( + color: Colors + .transparent, + borderRadius: + ReplyContent + .borderRadius, + child: + InkWell( + borderRadius: + ReplyContent + .borderRadius, + onTap: () => + scrollToEventId( + replyEvent + .eventId, + ), + child: + AbsorbPointer( + child: + ReplyContent( + replyEvent, + ownMessage: + ownMessage, + timeline: + timeline, + ), + ), + ), + ), + ); + }, + ), + MessageContent( + displayEvent, + textColor: + textColor, + linkColor: + linkColor, + onInfoTab: + onInfoTab, + borderRadius: + borderRadius, + timeline: timeline, + selected: selected, + // #Pangea + pangeaMessageEvent: + pangeaMessageEvent, + immersionMode: + immersionMode, + controller: + controller, + nextEvent: + nextEvent, + prevEvent: + previousEvent, + // Pangea# + ), + if (event + .hasAggregatedEvents( + timeline, + RelationshipTypes + .edit, + )) + Padding( + padding: + const EdgeInsets + .only( + bottom: 8.0, + left: 16.0, + right: 16.0, + ), + child: Row( + mainAxisSize: + MainAxisSize + .min, + spacing: 4.0, + children: [ + Icon( + Icons + .edit_outlined, + color: textColor + .withAlpha( + 164, + ), + size: 14, + ), + Text( + displayEvent + .originServerTs + .localizedTimeShort( + context, + ), + style: + TextStyle( + color: textColor + .withAlpha( + 164, + ), + fontSize: + 11, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ], + ), + ); + }, + ), + Padding( + padding: const EdgeInsets.only(left: Avatar.defaultSize + 8.0), + child: AnimatedSize( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + alignment: Alignment.bottomCenter, + child: singleSelected && event.room.canSendDefaultMessages + ? Padding( + padding: const EdgeInsets.only(bottom: 4.0), + child: Material( + elevation: 4, + borderRadius: + BorderRadius.circular(AppConfig.borderRadius), + shadowColor: theme.appBarTheme.shadowColor, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.reply_outlined), + tooltip: L10n.of(context).reply, + onPressed: onSwipe, + ), + if (ownMessage) + IconButton( + icon: const Icon(Icons.edit_outlined), + tooltip: L10n.of(context).edit, + onPressed: onEdit, + ), + IconButton( + icon: const Icon(Icons.add_reaction_outlined), + tooltip: L10n.of(context).customReaction, + onPressed: () async { + final emoji = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Row( + mainAxisSize: MainAxisSize.min, + spacing: 4, + children: [ + CloseButton( + onPressed: () => + Navigator.of(context) + .pop(null), + ), + Text( + L10n.of(context).customReaction, + ), + ], + ), + titlePadding: const EdgeInsets.all(8), + contentPadding: const EdgeInsets.all(0), + clipBehavior: Clip.hardEdge, + content: SizedBox( + width: 350, + height: 350, + child: EmojiPicker( + onEmojiSelected: (_, emoji) => + Navigator.of(context) + .pop(emoji.emoji), + config: Config( + emojiViewConfig: + const EmojiViewConfig( + backgroundColor: + Colors.transparent, + ), + bottomActionBarConfig: + const BottomActionBarConfig( + enabled: false, + ), + categoryViewConfig: + CategoryViewConfig( + initCategory: Category.SMILEYS, + backspaceColor: + theme.colorScheme.primary, + iconColor: theme + .colorScheme.primary + .withAlpha(128), + iconColorSelected: + theme.colorScheme.primary, + indicatorColor: + theme.colorScheme.primary, + backgroundColor: + theme.colorScheme.surface, + ), + skinToneConfig: SkinToneConfig( + dialogBackgroundColor: + Color.lerp( + theme.colorScheme.surface, + theme.colorScheme + .primaryContainer, + 0.75, + )!, + indicatorColor: + theme.colorScheme.onSurface, + ), + ), + ), + ), + ), + ); + if (emoji == null) return; + if (sentReactions.contains(emoji)) return; + + await event.room.sendReaction( + event.eventId, + emoji, + ); + }, + ), + ...AppConfig.defaultReactions.map( + (emoji) => IconButton( + padding: EdgeInsets.zero, + icon: Center( + child: Opacity( + opacity: sentReactions.contains(emoji) + ? 0.33 + : 1, + child: Text( + emoji, + style: const TextStyle(fontSize: 20), + textAlign: TextAlign.center, + ), + ), + ), + onPressed: sentReactions.contains(emoji) + ? null + : () { + onSelect(event); + event.room.sendReaction( + event.eventId, + emoji, + ); + }, + ), + ), + ], + ), + ), + ) + : const SizedBox.shrink(), + ), + ), + AnimatedSize( + duration: FluffyThemes.animationDuration, + curve: FluffyThemes.animationCurve, + // #Pangea + child: !showReceiptsRow && + !(pangeaMessageEvent?.showMessageButtons ?? false) + // child: !showReceiptsRow + // Pangea# + ? const SizedBox.shrink() + : Padding( + padding: EdgeInsets.only( + top: 4.0, + left: (ownMessage ? 0 : Avatar.defaultSize) + 12.0, + right: ownMessage ? 0 : 12.0, + ), + // #Pangea + child: Row( + mainAxisAlignment: ownMessage + ? MainAxisAlignment.end + : MainAxisAlignment.start, + children: [ + MessageReactions(event, timeline), + ], + ), + // child: MessageReactions(event, timeline), + // Pangea# + ), + ), + if (displayReadMarker) + Row( + children: [ + Expanded( + child: Divider( + color: theme.colorScheme.surfaceContainerHighest, + ), + ), + Container( + margin: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 16.0, + ), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(AppConfig.borderRadius / 3), + color: theme.colorScheme.surface.withAlpha(128), + ), + child: Text( + L10n.of(context).readUpToHere, + style: TextStyle( + fontSize: 12 * AppConfig.fontSizeFactor, + ), + ), + ), + Expanded( + child: Divider( + color: theme.colorScheme.surfaceContainerHighest, + ), + ), + ], + ), + ], + ), ), ), ); diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart index 8ad45aaf2..05e1fd8b3 100644 --- a/lib/pages/chat/input_bar.dart +++ b/lib/pages/chat/input_bar.dart @@ -519,6 +519,7 @@ class InputBar extends StatelessWidget { // builder: (context, controller, focusNode) => TextField( // controller: controller, // focusNode: focusNode, + // readOnly: readOnly, // contextMenuBuilder: (c, e) => markdownContextBuilder(c, e, controller), // contentInsertionConfiguration: ContentInsertionConfiguration( // onContentInserted: (KeyboardInsertedContent content) { diff --git a/lib/pages/chat/reactions_picker.dart b/lib/pages/chat/reactions_picker.dart deleted file mode 100644 index 10344d873..000000000 --- a/lib/pages/chat/reactions_picker.dart +++ /dev/null @@ -1,119 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:matrix/matrix.dart'; - -import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/config/app_emojis.dart'; -import 'package:fluffychat/pages/chat/chat.dart'; -import '../../config/themes.dart'; - -class ReactionsPicker extends StatelessWidget { - final ChatController controller; - - const ReactionsPicker(this.controller, {super.key}); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - - if (controller.showEmojiPicker) return const SizedBox.shrink(); - final display = controller.editEvent == null && - controller.replyEvent == null && - controller.room.canSendDefaultMessages && - controller.selectedEvents.isNotEmpty; - return AnimatedContainer( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - // #Pangea - // height: (display) ? 56 : 0, - height: (display) ? AppConfig.reactionsPickerHeight : 0, - // Pangea# - child: Material( - color: Colors.transparent, - child: Builder( - builder: (context) { - if (!display) { - return const SizedBox.shrink(); - } - final emojis = List.from(AppEmojis.emojis); - final allReactionEvents = controller.selectedEvents.first - .aggregatedEvents( - controller.timeline!, - RelationshipTypes.reaction, - ) - .where( - (event) => - event.senderId == event.room.client.userID && - event.type == 'm.reaction', - ); - - for (final event in allReactionEvents) { - try { - emojis.remove(event.content.tryGetMap('m.relates_to')!['key']); - } catch (_) {} - } - return Row( - children: [ - Expanded( - child: Container( - decoration: const BoxDecoration( - // #Pangea - // color: theme.colorScheme.onInverseSurface, - // Pangea# - borderRadius: BorderRadius.only( - bottomRight: Radius.circular(AppConfig.borderRadius), - ), - ), - padding: const EdgeInsets.only(right: 1), - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: emojis.length, - itemBuilder: (c, i) => InkWell( - borderRadius: BorderRadius.circular(8), - onTap: () => controller.sendEmojiAction(emojis[i]), - child: Container( - // #Pangea - // width: 56, - // height: 56, - width: AppConfig.reactionsPickerHeight, - height: AppConfig.reactionsPickerHeight, - // Pangea# - alignment: Alignment.center, - child: Text( - emojis[i], - // #Pangea - // style: const TextStyle(fontSize: 30), - style: const TextStyle(fontSize: 20), - // Pangea# - ), - ), - ), - ), - ), - ), - InkWell( - borderRadius: BorderRadius.circular(8), - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 8), - width: 36, - // #Pangea - // height: 56, - height: AppConfig.reactionsPickerHeight, - // Pangea# - decoration: BoxDecoration( - color: theme.colorScheme.onInverseSurface, - shape: BoxShape.circle, - ), - child: const Icon(Icons.add_outlined), - ), - onTap: () => - controller.pickEmojiReactionAction(allReactionEvents), - ), - ], - ); - }, - ), - ), - ); - } -} diff --git a/lib/pangea/chat/widgets/pangea_chat_input_row.dart b/lib/pangea/chat/widgets/pangea_chat_input_row.dart index c60652c7c..6ef29df12 100644 --- a/lib/pangea/chat/widgets/pangea_chat_input_row.dart +++ b/lib/pangea/chat/widgets/pangea_chat_input_row.dart @@ -124,8 +124,7 @@ class PangeaChatInputRowState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - if (_controller.showEmojiPicker && - _controller.emojiPickerType == EmojiPickerType.reaction) { + if (_controller.showEmojiPicker) { return const SizedBox.shrink(); } const height = 48.0; diff --git a/lib/pangea/lemmas/lemma_reaction_picker.dart b/lib/pangea/lemmas/lemma_reaction_picker.dart index 3b157c2b7..ebe41b680 100644 --- a/lib/pangea/lemmas/lemma_reaction_picker.dart +++ b/lib/pangea/lemmas/lemma_reaction_picker.dart @@ -39,7 +39,8 @@ class LemmaReactionPickerState extends State { } } - void setEmoji(String emoji) => widget.controller.sendEmojiAction(emoji); + void setEmoji(String emoji) {} + // widget.controller.sendEmojiAction(emoji); Future _refresh() async { setState(() { diff --git a/lib/pangea/toolbar/widgets/overlay_header.dart b/lib/pangea/toolbar/widgets/overlay_header.dart index 81bb4aba4..d748aac47 100644 --- a/lib/pangea/toolbar/widgets/overlay_header.dart +++ b/lib/pangea/toolbar/widgets/overlay_header.dart @@ -6,7 +6,6 @@ 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/extensions/pangea_event_extension.dart'; import 'package:fluffychat/pangea/events/utils/report_message.dart'; class OverlayHeader extends StatefulWidget { @@ -118,14 +117,14 @@ class OverlayHeaderState extends State { 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.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), diff --git a/lib/utils/show_scaffold_dialog.dart b/lib/utils/show_scaffold_dialog.dart index 0c09a7037..9a0a0c610 100644 --- a/lib/utils/show_scaffold_dialog.dart +++ b/lib/utils/show_scaffold_dialog.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; -Future showScaffoldDialog({ +Future showScaffoldDialog({ required BuildContext context, Color? barrierColor, Color? containerColor, @@ -11,7 +11,7 @@ Future showScaffoldDialog({ double maxHeight = 720, required Widget Function(BuildContext context) builder, }) => - showDialog( + showDialog( context: context, useSafeArea: false, builder: FluffyThemes.isColumnMode(context)