diff --git a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart index a1f3a78f75..a32abd33aa 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart @@ -14,6 +14,7 @@ import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart'; import 'package:appflowy/plugins/database/widgets/card/card_bloc.dart'; import 'package:appflowy/plugins/database/widgets/cell/card_cell_style_maps/desktop_board_card_cell_style.dart'; import 'package:appflowy/plugins/database/widgets/row/row_detail.dart'; +import 'package:appflowy/plugins/shared/callback_shortcuts.dart'; import 'package:appflowy/shared/conditional_listenable_builder.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; @@ -28,6 +29,7 @@ import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; import 'package:flutter/material.dart' hide Card; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:provider/provider.dart'; import '../../widgets/card/card.dart'; import '../../widgets/cell/card_cell_builder.dart'; @@ -320,64 +322,67 @@ class _BoardContentState extends State<_BoardContent> { }, ), ], - child: FocusScope( - autofocus: true, - child: BoardShortcutContainer( - focusScope: widget.focusScope, - child: Padding( - padding: const EdgeInsets.only(top: 8.0), - child: AppFlowyBoard( - boardScrollController: scrollManager, - scrollController: scrollController, - controller: context.read().boardController, - groupConstraints: const BoxConstraints.tightFor(width: 256), - config: config, - leading: HiddenGroupsColumn(margin: config.groupHeaderPadding), - trailing: context - .read() - .groupingFieldType - ?.canCreateNewGroup ?? - false - ? BoardTrailing(scrollController: scrollController) - : const HSpace(40), - headerBuilder: (_, groupData) => BlocProvider.value( - value: context.read(), - child: BoardColumnHeader( - groupData: groupData, - margin: config.groupHeaderPadding, + child: Provider( + create: (context) => AFCallbackShortcutsProvider(), + child: FocusScope( + autofocus: true, + child: BoardShortcutContainer( + focusScope: widget.focusScope, + child: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: AppFlowyBoard( + boardScrollController: scrollManager, + scrollController: scrollController, + controller: context.read().boardController, + groupConstraints: const BoxConstraints.tightFor(width: 256), + config: config, + leading: HiddenGroupsColumn(margin: config.groupHeaderPadding), + trailing: context + .read() + .groupingFieldType + ?.canCreateNewGroup ?? + false + ? BoardTrailing(scrollController: scrollController) + : const HSpace(40), + headerBuilder: (_, groupData) => BlocProvider.value( + value: context.read(), + child: BoardColumnHeader( + groupData: groupData, + margin: config.groupHeaderPadding, + ), ), - ), - footerBuilder: (_, groupData) => MultiBlocProvider( - providers: [ - BlocProvider.value( - value: context.read(), + footerBuilder: (_, groupData) => MultiBlocProvider( + providers: [ + BlocProvider.value( + value: context.read(), + ), + BlocProvider.value( + value: context.read(), + ), + ], + child: BoardColumnFooter( + columnData: groupData, + boardConfig: config, + scrollManager: scrollManager, ), - BlocProvider.value( - value: context.read(), - ), - ], - child: BoardColumnFooter( - columnData: groupData, - boardConfig: config, - scrollManager: scrollManager, ), - ), - cardBuilder: (_, column, columnItem) => MultiBlocProvider( - key: ValueKey("board_card_${column.id}_${columnItem.id}"), - providers: [ - BlocProvider.value( - value: context.read(), + cardBuilder: (_, column, columnItem) => MultiBlocProvider( + key: ValueKey("board_card_${column.id}_${columnItem.id}"), + providers: [ + BlocProvider.value( + value: context.read(), + ), + BlocProvider.value( + value: context.read(), + ), + ], + child: _BoardCard( + afGroupData: column, + groupItem: columnItem as GroupItem, + boardConfig: config, + notifier: widget.focusScope, + cellBuilder: cellBuilder, ), - BlocProvider.value( - value: context.read(), - ), - ], - child: _BoardCard( - afGroupData: column, - groupItem: columnItem as GroupItem, - boardConfig: config, - notifier: widget.focusScope, - cellBuilder: cellBuilder, ), ), ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_column_header.dart b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_column_header.dart index ff06319f73..53cd3c1e4f 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_column_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_column_header.dart @@ -3,6 +3,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/board/application/board_bloc.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/header/field_type_extension.dart'; +import 'package:appflowy/plugins/shared/callback_shortcuts.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_board/appflowy_board.dart'; @@ -30,6 +31,8 @@ class BoardColumnHeader extends StatefulWidget { class _BoardColumnHeaderState extends State { final FocusNode _focusNode = FocusNode(); + final FocusNode _keyboardListenerFocusNode = FocusNode(); + late final AFCallbackShortcutsProvider _shortcutsProvider; late final TextEditingController _controller = TextEditingController.fromValue( @@ -44,16 +47,23 @@ class _BoardColumnHeaderState extends State { @override void initState() { super.initState(); + _shortcutsProvider = context.read(); _focusNode.addListener(() { if (!_focusNode.hasFocus) { _saveEdit(); } }); + _keyboardListenerFocusNode.addListener(() { + _shortcutsProvider.isShortcutsEnabled.value = + !_keyboardListenerFocusNode.hasFocus; + }); } @override void dispose() { + _shortcutsProvider.isShortcutsEnabled.value = true; _focusNode.dispose(); + _keyboardListenerFocusNode.dispose(); _controller.dispose(); super.dispose(); } @@ -149,7 +159,7 @@ class _BoardColumnHeaderState extends State { Widget _buildTextField(BuildContext context) { return Expanded( child: KeyboardListener( - focusNode: FocusNode(), + focusNode: _keyboardListenerFocusNode, onKeyEvent: (event) { if ([LogicalKeyboardKey.enter, LogicalKeyboardKey.escape] .contains(event.logicalKey)) { diff --git a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_shortcut_container.dart b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_shortcut_container.dart index 022c25ff18..1035364702 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_shortcut_container.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_shortcut_container.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:appflowy/plugins/database/board/application/board_actions_bloc.dart'; import 'package:appflowy/plugins/database/board/application/board_bloc.dart'; +import 'package:appflowy/plugins/shared/callback_shortcuts.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -20,7 +21,9 @@ class BoardShortcutContainer extends StatelessWidget { @override Widget build(BuildContext context) { - return CallbackShortcuts( + return AFCallbackShortcuts( + canAcceptEvent: (_, __) => + context.read().isShortcutsEnabled.value, bindings: { const SingleActivator(LogicalKeyboardKey.arrowUp): focusScope.focusPrevious, @@ -93,9 +96,9 @@ class BoardShortcutContainer extends StatelessWidget { if (focusScope.value.length != 1) { return; } - context - .read() - .openCardWithRowId(focusScope.value.first.rowId); + context + .read() + .openCardWithRowId(focusScope.value.first.rowId); } void _shitEnterHandler(BuildContext context) { diff --git a/frontend/appflowy_flutter/lib/plugins/shared/callback_shortcuts.dart b/frontend/appflowy_flutter/lib/plugins/shared/callback_shortcuts.dart new file mode 100644 index 0000000000..26e188511e --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/shared/callback_shortcuts.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class AFCallbackShortcutsProvider { + final ValueNotifier isShortcutsEnabled = ValueNotifier(true); +} + +class AFCallbackShortcuts extends StatelessWidget { + const AFCallbackShortcuts({ + super.key, + required this.bindings, + required this.canAcceptEvent, + required this.child, + }); + + final Map bindings; + final bool Function(FocusNode node, KeyEvent event) canAcceptEvent; + final Widget child; + + bool _applyKeyEventBinding(ShortcutActivator activator, KeyEvent event) { + if (activator.accepts(event, HardwareKeyboard.instance)) { + bindings[activator]!.call(); + return true; + } + return false; + } + + @override + Widget build(BuildContext context) { + return Focus( + canRequestFocus: false, + skipTraversal: true, + onKeyEvent: (FocusNode node, KeyEvent event) { + if (!canAcceptEvent(node, event)) { + return KeyEventResult.ignored; + } + KeyEventResult result = KeyEventResult.ignored; + for (final ShortcutActivator activator in bindings.keys) { + result = _applyKeyEventBinding(activator, event) + ? KeyEventResult.handled + : result; + } + return result; + }, + child: child, + ); + } +}