Ver código fonte

feature: Shortcut for collapse the left sidebar #692

Naughtz 2 anos atrás
pai
commit
88423c1e86

+ 4 - 0
frontend/app_flowy/lib/main.dart

@@ -1,6 +1,7 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/user/presentation/splash_screen.dart';
 import 'package:easy_localization/easy_localization.dart';
+import 'package:hotkey_manager/hotkey_manager.dart';
 import 'package:flutter/material.dart';
 
 class FlowyApp implements EntryPoint {
@@ -14,5 +15,8 @@ void main() async {
   WidgetsFlutterBinding.ensureInitialized();
   await EasyLocalization.ensureInitialized();
 
+  WidgetsFlutterBinding.ensureInitialized();
+  await hotKeyManager.unregisterAll();
+
   await FlowyRunner.run(FlowyApp());
 }

+ 44 - 15
frontend/app_flowy/lib/workspace/presentation/home/home_screen.dart

@@ -1,5 +1,6 @@
 import 'package:app_flowy/plugin/plugin.dart';
 import 'package:app_flowy/workspace/application/home/home_bloc.dart';
+import 'package:app_flowy/workspace/presentation/home/hotkeys.dart';
 import 'package:app_flowy/workspace/presentation/widgets/edit_pannel/pannel_animation.dart';
 import 'package:app_flowy/workspace/presentation/widgets/float_bubble/question_bubble.dart';
 import 'package:app_flowy/startup/startup.dart';
@@ -21,7 +22,8 @@ import 'menu/menu.dart';
 class HomeScreen extends StatefulWidget {
   final UserProfilePB user;
   final CurrentWorkspaceSettingPB workspaceSetting;
-  const HomeScreen(this.user, this.workspaceSetting, {Key? key}) : super(key: key);
+  const HomeScreen(this.user, this.workspaceSetting, {Key? key})
+      : super(key: key);
 
   @override
   State<HomeScreen> createState() => _HomeScreenState();
@@ -47,11 +49,13 @@ class _HomeScreenState extends State<HomeScreen> {
       providers: [
         BlocProvider<HomeBloc>(
           create: (context) {
-            return HomeBloc(widget.user, widget.workspaceSetting)..add(const HomeEvent.initial());
+            return HomeBloc(widget.user, widget.workspaceSetting)
+              ..add(const HomeEvent.initial());
           },
         ),
       ],
-      child: Scaffold(
+      child: HomeHotKeys(
+          child: Scaffold(
         body: BlocListener<HomeBloc, HomeState>(
           listenWhen: (p, c) => p.unauthorized != c.unauthorized,
           listener: (context, state) {
@@ -62,9 +66,12 @@ class _HomeScreenState extends State<HomeScreen> {
           child: BlocBuilder<HomeBloc, HomeState>(
             buildWhen: (previous, current) => previous != current,
             builder: (context, state) {
-              final collapasedNotifier = getIt<HomeStackManager>().collapsedNotifier;
+              final collapasedNotifier =
+                  getIt<HomeStackManager>().collapsedNotifier;
               collapasedNotifier.addPublishListener((isCollapsed) {
-                context.read<HomeBloc>().add(HomeEvent.forceCollapse(isCollapsed));
+                context
+                    .read<HomeBloc>()
+                    .add(HomeEvent.forceCollapse(isCollapsed));
               });
               return FlowyContainer(
                 Theme.of(context).colorScheme.surface,
@@ -74,7 +81,7 @@ class _HomeScreenState extends State<HomeScreen> {
             },
           ),
         ),
-      ),
+      )),
     );
   }
 
@@ -107,7 +114,10 @@ class _HomeScreenState extends State<HomeScreen> {
     );
   }
 
-  Widget _buildHomeMenu({required HomeLayout layout, required BuildContext context, required HomeState state}) {
+  Widget _buildHomeMenu(
+      {required HomeLayout layout,
+      required BuildContext context,
+      required HomeState state}) {
     final workspaceSetting = state.workspaceSetting;
     if (initialView == null && workspaceSetting.hasLatestView()) {
       initialView = workspaceSetting.latestView;
@@ -124,7 +134,8 @@ class _HomeScreenState extends State<HomeScreen> {
       collapsedNotifier: getIt<HomeStackManager>().collapsedNotifier,
     );
 
-    final latestView = workspaceSetting.hasLatestView() ? workspaceSetting.latestView : null;
+    final latestView =
+        workspaceSetting.hasLatestView() ? workspaceSetting.latestView : null;
     if (getIt<MenuSharedState>().latestOpenView == null) {
       /// AppFlowy will open the view that the last time the user opened it. The _buildHomeMenu will get called when AppFlowy's screen resizes. So we only set the latestOpenView when it's null.
       getIt<MenuSharedState>().latestOpenView = latestView;
@@ -133,10 +144,14 @@ class _HomeScreenState extends State<HomeScreen> {
     return FocusTraversalGroup(child: RepaintBoundary(child: homeMenu));
   }
 
-  Widget _buildEditPannel({required HomeState homeState, required BuildContext context, required HomeLayout layout}) {
+  Widget _buildEditPannel(
+      {required HomeState homeState,
+      required BuildContext context,
+      required HomeLayout layout}) {
     final homeBloc = context.read<HomeBloc>();
     return BlocBuilder<HomeBloc, HomeState>(
-      buildWhen: (previous, current) => previous.pannelContext != current.pannelContext,
+      buildWhen: (previous, current) =>
+          previous.pannelContext != current.pannelContext,
       builder: (context, state) {
         return state.pannelContext.fold(
           () => const SizedBox(),
@@ -144,7 +159,8 @@ class _HomeScreenState extends State<HomeScreen> {
             child: RepaintBoundary(
               child: EditPannel(
                 pannelContext: pannelContext,
-                onEndEdit: () => homeBloc.add(const HomeEvent.dismissEditPannel()),
+                onEndEdit: () =>
+                    homeBloc.add(const HomeEvent.dismissEditPannel()),
               ),
             ),
           ),
@@ -161,7 +177,9 @@ class _HomeScreenState extends State<HomeScreen> {
       child: GestureDetector(
           dragStartBehavior: DragStartBehavior.down,
           onPanUpdate: ((details) {
-            context.read<HomeBloc>().add(HomeEvent.editPannelResized(details.delta.dx));
+            context
+                .read<HomeBloc>()
+                .add(HomeEvent.editPannelResized(details.delta.dx));
           }),
           behavior: HitTestBehavior.translucent,
           child: SizedBox(
@@ -186,11 +204,21 @@ class _HomeScreenState extends State<HomeScreen> {
               closeX: -layout.menuWidth,
               isClosed: !layout.showMenu,
             )
-            .positioned(left: 0, top: 0, width: layout.menuWidth, bottom: 0, animate: true)
+            .positioned(
+                left: 0,
+                top: 0,
+                width: layout.menuWidth,
+                bottom: 0,
+                animate: true)
             .animate(layout.animDuration, Curves.easeOut),
         homeStack
             .constrained(minWidth: 500)
-            .positioned(left: layout.homePageLOffset, right: layout.homePageROffset, bottom: 0, top: 0, animate: true)
+            .positioned(
+                left: layout.homePageLOffset,
+                right: layout.homePageROffset,
+                bottom: 0,
+                top: 0,
+                animate: true)
             .animate(layout.animDuration, Curves.easeOut),
         homeMenuResizer.positioned(left: layout.homePageLOffset - 5),
         bubble
@@ -206,7 +234,8 @@ class _HomeScreenState extends State<HomeScreen> {
               closeX: layout.editPannelWidth,
               isClosed: !layout.showEditPannel,
             )
-            .positioned(right: 0, top: 0, bottom: 0, width: layout.editPannelWidth),
+            .positioned(
+                right: 0, top: 0, bottom: 0, width: layout.editPannelWidth),
       ],
     );
   }

+ 32 - 0
frontend/app_flowy/lib/workspace/presentation/home/hotkeys.dart

@@ -0,0 +1,32 @@
+import 'dart:io';
+
+import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/workspace/application/home/home_bloc.dart';
+import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
+import 'package:flutter/material.dart';
+import 'package:hotkey_manager/hotkey_manager.dart';
+import 'package:provider/provider.dart';
+
+class HomeHotKeys extends StatelessWidget {
+  final Widget child;
+  const HomeHotKeys({required this.child});
+
+  @override
+  Widget build(BuildContext context) {
+    HotKey _hotKey = HotKey(
+      KeyCode.backslash,
+      modifiers: [Platform.isMacOS ? KeyModifier.meta : KeyModifier.control],
+      // Set hotkey scope (default is HotKeyScope.system)
+      scope: HotKeyScope.inapp, // Set as inapp-wide hotkey.
+    );
+    hotKeyManager.register(
+      _hotKey,
+      keyDownHandler: (hotKey) {
+        context.read<HomeBloc>().add(const HomeEvent.collapseMenu());
+        getIt<HomeStackManager>().collapsedNotifier.value =
+            !getIt<HomeStackManager>().collapsedNotifier.currentValue!;
+      },
+    );
+    return child;
+  }
+}

+ 32 - 13
frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart

@@ -49,7 +49,8 @@ class HomeMenu extends StatelessWidget {
       providers: [
         BlocProvider<MenuBloc>(
           create: (context) {
-            final menuBloc = getIt<MenuBloc>(param1: user, param2: workspaceSetting.workspace.id);
+            final menuBloc = getIt<MenuBloc>(
+                param1: user, param2: workspaceSetting.workspace.id);
             menuBloc.add(const MenuEvent.initial());
             return menuBloc;
           },
@@ -106,18 +107,22 @@ class HomeMenu extends StatelessWidget {
 
   Widget _renderApps(BuildContext context) {
     return ExpandableTheme(
-      data: ExpandableThemeData(useInkWell: true, animationDuration: Durations.medium),
+      data: ExpandableThemeData(
+          useInkWell: true, animationDuration: Durations.medium),
       child: Expanded(
         child: ScrollConfiguration(
           behavior: const ScrollBehavior().copyWith(scrollbars: false),
           child: BlocSelector<MenuBloc, MenuState, List<Widget>>(
-            selector: (state) => state.apps.map((app) => MenuApp(app, key: ValueKey(app.id))).toList(),
+            selector: (state) => state.apps
+                .map((app) => MenuApp(app, key: ValueKey(app.id)))
+                .toList(),
             builder: (context, menuItems) {
               return ReorderableListView.builder(
                 itemCount: menuItems.length,
                 buildDefaultDragHandles: false,
                 header: Padding(
-                  padding: EdgeInsets.only(bottom: 20.0 - MenuAppSizes.appVPadding),
+                  padding:
+                      EdgeInsets.only(bottom: 20.0 - MenuAppSizes.appVPadding),
                   child: MenuUser(user),
                 ),
                 onReorder: (oldIndex, newIndex) {
@@ -126,7 +131,9 @@ class HomeMenu extends StatelessWidget {
                   //  receive:  oldIndex: 0, newIndex: 2
                   //  Workaround: if newIndex > oldIndex, we just minus one
                   int index = newIndex > oldIndex ? newIndex - 1 : newIndex;
-                  context.read<MenuBloc>().add(MenuEvent.moveApp(oldIndex, index));
+                  context
+                      .read<MenuBloc>()
+                      .add(MenuEvent.moveApp(oldIndex, index));
                 },
                 physics: StyledScrollPhysics(),
                 itemBuilder: (BuildContext context, int index) {
@@ -134,7 +141,8 @@ class HomeMenu extends StatelessWidget {
                     key: ValueKey(menuItems[index].key),
                     index: index,
                     child: Padding(
-                      padding: EdgeInsets.symmetric(vertical: MenuAppSizes.appVPadding / 2),
+                      padding: EdgeInsets.symmetric(
+                          vertical: MenuAppSizes.appVPadding / 2),
                       child: menuItems[index],
                     ),
                   );
@@ -149,7 +157,8 @@ class HomeMenu extends StatelessWidget {
 
   Widget _renderNewAppButton(BuildContext context) {
     return NewAppButton(
-      press: (appName) => context.read<MenuBloc>().add(MenuEvent.createApp(appName, desc: "")),
+      press: (appName) =>
+          context.read<MenuBloc>().add(MenuEvent.createApp(appName, desc: "")),
     );
   }
 }
@@ -208,12 +217,22 @@ class MenuTopBar extends StatelessWidget {
             children: [
               renderIcon(context),
               const Spacer(),
-              FlowyIconButton(
-                width: 28,
-                onPressed: () => context.read<HomeBloc>().add(const HomeEvent.collapseMenu()),
-                iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
-                icon: svgWidget("home/hide_menu", color: theme.iconColor),
-              )
+              Tooltip(
+                  richMessage: TextSpan(children: [
+                    const TextSpan(text: "Close sidebar\n"),
+                    TextSpan(
+                      text: Platform.isMacOS ? "⌘+\\" : "Ctrl+\\",
+                      style: const TextStyle(color: Colors.white60),
+                    ),
+                  ]),
+                  child: FlowyIconButton(
+                    width: 28,
+                    onPressed: () => context
+                        .read<HomeBloc>()
+                        .add(const HomeEvent.collapseMenu()),
+                    iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
+                    icon: svgWidget("home/hide_menu", color: theme.iconColor),
+                  ))
             ],
           )),
         );

+ 27 - 13
frontend/app_flowy/lib/workspace/presentation/home/navigation.dart

@@ -1,3 +1,5 @@
+import 'dart:io';
+
 import 'package:app_flowy/workspace/application/home/home_bloc.dart';
 import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
 import 'package:flowy_infra/image.dart';
@@ -14,7 +16,8 @@ typedef NaviAction = void Function();
 class NavigationNotifier with ChangeNotifier {
   List<NavigationItem> navigationItems;
   PublishNotifier<bool> collapasedNotifier;
-  NavigationNotifier({required this.navigationItems, required this.collapasedNotifier});
+  NavigationNotifier(
+      {required this.navigationItems, required this.collapasedNotifier});
 
   void update(HomeStackNotifier notifier) {
     bool shouldNotify = false;
@@ -69,7 +72,8 @@ class FlowyNavigation extends StatelessWidget {
         child: Row(children: [
           Selector<NavigationNotifier, PublishNotifier<bool>>(
               selector: (context, notifier) => notifier.collapasedNotifier,
-              builder: (ctx, collapsedNotifier, child) => _renderCollapse(ctx, collapsedNotifier, theme)),
+              builder: (ctx, collapsedNotifier, child) =>
+                  _renderCollapse(ctx, collapsedNotifier, theme)),
           Selector<NavigationNotifier, List<NavigationItem>>(
             selector: (context, notifier) => notifier.navigationItems,
             builder: (ctx, items, child) => Expanded(
@@ -84,7 +88,8 @@ class FlowyNavigation extends StatelessWidget {
     );
   }
 
-  Widget _renderCollapse(BuildContext context, PublishNotifier<bool> collapsedNotifier, AppTheme theme) {
+  Widget _renderCollapse(BuildContext context,
+      PublishNotifier<bool> collapsedNotifier, AppTheme theme) {
     return ChangeNotifierProvider.value(
       value: collapsedNotifier,
       child: Consumer(
@@ -92,15 +97,23 @@ class FlowyNavigation extends StatelessWidget {
           if (notifier.currentValue ?? false) {
             return RotationTransition(
               turns: const AlwaysStoppedAnimation(180 / 360),
-              child: FlowyIconButton(
-                width: 24,
-                onPressed: () {
-                  notifier.value = false;
-                  ctx.read<HomeBloc>().add(const HomeEvent.collapseMenu());
-                },
-                iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2),
-                icon: svgWidget("home/hide_menu", color: theme.iconColor),
-              ),
+              child: Tooltip(
+                  richMessage: TextSpan(children: [
+                    const TextSpan(text: "Open sidebar\n"),
+                    TextSpan(
+                      text: Platform.isMacOS ? "⌘+\\" : "Ctrl+\\",
+                      style: const TextStyle(color: Colors.white60),
+                    ),
+                  ]),
+                  child: FlowyIconButton(
+                    width: 24,
+                    onPressed: () {
+                      notifier.value = false;
+                      ctx.read<HomeBloc>().add(const HomeEvent.collapseMenu());
+                    },
+                    iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2),
+                    icon: svgWidget("home/hide_menu", color: theme.iconColor),
+                  )),
             );
           } else {
             return Container();
@@ -154,7 +167,8 @@ class NaviItemWidget extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return Expanded(child: item.leftBarItem.padding(horizontal: 2, vertical: 2));
+    return Expanded(
+        child: item.leftBarItem.padding(horizontal: 2, vertical: 2));
   }
 }
 

+ 7 - 0
frontend/app_flowy/pubspec.lock

@@ -555,6 +555,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "2.1.0"
+  hotkey_manager:
+    dependency: "direct main"
+    description:
+      name: hotkey_manager
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.1.7"
   html:
     dependency: transitive
     description:

+ 1 - 0
frontend/app_flowy/pubspec.yaml

@@ -76,6 +76,7 @@ dependencies:
   table_calendar: ^3.0.5
   reorderables: ^0.5.0
   linked_scroll_controller: ^0.2.0
+  hotkey_manager: ^0.1.7
 
 dev_dependencies:
   flutter_lints: ^1.0.0