import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart'; import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/plugins/grid/application/grid_bloc.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_scrollview.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter/material.dart'; import 'package:linked_scroll_controller/linked_scroll_controller.dart'; import '../application/row/row_cache.dart'; import 'controller/grid_scroll.dart'; import 'layout/layout.dart'; import 'layout/sizes.dart'; import 'widgets/cell/cell_builder.dart'; import 'widgets/row/grid_row.dart'; import 'widgets/footer/grid_footer.dart'; import 'widgets/header/grid_header.dart'; import 'widgets/row/row_detail.dart'; import 'widgets/shortcuts.dart'; import 'widgets/toolbar/grid_toolbar.dart'; class GridPage extends StatefulWidget { final ViewPB view; GridPage({Key? key, required this.view}) : super(key: ValueKey(view.id)); @override State createState() => _GridPageState(); } class _GridPageState extends State { @override Widget build(BuildContext context) { return MultiBlocProvider( providers: [ BlocProvider( create: (context) => getIt(param1: widget.view) ..add(const GridEvent.initial()), ), ], child: BlocBuilder( builder: (context, state) { return state.loadingState.map( loading: (_) => const Center(child: CircularProgressIndicator.adaptive()), finish: (result) => result.successOrFail.fold( (_) => const GridShortcuts(child: FlowyGrid()), (err) => FlowyErrorPage(err.toString()), ), ); }, ), ); } @override void dispose() { super.dispose(); } @override void deactivate() { super.deactivate(); } @override void didUpdateWidget(covariant GridPage oldWidget) { super.didUpdateWidget(oldWidget); } } class FlowyGrid extends StatefulWidget { const FlowyGrid({Key? key}) : super(key: key); @override State createState() => _FlowyGridState(); } class _FlowyGridState extends State { final _scrollController = GridScrollController( scrollGroupController: LinkedScrollControllerGroup()); late ScrollController headerScrollController; @override void initState() { headerScrollController = _scrollController.linkHorizontalController(); super.initState(); } @override void dispose() { _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return BlocBuilder( buildWhen: (previous, current) => previous.fields != current.fields, builder: (context, state) { final contentWidth = GridLayout.headerWidth(state.fields.value); final child = _wrapScrollView( contentWidth, [ const _GridRows(), const _GridFooter(), ], ); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const _GridToolbarAdaptor(), _gridHeader(context, state.gridId), Flexible(child: child), ], ); }, ); } Widget _wrapScrollView( double contentWidth, List slivers, ) { final verticalScrollView = ScrollConfiguration( behavior: const ScrollBehavior().copyWith(scrollbars: false), child: CustomScrollView( physics: StyledScrollPhysics(), controller: _scrollController.verticalController, slivers: slivers, ), ); final sizedVerticalScrollView = SizedBox( width: contentWidth, child: verticalScrollView, ); final horizontalScrollView = StyledSingleChildScrollView( controller: _scrollController.horizontalController, axis: Axis.horizontal, child: sizedVerticalScrollView, ); return ScrollbarListStack( axis: Axis.vertical, controller: _scrollController.verticalController, barSize: GridSize.scrollBarSize, child: horizontalScrollView, ); } Widget _gridHeader(BuildContext context, String gridId) { final fieldController = context.read().dataController.fieldController; return GridHeaderSliverAdaptor( gridId: gridId, fieldController: fieldController, anchorScrollController: headerScrollController, ); } } class _GridToolbarAdaptor extends StatelessWidget { const _GridToolbarAdaptor({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return BlocSelector( selector: (state) { final fieldController = context.read().dataController.fieldController; return GridToolbarContext( gridId: state.gridId, fieldController: fieldController, ); }, builder: (context, toolbarContext) { return GridToolbar(toolbarContext: toolbarContext); }, ); } } class _GridRows extends StatefulWidget { const _GridRows({Key? key}) : super(key: key); @override State<_GridRows> createState() => _GridRowsState(); } class _GridRowsState extends State<_GridRows> { final _key = GlobalKey(); @override Widget build(BuildContext context) { return BlocConsumer( listenWhen: (previous, current) => previous.reason != current.reason, listener: (context, state) { state.reason.mapOrNull( insert: (value) { for (final item in value.items) { _key.currentState?.insertItem(item.index); } }, delete: (value) { for (final item in value.items) { _key.currentState?.removeItem( item.index, (context, animation) => _renderRow(context, item.row, animation), ); } }, ); }, buildWhen: (previous, current) => false, builder: (context, state) { return SliverAnimatedList( key: _key, initialItemCount: context.read().state.rowInfos.length, itemBuilder: (BuildContext context, int index, Animation animation) { final RowInfo rowInfo = context.read().state.rowInfos[index]; return _renderRow(context, rowInfo, animation); }, ); }, ); } Widget _renderRow( BuildContext context, RowInfo rowInfo, Animation animation, ) { final rowCache = context.read().getRowCache( rowInfo.rowPB.blockId, rowInfo.rowPB.id, ); /// Return placeholder widget if the rowCache is null. if (rowCache == null) return const SizedBox(); final fieldController = context.read().dataController.fieldController; final dataController = GridRowDataController( rowInfo: rowInfo, fieldController: fieldController, rowCache: rowCache, ); return SizeTransition( sizeFactor: animation, child: GridRowWidget( rowInfo: rowInfo, dataController: dataController, cellBuilder: GridCellBuilder(delegate: dataController), openDetailPage: (context, cellBuilder) { _openRowDetailPage( context, rowInfo, fieldController, rowCache, cellBuilder, ); }, key: ValueKey(rowInfo.rowPB.id), ), ); } void _openRowDetailPage( BuildContext context, RowInfo rowInfo, GridFieldController fieldController, GridRowCache rowCache, GridCellBuilder cellBuilder, ) { final dataController = GridRowDataController( rowInfo: rowInfo, fieldController: fieldController, rowCache: rowCache, ); RowDetailPage( cellBuilder: cellBuilder, dataController: dataController, ).show(context); } } class _GridFooter extends StatelessWidget { const _GridFooter({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final rowCount = context.watch().state.rowInfos.length; final theme = context.watch(); return SliverPadding( padding: const EdgeInsets.only(bottom: 200), sliver: SliverToBoxAdapter( child: SizedBox( height: GridSize.footerHeight, child: Padding( padding: GridSize.headerContentInsets, child: Row( children: [ SizedBox(width: GridSize.leadingHeaderPadding), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(width: 120, child: GridAddRowButton()), const SizedBox(height: 30), _rowCountTextWidget(theme: theme, count: rowCount) ], ), ], ), ), ), ), ); } Widget _rowCountTextWidget({required AppTheme theme, required int count}) { return Row( mainAxisAlignment: MainAxisAlignment.start, children: [ FlowyText.regular( 'Count : ', fontSize: 13, color: theme.shader3, ), FlowyText.regular( count.toString(), fontSize: 13, ), ], ); } }