menu.dart 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import 'dart:io' show Platform;
  2. import 'package:appflowy/core/frameless_window.dart';
  3. import 'package:appflowy/generated/locale_keys.g.dart';
  4. import 'package:appflowy/plugins/trash/menu.dart';
  5. import 'package:appflowy/startup/startup.dart';
  6. import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
  7. import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
  8. import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
  9. import 'package:appflowy/workspace/presentation/home/home_stack.dart';
  10. import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
  11. import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
  12. import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
  13. show UserProfilePB;
  14. import 'package:easy_localization/easy_localization.dart';
  15. import 'package:expandable/expandable.dart';
  16. // import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
  17. import 'package:flowy_infra/image.dart';
  18. import 'package:flowy_infra/size.dart';
  19. import 'package:flowy_infra/time/duration.dart';
  20. import 'package:flowy_infra_ui/style_widget/icon_button.dart';
  21. import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
  22. import 'package:flowy_infra_ui/widget/spacing.dart';
  23. import 'package:flutter/material.dart';
  24. import 'package:flutter_bloc/flutter_bloc.dart';
  25. import 'package:styled_widget/styled_widget.dart';
  26. import '../navigation.dart';
  27. import 'app/create_button.dart';
  28. import 'app/menu_app.dart';
  29. import 'menu_user.dart';
  30. export './app/header/header.dart';
  31. export './app/menu_app.dart';
  32. class HomeMenu extends StatelessWidget {
  33. final UserProfilePB user;
  34. final WorkspaceSettingPB workspaceSetting;
  35. const HomeMenu({
  36. Key? key,
  37. required this.user,
  38. required this.workspaceSetting,
  39. }) : super(key: key);
  40. @override
  41. Widget build(BuildContext context) {
  42. return MultiBlocProvider(
  43. providers: [
  44. BlocProvider<MenuBloc>(
  45. create: (context) {
  46. final menuBloc = MenuBloc(
  47. user: user,
  48. workspace: workspaceSetting.workspace,
  49. );
  50. menuBloc.add(const MenuEvent.initial());
  51. return menuBloc;
  52. },
  53. ),
  54. ],
  55. child: MultiBlocListener(
  56. listeners: [
  57. BlocListener<MenuBloc, MenuState>(
  58. listenWhen: (p, c) => p.plugin.id != c.plugin.id,
  59. listener: (context, state) {
  60. getIt<HomeStackManager>().setPlugin(state.plugin);
  61. },
  62. ),
  63. ],
  64. child: BlocBuilder<MenuBloc, MenuState>(
  65. builder: (context, state) => _renderBody(context),
  66. ),
  67. ),
  68. );
  69. }
  70. Widget _renderBody(BuildContext context) {
  71. // nested column: https://siddharthmolleti.com/flutter-box-constraints-nested-column-s-row-s-3dfacada7361
  72. return Container(
  73. decoration: BoxDecoration(
  74. color: Theme.of(context).colorScheme.surfaceVariant,
  75. border:
  76. Border(right: BorderSide(color: Theme.of(context).dividerColor)),
  77. ),
  78. child: Column(
  79. mainAxisAlignment: MainAxisAlignment.start,
  80. children: [
  81. Expanded(
  82. child: Column(
  83. mainAxisAlignment: MainAxisAlignment.start,
  84. children: [
  85. const MenuTopBar(),
  86. const VSpace(10),
  87. _renderApps(context),
  88. ],
  89. ).padding(horizontal: Insets.l),
  90. ),
  91. const VSpace(20),
  92. const MenuTrash(),
  93. const VSpace(20),
  94. _renderNewAppButton(context),
  95. ],
  96. ),
  97. );
  98. }
  99. Widget _renderApps(BuildContext context) {
  100. return ExpandableTheme(
  101. data: ExpandableThemeData(
  102. useInkWell: true, animationDuration: Durations.medium),
  103. child: Expanded(
  104. child: ScrollConfiguration(
  105. behavior: const ScrollBehavior().copyWith(scrollbars: false),
  106. child: BlocSelector<MenuBloc, MenuState, List<Widget>>(
  107. selector: (state) => state.apps
  108. .map((app) => MenuApp(app, key: ValueKey(app.id)))
  109. .toList(),
  110. builder: (context, menuItems) {
  111. return ReorderableListView.builder(
  112. itemCount: menuItems.length,
  113. buildDefaultDragHandles: false,
  114. header: Padding(
  115. padding:
  116. EdgeInsets.only(bottom: 20.0 - MenuAppSizes.appVPadding),
  117. child: MenuUser(user),
  118. ),
  119. onReorder: (oldIndex, newIndex) {
  120. // Moving item1 from index 0 to index 1
  121. // expect: oldIndex: 0, newIndex: 1
  122. // receive: oldIndex: 0, newIndex: 2
  123. // Workaround: if newIndex > oldIndex, we just minus one
  124. int index = newIndex > oldIndex ? newIndex - 1 : newIndex;
  125. context
  126. .read<MenuBloc>()
  127. .add(MenuEvent.moveApp(oldIndex, index));
  128. },
  129. physics: StyledScrollPhysics(),
  130. itemBuilder: (BuildContext context, int index) {
  131. return ReorderableDragStartListener(
  132. key: ValueKey(menuItems[index].key),
  133. index: index,
  134. child: Padding(
  135. padding: EdgeInsets.symmetric(
  136. vertical: MenuAppSizes.appVPadding / 2),
  137. child: menuItems[index],
  138. ),
  139. );
  140. },
  141. );
  142. },
  143. ),
  144. ),
  145. ),
  146. );
  147. }
  148. Widget _renderNewAppButton(BuildContext context) {
  149. return NewAppButton(
  150. press: (appName) =>
  151. context.read<MenuBloc>().add(MenuEvent.createApp(appName, desc: "")),
  152. );
  153. }
  154. }
  155. class MenuSharedState {
  156. final ValueNotifier<ViewPB?> _latestOpenView = ValueNotifier<ViewPB?>(null);
  157. MenuSharedState({ViewPB? view}) {
  158. _latestOpenView.value = view;
  159. }
  160. ViewPB? get latestOpenView => _latestOpenView.value;
  161. ValueNotifier<ViewPB?> get notifier => _latestOpenView;
  162. set latestOpenView(ViewPB? view) {
  163. if (_latestOpenView.value != view) {
  164. _latestOpenView.value = view;
  165. }
  166. }
  167. VoidCallback addLatestViewListener(void Function(ViewPB?) callback) {
  168. listener() {
  169. callback(_latestOpenView.value);
  170. }
  171. _latestOpenView.addListener(listener);
  172. return listener;
  173. }
  174. void removeLatestViewListener(VoidCallback listener) {
  175. _latestOpenView.removeListener(listener);
  176. }
  177. }
  178. class MenuTopBar extends StatelessWidget {
  179. const MenuTopBar({Key? key}) : super(key: key);
  180. Widget renderIcon(BuildContext context) {
  181. if (Platform.isMacOS) {
  182. return Container();
  183. }
  184. return (Theme.of(context).brightness == Brightness.dark
  185. ? svgWidget("flowy_logo_dark_mode", size: const Size(92, 17))
  186. : svgWidget("flowy_logo_with_text", size: const Size(92, 17)));
  187. }
  188. @override
  189. Widget build(BuildContext context) {
  190. return BlocBuilder<MenuBloc, MenuState>(
  191. builder: (context, state) {
  192. return SizedBox(
  193. height: HomeSizes.topBarHeight,
  194. child: MoveWindowDetector(
  195. child: Row(
  196. children: [
  197. renderIcon(context),
  198. const Spacer(),
  199. Tooltip(
  200. richMessage: sidebarTooltipTextSpan(
  201. context,
  202. LocaleKeys.sideBar_closeSidebar.tr(),
  203. ),
  204. child: FlowyIconButton(
  205. width: 28,
  206. hoverColor: Colors.transparent,
  207. onPressed: () => context
  208. .read<HomeSettingBloc>()
  209. .add(const HomeSettingEvent.collapseMenu()),
  210. iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
  211. icon: svgWidget(
  212. "home/hide_menu",
  213. color: Theme.of(context).iconTheme.color,
  214. ),
  215. ),
  216. )
  217. ],
  218. ),
  219. ),
  220. );
  221. },
  222. );
  223. }
  224. }