Lucas.Xu 29fb4af40a
chore: improve UI design on Desktop (#5792)
* fix: only show collapse page button when the children of the page is not emtpy

* chore: set minimum sidebar width to 268

* chore: replace space lock icon and pin & unpin icon

* chore: change divider color

* chore: update divider color

* chore: improve create space color

* feat: highlight delete button when hovering

* chore: update translations

* fix: icon align issue

* feat: highlight sidebar resizer when hovering

* feat: add border to popover

* feat: optimize scroll bar

* feat: improve scrollbar hover color

* feat: support creating a new page via cmd+n

* chore: improve scrollbar color

* feat: improve tooltip style

* chore: fix unit test

* chore: bump version 0.6.6
2024-07-26 09:49:13 +08:00

240 lines
6.8 KiB
Dart

import 'dart:io';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/startup/tasks/app_window_size_manager.dart';
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy/workspace/application/sidebar/rename_view/rename_view_bloc.dart';
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_setting.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:flutter/material.dart';
import 'package:hotkey_manager/hotkey_manager.dart';
import 'package:provider/provider.dart';
import 'package:scaled_app/scaled_app.dart';
typedef KeyDownHandler = void Function(HotKey hotKey);
ValueNotifier<int> switchToTheNextSpace = ValueNotifier(0);
ValueNotifier<int> createNewPageNotifier = ValueNotifier(0);
/// Helper class that utilizes the global [HotKeyManager] to easily
/// add a [HotKey] with different handlers.
///
/// Makes registration of a [HotKey] simple and easy to read, and makes
/// sure the [KeyDownHandler], and other handlers, are grouped with the
/// relevant [HotKey].
///
class HotKeyItem {
HotKeyItem({
required this.hotKey,
this.keyDownHandler,
});
final HotKey hotKey;
final KeyDownHandler? keyDownHandler;
void register() =>
hotKeyManager.register(hotKey, keyDownHandler: keyDownHandler);
}
class HomeHotKeys extends StatefulWidget {
const HomeHotKeys({
super.key,
required this.userProfile,
required this.child,
});
final UserProfilePB userProfile;
final Widget child;
@override
State<HomeHotKeys> createState() => _HomeHotKeysState();
}
class _HomeHotKeysState extends State<HomeHotKeys> {
final windowSizeManager = WindowSizeManager();
late final items = [
// Collapse sidebar menu (using slash)
HotKeyItem(
hotKey: HotKey(
KeyCode.backslash,
modifiers: [Platform.isMacOS ? KeyModifier.meta : KeyModifier.control],
scope: HotKeyScope.inapp,
),
keyDownHandler: (_) => context
.read<HomeSettingBloc>()
.add(const HomeSettingEvent.collapseMenu()),
),
// Collapse sidebar menu (using .)
HotKeyItem(
hotKey: HotKey(
KeyCode.period,
modifiers: [Platform.isMacOS ? KeyModifier.meta : KeyModifier.control],
scope: HotKeyScope.inapp,
),
keyDownHandler: (_) => context
.read<HomeSettingBloc>()
.add(const HomeSettingEvent.collapseMenu()),
),
// Toggle theme mode light/dark
HotKeyItem(
hotKey: HotKey(
KeyCode.keyL,
modifiers: [
Platform.isMacOS ? KeyModifier.meta : KeyModifier.control,
KeyModifier.shift,
],
scope: HotKeyScope.inapp,
),
keyDownHandler: (_) =>
context.read<AppearanceSettingsCubit>().toggleThemeMode(),
),
// Close current tab
HotKeyItem(
hotKey: HotKey(
KeyCode.keyW,
modifiers: [Platform.isMacOS ? KeyModifier.meta : KeyModifier.control],
scope: HotKeyScope.inapp,
),
keyDownHandler: (_) =>
context.read<TabsBloc>().add(const TabsEvent.closeCurrentTab()),
),
// Go to previous tab
HotKeyItem(
hotKey: HotKey(
KeyCode.pageUp,
modifiers: [Platform.isMacOS ? KeyModifier.meta : KeyModifier.control],
scope: HotKeyScope.inapp,
),
keyDownHandler: (_) => _selectTab(context, -1),
),
// Go to next tab
HotKeyItem(
hotKey: HotKey(
KeyCode.pageDown,
modifiers: [Platform.isMacOS ? KeyModifier.meta : KeyModifier.control],
scope: HotKeyScope.inapp,
),
keyDownHandler: (_) => _selectTab(context, 1),
),
// Rename current view
HotKeyItem(
hotKey: HotKey(
KeyCode.f2,
scope: HotKeyScope.inapp,
),
keyDownHandler: (_) =>
getIt<RenameViewBloc>().add(const RenameViewEvent.open()),
),
// Scale up/down the app
// In some keyboards, the system returns equal as + keycode, while others may return add as + keycode, so add them both as zoom in key.
...[KeyCode.equal, KeyCode.add].map(
(keycode) => HotKeyItem(
hotKey: HotKey(
keycode,
modifiers: [
Platform.isMacOS ? KeyModifier.meta : KeyModifier.control,
],
scope: HotKeyScope.inapp,
),
keyDownHandler: (_) => _scaleWithStep(0.1),
),
),
HotKeyItem(
hotKey: HotKey(
KeyCode.minus,
modifiers: [Platform.isMacOS ? KeyModifier.meta : KeyModifier.control],
scope: HotKeyScope.inapp,
),
keyDownHandler: (_) => _scaleWithStep(-0.1),
),
// Reset app scaling
HotKeyItem(
hotKey: HotKey(
KeyCode.digit0,
modifiers: [Platform.isMacOS ? KeyModifier.meta : KeyModifier.control],
scope: HotKeyScope.inapp,
),
keyDownHandler: (_) => _scaleToSize(1),
),
// Switch to the next space
HotKeyItem(
hotKey: HotKey(
KeyCode.keyO,
modifiers: [Platform.isMacOS ? KeyModifier.meta : KeyModifier.control],
scope: HotKeyScope.inapp,
),
keyDownHandler: (_) => switchToTheNextSpace.value++,
),
// Create a new page
HotKeyItem(
hotKey: HotKey(
KeyCode.keyN,
modifiers: [Platform.isMacOS ? KeyModifier.meta : KeyModifier.control],
scope: HotKeyScope.inapp,
),
keyDownHandler: (_) => createNewPageNotifier.value++,
),
// Open settings dialog
openSettingsHotKey(context, widget.userProfile),
];
@override
void initState() {
super.initState();
_registerHotKeys(context);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_registerHotKeys(context);
}
@override
Widget build(BuildContext context) => widget.child;
void _registerHotKeys(BuildContext context) {
for (final element in items) {
element.register();
}
}
void _selectTab(BuildContext context, int change) {
final bloc = context.read<TabsBloc>();
bloc.add(TabsEvent.selectTab(bloc.state.currentIndex + change));
}
Future<void> _scaleWithStep(double step) async {
final currentScaleFactor = await windowSizeManager.getScaleFactor();
final textScale = (currentScaleFactor + step).clamp(
WindowSizeManager.minScaleFactor,
WindowSizeManager.maxScaleFactor,
);
Log.info('scale the app from $currentScaleFactor to $textScale');
await _scaleToSize(textScale);
}
Future<void> _scaleToSize(double size) async {
ScaledWidgetsFlutterBinding.instance.scaleFactor = (_) => size;
await windowSizeManager.setScaleFactor(size);
}
}