Browse Source

chore: scroll to bottom after post frame

appflowy 2 years ago
parent
commit
82c0006868

+ 41 - 18
frontend/app_flowy/lib/plugins/board/application/board_bloc.dart

@@ -72,16 +72,19 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
           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<BoardEvent, BoardState> {
 
   void initializeGroups(List<GroupPB> 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<BoardEvent, BoardState> {
 
 @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<GridPB> grid,
     required List<String> groupIds,
-    required Option<RowPB> editingRow,
+    required Option<BoardEditingRow> editingRow,
     required GridLoadingState loadingState,
     required Option<FlowyError> 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,
+  });
+}

+ 45 - 25
frontend/app_flowy/lib/plugins/board/presentation/board_page.dart

@@ -60,6 +60,8 @@ class BoardContent extends StatefulWidget {
 
 class _BoardContentState extends State<BoardContent> {
   late ScrollController scrollController;
+  late AFBoardScrollManager scrollManager;
+
   final config = AFBoardConfig(
     columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
   );
@@ -67,37 +69,55 @@ class _BoardContentState extends State<BoardContent> {
   @override
   void initState() {
     scrollController = ScrollController();
+    scrollManager = AFBoardScrollManager();
     super.initState();
   }
 
   @override
   Widget build(BuildContext context) {
-    return BlocBuilder<BoardBloc, BoardState>(
-      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<BoardBloc>().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<BoardBloc, BoardState>(
+      listener: (context, state) {
+        state.editingRow.fold(
+          () => null,
+          (editingRow) {
+            WidgetsBinding.instance.addPostFrameCallback((_) {
+              scrollManager.scrollToBottom(editingRow.columnId, () {
+                context
+                    .read<BoardBloc>()
+                    .add(BoardEvent.endEditRow(editingRow.row.id));
+              });
+            });
+          },
         );
       },
+      child: BlocBuilder<BoardBloc, BoardState>(
+        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<BoardBloc>().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<BoardContent> {
     final cellBuilder = BoardCellBuilder(cardController);
     final isEditing = context.read<BoardBloc>().state.editingRow.fold(
           () => false,
-          (editingRow) => editingRow.id == rowPB.id,
+          (editingRow) => editingRow.row.id == rowPB.id,
         );
 
     return AppFlowyColumnItemCard(

+ 59 - 21
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<AFBoardDataController>(
         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<AFBoardContent> {
-  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<AFBoardContent> {
   @override
   Widget build(BuildContext context) {
     return BoardOverlay(
-      key: _columnContainerOverlayKey,
+      key: _boardContentKey,
       initialEntries: [_overlayEntry],
     );
   }
@@ -220,6 +242,8 @@ class _AFBoardContentState extends State<AFBoardContent> {
           value: widget.dataController.getColumnController(columnData.id),
           child: Consumer<AFBoardColumnDataController>(
             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<AFBoardContent> {
                 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<String> get acceptedColumnIds => dataController.columnIds;
 }
 
-class _BoardColumnState {
+class BoardColumnState {
+  /// Quick access to the [AFBoardColumnWidget]
   final Map<String, GlobalKey> columnKeys = {};
 
-  void cacheColumn(String columnId, GlobalKey key) {
+  /// Records the position of the [AFBoardColumnWidget]
+  final Map<String, ScrollPosition> 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.currentWidget is ReorderFlex) {
-      //   final reorderFlex = flexGlobalKey.currentWidget as ReorderFlex;
-      //   final offset = reorderFlex.scrollController!.offset;
-      //   scrollController = ScrollController(initialScrollOffset: offset);
-      // }
-    }
+    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();
+
     return scrollController;
   }
 }

+ 1 - 5
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart

@@ -116,13 +116,10 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
   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<AFBoardColumnWidget> {
         );
 
         Widget reorderFlex = ReorderFlex(
+          key: widget.globalKey,
           scrollController: widget.scrollController,
           config: widget.config,
           onDragStarted: (index) {
@@ -165,8 +163,6 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
           children: children,
         );
 
-        reorderFlex = KeyedSubtree(key: _indexGlobalKey, child: reorderFlex);
-
         return Container(
           margin: widget.margin,
           clipBehavior: Clip.hardEdge,

+ 19 - 9
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart

@@ -177,9 +177,6 @@ class ReorderFlexState extends State<ReorderFlex>
       //   ));
       // }
     }
-    Future.delayed(Duration(seconds: 3), () {
-      scrollToBottom();
-    });
 
     final child = _wrapContainer(children);
     return _wrapScrollView(child: child);
@@ -532,17 +529,25 @@ class ReorderFlexState extends State<ReorderFlex>
     }
   }
 
-  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<ReorderFlex>
           duration: const Duration(milliseconds: 120),
         )
             .then((value) {
-          setState(() => _scrolling = false);
+          setState(() {
+            _scrolling = false;
+            completed?.call();
+          });
         });
+      } else {
+        completed?.call();
       }
     }
   }