diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/base/link_to_page_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/base/link_to_page_widget.dart index 46c8298b49..19c21739cc 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/base/link_to_page_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/base/link_to_page_widget.dart @@ -7,6 +7,7 @@ import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'insert_page_command.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -92,14 +93,50 @@ class LinkToPageMenu extends StatefulWidget { } class _LinkToPageMenuState extends State { + final _focusNode = FocusNode(debugLabel: 'reference_list_widget'); EditorStyle get style => widget.editorState.editorStyle; + int _selectedIndex = 0; + int _totalItems = 0; + Future>>>? _availableLayout; + final Map> _items = {}; + + Future>>> fetchItems() async { + final items = await AppBackendService().fetchViews(widget.layoutType); + + int index = 0; + for (final app in items) { + for (final view in app.value2) { + _items.putIfAbsent(index, () => dartz.Tuple2(app.value1, view)); + index += 1; + } + } + + _totalItems = _items.length; + return items; + } + + @override + void initState() { + _availableLayout = fetchItems(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _focusNode.requestFocus(); + }); + super.initState(); + } + + @override + void dispose() { + _focusNode.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { - return Container( - color: Colors.transparent, - width: 300, + return Focus( + focusNode: _focusNode, + onKey: _onKey, child: Container( + width: 300, padding: const EdgeInsets.fromLTRB(10, 6, 10, 6), decoration: BoxDecoration( color: style.selectionMenuBackgroundColor, @@ -112,12 +149,54 @@ class _LinkToPageMenuState extends State { ], borderRadius: BorderRadius.circular(6.0), ), - child: _buildListWidget(context), + child: _buildListWidget(context, _selectedIndex, _availableLayout), ), ); } - Widget _buildListWidget(BuildContext context) { + KeyEventResult _onKey(FocusNode node, RawKeyEvent event) { + if (event is! RawKeyDownEvent || + _availableLayout == null || + _items.isEmpty) { + return KeyEventResult.ignored; + } + + final acceptedKeys = [ + LogicalKeyboardKey.arrowUp, + LogicalKeyboardKey.arrowDown, + LogicalKeyboardKey.tab, + LogicalKeyboardKey.enter + ]; + + if (!acceptedKeys.contains(event.logicalKey)) { + return KeyEventResult.handled; + } + + var newSelectedIndex = _selectedIndex; + if (event.logicalKey == LogicalKeyboardKey.arrowDown && + newSelectedIndex != _totalItems - 1) { + newSelectedIndex += 1; + } else if (event.logicalKey == LogicalKeyboardKey.arrowUp && + newSelectedIndex != 0) { + newSelectedIndex -= 1; + } else if (event.logicalKey == LogicalKeyboardKey.tab) { + newSelectedIndex += 1; + newSelectedIndex %= _totalItems; + } else if (event.logicalKey == LogicalKeyboardKey.enter) { + widget.onSelected( + _items[_selectedIndex]!.value1, _items[_selectedIndex]!.value2); + } + + setState(() { + _selectedIndex = newSelectedIndex; + }); + + return KeyEventResult.handled; + } + + Widget _buildListWidget(BuildContext context, int selectedIndex, + Future>>>? items) { + int index = 0; return FutureBuilder>>>( builder: (context, snapshot) { if (snapshot.hasData && @@ -147,6 +226,7 @@ class _LinkToPageMenuState extends State { for (final value in app.value2) { children.add( FlowyButton( + isSelected: index == _selectedIndex, leftIcon: svgWidget( _iconName(value), color: Theme.of(context).iconTheme.color, @@ -155,6 +235,8 @@ class _LinkToPageMenuState extends State { onTap: () => widget.onSelected(app.value1, value), ), ); + + index += 1; } } } @@ -169,7 +251,7 @@ class _LinkToPageMenuState extends State { ); } }, - future: AppBackendService().fetchViews(widget.layoutType), + future: items, ); }