浏览代码

fix: support for arrow key and tab selection in referenced board/grid (#2165)

* fix: added support for navigating reference board/grid menu using keyboard keys

* refactor: made some minor changes according to reviews

* refactor: replaced loading logic with future builder
Samiksha Garg 2 年之前
父节点
当前提交
cf93e92e64

+ 88 - 6
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<LinkToPageMenu> {
+  final _focusNode = FocusNode(debugLabel: 'reference_list_widget');
   EditorStyle get style => widget.editorState.editorStyle;
+  int _selectedIndex = 0;
+  int _totalItems = 0;
+  Future<List<dartz.Tuple2<AppPB, List<ViewPB>>>>? _availableLayout;
+  final Map<int, dartz.Tuple2<AppPB, ViewPB>> _items = {};
+
+  Future<List<dartz.Tuple2<AppPB, List<ViewPB>>>> 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<LinkToPageMenu> {
           ],
           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<List<dartz.Tuple2<AppPB, List<ViewPB>>>>? items) {
+    int index = 0;
     return FutureBuilder<List<dartz.Tuple2<AppPB, List<ViewPB>>>>(
       builder: (context, snapshot) {
         if (snapshot.hasData &&
@@ -147,6 +226,7 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
                 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<LinkToPageMenu> {
                       onTap: () => widget.onSelected(app.value1, value),
                     ),
                   );
+
+                  index += 1;
                 }
               }
             }
@@ -169,7 +251,7 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
           );
         }
       },
-      future: AppBackendService().fetchViews(widget.layoutType),
+      future: items,
     );
   }