home_screen.dart 10 KB

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