home_screen.dart 9.0 KB

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