diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index c175d4e954..482e373d42 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -27,6 +27,7 @@ import 'package:fluffychat/presentation/mixins/send_files_mixin.dart'; import 'package:fluffychat/presentation/model/forward/forward_argument.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/extension/build_context_extension.dart'; +import 'package:fluffychat/utils/extension/value_notifier_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/ios_badge_client_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; @@ -42,6 +43,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:go_router/go_router.dart'; @@ -116,23 +118,25 @@ class ChatController extends State ); final AutoScrollController scrollController = AutoScrollController(); - final AutoScrollController forwardListController = AutoScrollController(); + final KeyboardVisibilityController keyboardVisibilityController = + KeyboardVisibilityController(); final ValueNotifier focusHover = ValueNotifier(null); final ValueNotifier openingPopupMenu = ValueNotifier(false); - + final ValueNotifier draggingNotifier = ValueNotifier(false); + final ValueNotifier showScrollDownButtonNotifier = ValueNotifier(false); + final ValueNotifier showEmojiPickerNotifier = ValueNotifier(false); FocusNode inputFocus = FocusNode(); Timer? typingCoolDown; Timer? typingTimeout; bool currentlyTyping = false; - bool dragging = false; - void onDragEntered(_) => setState(() => dragging = true); + void onDragEntered(_) => draggingNotifier.value = true; - void onDragExited(_) => setState(() => dragging = false); + void onDragExited(_) => draggingNotifier.value = false; void onDragDone(DropDoneDetails details) async { - setState(() => dragging = false); + draggingNotifier.value = false; final bytesList = await showFutureLoadingDialog( context: context, future: () => Future.wait( @@ -181,8 +185,6 @@ class ChatController extends State Event? editEvent; - bool showScrollDownButton = false; - bool get selectMode => selectedEvents.isNotEmpty; final int _loadHistoryCount = 100; @@ -195,8 +197,6 @@ class ChatController extends State timeline!.events.isEmpty || timeline!.events.last.type != EventTypes.RoomCreate; - bool showEmojiPicker = false; - void recreateChat() async { final room = this.room; final userId = room?.directChatMatrixID; @@ -259,6 +259,7 @@ class ChatController extends State if (!mounted) { return; } + hideKeyboardChatScreen(); setReadMarker(); if (!scrollController.hasClients) return; if (scrollController.position.pixels == @@ -268,11 +269,12 @@ class ChatController extends State EventTypes.RoomCreate) { requestHistory(); } - if (scrollController.position.pixels > 0 && showScrollDownButton == false) { - setState(() => showScrollDownButton = true); + if (scrollController.position.pixels > 0 && + !showScrollDownButtonNotifier.value) { + showScrollDownButtonNotifier.value = true; } else if (scrollController.position.pixels == 0 && - showScrollDownButton == true) { - setState(() => showScrollDownButton = false); + showScrollDownButtonNotifier.value) { + showScrollDownButtonNotifier.value = false; } } @@ -285,8 +287,15 @@ class ChatController extends State } } + void _keyboardListener(bool isKeyboardVisible) { + if (isKeyboardVisible && showEmojiPickerNotifier.value == true) { + showEmojiPickerNotifier.value = false; + } + } + @override void initState() { + keyboardVisibilityController.onChange.listen(_keyboardListener); scrollController.addListener(_updateScrollController); inputFocus.addListener(_inputFocusListener); _loadDraft(); @@ -685,19 +694,19 @@ class ChatController extends State } void emojiPickerAction() { - if (showEmojiPicker) { + if (showEmojiPickerNotifier.value) { inputFocus.requestFocus(); } else { inputFocus.unfocus(); } emojiPickerType = EmojiPickerType.keyboard; - setState(() => showEmojiPicker = !showEmojiPicker); + showEmojiPickerNotifier.toggle(); } void _inputFocusListener() { - if (showEmojiPicker && inputFocus.hasFocus) { + if (showEmojiPickerNotifier.value && inputFocus.hasFocus) { emojiPickerType = EmojiPickerType.keyboard; - setState(() => showEmojiPicker = false); + showEmojiPickerNotifier.value = true; } } @@ -728,8 +737,8 @@ class ChatController extends State void copyEventsAction() { Clipboard.setData(ClipboardData(text: _getSelectedEventString())); + showEmojiPickerNotifier.value = false; setState(() { - showEmojiPicker = false; selectedEvents.clear(); }); } @@ -777,8 +786,8 @@ class ChatController extends State ), ); if (result.error != null) return; + showEmojiPickerNotifier.value = false; setState(() { - showEmojiPicker = false; selectedEvents.clear(); }); ScaffoldMessenger.of(context).showSnackBar( @@ -820,8 +829,8 @@ class ChatController extends State }, ); } + showEmojiPickerNotifier.value = false; setState(() { - showEmojiPicker = false; selectedEvents.clear(); }); } @@ -970,7 +979,7 @@ class ChatController extends State } void senEmojiReaction(Emoji? emoji) { - setState(() => showEmojiPicker = false); + showEmojiPickerNotifier.value = false; if (emoji == null) return; // make sure we don't send the same emoji twice if (_allReactionEvents.any((e) { @@ -1012,7 +1021,7 @@ class ChatController extends State void emojiPickerBackspace() { switch (emojiPickerType) { case EmojiPickerType.reaction: - setState(() => showEmojiPicker = false); + showEmojiPickerNotifier.value = false; break; case EmojiPickerType.keyboard: sendController @@ -1027,7 +1036,7 @@ class ChatController extends State void pickEmojiReactionAction(Iterable allReactionEvents) async { _allReactionEvents = allReactionEvents; emojiPickerType = EmojiPickerType.reaction; - setState(() => showEmojiPicker = true); + showEmojiPickerNotifier.value = true; } void sendEmojiAction(String? emoji) async { @@ -1041,10 +1050,13 @@ class ChatController extends State } } - void clearSelectedEvents() => setState(() { - selectedEvents.clear(); - showEmojiPicker = false; - }); + void clearSelectedEvents() { + showEmojiPickerNotifier.value = false; + + setState(() { + selectedEvents.clear(); + }); + } void clearSingleSelectedEvent() { if (selectedEvents.length <= 1) { @@ -1449,6 +1461,22 @@ class ChatController extends State ); } + void hideKeyboardChatScreen() { + if (keyboardVisibilityController.isVisible || inputFocus.hasFocus) { + inputFocus.unfocus(); + } + } + + void handleOnClickKeyboardAction() { + showEmojiPickerNotifier.toggle(); + inputFocus.requestFocus(); + } + + void handleOnLongPressMessage(Event event) { + onSelectMessage(event); + handleContextMenuAction(context, event); + } + @override Widget build(BuildContext context) { return ChatView(this, key: widget.key); diff --git a/lib/pages/chat/chat_emoji_picker.dart b/lib/pages/chat/chat_emoji_picker.dart index d111d0f2e8..9aa7d4a616 100644 --- a/lib/pages/chat/chat_emoji_picker.dart +++ b/lib/pages/chat/chat_emoji_picker.dart @@ -10,7 +10,7 @@ class ChatEmojiPicker extends StatelessWidget { @override Widget build(BuildContext context) { return ValueListenableBuilder( - valueListenable: controller.emojiPickerNotifier, + valueListenable: controller.showEmojiPickerNotifier, builder: (context, showEmojiPicker, _) { return AnimatedContainer( duration: FluffyThemes.animationDuration, diff --git a/lib/pages/chat/chat_input_row.dart b/lib/pages/chat/chat_input_row.dart index 4530485b21..702d1579e2 100644 --- a/lib/pages/chat/chat_input_row.dart +++ b/lib/pages/chat/chat_input_row.dart @@ -22,7 +22,7 @@ class ChatInputRow extends StatelessWidget { @override Widget build(BuildContext context) { - if (controller.emojiPickerNotifier.value && + if (controller.showEmojiPickerNotifier.value && controller.emojiPickerType == EmojiPickerType.reaction) { return Container(); } @@ -133,27 +133,19 @@ class ChatInputRow extends StatelessWidget { ChatInputRowMobile _buildMobileInputRow(BuildContext context) { return ChatInputRowMobile( inputBar: _buildInputBar(context), - emojiPickerNotifier: controller.emojiPickerNotifier, + emojiPickerNotifier: controller.showEmojiPickerNotifier, onEmojiAction: controller.emojiPickerAction, - onKeyboardAction: () { - controller.emojiPickerNotifier.value = - !controller.emojiPickerNotifier.value; - controller.inputFocus.requestFocus(); - }, + onKeyboardAction: controller.handleOnClickKeyboardAction, ); } ChatInputRowWeb _buildWebInputRow(BuildContext context) { return ChatInputRowWeb( inputBar: _buildInputBar(context), - emojiPickerNotifier: controller.emojiPickerNotifier, + emojiPickerNotifier: controller.showEmojiPickerNotifier, onTapMoreBtn: () => controller.onSendFileClick(context), onEmojiAction: controller.emojiPickerAction, - onKeyboardAction: () { - controller.emojiPickerNotifier.value = - !controller.emojiPickerNotifier.value; - controller.inputFocus.requestFocus(); - }, + onKeyboardAction: controller.handleOnClickKeyboardAction, ); } diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index a949890994..fd22fedaac 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -173,7 +173,7 @@ class ChatView extends StatelessWidget { ), ), floatingActionButton: ValueListenableBuilder( - valueListenable: controller.scrollDownButtonNotifier, + valueListenable: controller.showScrollDownButtonNotifier, builder: (context, showScrollDownButton, _) { if (showScrollDownButton && controller.selectedEvents.isEmpty) { diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index aa158963c1..3193a74ed6 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -72,7 +72,7 @@ class Message extends StatelessWidget { Widget build(BuildContext context) { return InkWell( hoverColor: Colors.transparent, - onTap: () {}, + onTap: () => controller.hideKeyboardChatScreen(), onHover: (hover) { onHover!(hover, event); }, @@ -542,8 +542,9 @@ class Message extends StatelessWidget { GestureDetector( onLongPress: () => controller.selectMode ? onSelect!(event) : null, - onTap: () => - controller.selectMode ? onSelect!(event) : null, + onTap: () => controller.selectMode + ? onSelect!(event) + : controller.hideKeyboardChatScreen(), child: Center( child: Container( margin: EdgeInsetsDirectional.only( diff --git a/lib/pages/chat/reactions_picker.dart b/lib/pages/chat/reactions_picker.dart index 2add94b13d..b6e0387743 100644 --- a/lib/pages/chat/reactions_picker.dart +++ b/lib/pages/chat/reactions_picker.dart @@ -15,7 +15,7 @@ class ReactionsPicker extends StatelessWidget { @override Widget build(BuildContext context) { - if (controller.emojiPickerNotifier.value) return Container(); + if (controller.showEmojiPickerNotifier.value) return Container(); final display = controller.editEvent == null && controller.replyEvent == null && controller.room!.canSendDefaultMessages && diff --git a/lib/utils/extension/value_notifier_extension.dart b/lib/utils/extension/value_notifier_extension.dart new file mode 100644 index 0000000000..d4f0887535 --- /dev/null +++ b/lib/utils/extension/value_notifier_extension.dart @@ -0,0 +1,5 @@ +import 'package:flutter/material.dart'; + +extension ValueNotifierExtension on ValueNotifier { + void toggle() => value = !value; +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 438a5e411c..73d3fa7658 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -30,6 +31,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) emoji_picker_flutter_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "EmojiPickerFlutterPlugin"); emoji_picker_flutter_plugin_register_with_registrar(emoji_picker_flutter_registrar); + g_autoptr(FlPluginRegistrar) file_saver_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin"); + file_saver_plugin_register_with_registrar(file_saver_registrar); g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index f799b57b42..04f5ccd17b 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_lifecycle dynamic_color emoji_picker_flutter + file_saver file_selector_linux flutter_secure_storage_linux flutter_webrtc diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index eb2133a82e..a6dd3328a5 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -12,6 +12,7 @@ import desktop_lifecycle import device_info_plus import dynamic_color import emoji_picker_flutter +import file_saver import file_selector_macos import firebase_core import flutter_app_badger @@ -43,6 +44,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) EmojiPickerFlutterPlugin.register(with: registry.registrar(forPlugin: "EmojiPickerFlutterPlugin")) + FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FlutterAppBadgerPlugin.register(with: registry.registrar(forPlugin: "FlutterAppBadgerPlugin")) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 1dff97c2c2..6b3e2af1a3 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); EmojiPickerFlutterPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("EmojiPickerFlutterPluginCApi")); + FileSaverPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSaverPlugin")); FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); FlutterWebRTCPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b39d7c7aa8..c47f150287 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_lifecycle dynamic_color emoji_picker_flutter + file_saver file_selector_windows flutter_webrtc permission_handler_windows