feature: Shortcut for collapse the left sidebar #692

This commit is contained in:
Naughtz 2022-08-08 16:36:26 +08:00
parent 4a00f3c2ca
commit 88423c1e86
7 changed files with 147 additions and 41 deletions

View File

@ -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());
}

View File

@ -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),
],
);
}

View File

@ -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;
}
}

View File

@ -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),
))
],
)),
);

View File

@ -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));
}
}

View File

@ -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:

View File

@ -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