grid_page.dart 9.9 KB

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