home_screen.dart 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. import 'package:appflowy/plugins/blank/blank.dart';
  2. import 'package:appflowy/startup/plugin/plugin.dart';
  3. import 'package:appflowy/startup/startup.dart';
  4. import 'package:appflowy/workspace/application/appearance.dart';
  5. import 'package:appflowy/workspace/application/home/home_bloc.dart';
  6. import 'package:appflowy/workspace/application/home/home_service.dart';
  7. import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
  8. import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
  9. import 'package:appflowy/workspace/application/view/view_ext.dart';
  10. import 'package:appflowy/workspace/presentation/home/hotkeys.dart';
  11. import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar.dart';
  12. import 'package:appflowy/workspace/presentation/widgets/edit_panel/panel_animation.dart';
  13. import 'package:appflowy/workspace/presentation/widgets/float_bubble/question_bubble.dart';
  14. import 'package:appflowy_backend/log.dart';
  15. import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
  16. import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
  17. show UserProfilePB;
  18. import 'package:flowy_infra_ui/style_widget/container.dart';
  19. import 'package:flutter/gestures.dart';
  20. import 'package:flutter/material.dart';
  21. import 'package:flutter_bloc/flutter_bloc.dart';
  22. import 'package:styled_widget/styled_widget.dart';
  23. import '../widgets/edit_panel/edit_panel.dart';
  24. import 'home_layout.dart';
  25. import 'home_stack.dart';
  26. class DesktopHomeScreen extends StatefulWidget {
  27. static const routeName = '/DesktopHomeScreen';
  28. final UserProfilePB userProfile;
  29. final WorkspaceSettingPB workspaceSetting;
  30. const DesktopHomeScreen({
  31. super.key,
  32. required this.userProfile,
  33. required this.workspaceSetting,
  34. });
  35. @override
  36. State<DesktopHomeScreen> createState() => _DesktopHomeScreenState();
  37. }
  38. class _DesktopHomeScreenState extends State<DesktopHomeScreen> {
  39. @override
  40. Widget build(BuildContext context) {
  41. return MultiBlocProvider(
  42. providers: [
  43. BlocProvider<TabsBloc>.value(value: getIt<TabsBloc>()),
  44. BlocProvider<HomeBloc>(
  45. create: (context) {
  46. return HomeBloc(widget.userProfile, widget.workspaceSetting)
  47. ..add(const HomeEvent.initial());
  48. },
  49. ),
  50. BlocProvider<HomeSettingBloc>(
  51. create: (context) {
  52. return HomeSettingBloc(
  53. widget.userProfile,
  54. widget.workspaceSetting,
  55. context.read<AppearanceSettingsCubit>(),
  56. )..add(const HomeSettingEvent.initial());
  57. },
  58. ),
  59. ],
  60. child: HomeHotKeys(
  61. child: Scaffold(
  62. body: MultiBlocListener(
  63. listeners: [
  64. BlocListener<HomeBloc, HomeState>(
  65. listenWhen: (p, c) => p.latestView != c.latestView,
  66. listener: (context, state) {
  67. final view = state.latestView;
  68. if (view != null) {
  69. // Only open the last opened view if the [TabsState.currentPageManager] current opened plugin is blank and the last opened view is not null.
  70. // All opened widgets that display on the home screen are in the form of plugins. There is a list of built-in plugins defined in the [PluginType] enum, including board, grid and trash.
  71. final currentPageManager =
  72. context.read<TabsBloc>().state.currentPageManager;
  73. if (currentPageManager.plugin.pluginType ==
  74. PluginType.blank) {
  75. getIt<TabsBloc>().add(
  76. TabsEvent.openPlugin(
  77. plugin: view.plugin(listenOnViewChanged: true),
  78. ),
  79. );
  80. }
  81. }
  82. },
  83. ),
  84. ],
  85. child: BlocBuilder<HomeSettingBloc, HomeSettingState>(
  86. buildWhen: (previous, current) => previous != current,
  87. builder: (context, state) {
  88. return FlowyContainer(
  89. Theme.of(context).colorScheme.surface,
  90. child: _buildBody(context),
  91. );
  92. },
  93. ),
  94. ),
  95. ),
  96. ),
  97. );
  98. }
  99. Widget _buildBody(BuildContext context) {
  100. return LayoutBuilder(
  101. builder: (BuildContext context, BoxConstraints constraints) {
  102. final layout = HomeLayout(context, constraints);
  103. final homeStack = HomeStack(
  104. layout: layout,
  105. delegate: DesktopHomeScreenStackAdaptor(
  106. buildContext: context,
  107. ),
  108. );
  109. final menu = _buildHomeSidebar(
  110. layout: layout,
  111. context: context,
  112. );
  113. final homeMenuResizer = _buildHomeMenuResizer(context: context);
  114. final editPanel = _buildEditPanel(
  115. layout: layout,
  116. context: context,
  117. );
  118. const bubble = QuestionBubble();
  119. return _layoutWidgets(
  120. layout: layout,
  121. homeStack: homeStack,
  122. homeMenu: menu,
  123. editPanel: editPanel,
  124. bubble: bubble,
  125. homeMenuResizer: homeMenuResizer,
  126. );
  127. },
  128. );
  129. }
  130. Widget _buildHomeSidebar({
  131. required HomeLayout layout,
  132. required BuildContext context,
  133. }) {
  134. final workspaceSetting = widget.workspaceSetting;
  135. final homeMenu = HomeSideBar(
  136. user: widget.userProfile,
  137. workspaceSetting: workspaceSetting,
  138. );
  139. return FocusTraversalGroup(child: RepaintBoundary(child: homeMenu));
  140. }
  141. Widget _buildEditPanel({
  142. required BuildContext context,
  143. required HomeLayout layout,
  144. }) {
  145. final homeBloc = context.read<HomeSettingBloc>();
  146. return BlocBuilder<HomeSettingBloc, HomeSettingState>(
  147. buildWhen: (previous, current) =>
  148. previous.panelContext != current.panelContext,
  149. builder: (context, state) {
  150. return state.panelContext.fold(
  151. () => const SizedBox(),
  152. (panelContext) => FocusTraversalGroup(
  153. child: RepaintBoundary(
  154. child: EditPanel(
  155. panelContext: panelContext,
  156. onEndEdit: () =>
  157. homeBloc.add(const HomeSettingEvent.dismissEditPanel()),
  158. ),
  159. ),
  160. ),
  161. );
  162. },
  163. );
  164. }
  165. Widget _buildHomeMenuResizer({
  166. required BuildContext context,
  167. }) {
  168. return MouseRegion(
  169. cursor: SystemMouseCursors.resizeLeftRight,
  170. child: GestureDetector(
  171. dragStartBehavior: DragStartBehavior.down,
  172. onHorizontalDragStart: (details) => context
  173. .read<HomeSettingBloc>()
  174. .add(const HomeSettingEvent.editPanelResizeStart()),
  175. onHorizontalDragUpdate: (details) => context
  176. .read<HomeSettingBloc>()
  177. .add(HomeSettingEvent.editPanelResized(details.localPosition.dx)),
  178. onHorizontalDragEnd: (details) => context
  179. .read<HomeSettingBloc>()
  180. .add(const HomeSettingEvent.editPanelResizeEnd()),
  181. onHorizontalDragCancel: () => context
  182. .read<HomeSettingBloc>()
  183. .add(const HomeSettingEvent.editPanelResizeEnd()),
  184. behavior: HitTestBehavior.translucent,
  185. child: SizedBox(
  186. width: 10,
  187. height: MediaQuery.of(context).size.height,
  188. ),
  189. ),
  190. );
  191. }
  192. Widget _layoutWidgets({
  193. required HomeLayout layout,
  194. required Widget homeMenu,
  195. required Widget homeStack,
  196. required Widget editPanel,
  197. required Widget bubble,
  198. required Widget homeMenuResizer,
  199. }) {
  200. return Stack(
  201. children: [
  202. homeStack
  203. .constrained(minWidth: 500)
  204. .positioned(
  205. left: layout.homePageLOffset,
  206. right: layout.homePageROffset,
  207. bottom: 0,
  208. top: 0,
  209. animate: true,
  210. )
  211. .animate(layout.animDuration, Curves.easeOut),
  212. bubble
  213. .positioned(
  214. right: 20,
  215. bottom: 16,
  216. animate: true,
  217. )
  218. .animate(layout.animDuration, Curves.easeOut),
  219. editPanel
  220. .animatedPanelX(
  221. duration: layout.animDuration.inMilliseconds * 0.001,
  222. closeX: layout.editPanelWidth,
  223. isClosed: !layout.showEditPanel,
  224. )
  225. .positioned(
  226. right: 0,
  227. top: 0,
  228. bottom: 0,
  229. width: layout.editPanelWidth,
  230. ),
  231. homeMenu
  232. .animatedPanelX(
  233. closeX: -layout.menuWidth,
  234. isClosed: !layout.showMenu,
  235. )
  236. .positioned(
  237. left: 0,
  238. top: 0,
  239. width: layout.menuWidth,
  240. bottom: 0,
  241. animate: true,
  242. )
  243. .animate(layout.animDuration, Curves.easeOut),
  244. homeMenuResizer
  245. .positioned(left: layout.menuWidth - 5)
  246. .animate(layout.animDuration, Curves.easeOut),
  247. ],
  248. );
  249. }
  250. }
  251. class DesktopHomeScreenStackAdaptor extends HomeStackDelegate {
  252. final BuildContext buildContext;
  253. DesktopHomeScreenStackAdaptor({
  254. required this.buildContext,
  255. });
  256. @override
  257. void didDeleteStackWidget(ViewPB view, int? index) {
  258. final homeService = HomeService();
  259. homeService.readApp(appId: view.parentViewId).then((result) {
  260. result.fold(
  261. (parentView) {
  262. final List<ViewPB> views = parentView.childViews;
  263. if (views.isNotEmpty) {
  264. ViewPB lastView = views.last;
  265. if (index != null && index != 0 && views.length > index - 1) {
  266. lastView = views[index - 1];
  267. }
  268. getIt<TabsBloc>().add(
  269. TabsEvent.openPlugin(
  270. plugin: lastView.plugin(listenOnViewChanged: true),
  271. ),
  272. );
  273. } else {
  274. getIt<TabsBloc>().add(
  275. TabsEvent.openPlugin(
  276. plugin: BlankPagePlugin(),
  277. ),
  278. );
  279. }
  280. },
  281. (err) => Log.error(err),
  282. );
  283. });
  284. }
  285. }