fix: board shortcuts conflict with textfield (#5487)

* fix: board shortcuts conflict with text field

* fix: typo

* fix: favorite icon issue in dark mode
This commit is contained in:
Lucas.Xu 2024-06-10 09:04:01 +08:00 committed by GitHub
parent bb3e9d5bd8
commit 38d2bd7ee8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 165 additions and 156 deletions

View File

@ -14,7 +14,6 @@ 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/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/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/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/shared/conditional_listenable_builder.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
@ -29,7 +28,6 @@ import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart' hide Card; import 'package:flutter/material.dart' hide Card;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
import '../../widgets/card/card.dart'; import '../../widgets/card/card.dart';
import '../../widgets/cell/card_cell_builder.dart'; import '../../widgets/cell/card_cell_builder.dart';
@ -322,67 +320,64 @@ class _BoardContentState extends State<_BoardContent> {
}, },
), ),
], ],
child: Provider( child: FocusScope(
create: (context) => AFCallbackShortcutsProvider(), autofocus: true,
child: FocusScope( child: BoardShortcutContainer(
autofocus: true, focusScope: widget.focusScope,
child: BoardShortcutContainer( child: Padding(
focusScope: widget.focusScope, padding: const EdgeInsets.only(top: 8.0),
child: Padding( child: AppFlowyBoard(
padding: const EdgeInsets.only(top: 8.0), boardScrollController: scrollManager,
child: AppFlowyBoard( scrollController: scrollController,
boardScrollController: scrollManager, controller: context.read<BoardBloc>().boardController,
scrollController: scrollController, groupConstraints: const BoxConstraints.tightFor(width: 256),
controller: context.read<BoardBloc>().boardController, config: config,
groupConstraints: const BoxConstraints.tightFor(width: 256), leading: HiddenGroupsColumn(margin: config.groupHeaderPadding),
config: config, trailing: context
leading: HiddenGroupsColumn(margin: config.groupHeaderPadding), .read<BoardBloc>()
trailing: context .groupingFieldType
.read<BoardBloc>() ?.canCreateNewGroup ??
.groupingFieldType false
?.canCreateNewGroup ?? ? BoardTrailing(scrollController: scrollController)
false : const HSpace(40),
? BoardTrailing(scrollController: scrollController) headerBuilder: (_, groupData) => BlocProvider<BoardBloc>.value(
: const HSpace(40), value: context.read<BoardBloc>(),
headerBuilder: (_, groupData) => BlocProvider<BoardBloc>.value( child: BoardColumnHeader(
value: context.read<BoardBloc>(), groupData: groupData,
child: BoardColumnHeader( margin: config.groupHeaderPadding,
groupData: groupData,
margin: config.groupHeaderPadding,
),
), ),
footerBuilder: (_, groupData) => MultiBlocProvider( ),
providers: [ footerBuilder: (_, groupData) => MultiBlocProvider(
BlocProvider.value( providers: [
value: context.read<BoardBloc>(), BlocProvider.value(
), value: context.read<BoardBloc>(),
BlocProvider.value(
value: context.read<BoardActionsCubit>(),
),
],
child: BoardColumnFooter(
columnData: groupData,
boardConfig: config,
scrollManager: scrollManager,
), ),
BlocProvider.value(
value: context.read<BoardActionsCubit>(),
),
],
child: BoardColumnFooter(
columnData: groupData,
boardConfig: config,
scrollManager: scrollManager,
), ),
cardBuilder: (_, column, columnItem) => MultiBlocProvider( ),
key: ValueKey("board_card_${column.id}_${columnItem.id}"), cardBuilder: (_, column, columnItem) => MultiBlocProvider(
providers: [ key: ValueKey("board_card_${column.id}_${columnItem.id}"),
BlocProvider<BoardBloc>.value( providers: [
value: context.read<BoardBloc>(), BlocProvider<BoardBloc>.value(
), value: context.read<BoardBloc>(),
BlocProvider.value(
value: context.read<BoardActionsCubit>(),
),
],
child: _BoardCard(
afGroupData: column,
groupItem: columnItem as GroupItem,
boardConfig: config,
notifier: widget.focusScope,
cellBuilder: cellBuilder,
), ),
BlocProvider.value(
value: context.read<BoardActionsCubit>(),
),
],
child: _BoardCard(
afGroupData: column,
groupItem: columnItem as GroupItem,
boardConfig: config,
notifier: widget.focusScope,
cellBuilder: cellBuilder,
), ),
), ),
), ),

View File

@ -3,7 +3,6 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/board/application/board_bloc.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/layout/sizes.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/field_type_extension.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/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_board/appflowy_board.dart'; import 'package:appflowy_board/appflowy_board.dart';
@ -32,7 +31,6 @@ class BoardColumnHeader extends StatefulWidget {
class _BoardColumnHeaderState extends State<BoardColumnHeader> { class _BoardColumnHeaderState extends State<BoardColumnHeader> {
final FocusNode _focusNode = FocusNode(); final FocusNode _focusNode = FocusNode();
final FocusNode _keyboardListenerFocusNode = FocusNode(); final FocusNode _keyboardListenerFocusNode = FocusNode();
late final AFCallbackShortcutsProvider _shortcutsProvider;
late final TextEditingController _controller = late final TextEditingController _controller =
TextEditingController.fromValue( TextEditingController.fromValue(
@ -47,21 +45,15 @@ class _BoardColumnHeaderState extends State<BoardColumnHeader> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_shortcutsProvider = context.read<AFCallbackShortcutsProvider>();
_focusNode.addListener(() { _focusNode.addListener(() {
if (!_focusNode.hasFocus) { if (!_focusNode.hasFocus) {
_saveEdit(); _saveEdit();
} }
}); });
_keyboardListenerFocusNode.addListener(() {
_shortcutsProvider.isShortcutsEnabled.value =
!_keyboardListenerFocusNode.hasFocus;
});
} }
@override @override
void dispose() { void dispose() {
_shortcutsProvider.isShortcutsEnabled.value = true;
_focusNode.dispose(); _focusNode.dispose();
_keyboardListenerFocusNode.dispose(); _keyboardListenerFocusNode.dispose();
_controller.dispose(); _controller.dispose();

View File

@ -39,14 +39,14 @@ class BoardFocusScope extends ChangeNotifier
notifyListeners(); notifyListeners();
} }
void focusNext() { bool focusNext() {
_deepCopy(); _deepCopy();
// if no card is focused, focus on the first card in the board // if no card is focused, focus on the first card in the board
if (_focusedCards.isEmpty) { if (_focusedCards.isEmpty) {
_focusFirstCard(); _focusFirstCard();
notifyListeners(); notifyListeners();
return; return true;
} }
final lastFocusedCard = _focusedCards.last; final lastFocusedCard = _focusedCards.last;
@ -58,7 +58,7 @@ class BoardFocusScope extends ChangeNotifier
if (iterable == null || iterable.isEmpty) { if (iterable == null || iterable.isEmpty) {
_focusFirstCard(); _focusFirstCard();
notifyListeners(); notifyListeners();
return; return true;
} }
if (iterable.length == 1) { if (iterable.length == 1) {
@ -90,16 +90,18 @@ class BoardFocusScope extends ChangeNotifier
} }
notifyListeners(); notifyListeners();
return true;
} }
void focusPrevious() { bool focusPrevious() {
_deepCopy(); _deepCopy();
// if no card is focused, focus on the last card in the board // if no card is focused, focus on the last card in the board
if (_focusedCards.isEmpty) { if (_focusedCards.isEmpty) {
_focusLastCard(); _focusLastCard();
notifyListeners(); notifyListeners();
return; return true;
} }
final lastFocusedCard = _focusedCards.last; final lastFocusedCard = _focusedCards.last;
@ -111,7 +113,7 @@ class BoardFocusScope extends ChangeNotifier
if (iterable == null || iterable.isEmpty) { if (iterable == null || iterable.isEmpty) {
_focusLastCard(); _focusLastCard();
notifyListeners(); notifyListeners();
return; return true;
} }
if (iterable.length == 1) { if (iterable.length == 1) {
@ -143,16 +145,18 @@ class BoardFocusScope extends ChangeNotifier
} }
notifyListeners(); notifyListeners();
return true;
} }
void adjustRangeDown() { bool adjustRangeDown() {
_deepCopy(); _deepCopy();
// if no card is focused, focus on the first card in the board // if no card is focused, focus on the first card in the board
if (_focusedCards.isEmpty) { if (_focusedCards.isEmpty) {
_focusFirstCard(); _focusFirstCard();
notifyListeners(); notifyListeners();
return; return true;
} }
final firstFocusedCard = _focusedCards.first; final firstFocusedCard = _focusedCards.first;
@ -171,7 +175,7 @@ class BoardFocusScope extends ChangeNotifier
if (firstGroupIndex == -1 || lastGroupIndex == -1) { if (firstGroupIndex == -1 || lastGroupIndex == -1) {
_focusFirstCard(); _focusFirstCard();
notifyListeners(); notifyListeners();
return; return true;
} }
if (firstGroupIndex < lastGroupIndex) { if (firstGroupIndex < lastGroupIndex) {
@ -189,7 +193,7 @@ class BoardFocusScope extends ChangeNotifier
if (firstCardIndex == -1 || lastCardIndex == -1) { if (firstCardIndex == -1 || lastCardIndex == -1) {
_focusFirstCard(); _focusFirstCard();
notifyListeners(); notifyListeners();
return; return true;
} }
isExpand = firstCardIndex < lastCardIndex; isExpand = firstCardIndex < lastCardIndex;
@ -203,7 +207,7 @@ class BoardFocusScope extends ChangeNotifier
if (groupController == null) { if (groupController == null) {
_focusFirstCard(); _focusFirstCard();
notifyListeners(); notifyListeners();
return; return true;
} }
final iterable = groupController.items final iterable = groupController.items
@ -236,16 +240,17 @@ class BoardFocusScope extends ChangeNotifier
} }
notifyListeners(); notifyListeners();
return true;
} }
void adjustRangeUp() { bool adjustRangeUp() {
_deepCopy(); _deepCopy();
// if no card is focused, focus on the first card in the board // if no card is focused, focus on the first card in the board
if (_focusedCards.isEmpty) { if (_focusedCards.isEmpty) {
_focusLastCard(); _focusLastCard();
notifyListeners(); notifyListeners();
return; return true;
} }
final firstFocusedCard = _focusedCards.first; final firstFocusedCard = _focusedCards.first;
@ -264,7 +269,7 @@ class BoardFocusScope extends ChangeNotifier
if (firstGroupIndex == -1 || lastGroupIndex == -1) { if (firstGroupIndex == -1 || lastGroupIndex == -1) {
_focusLastCard(); _focusLastCard();
notifyListeners(); notifyListeners();
return; return true;
} }
if (firstGroupIndex < lastGroupIndex) { if (firstGroupIndex < lastGroupIndex) {
@ -282,7 +287,7 @@ class BoardFocusScope extends ChangeNotifier
if (firstCardIndex == -1 || lastCardIndex == -1) { if (firstCardIndex == -1 || lastCardIndex == -1) {
_focusLastCard(); _focusLastCard();
notifyListeners(); notifyListeners();
return; return true;
} }
isExpand = firstCardIndex > lastCardIndex; isExpand = firstCardIndex > lastCardIndex;
@ -296,7 +301,7 @@ class BoardFocusScope extends ChangeNotifier
if (groupController == null) { if (groupController == null) {
_focusLastCard(); _focusLastCard();
notifyListeners(); notifyListeners();
return; return true;
} }
final iterable = groupController.items.reversed final iterable = groupController.items.reversed
@ -329,12 +334,15 @@ class BoardFocusScope extends ChangeNotifier
} }
notifyListeners(); notifyListeners();
return true;
} }
void clear() { bool clear() {
_deepCopy(); _deepCopy();
_focusedCards.clear(); _focusedCards.clear();
notifyListeners(); notifyListeners();
return true;
} }
void _focusFirstCard() { void _focusFirstCard() {

View File

@ -22,56 +22,7 @@ class BoardShortcutContainer extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AFCallbackShortcuts( return AFCallbackShortcuts(
canAcceptEvent: (_, __) => bindings: _shortcutBindings(context),
context.read<AFCallbackShortcutsProvider>().isShortcutsEnabled.value,
bindings: {
const SingleActivator(LogicalKeyboardKey.arrowUp):
focusScope.focusPrevious,
const SingleActivator(LogicalKeyboardKey.arrowDown):
focusScope.focusNext,
const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true):
focusScope.adjustRangeUp,
const SingleActivator(LogicalKeyboardKey.arrowDown, shift: true):
focusScope.adjustRangeDown,
const SingleActivator(LogicalKeyboardKey.escape): focusScope.clear,
const SingleActivator(LogicalKeyboardKey.keyE): () {
if (focusScope.value.length != 1) {
return;
}
context
.read<BoardActionsCubit>()
.startEditingRow(focusScope.value.first);
},
const SingleActivator(LogicalKeyboardKey.keyN): () {
if (focusScope.value.length != 1) {
return;
}
context
.read<BoardActionsCubit>()
.startCreateBottomRow(focusScope.value.first.groupId);
focusScope.clear();
},
const SingleActivator(LogicalKeyboardKey.delete): () =>
_removeHandler(context),
const SingleActivator(LogicalKeyboardKey.backspace): () =>
_removeHandler(context),
SingleActivator(
LogicalKeyboardKey.arrowUp,
shift: true,
meta: Platform.isMacOS,
control: !Platform.isMacOS,
): () => _shiftCmdUpHandler(context),
const SingleActivator(LogicalKeyboardKey.enter): () =>
_enterHandler(context),
const SingleActivator(LogicalKeyboardKey.numpadEnter): () =>
_enterHandler(context),
const SingleActivator(LogicalKeyboardKey.enter, shift: true): () =>
_shitEnterHandler(context),
const SingleActivator(LogicalKeyboardKey.comma): () =>
_moveGroupToAdjacentGroup(context, true),
const SingleActivator(LogicalKeyboardKey.period): () =>
_moveGroupToAdjacentGroup(context, false),
},
child: FocusScope( child: FocusScope(
child: Focus( child: Focus(
child: Builder( child: Builder(
@ -92,16 +43,75 @@ class BoardShortcutContainer extends StatelessWidget {
); );
} }
void _enterHandler(BuildContext context) { Map<ShortcutActivator, AFBindingCallback> _shortcutBindings(
BuildContext context,
) {
return {
const SingleActivator(LogicalKeyboardKey.arrowUp):
focusScope.focusPrevious,
const SingleActivator(LogicalKeyboardKey.arrowDown): focusScope.focusNext,
const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true):
focusScope.adjustRangeUp,
const SingleActivator(LogicalKeyboardKey.arrowDown, shift: true):
focusScope.adjustRangeDown,
const SingleActivator(LogicalKeyboardKey.escape): focusScope.clear,
const SingleActivator(LogicalKeyboardKey.delete): () =>
_removeHandler(context),
const SingleActivator(LogicalKeyboardKey.backspace): () =>
_removeHandler(context),
SingleActivator(
LogicalKeyboardKey.arrowUp,
shift: true,
meta: Platform.isMacOS,
control: !Platform.isMacOS,
): () => _shiftCmdUpHandler(context),
const SingleActivator(LogicalKeyboardKey.enter): () =>
_enterHandler(context),
const SingleActivator(LogicalKeyboardKey.numpadEnter): () =>
_enterHandler(context),
const SingleActivator(LogicalKeyboardKey.enter, shift: true): () =>
_shiftEnterHandler(context),
const SingleActivator(LogicalKeyboardKey.comma): () =>
_moveGroupToAdjacentGroup(context, true),
const SingleActivator(LogicalKeyboardKey.period): () =>
_moveGroupToAdjacentGroup(context, false),
const SingleActivator(LogicalKeyboardKey.keyE): () =>
_keyEHandler(context),
const SingleActivator(LogicalKeyboardKey.keyN): () =>
_keyNHandler(context),
};
}
bool _keyEHandler(BuildContext context) {
if (focusScope.value.length != 1) { if (focusScope.value.length != 1) {
return; return false;
}
context.read<BoardActionsCubit>().startEditingRow(focusScope.value.first);
return true;
}
bool _keyNHandler(BuildContext context) {
if (focusScope.value.length != 1) {
return false;
}
context
.read<BoardActionsCubit>()
.startCreateBottomRow(focusScope.value.first.groupId);
focusScope.clear();
return true;
}
bool _enterHandler(BuildContext context) {
if (focusScope.value.length != 1) {
return false;
} }
context context
.read<BoardActionsCubit>() .read<BoardActionsCubit>()
.openCardWithRowId(focusScope.value.first.rowId); .openCardWithRowId(focusScope.value.first.rowId);
return true;
} }
void _shitEnterHandler(BuildContext context) { bool _shiftEnterHandler(BuildContext context) {
if (focusScope.value.isEmpty) { if (focusScope.value.isEmpty) {
context context
.read<BoardActionsCubit>() .read<BoardActionsCubit>()
@ -111,10 +121,13 @@ class BoardShortcutContainer extends StatelessWidget {
focusScope.value.first, focusScope.value.first,
CreateBoardCardRelativePosition.after, CreateBoardCardRelativePosition.after,
); );
} else {
return false;
} }
return true;
} }
void _shiftCmdUpHandler(BuildContext context) { bool _shiftCmdUpHandler(BuildContext context) {
if (focusScope.value.isEmpty) { if (focusScope.value.isEmpty) {
context context
.read<BoardActionsCubit>() .read<BoardActionsCubit>()
@ -124,19 +137,23 @@ class BoardShortcutContainer extends StatelessWidget {
focusScope.value.first, focusScope.value.first,
CreateBoardCardRelativePosition.before, CreateBoardCardRelativePosition.before,
); );
} else {
return false;
} }
return true;
} }
void _removeHandler(BuildContext context) { bool _removeHandler(BuildContext context) {
if (focusScope.value.isEmpty) { if (focusScope.value.length != 1) {
return; return false;
} }
context.read<BoardBloc>().add(BoardEvent.deleteCards(focusScope.value)); context.read<BoardBloc>().add(BoardEvent.deleteCards(focusScope.value));
return true;
} }
void _moveGroupToAdjacentGroup(BuildContext context, bool toPrevious) { bool _moveGroupToAdjacentGroup(BuildContext context, bool toPrevious) {
if (focusScope.value.length != 1) { if (focusScope.value.length != 1) {
return; return false;
} }
context.read<BoardBloc>().add( context.read<BoardBloc>().add(
BoardEvent.moveGroupToAdjacentGroup( BoardEvent.moveGroupToAdjacentGroup(
@ -145,5 +162,6 @@ class BoardShortcutContainer extends StatelessWidget {
), ),
); );
focusScope.clear(); focusScope.clear();
return true;
} }
} }

View File

@ -1,26 +1,24 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
class AFCallbackShortcutsProvider { typedef AFBindingCallback = bool Function();
final ValueNotifier<bool> isShortcutsEnabled = ValueNotifier(true);
}
class AFCallbackShortcuts extends StatelessWidget { class AFCallbackShortcuts extends StatelessWidget {
const AFCallbackShortcuts({ const AFCallbackShortcuts({
super.key, super.key,
required this.bindings, required this.bindings,
required this.canAcceptEvent,
required this.child, required this.child,
}); });
final Map<ShortcutActivator, VoidCallback> bindings; // The bindings for the shortcuts
final bool Function(FocusNode node, KeyEvent event) canAcceptEvent; //
// The result of the callback will be used to determine if the event is handled
final Map<ShortcutActivator, AFBindingCallback> bindings;
final Widget child; final Widget child;
bool _applyKeyEventBinding(ShortcutActivator activator, KeyEvent event) { bool _applyKeyEventBinding(ShortcutActivator activator, KeyEvent event) {
if (activator.accepts(event, HardwareKeyboard.instance)) { if (activator.accepts(event, HardwareKeyboard.instance)) {
bindings[activator]!.call(); return bindings[activator]?.call() ?? false;
return true;
} }
return false; return false;
} }
@ -31,9 +29,6 @@ class AFCallbackShortcuts extends StatelessWidget {
canRequestFocus: false, canRequestFocus: false,
skipTraversal: true, skipTraversal: true,
onKeyEvent: (FocusNode node, KeyEvent event) { onKeyEvent: (FocusNode node, KeyEvent event) {
if (!canAcceptEvent(node, event)) {
return KeyEventResult.ignored;
}
KeyEventResult result = KeyEventResult.ignored; KeyEventResult result = KeyEventResult.ignored;
for (final ShortcutActivator activator in bindings.keys) { for (final ShortcutActivator activator in bindings.keys) {
result = _applyKeyEventBinding(activator, event) result = _applyKeyEventBinding(activator, event)

View File

@ -60,7 +60,8 @@ class FavoriteBloc extends Bloc<FavoriteEvent, FavoriteState> {
); );
}, },
toggle: (view) async { toggle: (view) async {
if (view.isFavorite) { final isFavorited = state.views.any((v) => v.item.id == view.id);
if (isFavorited) {
await _service.unpinFavorite(view); await _service.unpinFavorite(view);
} else if (state.pinnedViews.length < 3) { } else if (state.pinnedViews.length < 3) {
// pin the view if there are less than 3 pinned views // pin the view if there are less than 3 pinned views

View File

@ -35,7 +35,7 @@ class ViewFavoriteButton extends StatelessWidget {
child: FlowySvg( child: FlowySvg(
isFavorite ? FlowySvgs.favorited_s : FlowySvgs.favorite_s, isFavorite ? FlowySvgs.favorited_s : FlowySvgs.favorite_s,
size: const Size.square(18), size: const Size.square(18),
blendMode: null, blendMode: isFavorite ? null : BlendMode.srcIn,
), ),
), ),
), ),