grid_page.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. import 'package:app_flowy/generated/locale_keys.g.dart';
  2. import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
  3. import 'package:app_flowy/plugins/grid/application/filter/filter_menu_bloc.dart';
  4. import 'package:app_flowy/plugins/grid/application/grid_data_controller.dart';
  5. import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
  6. import 'package:app_flowy/plugins/grid/application/grid_bloc.dart';
  7. import 'package:app_flowy/plugins/grid/application/sort/sort_menu_bloc.dart';
  8. import 'package:easy_localization/easy_localization.dart';
  9. import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
  10. import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
  11. import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
  12. import 'package:flowy_infra_ui/style_widget/scrolling/styled_scrollview.dart';
  13. import 'package:flowy_infra_ui/style_widget/text.dart';
  14. import 'package:flowy_infra_ui/widget/error_page.dart';
  15. import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
  16. import 'package:flutter_bloc/flutter_bloc.dart';
  17. import 'package:flutter/material.dart';
  18. import 'package:linked_scroll_controller/linked_scroll_controller.dart';
  19. import '../application/row/row_cache.dart';
  20. import '../application/setting/setting_bloc.dart';
  21. import 'controller/grid_scroll.dart';
  22. import 'layout/layout.dart';
  23. import 'layout/sizes.dart';
  24. import 'widgets/accessory_menu.dart';
  25. import 'widgets/cell/cell_builder.dart';
  26. import 'widgets/row/grid_row.dart';
  27. import 'widgets/footer/grid_footer.dart';
  28. import 'widgets/header/grid_header.dart';
  29. import 'widgets/row/row_detail.dart';
  30. import 'widgets/shortcuts.dart';
  31. import 'widgets/toolbar/grid_toolbar.dart';
  32. class GridPage extends StatefulWidget {
  33. final ViewPB view;
  34. final GridController gridController;
  35. final VoidCallback? onDeleted;
  36. GridPage({
  37. required this.view,
  38. this.onDeleted,
  39. Key? key,
  40. }) : gridController = GridController(view: view),
  41. super(key: key);
  42. @override
  43. State<GridPage> createState() => _GridPageState();
  44. }
  45. class _GridPageState extends State<GridPage> {
  46. @override
  47. Widget build(BuildContext context) {
  48. return MultiBlocProvider(
  49. providers: [
  50. BlocProvider<GridBloc>(
  51. create: (context) => GridBloc(
  52. view: widget.view,
  53. gridController: widget.gridController,
  54. )..add(const GridEvent.initial()),
  55. ),
  56. BlocProvider<GridFilterMenuBloc>(
  57. create: (context) => GridFilterMenuBloc(
  58. viewId: widget.view.id,
  59. fieldController: widget.gridController.fieldController,
  60. )..add(const GridFilterMenuEvent.initial()),
  61. ),
  62. BlocProvider<SortMenuBloc>(
  63. create: (context) => SortMenuBloc(
  64. viewId: widget.view.id,
  65. fieldController: widget.gridController.fieldController,
  66. )..add(const SortMenuEvent.initial()),
  67. ),
  68. BlocProvider<GridSettingBloc>(
  69. create: (context) => GridSettingBloc(gridId: widget.view.id),
  70. ),
  71. ],
  72. child: BlocBuilder<GridBloc, GridState>(
  73. builder: (context, state) {
  74. return state.loadingState.map(
  75. loading: (_) =>
  76. const Center(child: CircularProgressIndicator.adaptive()),
  77. finish: (result) => result.successOrFail.fold(
  78. (_) => const GridShortcuts(child: FlowyGrid()),
  79. (err) => FlowyErrorPage(err.toString()),
  80. ),
  81. );
  82. },
  83. ),
  84. );
  85. }
  86. @override
  87. void dispose() {
  88. super.dispose();
  89. }
  90. @override
  91. void deactivate() {
  92. super.deactivate();
  93. }
  94. @override
  95. void didUpdateWidget(covariant GridPage oldWidget) {
  96. super.didUpdateWidget(oldWidget);
  97. }
  98. }
  99. class FlowyGrid extends StatefulWidget {
  100. const FlowyGrid({Key? key}) : super(key: key);
  101. @override
  102. State<FlowyGrid> createState() => _FlowyGridState();
  103. }
  104. class _FlowyGridState extends State<FlowyGrid> {
  105. final _scrollController = GridScrollController(
  106. scrollGroupController: LinkedScrollControllerGroup());
  107. late ScrollController headerScrollController;
  108. @override
  109. void initState() {
  110. headerScrollController = _scrollController.linkHorizontalController();
  111. super.initState();
  112. }
  113. @override
  114. void dispose() {
  115. _scrollController.dispose();
  116. super.dispose();
  117. }
  118. @override
  119. Widget build(BuildContext context) {
  120. return BlocBuilder<GridBloc, GridState>(
  121. buildWhen: (previous, current) => previous.fields != current.fields,
  122. builder: (context, state) {
  123. final contentWidth = GridLayout.headerWidth(state.fields.value);
  124. final child = _wrapScrollView(
  125. contentWidth,
  126. [
  127. const _GridRows(),
  128. const _GridFooter(),
  129. ],
  130. );
  131. return Column(
  132. crossAxisAlignment: CrossAxisAlignment.start,
  133. children: [
  134. const GridToolbar(),
  135. GridAccessoryMenu(viewId: state.gridId),
  136. _gridHeader(context, state.gridId),
  137. Flexible(child: child),
  138. const RowCountBadge(),
  139. ],
  140. );
  141. },
  142. );
  143. }
  144. Widget _wrapScrollView(
  145. double contentWidth,
  146. List<Widget> slivers,
  147. ) {
  148. final verticalScrollView = ScrollConfiguration(
  149. behavior: const ScrollBehavior().copyWith(scrollbars: false),
  150. child: CustomScrollView(
  151. physics: StyledScrollPhysics(),
  152. controller: _scrollController.verticalController,
  153. slivers: slivers,
  154. ),
  155. );
  156. final sizedVerticalScrollView = SizedBox(
  157. width: contentWidth,
  158. child: verticalScrollView,
  159. );
  160. final horizontalScrollView = StyledSingleChildScrollView(
  161. controller: _scrollController.horizontalController,
  162. axis: Axis.horizontal,
  163. child: sizedVerticalScrollView,
  164. );
  165. return ScrollbarListStack(
  166. axis: Axis.vertical,
  167. controller: _scrollController.verticalController,
  168. barSize: GridSize.scrollBarSize,
  169. child: horizontalScrollView,
  170. );
  171. }
  172. Widget _gridHeader(BuildContext context, String gridId) {
  173. final fieldController =
  174. context.read<GridBloc>().gridController.fieldController;
  175. return GridHeaderSliverAdaptor(
  176. gridId: gridId,
  177. fieldController: fieldController,
  178. anchorScrollController: headerScrollController,
  179. );
  180. }
  181. }
  182. class _GridRows extends StatefulWidget {
  183. const _GridRows({Key? key}) : super(key: key);
  184. @override
  185. State<_GridRows> createState() => _GridRowsState();
  186. }
  187. class _GridRowsState extends State<_GridRows> {
  188. final _key = GlobalKey<SliverAnimatedListState>();
  189. @override
  190. Widget build(BuildContext context) {
  191. return BlocConsumer<GridBloc, GridState>(
  192. listenWhen: (previous, current) => previous.reason != current.reason,
  193. listener: (context, state) {
  194. state.reason.whenOrNull(
  195. insert: (item) {
  196. _key.currentState?.insertItem(item.index);
  197. },
  198. delete: (item) {
  199. _key.currentState?.removeItem(
  200. item.index,
  201. (context, animation) =>
  202. _renderRow(context, item.rowInfo, animation),
  203. );
  204. },
  205. reorderSingleRow: (reorderRow, rowInfo) {
  206. // _key.currentState?.removeItem(
  207. // reorderRow.oldIndex,
  208. // (context, animation) => _renderRow(context, rowInfo, animation),
  209. // );
  210. // _key.currentState?.insertItem(reorderRow.newIndex);
  211. },
  212. );
  213. },
  214. buildWhen: (previous, current) {
  215. return current.reason.whenOrNull(
  216. reorderRows: () => true,
  217. reorderSingleRow: (reorderRow, rowInfo) => true,
  218. ) ??
  219. false;
  220. },
  221. builder: (context, state) {
  222. return SliverAnimatedList(
  223. key: _key,
  224. initialItemCount: context.read<GridBloc>().state.rowInfos.length,
  225. itemBuilder:
  226. (BuildContext context, int index, Animation<double> animation) {
  227. final rowInfos = context.read<GridBloc>().state.rowInfos;
  228. if (index >= rowInfos.length) {
  229. return const SizedBox();
  230. } else {
  231. final RowInfo rowInfo = rowInfos[index];
  232. return _renderRow(context, rowInfo, animation);
  233. }
  234. },
  235. );
  236. },
  237. );
  238. }
  239. Widget _renderRow(
  240. BuildContext context,
  241. RowInfo rowInfo,
  242. Animation<double> animation,
  243. ) {
  244. final rowCache = context.read<GridBloc>().getRowCache(
  245. rowInfo.rowPB.blockId,
  246. rowInfo.rowPB.id,
  247. );
  248. /// Return placeholder widget if the rowCache is null.
  249. if (rowCache == null) return const SizedBox();
  250. final fieldController =
  251. context.read<GridBloc>().gridController.fieldController;
  252. final dataController = RowDataController(
  253. rowInfo: rowInfo,
  254. fieldController: fieldController,
  255. rowCache: rowCache,
  256. );
  257. return SizeTransition(
  258. sizeFactor: animation,
  259. child: GridRowWidget(
  260. rowInfo: rowInfo,
  261. dataController: dataController,
  262. cellBuilder: GridCellBuilder(delegate: dataController),
  263. openDetailPage: (context, cellBuilder) {
  264. _openRowDetailPage(
  265. context,
  266. rowInfo,
  267. fieldController,
  268. rowCache,
  269. cellBuilder,
  270. );
  271. },
  272. key: ValueKey(rowInfo.rowPB.id),
  273. ),
  274. );
  275. }
  276. void _openRowDetailPage(
  277. BuildContext context,
  278. RowInfo rowInfo,
  279. GridFieldController fieldController,
  280. GridRowCache rowCache,
  281. GridCellBuilder cellBuilder,
  282. ) {
  283. final dataController = RowDataController(
  284. rowInfo: rowInfo,
  285. fieldController: fieldController,
  286. rowCache: rowCache,
  287. );
  288. FlowyOverlay.show(
  289. context: context,
  290. builder: (BuildContext context) {
  291. return RowDetailPage(
  292. cellBuilder: cellBuilder,
  293. dataController: dataController,
  294. );
  295. });
  296. }
  297. }
  298. class _GridFooter extends StatelessWidget {
  299. const _GridFooter({Key? key}) : super(key: key);
  300. @override
  301. Widget build(BuildContext context) {
  302. return SliverPadding(
  303. padding: const EdgeInsets.only(bottom: 200),
  304. sliver: SliverToBoxAdapter(
  305. child: SizedBox(
  306. height: GridSize.footerHeight,
  307. child: Padding(
  308. padding: GridSize.footerContentInsets,
  309. child: const SizedBox(height: 40, child: GridAddRowButton()),
  310. ),
  311. ),
  312. ),
  313. );
  314. }
  315. }
  316. class RowCountBadge extends StatelessWidget {
  317. const RowCountBadge({Key? key}) : super(key: key);
  318. @override
  319. Widget build(BuildContext context) {
  320. return BlocSelector<GridBloc, GridState, int>(
  321. selector: (state) => state.rowCount,
  322. builder: (context, rowCount) {
  323. return Padding(
  324. padding: GridSize.footerContentInsets,
  325. child: Row(
  326. mainAxisAlignment: MainAxisAlignment.start,
  327. children: [
  328. FlowyText.medium(
  329. '${LocaleKeys.grid_row_count.tr()} : ',
  330. color: Theme.of(context).hintColor,
  331. ),
  332. FlowyText.medium(rowCount.toString()),
  333. ],
  334. ),
  335. );
  336. },
  337. );
  338. }
  339. }