home_screen.dart 10 KB

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