Skip to content

Commit

Permalink
TW-594: Handle show/hide keyboard while scrolling
Browse files Browse the repository at this point in the history
  • Loading branch information
nqhhdev authored and hoangdat committed Sep 14, 2023
1 parent 09bd086 commit acbfad3
Show file tree
Hide file tree
Showing 12 changed files with 84 additions and 47 deletions.
84 changes: 56 additions & 28 deletions lib/pages/chat/chat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -116,23 +118,25 @@ class ChatController extends State<Chat>
);

final AutoScrollController scrollController = AutoScrollController();
final AutoScrollController forwardListController = AutoScrollController();
final KeyboardVisibilityController keyboardVisibilityController =
KeyboardVisibilityController();
final ValueNotifier<String?> focusHover = ValueNotifier(null);
final ValueNotifier<bool> openingPopupMenu = ValueNotifier(false);

final ValueNotifier<bool> draggingNotifier = ValueNotifier(false);
final ValueNotifier<bool> showScrollDownButtonNotifier = ValueNotifier(false);
final ValueNotifier<bool> 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(
Expand Down Expand Up @@ -181,8 +185,6 @@ class ChatController extends State<Chat>

Event? editEvent;

bool showScrollDownButton = false;

bool get selectMode => selectedEvents.isNotEmpty;

final int _loadHistoryCount = 100;
Expand All @@ -195,8 +197,6 @@ class ChatController extends State<Chat>
timeline!.events.isEmpty ||
timeline!.events.last.type != EventTypes.RoomCreate;

bool showEmojiPicker = false;

void recreateChat() async {
final room = this.room;
final userId = room?.directChatMatrixID;
Expand Down Expand Up @@ -259,6 +259,7 @@ class ChatController extends State<Chat>
if (!mounted) {
return;
}
hideKeyboardChatScreen();
setReadMarker();
if (!scrollController.hasClients) return;
if (scrollController.position.pixels ==
Expand All @@ -268,11 +269,12 @@ class ChatController extends State<Chat>
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;
}
}

Expand All @@ -285,8 +287,15 @@ class ChatController extends State<Chat>
}
}

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();
Expand Down Expand Up @@ -685,19 +694,19 @@ class ChatController extends State<Chat>
}

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;
}
}

Expand Down Expand Up @@ -728,8 +737,8 @@ class ChatController extends State<Chat>

void copyEventsAction() {
Clipboard.setData(ClipboardData(text: _getSelectedEventString()));
showEmojiPickerNotifier.value = false;
setState(() {
showEmojiPicker = false;
selectedEvents.clear();
});
}
Expand Down Expand Up @@ -777,8 +786,8 @@ class ChatController extends State<Chat>
),
);
if (result.error != null) return;
showEmojiPickerNotifier.value = false;
setState(() {
showEmojiPicker = false;
selectedEvents.clear();
});
ScaffoldMessenger.of(context).showSnackBar(
Expand Down Expand Up @@ -820,8 +829,8 @@ class ChatController extends State<Chat>
},
);
}
showEmojiPickerNotifier.value = false;
setState(() {
showEmojiPicker = false;
selectedEvents.clear();
});
}
Expand Down Expand Up @@ -970,7 +979,7 @@ class ChatController extends State<Chat>
}

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) {
Expand Down Expand Up @@ -1012,7 +1021,7 @@ class ChatController extends State<Chat>
void emojiPickerBackspace() {
switch (emojiPickerType) {
case EmojiPickerType.reaction:
setState(() => showEmojiPicker = false);
showEmojiPickerNotifier.value = false;
break;
case EmojiPickerType.keyboard:
sendController
Expand All @@ -1027,7 +1036,7 @@ class ChatController extends State<Chat>
void pickEmojiReactionAction(Iterable<Event> allReactionEvents) async {
_allReactionEvents = allReactionEvents;
emojiPickerType = EmojiPickerType.reaction;
setState(() => showEmojiPicker = true);
showEmojiPickerNotifier.value = true;
}

void sendEmojiAction(String? emoji) async {
Expand All @@ -1041,10 +1050,13 @@ class ChatController extends State<Chat>
}
}

void clearSelectedEvents() => setState(() {
selectedEvents.clear();
showEmojiPicker = false;
});
void clearSelectedEvents() {
showEmojiPickerNotifier.value = false;

setState(() {
selectedEvents.clear();
});
}

void clearSingleSelectedEvent() {
if (selectedEvents.length <= 1) {
Expand Down Expand Up @@ -1449,6 +1461,22 @@ class ChatController extends State<Chat>
);
}

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);
Expand Down
2 changes: 1 addition & 1 deletion lib/pages/chat/chat_emoji_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
18 changes: 5 additions & 13 deletions lib/pages/chat/chat_input_row.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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,
);
}

Expand Down
2 changes: 1 addition & 1 deletion lib/pages/chat/chat_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ class ChatView extends StatelessWidget {
),
),
floatingActionButton: ValueListenableBuilder(
valueListenable: controller.scrollDownButtonNotifier,
valueListenable: controller.showScrollDownButtonNotifier,
builder: (context, showScrollDownButton, _) {
if (showScrollDownButton &&
controller.selectedEvents.isEmpty) {
Expand Down
7 changes: 4 additions & 3 deletions lib/pages/chat/events/message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
},
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion lib/pages/chat/reactions_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down
5 changes: 5 additions & 0 deletions lib/utils/extension/value_notifier_extension.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import 'package:flutter/material.dart';

extension ValueNotifierExtension<bool> on ValueNotifier {
void toggle() => value = !value;
}
4 changes: 4 additions & 0 deletions linux/flutter/generated_plugin_registrant.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <desktop_lifecycle/desktop_lifecycle_plugin.h>
#include <dynamic_color/dynamic_color_plugin.h>
#include <emoji_picker_flutter/emoji_picker_flutter_plugin.h>
#include <file_saver/file_saver_plugin.h>
#include <file_selector_linux/file_selector_plugin.h>
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions linux/flutter/generated_plugins.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions macos/Flutter/GeneratedPluginRegistrant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"))
Expand Down
3 changes: 3 additions & 0 deletions windows/flutter/generated_plugin_registrant.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <desktop_lifecycle/desktop_lifecycle_plugin.h>
#include <dynamic_color/dynamic_color_plugin_c_api.h>
#include <emoji_picker_flutter/emoji_picker_flutter_plugin_c_api.h>
#include <file_saver/file_saver_plugin.h>
#include <file_selector_windows/file_selector_windows.h>
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
Expand All @@ -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(
Expand Down
1 change: 1 addition & 0 deletions windows/flutter/generated_plugins.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit acbfad3

Please sign in to comment.