menu.dart 7.1 KB

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