grid_page.dart 10.0 KB

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