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/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_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/mail/mail_view_action_shortcut_type.dart';
|
||||||
|
import 'package:tmail_ui_user/features/base/shortcut/search/search_action_shortcut_type.dart';
|
||||||
|
|
||||||
class AppShortcutManager {
|
class AppShortcutManager {
|
||||||
static MailViewActionShortcutType? getMailViewActionFromEvent(KeyEvent event) {
|
static MailViewActionShortcutType? getMailViewActionFromEvent(KeyEvent event) {
|
||||||
@@ -42,4 +43,14 @@ class AppShortcutManager {
|
|||||||
return null;
|
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/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/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/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_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/email_sort_order_type.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/model/search/quick_search_filter.dart';
|
||||||
@@ -52,6 +53,7 @@ class SearchController extends BaseController with DateRangePickerMixin {
|
|||||||
SearchQuery? get searchQuery => searchEmailFilter.value.text;
|
SearchQuery? get searchQuery => searchEmailFilter.value.text;
|
||||||
|
|
||||||
FocusNode searchFocus = FocusNode();
|
FocusNode searchFocus = FocusNode();
|
||||||
|
FocusNode? keyboardFocusNode;
|
||||||
String currentSearchText = '';
|
String currentSearchText = '';
|
||||||
|
|
||||||
SearchController(
|
SearchController(
|
||||||
@@ -64,11 +66,17 @@ class SearchController extends BaseController with DateRangePickerMixin {
|
|||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
searchFocus.addListener(_onSearchFocusChanged);
|
searchFocus.addListener(_onSearchFocusChanged);
|
||||||
|
onKeyboardShortcutInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSearchFocusChanged() {
|
void _onSearchFocusChanged() {
|
||||||
log('SearchController::_onSearchFocusChanged: ${searchFocus.hasFocus}');
|
log('SearchController::_onSearchFocusChanged: ${searchFocus.hasFocus}');
|
||||||
isSearchInputFocused.value = searchFocus.hasFocus;
|
isSearchInputFocused.value = searchFocus.hasFocus;
|
||||||
|
if (searchFocus.hasFocus) {
|
||||||
|
refocusKeyboardShortcutFocus();
|
||||||
|
} else {
|
||||||
|
clearKeyboardShortcutFocus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void openAdvanceSearch() {
|
void openAdvanceSearch() {
|
||||||
@@ -336,6 +344,7 @@ class SearchController extends BaseController with DateRangePickerMixin {
|
|||||||
searchInputController.dispose();
|
searchInputController.dispose();
|
||||||
searchFocus.removeListener(_onSearchFocusChanged);
|
searchFocus.removeListener(_onSearchFocusChanged);
|
||||||
searchFocus.dispose();
|
searchFocus.dispose();
|
||||||
|
onKeyboardShortcutDispose();
|
||||||
super.onClose();
|
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:model/email/presentation_email.dart';
|
||||||
import 'package:pointer_interceptor/pointer_interceptor.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/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/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/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/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/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/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/advanced_search_filter_overlay.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/advanced_search/icon_open_advanced_search_widget.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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Obx(() {
|
return Obx(() {
|
||||||
return PortalTarget(
|
Widget searchInputForm = QuickSearchInputForm<PresentationEmail, EmailAddress, RecentSearch>(
|
||||||
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>(
|
|
||||||
maxHeight: 52,
|
maxHeight: 52,
|
||||||
suggestionsBoxVerticalOffset: 0.0,
|
suggestionsBoxVerticalOffset: 0.0,
|
||||||
minInputLengthAutocomplete: _dashBoardController.minInputLengthAutocomplete,
|
minInputLengthAutocomplete: _dashBoardController.minInputLengthAutocomplete,
|
||||||
@@ -153,7 +134,38 @@ class SearchInputFormWidget extends StatelessWidget with AppLoaderMixin {
|
|||||||
contactItemBuilder: (context, emailAddress) => ContactQuickSearchItem(emailAddress: emailAddress),
|
contactItemBuilder: (context, emailAddress) => ContactQuickSearchItem(emailAddress: emailAddress),
|
||||||
contactSuggestionsCallback: _dashBoardController.getContactSuggestion,
|
contactSuggestionsCallback: _dashBoardController.getContactSuggestion,
|
||||||
onContactSuggestionSelected: _invokeSelectContactSuggestion,
|
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