home_screen.dart 9.8 KB

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