From 82c0006868dce79a29afd87465f30bb12815222d Mon Sep 17 00:00:00 2001 From: appflowy Date: Sat, 27 Aug 2022 09:52:08 +0800 Subject: [PATCH] chore: scroll to bottom after post frame --- .../plugins/board/application/board_bloc.dart | 59 +++++++++----- .../board/presentation/board_page.dart | 70 +++++++++++------ .../appflowy_board/lib/src/widgets/board.dart | 78 ++++++++++++++----- .../widgets/board_column/board_column.dart | 6 +- .../widgets/reorder_flex/reorder_flex.dart | 28 ++++--- 5 files changed, 164 insertions(+), 77 deletions(-) diff --git a/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart index 48d16e854d..c9709698de 100644 --- a/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart @@ -72,16 +72,19 @@ class BoardBloc extends Bloc { createRow: (groupId) async { final result = await _gridDataController.createBoardCard(groupId); result.fold( - (rowPB) { - emit(state.copyWith(editingRow: some(rowPB))); - }, + (_) {}, (err) => Log.error(err), ); }, + didCreateRow: (String groupId, RowPB row) { + emit(state.copyWith( + editingRow: Some(BoardEditingRow(columnId: groupId, row: row)), + )); + }, endEditRow: (rowId) { assert(state.editingRow.isSome()); - state.editingRow.fold(() => null, (row) { - assert(row.id == rowId); + state.editingRow.fold(() => null, (editingRow) { + assert(editingRow.row.id == rowId); emit(state.copyWith(editingRow: none())); }); }, @@ -137,7 +140,12 @@ class BoardBloc extends Bloc { void initializeGroups(List groups) { for (final group in groups) { - final delegate = GroupControllerDelegateImpl(boardController); + final delegate = GroupControllerDelegateImpl( + controller: boardController, + didAddColumnItem: (groupId, row) { + add(BoardEvent.didCreateRow(groupId, row)); + }, + ); final controller = GroupController( gridId: state.gridId, group: group, @@ -222,8 +230,10 @@ class BoardBloc extends Bloc { @freezed class BoardEvent with _$BoardEvent { - const factory BoardEvent.initial() = InitialGrid; + const factory BoardEvent.initial() = InitialBrid; const factory BoardEvent.createRow(String groupId) = _CreateRow; + const factory BoardEvent.didCreateRow(String groupId, RowPB row) = + _DidCreateRow; const factory BoardEvent.endEditRow(String rowId) = _EndEditRow; const factory BoardEvent.didReceiveError(FlowyError error) = _DidReceiveError; const factory BoardEvent.didReceiveGridUpdate( @@ -239,7 +249,7 @@ class BoardState with _$BoardState { required String gridId, required Option grid, required List groupIds, - required Option editingRow, + required Option editingRow, required GridLoadingState loadingState, required Option noneOrError, }) = _BoardState; @@ -303,16 +313,17 @@ class BoardColumnItem extends AFColumnItem { class GroupControllerDelegateImpl extends GroupControllerDelegate { final AFBoardDataController controller; + final void Function(String, RowPB) didAddColumnItem; - GroupControllerDelegateImpl(this.controller); + GroupControllerDelegateImpl({ + required this.controller, + required this.didAddColumnItem, + }); @override void insertRow(GroupPB group, RowPB row, int? index) { if (index != null) { - final item = BoardColumnItem( - row: row, - fieldId: group.fieldId, - ); + final item = BoardColumnItem(row: row, fieldId: group.fieldId); controller.insertColumnItem(group.groupId, index, item); } else { final item = BoardColumnItem( @@ -321,6 +332,7 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate { requestFocus: true, ); controller.addColumnItem(group.groupId, item); + didAddColumnItem(group.groupId, row); } } @@ -332,10 +344,21 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate { @override void updateRow(GroupPB group, RowPB row) { controller.updateColumnItem( - group.groupId, - BoardColumnItem( - row: row, - fieldId: group.fieldId, - )); + group.groupId, + BoardColumnItem( + row: row, + fieldId: group.fieldId, + ), + ); } } + +class BoardEditingRow { + String columnId; + RowPB row; + + BoardEditingRow({ + required this.columnId, + required this.row, + }); +} diff --git a/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart b/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart index bd1bf54296..d0884e4efc 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart @@ -60,6 +60,8 @@ class BoardContent extends StatefulWidget { class _BoardContentState extends State { late ScrollController scrollController; + late AFBoardScrollManager scrollManager; + final config = AFBoardConfig( columnBackgroundColor: HexColor.fromHex('#F7F8FC'), ); @@ -67,37 +69,55 @@ class _BoardContentState extends State { @override void initState() { scrollController = ScrollController(); + scrollManager = AFBoardScrollManager(); super.initState(); } @override Widget build(BuildContext context) { - return BlocBuilder( - buildWhen: (previous, current) => - previous.groupIds.length != current.groupIds.length, - builder: (context, state) { - return Container( - color: Colors.white, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), - child: AFBoard( - scrollController: scrollController, - dataController: context.read().boardController, - headerBuilder: _buildHeader, - footBuilder: _buildFooter, - cardBuilder: (_, column, columnItem) => _buildCard( - context, - column, - columnItem, - ), - columnConstraints: const BoxConstraints.tightFor(width: 240), - config: AFBoardConfig( - columnBackgroundColor: HexColor.fromHex('#F7F8FC'), - ), - ), - ), + return BlocListener( + listener: (context, state) { + state.editingRow.fold( + () => null, + (editingRow) { + WidgetsBinding.instance.addPostFrameCallback((_) { + scrollManager.scrollToBottom(editingRow.columnId, () { + context + .read() + .add(BoardEvent.endEditRow(editingRow.row.id)); + }); + }); + }, ); }, + child: BlocBuilder( + buildWhen: (previous, current) => + previous.groupIds.length != current.groupIds.length, + builder: (context, state) { + return Container( + color: Colors.white, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20), + child: AFBoard( + scrollManager: scrollManager, + scrollController: scrollController, + dataController: context.read().boardController, + headerBuilder: _buildHeader, + footBuilder: _buildFooter, + cardBuilder: (_, column, columnItem) => _buildCard( + context, + column, + columnItem, + ), + columnConstraints: const BoxConstraints.tightFor(width: 240), + config: AFBoardConfig( + columnBackgroundColor: HexColor.fromHex('#F7F8FC'), + ), + ), + ), + ); + }, + ), ); } @@ -178,7 +198,7 @@ class _BoardContentState extends State { final cellBuilder = BoardCellBuilder(cardController); final isEditing = context.read().state.editingRow.fold( () => false, - (editingRow) => editingRow.id == rowPB.id, + (editingRow) => editingRow.row.id == rowPB.id, ); return AppFlowyColumnItemCard( diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart index ccb15d3830..007a9b82a8 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart @@ -10,6 +10,16 @@ import 'reorder_flex/reorder_flex.dart'; import 'reorder_phantom/phantom_controller.dart'; import '../rendering/board_overlay.dart'; +class AFBoardScrollManager { + BoardColumnState? _columnState; + + // AFBoardScrollManager(); + + void scrollToBottom(String columnId, VoidCallback? completed) { + _columnState?.reorderFlexStateAtColumn(columnId)?.scrollToBottom(completed); + } +} + class AFBoardConfig { final double cornerRadius; final EdgeInsets columnPadding; @@ -58,6 +68,10 @@ class AFBoard extends StatelessWidget { final AFBoardConfig config; + final AFBoardScrollManager? scrollManager; + + final BoardColumnState _columnState = BoardColumnState(); + AFBoard({ required this.dataController, required this.cardBuilder, @@ -65,6 +79,7 @@ class AFBoard extends StatelessWidget { this.footBuilder, this.headerBuilder, this.scrollController, + this.scrollManager, this.columnConstraints = const BoxConstraints(maxWidth: 200), this.config = const AFBoardConfig(), Key? key, @@ -77,10 +92,16 @@ class AFBoard extends StatelessWidget { value: dataController, child: Consumer( builder: (context, notifier, child) { + if (scrollManager != null) { + scrollManager!._columnState = _columnState; + } + return AFBoardContent( config: config, dataController: dataController, scrollController: scrollController, + scrollManager: scrollManager, + columnState: _columnState, background: background, delegate: phantomController, columnConstraints: columnConstraints, @@ -106,6 +127,8 @@ class AFBoardContent extends StatefulWidget { final AFBoardConfig config; final ReorderFlexConfig reorderFlexConfig; final BoxConstraints columnConstraints; + final AFBoardScrollManager? scrollManager; + final BoardColumnState columnState; /// final AFBoardColumnCardBuilder cardBuilder; @@ -125,6 +148,8 @@ class AFBoardContent extends StatefulWidget { required this.onReorder, required this.delegate, required this.dataController, + required this.scrollManager, + required this.columnState, this.onDragStarted, this.onDragEnded, this.scrollController, @@ -143,22 +168,19 @@ class AFBoardContent extends StatefulWidget { } class _AFBoardContentState extends State { - late _BoardColumnState columnState; - - final GlobalKey _columnContainerOverlayKey = + final GlobalKey _boardContentKey = GlobalKey(debugLabel: '$AFBoardContent overlay key'); late BoardOverlayEntry _overlayEntry; @override void initState() { - columnState = _BoardColumnState(); _overlayEntry = BoardOverlayEntry( builder: (BuildContext context) { final interceptor = OverlappingDragTargetInterceptor( reorderFlexId: widget.dataController.identifier, acceptedReorderFlexId: widget.dataController.columnIds, delegate: widget.delegate, - columnKeys: UnmodifiableMapView(columnState.columnKeys), + columnKeys: UnmodifiableMapView(widget.columnState.columnKeys), ); final reorderFlex = ReorderFlex( @@ -198,7 +220,7 @@ class _AFBoardContentState extends State { @override Widget build(BuildContext context) { return BoardOverlay( - key: _columnContainerOverlayKey, + key: _boardContentKey, initialEntries: [_overlayEntry], ); } @@ -220,6 +242,8 @@ class _AFBoardContentState extends State { value: widget.dataController.getColumnController(columnData.id), child: Consumer( builder: (context, value, child) { + final scrollController = + widget.columnState.scrollController(columnData.id); final boardColumn = AFBoardColumnWidget( key: ValueKey(columnData.id), margin: _marginFromIndex(columnIndex), @@ -228,14 +252,17 @@ class _AFBoardContentState extends State { footBuilder: widget.footBuilder, cardBuilder: widget.cardBuilder, dataSource: dataSource, - scrollController: columnState.scrollController(columnData.id), + scrollController: scrollController, phantomController: widget.phantomController, onReorder: widget.dataController.moveColumnItem, cornerRadius: widget.config.cornerRadius, backgroundColor: widget.config.columnBackgroundColor, ); - columnState.cacheColumn(columnData.id, boardColumn.globalKey); + widget.columnState.addColumn( + columnData.id, + boardColumn.globalKey, + ); return ConstrainedBox( constraints: widget.columnConstraints, @@ -297,25 +324,36 @@ class _BoardColumnDataSourceImpl extends AFBoardColumnDataDataSource { List get acceptedColumnIds => dataController.columnIds; } -class _BoardColumnState { +class BoardColumnState { + /// Quick access to the [AFBoardColumnWidget] final Map columnKeys = {}; - void cacheColumn(String columnId, GlobalKey key) { + /// Records the position of the [AFBoardColumnWidget] + final Map columnScrollPositions = {}; + + void addColumn(String columnId, GlobalKey key) { columnKeys[columnId] = key; } - ScrollController scrollController(String columnId) { + ReorderFlexState? reorderFlexStateAtColumn(String columnId) { final flexGlobalKey = columnKeys[columnId]; - var scrollController = ScrollController(); - if (flexGlobalKey != null) { - // assert(flexGlobalKey.currentWidget is ReorderFlex); + if (flexGlobalKey == null) return null; + if (flexGlobalKey.currentState is! ReorderFlexState) return null; + final state = flexGlobalKey.currentState as ReorderFlexState; + return state; + } + + ReorderFlex? reorderFlexAtColumn(String columnId) { + final flexGlobalKey = columnKeys[columnId]; + if (flexGlobalKey == null) return null; + if (flexGlobalKey.currentWidget is! ReorderFlex) return null; + final widget = flexGlobalKey.currentWidget as ReorderFlex; + return widget; + } + + ScrollController scrollController(String columnId) { + ScrollController scrollController = ScrollController(); - // if (flexGlobalKey.currentWidget is ReorderFlex) { - // final reorderFlex = flexGlobalKey.currentWidget as ReorderFlex; - // final offset = reorderFlex.scrollController!.offset; - // scrollController = ScrollController(initialScrollOffset: offset); - // } - } return scrollController; } } diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart index d2aab18850..c4a80bd80b 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart @@ -116,13 +116,10 @@ class _AFBoardColumnWidgetState extends State { final GlobalKey _columnOverlayKey = GlobalKey(debugLabel: '$AFBoardColumnWidget overlay key'); - late GlobalObjectKey _indexGlobalKey; - late BoardOverlayEntry _overlayEntry; @override void initState() { - _indexGlobalKey = GlobalObjectKey(widget.key!); _overlayEntry = BoardOverlayEntry( builder: (BuildContext context) { final children = widget.dataSource.columnData.items @@ -143,6 +140,7 @@ class _AFBoardColumnWidgetState extends State { ); Widget reorderFlex = ReorderFlex( + key: widget.globalKey, scrollController: widget.scrollController, config: widget.config, onDragStarted: (index) { @@ -165,8 +163,6 @@ class _AFBoardColumnWidgetState extends State { children: children, ); - reorderFlex = KeyedSubtree(key: _indexGlobalKey, child: reorderFlex); - return Container( margin: widget.margin, clipBehavior: Clip.hardEdge, diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart index 3040611e74..d90b7ef3d9 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart @@ -177,9 +177,6 @@ class ReorderFlexState extends State // )); // } } - Future.delayed(Duration(seconds: 3), () { - scrollToBottom(); - }); final child = _wrapContainer(children); return _wrapScrollView(child: child); @@ -532,17 +529,25 @@ class ReorderFlexState extends State } } - void scrollToBottom() { - if (_scrolling) return; + void scrollToBottom(VoidCallback? completed) { + if (_scrolling) { + completed?.call(); + return; + } if (widget.dataSource.items.isNotEmpty) { final item = widget.dataSource.items.last; final indexKey = _childKeys[item.id]; - if (indexKey == null) return; + if (indexKey == null) { + completed?.call(); + return; + } final indexContext = indexKey.currentContext; - if (indexContext == null) return; - if (_scrollController.hasClients == false) return; + if (indexContext == null || _scrollController.hasClients == false) { + completed?.call(); + return; + } final renderObject = indexContext.findRenderObject(); if (renderObject != null) { @@ -554,8 +559,13 @@ class ReorderFlexState extends State duration: const Duration(milliseconds: 120), ) .then((value) { - setState(() => _scrolling = false); + setState(() { + _scrolling = false; + completed?.call(); + }); }); + } else { + completed?.call(); } } }