home_screen.dart 10 KB

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