home_screen.dart 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  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. ViewPB? initialView;
  32. @override
  33. void initState() {
  34. super.initState();
  35. }
  36. @override
  37. void didUpdateWidget(covariant HomeScreen oldWidget) {
  38. initialView = null;
  39. super.didUpdateWidget(oldWidget);
  40. }
  41. @override
  42. Widget build(BuildContext context) {
  43. return MultiBlocProvider(
  44. providers: [
  45. BlocProvider<HomeBloc>(
  46. create: (context) {
  47. return HomeBloc(widget.user, widget.workspaceSetting)
  48. ..add(const HomeEvent.initial());
  49. },
  50. ),
  51. ],
  52. child: HomeHotKeys(
  53. child: Scaffold(
  54. body: 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. child: BlocBuilder<HomeBloc, HomeState>(
  62. buildWhen: (previous, current) => previous != current,
  63. builder: (context, state) {
  64. final collapsedNotifier =
  65. getIt<HomeStackManager>().collapsedNotifier;
  66. collapsedNotifier.addPublishListener((isCollapsed) {
  67. context
  68. .read<HomeBloc>()
  69. .add(HomeEvent.forceCollapse(isCollapsed));
  70. });
  71. return FlowyContainer(
  72. Theme.of(context).colorScheme.surface,
  73. // Colors.white,
  74. child: _buildBody(context, state),
  75. );
  76. },
  77. ),
  78. ),
  79. )),
  80. );
  81. }
  82. Widget _buildBody(BuildContext context, HomeState state) {
  83. return LayoutBuilder(
  84. builder: (BuildContext context, BoxConstraints constraints) {
  85. final layout = HomeLayout(context, constraints, state.forceCollapse);
  86. final homeStack = HomeStack(
  87. layout: layout,
  88. delegate: HomeScreenStackAdaptor(
  89. buildContext: context,
  90. homeState: state,
  91. ),
  92. );
  93. final menu = _buildHomeMenu(
  94. layout: layout,
  95. context: context,
  96. state: state,
  97. );
  98. final homeMenuResizer = _buildHomeMenuResizer(context: context);
  99. final editPanel = _buildEditPanel(
  100. homeState: state,
  101. layout: layout,
  102. context: context,
  103. );
  104. const bubble = QuestionBubble();
  105. return _layoutWidgets(
  106. layout: layout,
  107. homeStack: homeStack,
  108. homeMenu: menu,
  109. editPanel: editPanel,
  110. bubble: bubble,
  111. homeMenuResizer: homeMenuResizer,
  112. );
  113. },
  114. );
  115. }
  116. Widget _buildHomeMenu(
  117. {required HomeLayout layout,
  118. required BuildContext context,
  119. required HomeState state}) {
  120. final workspaceSetting = state.workspaceSetting;
  121. if (initialView == null && workspaceSetting.hasLatestView()) {
  122. initialView = workspaceSetting.latestView;
  123. final plugin = makePlugin(
  124. pluginType: initialView!.pluginType,
  125. data: initialView,
  126. );
  127. getIt<HomeStackManager>().setPlugin(plugin);
  128. }
  129. final homeMenu = HomeMenu(
  130. user: widget.user,
  131. workspaceSetting: workspaceSetting,
  132. collapsedNotifier: getIt<HomeStackManager>().collapsedNotifier,
  133. );
  134. // Only open the last opened view if the [HomeStackManager] current opened
  135. // plugin is blank and the last opened view is not null.
  136. //
  137. // All opened widgets that display on the home screen are in the form
  138. // of plugins. There is a list of built-in plugins defined in the
  139. // [PluginType] enum, including board, grid and trash.
  140. if (getIt<HomeStackManager>().plugin.ty == PluginType.blank) {
  141. // Open the last opened view.
  142. if (workspaceSetting.hasLatestView()) {
  143. final view = workspaceSetting.latestView;
  144. final plugin = makePlugin(
  145. pluginType: view.pluginType,
  146. data: view,
  147. );
  148. getIt<HomeStackManager>().setPlugin(plugin);
  149. getIt<MenuSharedState>().latestOpenView = view;
  150. }
  151. }
  152. return FocusTraversalGroup(child: RepaintBoundary(child: homeMenu));
  153. }
  154. Widget _buildEditPanel(
  155. {required HomeState homeState,
  156. required BuildContext context,
  157. required HomeLayout layout}) {
  158. final homeBloc = context.read<HomeBloc>();
  159. return BlocBuilder<HomeBloc, HomeState>(
  160. buildWhen: (previous, current) =>
  161. previous.panelContext != current.panelContext,
  162. builder: (context, state) {
  163. return state.panelContext.fold(
  164. () => const SizedBox(),
  165. (panelContext) => FocusTraversalGroup(
  166. child: RepaintBoundary(
  167. child: EditPanel(
  168. panelContext: panelContext,
  169. onEndEdit: () =>
  170. homeBloc.add(const HomeEvent.dismissEditPanel()),
  171. ),
  172. ),
  173. ),
  174. );
  175. },
  176. );
  177. }
  178. Widget _buildHomeMenuResizer({
  179. required BuildContext context,
  180. }) {
  181. return MouseRegion(
  182. cursor: SystemMouseCursors.resizeLeftRight,
  183. child: GestureDetector(
  184. dragStartBehavior: DragStartBehavior.down,
  185. onPanUpdate: ((details) {
  186. context
  187. .read<HomeBloc>()
  188. .add(HomeEvent.editPanelResized(details.delta.dx));
  189. }),
  190. behavior: HitTestBehavior.translucent,
  191. child: SizedBox(
  192. width: 10,
  193. height: MediaQuery.of(context).size.height,
  194. )),
  195. );
  196. }
  197. Widget _layoutWidgets({
  198. required HomeLayout layout,
  199. required Widget homeMenu,
  200. required Widget homeStack,
  201. required Widget editPanel,
  202. required Widget bubble,
  203. required Widget homeMenuResizer,
  204. }) {
  205. return Stack(
  206. children: [
  207. homeStack
  208. .constrained(minWidth: 500)
  209. .positioned(
  210. left: layout.homePageLOffset,
  211. right: layout.homePageROffset,
  212. bottom: 0,
  213. top: 0,
  214. animate: true)
  215. .animate(layout.animDuration, Curves.easeOut),
  216. homeMenuResizer.positioned(left: layout.homePageLOffset - 5),
  217. bubble
  218. .positioned(
  219. right: 20,
  220. bottom: 16,
  221. animate: true,
  222. )
  223. .animate(layout.animDuration, Curves.easeOut),
  224. editPanel
  225. .animatedPanelX(
  226. duration: layout.animDuration.inMilliseconds * 0.001,
  227. closeX: layout.editPanelWidth,
  228. isClosed: !layout.showEditPanel,
  229. )
  230. .positioned(
  231. right: 0, top: 0, bottom: 0, width: layout.editPanelWidth),
  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. .animate(layout.animDuration, Curves.easeOut),
  244. ],
  245. );
  246. }
  247. }
  248. class HomeScreenStackAdaptor extends HomeStackDelegate {
  249. final BuildContext buildContext;
  250. final HomeState homeState;
  251. HomeScreenStackAdaptor({
  252. required this.buildContext,
  253. required this.homeState,
  254. });
  255. @override
  256. void didDeleteStackWidget(ViewPB view) {
  257. final homeService = HomeService();
  258. homeService.readApp(appId: view.appId).then((result) {
  259. result.fold(
  260. (appPB) {
  261. final List<ViewPB> views = appPB.belongings.items;
  262. if (views.isNotEmpty) {
  263. final lastView = views.last;
  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. }