TF-3911 Support keyboard shortcut actions ESC to unfocus search input on web
Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:tmail_ui_user/features/base/extensions/logical_key_set_helper.dart';
|
||||
import 'package:tmail_ui_user/features/base/shortcut/mail/mail_list_action_shortcut_type.dart';
|
||||
import 'package:tmail_ui_user/features/base/shortcut/mail/mail_view_action_shortcut_type.dart';
|
||||
import 'package:tmail_ui_user/features/base/shortcut/search/search_action_shortcut_type.dart';
|
||||
|
||||
class AppShortcutManager {
|
||||
static MailViewActionShortcutType? getMailViewActionFromEvent(KeyEvent event) {
|
||||
@@ -42,4 +43,14 @@ class AppShortcutManager {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static SearchActionShortcutType? getSearchActionFromEvent(KeyEvent event) {
|
||||
final keysPressed = HardwareKeyboard.instance.logicalKeysPressed;
|
||||
log('AppShortcutManager::getSearchActionFromEvent: Keys pressed: $keysPressed');
|
||||
if (keysPressed.isOnly(LogicalKeyboardKey.escape)) {
|
||||
return SearchActionShortcutType.unFocus;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
enum SearchActionShortcutType {
|
||||
unFocus;
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/quick_sear
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/get_all_recent_search_latest_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/quick_search_email_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/save_recent_search_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_keyboard_shortcut_actions_extension.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_sort_order_type.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/quick_search_filter.dart';
|
||||
@@ -52,6 +53,7 @@ class SearchController extends BaseController with DateRangePickerMixin {
|
||||
SearchQuery? get searchQuery => searchEmailFilter.value.text;
|
||||
|
||||
FocusNode searchFocus = FocusNode();
|
||||
FocusNode? keyboardFocusNode;
|
||||
String currentSearchText = '';
|
||||
|
||||
SearchController(
|
||||
@@ -64,11 +66,17 @@ class SearchController extends BaseController with DateRangePickerMixin {
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
searchFocus.addListener(_onSearchFocusChanged);
|
||||
onKeyboardShortcutInit();
|
||||
}
|
||||
|
||||
void _onSearchFocusChanged() {
|
||||
log('SearchController::_onSearchFocusChanged: ${searchFocus.hasFocus}');
|
||||
isSearchInputFocused.value = searchFocus.hasFocus;
|
||||
if (searchFocus.hasFocus) {
|
||||
refocusKeyboardShortcutFocus();
|
||||
} else {
|
||||
clearKeyboardShortcutFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void openAdvanceSearch() {
|
||||
@@ -336,6 +344,7 @@ class SearchController extends BaseController with DateRangePickerMixin {
|
||||
searchInputController.dispose();
|
||||
searchFocus.removeListener(_onSearchFocusChanged);
|
||||
searchFocus.dispose();
|
||||
onKeyboardShortcutDispose();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:flutter/material.dart' hide SearchController;
|
||||
import 'package:tmail_ui_user/features/base/shortcut/app_shortcut_manager.dart';
|
||||
import 'package:tmail_ui_user/features/base/shortcut/search/search_action_shortcut_type.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart';
|
||||
|
||||
extension HandleKeyboardShortcutActionsExtension on SearchController {
|
||||
void onKeyboardShortcutInit() {
|
||||
if (PlatformInfo.isWeb) {
|
||||
keyboardFocusNode = FocusNode();
|
||||
}
|
||||
}
|
||||
|
||||
void onKeyboardShortcutDispose() {
|
||||
if (PlatformInfo.isWeb) {
|
||||
keyboardFocusNode?.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
void clearKeyboardShortcutFocus() {
|
||||
if (keyboardFocusNode?.hasFocus == true) {
|
||||
keyboardFocusNode?.unfocus();
|
||||
}
|
||||
}
|
||||
|
||||
void refocusKeyboardShortcutFocus() {
|
||||
if (keyboardFocusNode?.hasFocus == false) {
|
||||
keyboardFocusNode?.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void onKeyDownEventAction(KeyEvent event) {
|
||||
final shortcutType = AppShortcutManager.getSearchActionFromEvent(event);
|
||||
log('$runtimeType::onKeyDownEventAction:🔥Shortcut triggered: $shortcutType');
|
||||
if (shortcutType == null) return;
|
||||
handleSearchShortcutAction(shortcutType);
|
||||
}
|
||||
|
||||
void handleSearchShortcutAction(SearchActionShortcutType shortcutType) {
|
||||
log('$runtimeType::handleSearchShortcutAction: 🔥 Shortcut triggered: $shortcutType');
|
||||
if (searchFocus.hasFocus) {
|
||||
searchFocus.unfocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,12 @@ import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:pointer_interceptor/pointer_interceptor.dart';
|
||||
import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart';
|
||||
import 'package:tmail_ui_user/features/base/widget/keyboard/keyboard_handler_wrapper.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/recent_search.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart' as search;
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_keyboard_shortcut_actions_extension.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/quick_search_filter.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/advanced_search_filter_overlay.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/icon_open_advanced_search_widget.dart';
|
||||
@@ -52,28 +54,7 @@ class SearchInputFormWidget extends StatelessWidget with AppLoaderMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(() {
|
||||
return PortalTarget(
|
||||
visible: _searchController.isAdvancedSearchViewOpen.isTrue,
|
||||
portalFollower: PointerInterceptor(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: _searchController.closeAdvanceSearch
|
||||
),
|
||||
),
|
||||
child: PortalTarget(
|
||||
visible: _searchController.isAdvancedSearchViewOpen.isTrue,
|
||||
anchor: const Aligned(
|
||||
follower: Alignment.topRight,
|
||||
target: Alignment.bottomRight,
|
||||
widthFactor: 1,
|
||||
backup: Aligned(
|
||||
follower: Alignment.topRight,
|
||||
target: Alignment.bottomRight,
|
||||
widthFactor: 1,
|
||||
),
|
||||
),
|
||||
portalFollower: const AdvancedSearchFilterOverlay(),
|
||||
child: QuickSearchInputForm<PresentationEmail, EmailAddress, RecentSearch>(
|
||||
Widget searchInputForm = QuickSearchInputForm<PresentationEmail, EmailAddress, RecentSearch>(
|
||||
maxHeight: 52,
|
||||
suggestionsBoxVerticalOffset: 0.0,
|
||||
minInputLengthAutocomplete: _dashBoardController.minInputLengthAutocomplete,
|
||||
@@ -153,7 +134,38 @@ class SearchInputFormWidget extends StatelessWidget with AppLoaderMixin {
|
||||
contactItemBuilder: (context, emailAddress) => ContactQuickSearchItem(emailAddress: emailAddress),
|
||||
contactSuggestionsCallback: _dashBoardController.getContactSuggestion,
|
||||
onContactSuggestionSelected: _invokeSelectContactSuggestion,
|
||||
)
|
||||
);
|
||||
|
||||
if (_searchController.keyboardFocusNode != null) {
|
||||
searchInputForm = KeyboardHandlerWrapper(
|
||||
focusNode: _searchController.keyboardFocusNode!,
|
||||
onKeyDownEventAction: _searchController.onKeyDownEventAction,
|
||||
child: searchInputForm,
|
||||
);
|
||||
}
|
||||
|
||||
return PortalTarget(
|
||||
visible: _searchController.isAdvancedSearchViewOpen.isTrue,
|
||||
portalFollower: PointerInterceptor(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: _searchController.closeAdvanceSearch
|
||||
),
|
||||
),
|
||||
child: PortalTarget(
|
||||
visible: _searchController.isAdvancedSearchViewOpen.isTrue,
|
||||
anchor: const Aligned(
|
||||
follower: Alignment.topRight,
|
||||
target: Alignment.bottomRight,
|
||||
widthFactor: 1,
|
||||
backup: Aligned(
|
||||
follower: Alignment.topRight,
|
||||
target: Alignment.bottomRight,
|
||||
widthFactor: 1,
|
||||
),
|
||||
),
|
||||
portalFollower: const AdvancedSearchFilterOverlay(),
|
||||
child: searchInputForm,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user