Skip to content

Commit fce446f

Browse files
authored
TW-2307: Improve context menu in tablet (#2338)
* TW-2307: Improve context menu in tablet * fixup! TW-2307: Improve context menu in tablet * fixup! fixup! TW-2307: Improve context menu in tablet * fixup! fixup! fixup! TW-2307: Improve context menu in tablet * fixup! fixup! fixup! fixup! TW-2307: Improve context menu in tablet
1 parent 78d34b6 commit fce446f

File tree

10 files changed

+633
-148
lines changed

10 files changed

+633
-148
lines changed

lib/pages/chat/chat.dart

Lines changed: 341 additions & 30 deletions
Large diffs are not rendered by default.

lib/pages/chat/chat_context_menu_actions.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ enum ChatContextMenuActions {
77
copyMessage,
88
pinChat,
99
forward,
10+
reply,
1011
downloadFile,
1112
jumpToMessage;
1213

@@ -20,6 +21,8 @@ enum ChatContextMenuActions {
2021
return isSelected
2122
? L10n.of(context)!.unselect
2223
: L10n.of(context)!.select;
24+
case ChatContextMenuActions.reply:
25+
return L10n.of(context)!.reply;
2326
case ChatContextMenuActions.copyMessage:
2427
return L10n.of(context)!.copyMessageText;
2528
case ChatContextMenuActions.pinChat:
@@ -57,6 +60,8 @@ enum ChatContextMenuActions {
5760
bool unpin = false,
5861
}) {
5962
switch (this) {
63+
case ChatContextMenuActions.reply:
64+
return ImagePaths.icReply;
6065
case ChatContextMenuActions.jumpToMessage:
6166
return ImagePaths.icGoTo;
6267
case ChatContextMenuActions.pinChat:

lib/pages/chat/chat_event_list.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
import 'dart:ui';
22

3+
import 'package:fluffychat/config/themes.dart';
4+
import 'package:fluffychat/pages/chat/chat.dart';
5+
import 'package:fluffychat/pages/chat/events/message/message.dart';
36
import 'package:fluffychat/pages/chat/group_chat_empty_view.dart';
47
import 'package:fluffychat/pages/chat_draft/draft_chat_empty_widget.dart';
58
import 'package:fluffychat/presentation/model/search/presentation_search.dart';
9+
import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart';
610
import 'package:fluffychat/utils/platform_infos.dart';
711
import 'package:fluffychat/widgets/context_menu/context_menu_action.dart';
812
import 'package:flutter/cupertino.dart';
913
import 'package:flutter/material.dart';
10-
1114
import 'package:flutter_gen/gen_l10n/l10n.dart';
1215
import 'package:inview_notifier_list/inview_notifier_list.dart';
1316
import 'package:matrix/matrix.dart';
1417
import 'package:scroll_to_index/scroll_to_index.dart';
1518

16-
import 'package:fluffychat/config/themes.dart';
17-
import 'package:fluffychat/pages/chat/chat.dart';
18-
import 'package:fluffychat/pages/chat/events/message/message.dart';
19-
import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart';
20-
2119
class ChatEventList extends StatelessWidget {
2220
final ChatController controller;
2321

@@ -213,6 +211,8 @@ class ChatEventList extends StatelessWidget {
213211
onPin: (event) {
214212
controller.pinEventAction(event);
215213
},
214+
onTapMoreButton:
215+
controller.handleOnTapMoreButtonOnWeb,
216216
)
217217
: const SizedBox(),
218218
);

lib/pages/chat/chat_view_body.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,19 +189,19 @@ class ChatViewBody extends StatelessWidget with MessageContentMixin {
189189
},
190190
),
191191
ValueListenableBuilder(
192-
valueListenable: controller.showFullEmojiPickerOnWebNotifier,
192+
valueListenable: controller.showEmojiPickerComposerNotifier,
193193
builder: (context, display, _) {
194194
if (!display) return const SizedBox.shrink();
195195
return Positioned(
196196
bottom: 72,
197197
right: 64,
198198
child: MouseRegion(
199199
onHover: (_) {
200-
controller.showFullEmojiPickerOnWebNotifier.value = true;
200+
controller.showEmojiPickerComposerNotifier.value = true;
201201
},
202202
onExit: (_) async {
203203
await Future.delayed(const Duration(seconds: 1));
204-
controller.showFullEmojiPickerOnWebNotifier.value = false;
204+
controller.showEmojiPickerComposerNotifier.value = false;
205205
},
206206
child: Container(
207207
padding: const EdgeInsets.all(12),

lib/pages/chat/events/message/message.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import 'package:fluffychat/pages/chat/events/message/swipeable_message.dart';
1010
import 'package:fluffychat/pages/chat/events/state_message.dart';
1111
import 'package:fluffychat/pages/chat/events/verification_request_content.dart';
1212
import 'package:fluffychat/pages/chat/sticky_timestamp_widget.dart';
13+
import 'package:fluffychat/presentation/mixins/message_avatar_mixin.dart';
1314
import 'package:fluffychat/utils/date_time_extension.dart';
1415
import 'package:fluffychat/utils/extension/event_status_custom_extension.dart';
1516
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
1617
import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart';
17-
import 'package:fluffychat/presentation/mixins/message_avatar_mixin.dart';
1818
import 'package:fluffychat/utils/responsive/responsive_utils.dart';
1919
import 'package:fluffychat/widgets/context_menu/context_menu_action.dart';
2020
import 'package:fluffychat/widgets/swipeable.dart';
@@ -83,6 +83,8 @@ class Message extends StatefulWidget {
8383
final void Function(Event)? onForward;
8484
final void Function(Event)? onCopy;
8585
final void Function(Event)? onPin;
86+
final void Function(BuildContext context, Event, TapDownDetails)?
87+
onTapMoreButton;
8688

8789
const Message(
8890
this.event, {
@@ -115,6 +117,7 @@ class Message extends StatefulWidget {
115117
this.onForward,
116118
this.onCopy,
117119
this.onPin,
120+
this.onTapMoreButton,
118121
});
119122

120123
/// Indicates wheither the user may use a mouse instead
@@ -247,6 +250,7 @@ class _MessageState extends State<Message> with MessageAvatarMixin {
247250
onCopy: widget.onCopy,
248251
onLongPressMessage: widget.onLongPressMessage,
249252
onPin: widget.onPin,
253+
onTapMoreButton: widget.onTapMoreButton,
250254
),
251255
),
252256
];

lib/pages/chat/events/message/message_content_with_timestamp_builder.dart

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ import 'package:fluffychat/widgets/matrix.dart';
1919
import 'package:fluffychat/widgets/twake_components/twake_icon_button.dart';
2020
import 'package:flutter/material.dart';
2121
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
22+
import 'package:flutter_gen/gen_l10n/l10n.dart';
2223
import 'package:flutter_svg/flutter_svg.dart';
2324
import 'package:linagora_design_flutter/linagora_design_flutter.dart';
2425
import 'package:matrix/matrix.dart';
26+
import 'package:overflow_view/overflow_view.dart';
2527
import 'package:pull_down_button/pull_down_button.dart';
26-
import 'package:flutter_gen/gen_l10n/l10n.dart';
2728

2829
typedef ContextMenuBuilder = List<Widget> Function(BuildContext context);
2930

@@ -50,6 +51,8 @@ class MessageContentWithTimestampBuilder extends StatefulWidget {
5051
final void Function(Event)? onForward;
5152
final void Function(Event)? onCopy;
5253
final void Function(Event)? onPin;
54+
final void Function(BuildContext context, Event, TapDownDetails)?
55+
onTapMoreButton;
5356

5457
static final responsiveUtils = getIt.get<ResponsiveUtils>();
5558

@@ -77,6 +80,7 @@ class MessageContentWithTimestampBuilder extends StatefulWidget {
7780
this.onCopy,
7881
this.onLongPressMessage,
7982
this.onPin,
83+
this.onTapMoreButton,
8084
});
8185

8286
@override
@@ -104,6 +108,34 @@ class _MessageContentWithTimestampBuilderState
104108
MessageTypes.BadEncrypted,
105109
}.contains(widget.event.messageType);
106110

111+
return OverflowView.flexible(
112+
builder: (context, index) {
113+
return _messageContentWithTimestampBuilder(
114+
context: context,
115+
displayTime: displayTime,
116+
noBubble: noBubble,
117+
timelineText: timelineText,
118+
overlayContextMenu: true,
119+
);
120+
},
121+
children: [
122+
_messageContentWithTimestampBuilder(
123+
context: context,
124+
displayTime: displayTime,
125+
noBubble: noBubble,
126+
timelineText: timelineText,
127+
),
128+
],
129+
);
130+
}
131+
132+
Widget _messageContentWithTimestampBuilder({
133+
required BuildContext context,
134+
required bool displayTime,
135+
required bool noBubble,
136+
required bool timelineText,
137+
bool overlayContextMenu = false,
138+
}) {
107139
return Row(
108140
mainAxisSize: MainAxisSize.min,
109141
mainAxisAlignment: MessageStyle.messageAlignment(widget.event, context),
@@ -439,7 +471,28 @@ class _MessageContentWithTimestampBuilderState
439471
),
440472
),
441473
),
442-
if (widget.event.status.isAvailable) _menuActionsRowBuilder(context),
474+
if (widget.event.status.isAvailable) ...[
475+
if (overlayContextMenu) ...[
476+
Container(
477+
padding: const EdgeInsets.only(left: 8),
478+
decoration: BoxDecoration(
479+
color: Colors.transparent,
480+
borderRadius: BorderRadius.circular(24),
481+
),
482+
child: TwakeIconButton(
483+
onTapDown: (tapDownDetails) => widget.onTapMoreButton?.call(
484+
context,
485+
widget.event,
486+
tapDownDetails,
487+
),
488+
icon: Icons.more_horiz,
489+
tooltip: L10n.of(context)!.more,
490+
preferBelow: false,
491+
),
492+
),
493+
] else
494+
_menuActionsRowBuilder(context),
495+
],
443496
],
444497
);
445498
}

lib/pages/chat/events/message_reactions.dart

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -112,28 +112,50 @@ class ReactionsList extends StatelessWidget {
112112
);
113113
},
114114
children: [
115-
...reactionList.map(
116-
(r) => Reaction(
117-
reactionKey: r.key,
118-
count: event.room.isDirectChat ? null : r.count,
119-
reacted: r.reacted,
120-
onTap: () async {
121-
if (r.reacted) {
122-
final evt = allReactionEvents.firstWhereOrNull((e) {
123-
final relatedTo = e.content['m.relates_to'];
124-
return e.senderId == e.room.client.userID &&
125-
relatedTo is Map &&
126-
relatedTo['key'] == r.key;
127-
});
128-
if (evt != null) {
129-
await evt.redactEvent();
130-
}
131-
} else {
132-
event.room.sendReaction(event.eventId, r.key!);
133-
}
134-
},
115+
...reactionList.take(3).map(
116+
(r) => Reaction(
117+
reactionKey: r.key,
118+
count: event.room.isDirectChat ? null : r.count,
119+
reacted: r.reacted,
120+
onTap: () async {
121+
if (r.reacted) {
122+
final evt = allReactionEvents.firstWhereOrNull((e) {
123+
final relatedTo = e.content['m.relates_to'];
124+
return e.senderId == e.room.client.userID &&
125+
relatedTo is Map &&
126+
relatedTo['key'] == r.key;
127+
});
128+
if (evt != null) {
129+
await evt.redactEvent();
130+
}
131+
} else {
132+
event.room.sendReaction(event.eventId, r.key!);
133+
}
134+
},
135+
),
136+
),
137+
if (reactionList.length > 3)
138+
Container(
139+
width: MessageReactionsStyle.moreReactionContainer,
140+
height: MessageReactionsStyle.moreReactionContainer,
141+
decoration: BoxDecoration(
142+
color: Theme.of(context).colorScheme.surface,
143+
border: Border.all(color: MessageReactionsStyle.borderColor),
144+
shape: BoxShape.circle,
145+
),
146+
padding: const EdgeInsets.only(
147+
left: 4,
148+
right: 4,
149+
),
150+
child: Center(
151+
child: Text(
152+
'+${reactionList.length - 3}',
153+
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
154+
color: LinagoraRefColors.material().neutral[50],
155+
),
156+
),
157+
),
135158
),
136-
),
137159
if (!event.room.isDirectChat && reactionList.isNotEmpty)
138160
InkWell(
139161
hoverColor: Colors.transparent,
@@ -273,7 +295,7 @@ class ReactionsList extends StatelessWidget {
273295
required TapDownDetails tapDownDetails,
274296
}) async {
275297
final responsive = getIt.get<ResponsiveUtils>();
276-
if (responsive.isDesktop(context)) {
298+
if (!responsive.isMobile(context)) {
277299
_handleDisplayReactionInfoWeb(
278300
context: context,
279301
tapDownDetails: tapDownDetails,

lib/pages/chat_draft/draft_chat.dart

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import 'dart:async';
22

33
import 'package:desktop_drop/desktop_drop.dart';
4-
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
54
import 'package:file_picker/file_picker.dart';
65
import 'package:fluffychat/di/global/get_it_initializer.dart';
76
import 'package:fluffychat/domain/app_state/direct_chat/create_direct_chat_success.dart';
@@ -91,7 +90,7 @@ class DraftChatController extends State<DraftChat>
9190

9291
ValueNotifier<String> inputText = ValueNotifier('');
9392

94-
ValueNotifier<bool> showEmojiPickerNotifier = ValueNotifier(false);
93+
ValueNotifier<bool> showEmojiPickerComposerNotifier = ValueNotifier(false);
9594

9695
final ValueNotifier<Profile?> _userProfile = ValueNotifier(null);
9796

@@ -156,7 +155,7 @@ class DraftChatController extends State<DraftChat>
156155
_createRoomSubscription?.cancel();
157156
focusSuggestionController.dispose();
158157
inputText.dispose();
159-
showEmojiPickerNotifier.dispose();
158+
showEmojiPickerComposerNotifier.dispose();
160159
_userProfile.dispose();
161160
super.dispose();
162161
}
@@ -239,19 +238,12 @@ class DraftChatController extends State<DraftChat>
239238
});
240239
}
241240

242-
void onKeyboardAction() {
243-
showEmojiPickerNotifier.value = false;
241+
void onEmojiAction(TapDownDetails details) {
242+
if (PlatformInfos.isMobile) return;
243+
244244
inputFocus.requestFocus();
245-
}
246245

247-
void onEmojiAction(TapDownDetails details) {
248-
emojiPickerType = EmojiPickerType.keyboard;
249-
showEmojiPickerNotifier.value = true;
250-
if (PlatformInfos.isMobile) {
251-
hideKeyboardChatScreen();
252-
} else {
253-
inputFocus.requestFocus();
254-
}
246+
showEmojiPickerComposerNotifier.value = true;
255247
}
256248

257249
void scrollDown() {
@@ -260,28 +252,21 @@ class DraftChatController extends State<DraftChat>
260252
}
261253
}
262254

263-
void onEmojiBottomSheetSelected(Emoji? emoji) {
264-
typeEmoji(emoji);
265-
onInputBarChanged(sendController.text);
266-
if (PlatformInfos.isWeb) {
267-
inputFocus.requestFocus();
268-
}
269-
}
270-
271-
void typeEmoji(Emoji? emoji) {
272-
if (emoji == null) return;
255+
void typeEmoji(String emoji) {
256+
if (emoji.isEmpty) return;
273257
final text = sendController.text;
274258
final selection = sendController.selection;
275259
final newText = sendController.text.isEmpty
276-
? emoji.emoji
277-
: text.replaceRange(selection.start, selection.end, emoji.emoji);
260+
? emoji
261+
: text.replaceRange(selection.start, selection.end, emoji);
278262
sendController.value = TextEditingValue(
279263
text: newText,
280264
selection: TextSelection.collapsed(
281265
// don't forget an UTF-8 combined emoji might have a length > 1
282-
offset: selection.baseOffset + emoji.emoji.length,
266+
offset: selection.baseOffset + emoji.length,
283267
),
284268
);
269+
inputFocus.requestFocus();
285270
}
286271

287272
void emojiPickerBackspace() {
@@ -440,11 +425,7 @@ class DraftChatController extends State<DraftChat>
440425
}
441426
}
442427

443-
void _keyboardListener(bool isKeyboardVisible) {
444-
if (isKeyboardVisible && showEmojiPickerNotifier.value == true) {
445-
showEmojiPickerNotifier.value = false;
446-
}
447-
}
428+
void _keyboardListener(bool isKeyboardVisible) {}
448429

449430
@override
450431
Widget build(BuildContext context) {

0 commit comments

Comments
 (0)