瀏覽代碼

chore: scroll to bottom when create new card

appflowy 2 年之前
父節點
當前提交
d6162159aa

+ 20 - 8
frontend/app_flowy/lib/plugins/board/application/board_bloc.dart

@@ -198,7 +198,10 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
 
   List<AFColumnItem> _buildRows(GroupPB group) {
     final items = group.rows.map((row) {
-      return BoardColumnItem(row: row, fieldId: group.fieldId);
+      return BoardColumnItem(
+        row: row,
+        fieldId: group.fieldId,
+      );
     }).toList();
 
     return <AFColumnItem>[...items];
@@ -286,15 +289,16 @@ class BoardColumnItem extends AFColumnItem {
 
   final String fieldId;
 
-  BoardColumnItem({required this.row, required this.fieldId});
+  final bool requestFocus;
 
-  @override
-  String get id => row.id;
-}
+  BoardColumnItem({
+    required this.row,
+    required this.fieldId,
+    this.requestFocus = false,
+  });
 
-class CreateCardItem extends AFColumnItem {
   @override
-  String get id => '$CreateCardItem';
+  String get id => row.id;
 }
 
 class GroupControllerDelegateImpl extends GroupControllerDelegate {
@@ -304,10 +308,18 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
 
   @override
   void insertRow(GroupPB group, RowPB row, int? index) {
-    final item = BoardColumnItem(row: row, fieldId: group.fieldId);
     if (index != null) {
+      final item = BoardColumnItem(
+        row: row,
+        fieldId: group.fieldId,
+      );
       controller.insertColumnItem(group.groupId, index, item);
     } else {
+      final item = BoardColumnItem(
+        row: row,
+        fieldId: group.fieldId,
+        requestFocus: true,
+      );
       controller.addColumnItem(group.groupId, item);
     }
   }

+ 33 - 11
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart

@@ -1,3 +1,5 @@
+import 'dart:collection';
+
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 import 'board_column/board_column.dart';
@@ -141,18 +143,22 @@ class AFBoardContent extends StatefulWidget {
 }
 
 class _AFBoardContentState extends State<AFBoardContent> {
+  late _BoardColumnState columnState;
+
   final GlobalKey _columnContainerOverlayKey =
       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),
         );
 
         final reorderFlex = ReorderFlex(
@@ -165,7 +171,7 @@ class _AFBoardContentState extends State<AFBoardContent> {
           dataSource: widget.dataController,
           direction: Axis.horizontal,
           interceptor: interceptor,
-          children: _buildColumns(interceptor.columnKeys),
+          children: _buildColumns(),
         );
 
         return Stack(
@@ -197,7 +203,7 @@ class _AFBoardContentState extends State<AFBoardContent> {
     );
   }
 
-  List<Widget> _buildColumns(List<ColumnKey> columnKeys) {
+  List<Widget> _buildColumns() {
     final List<Widget> children =
         widget.dataController.columnDatas.asMap().entries.map(
       (item) {
@@ -222,21 +228,14 @@ class _AFBoardContentState extends State<AFBoardContent> {
                 footBuilder: widget.footBuilder,
                 cardBuilder: widget.cardBuilder,
                 dataSource: dataSource,
-                scrollController: ScrollController(),
+                scrollController: columnState.scrollController(columnData.id),
                 phantomController: widget.phantomController,
                 onReorder: widget.dataController.moveColumnItem,
                 cornerRadius: widget.config.cornerRadius,
                 backgroundColor: widget.config.columnBackgroundColor,
               );
 
-              // columnKeys
-              //     .removeWhere((element) => element.columnId == columnData.id);
-              // columnKeys.add(
-              //   ColumnKey(
-              //     columnId: columnData.id,
-              //     key: boardColumn.columnGlobalKey,
-              //   ),
-              // );
+              columnState.cacheColumn(columnData.id, boardColumn.globalKey);
 
               return ConstrainedBox(
                 constraints: widget.columnConstraints,
@@ -297,3 +296,26 @@ class _BoardColumnDataSourceImpl extends AFBoardColumnDataDataSource {
   @override
   List<String> get acceptedColumnIds => dataController.columnIds;
 }
+
+class _BoardColumnState {
+  final Map<String, GlobalKey> columnKeys = {};
+
+  void cacheColumn(String columnId, GlobalKey key) {
+    columnKeys[columnId] = key;
+  }
+
+  ScrollController scrollController(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);
+      // }
+    }
+    return scrollController;
+  }
+}

+ 9 - 6
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart

@@ -88,7 +88,9 @@ class AFBoardColumnWidget extends StatefulWidget {
 
   final Color backgroundColor;
 
-  const AFBoardColumnWidget({
+  final GlobalKey globalKey = GlobalKey();
+
+  AFBoardColumnWidget({
     Key? key,
     this.headerBuilder,
     this.footBuilder,
@@ -114,10 +116,13 @@ 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
@@ -138,7 +143,6 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
         );
 
         Widget reorderFlex = ReorderFlex(
-          key: widget.key,
           scrollController: widget.scrollController,
           config: widget.config,
           onDragStarted: (index) {
@@ -161,6 +165,8 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
           children: children,
         );
 
+        reorderFlex = KeyedSubtree(key: _indexGlobalKey, child: reorderFlex);
+
         return Container(
           margin: widget.margin,
           clipBehavior: Clip.hardEdge,
@@ -172,10 +178,7 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
             children: [
               if (header != null) header,
               Expanded(
-                child: Padding(
-                  padding: widget.itemMargin,
-                  child: reorderFlex,
-                ),
+                child: Padding(padding: widget.itemMargin, child: reorderFlex),
               ),
               if (footer != null) footer,
             ],

+ 6 - 6
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart

@@ -39,7 +39,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
   final Widget child;
   final T dragTargetData;
 
-  final GlobalObjectKey _indexGlobalKey;
+  final GlobalObjectKey indexGlobalKey;
 
   /// Called when dragTarget is being dragging.
   final DragTargetOnStarted onDragStarted;
@@ -69,9 +69,10 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
 
   final bool useMoveAnimation;
 
-  ReorderDragTarget({
+  const ReorderDragTarget({
     Key? key,
     required this.child,
+    required this.indexGlobalKey,
     required this.dragTargetData,
     required this.onDragStarted,
     required this.onDragEnded,
@@ -82,8 +83,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
     this.onAccept,
     this.onLeave,
     this.draggableTargetBuilder,
-  })  : _indexGlobalKey = GlobalObjectKey(child.key!),
-        super(key: key);
+  }) : super(key: key);
 
   @override
   State<ReorderDragTarget<T>> createState() => _ReorderDragTargetState<T>();
@@ -112,7 +112,7 @@ class _ReorderDragTargetState<T extends DragTargetData>
       },
     );
 
-    dragTarget = KeyedSubtree(key: widget._indexGlobalKey, child: dragTarget);
+    dragTarget = KeyedSubtree(key: widget.indexGlobalKey, child: dragTarget);
     return dragTarget;
   }
 
@@ -150,7 +150,7 @@ class _ReorderDragTargetState<T extends DragTargetData>
             child: widget.child,
           ),
           onDragStarted: () {
-            _draggingFeedbackSize = widget._indexGlobalKey.currentContext?.size;
+            _draggingFeedbackSize = widget.indexGlobalKey.currentContext?.size;
             widget.onDragStarted(
               widget.child,
               widget.dragTargetData.draggingIndex,

+ 3 - 7
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_interceptor.dart

@@ -1,4 +1,5 @@
 import 'dart:async';
+import 'dart:collection';
 
 import 'package:flutter/material.dart';
 
@@ -55,13 +56,14 @@ class OverlappingDragTargetInterceptor extends DragTargetInterceptor {
   final String reorderFlexId;
   final List<String> acceptedReorderFlexId;
   final OverlapDragTargetDelegate delegate;
-  final List<ColumnKey> columnKeys = [];
+  final UnmodifiableMapView<String, GlobalKey> columnKeys;
   Timer? _delayOperation;
 
   OverlappingDragTargetInterceptor({
     required this.delegate,
     required this.reorderFlexId,
     required this.acceptedReorderFlexId,
+    required this.columnKeys,
   });
 
   @override
@@ -105,12 +107,6 @@ class OverlappingDragTargetInterceptor extends DragTargetInterceptor {
   }
 }
 
-class ColumnKey {
-  String columnId;
-  GlobalKey key;
-  ColumnKey({required this.columnId, required this.key});
-}
-
 abstract class CrossReorderFlexDragTargetDelegate {
   /// * [reorderFlexId] is the id that the [ReorderFlex] passed in.
   bool acceptNewDragTargetData(

+ 51 - 6
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart

@@ -74,7 +74,7 @@ class ReorderFlex extends StatefulWidget {
 
   final DragTargetInterceptor? interceptor;
 
-  const ReorderFlex({
+  ReorderFlex({
     Key? key,
     this.scrollController,
     required this.dataSource,
@@ -85,7 +85,9 @@ class ReorderFlex extends StatefulWidget {
     this.onDragEnded,
     this.interceptor,
     this.direction = Axis.vertical,
-  }) : super(key: key);
+  })  : assert(children.every((Widget w) => w.key != null),
+            'All child must have a key.'),
+        super(key: key);
 
   @override
   State<ReorderFlex> createState() => ReorderFlexState();
@@ -112,10 +114,13 @@ class ReorderFlexState extends State<ReorderFlex>
 
   late ReorderFlexNotifier _notifier;
 
+  late Map<String, GlobalObjectKey> _childKeys;
+
   @override
   void initState() {
     _notifier = ReorderFlexNotifier();
     dragState = DraggingState(widget.reorderFlexId);
+    _childKeys = {};
 
     _animation = DragTargetAnimation(
       reorderAnimationDuration: widget.config.reorderAnimationDuration,
@@ -159,7 +164,11 @@ class ReorderFlexState extends State<ReorderFlex>
 
     for (int i = 0; i < widget.children.length; i += 1) {
       Widget child = widget.children[i];
-      children.add(_wrap(child, i));
+      final item = widget.dataSource.items[i];
+
+      final indexGlobalKey = GlobalObjectKey(child.key!);
+      _childKeys[item.id] = indexGlobalKey;
+      children.add(_wrap(child, i, indexGlobalKey));
 
       // if (widget.config.useMovePlaceholder) {
       //   children.add(DragTargeMovePlaceholder(
@@ -168,6 +177,9 @@ class ReorderFlexState extends State<ReorderFlex>
       //   ));
       // }
     }
+    Future.delayed(Duration(seconds: 3), () {
+      scrollToBottom();
+    });
 
     final child = _wrapContainer(children);
     return _wrapScrollView(child: child);
@@ -203,10 +215,10 @@ class ReorderFlexState extends State<ReorderFlex>
 
   /// [child]: the child will be wrapped with dartTarget
   /// [childIndex]: the index of the child in a list
-  Widget _wrap(Widget child, int childIndex) {
+  Widget _wrap(Widget child, int childIndex, GlobalObjectKey indexGlobalKey) {
     return Builder(builder: (context) {
       final ReorderDragTarget dragTarget =
-          _buildDragTarget(context, child, childIndex);
+          _buildDragTarget(context, child, childIndex, indexGlobalKey);
       int shiftedIndex = childIndex;
 
       if (dragState.isOverlapWithPhantom()) {
@@ -312,10 +324,15 @@ class ReorderFlexState extends State<ReorderFlex>
   }
 
   ReorderDragTarget _buildDragTarget(
-      BuildContext builderContext, Widget child, int dragTargetIndex) {
+    BuildContext builderContext,
+    Widget child,
+    int dragTargetIndex,
+    GlobalObjectKey indexGlobalKey,
+  ) {
     final ReoderFlexItem reorderFlexItem =
         widget.dataSource.items[dragTargetIndex];
     return ReorderDragTarget<FlexDragTargetData>(
+      indexGlobalKey: indexGlobalKey,
       dragTargetData: FlexDragTargetData(
         draggingIndex: dragTargetIndex,
         reorderFlexId: widget.reorderFlexId,
@@ -515,6 +532,34 @@ class ReorderFlexState extends State<ReorderFlex>
     }
   }
 
+  void scrollToBottom() {
+    if (_scrolling) return;
+
+    if (widget.dataSource.items.isNotEmpty) {
+      final item = widget.dataSource.items.last;
+      final indexKey = _childKeys[item.id];
+      if (indexKey == null) return;
+
+      final indexContext = indexKey.currentContext;
+      if (indexContext == null) return;
+      if (_scrollController.hasClients == false) return;
+
+      final renderObject = indexContext.findRenderObject();
+      if (renderObject != null) {
+        _scrolling = true;
+        _scrollController.position
+            .ensureVisible(
+          renderObject,
+          alignment: 0.5,
+          duration: const Duration(milliseconds: 120),
+        )
+            .then((value) {
+          setState(() => _scrolling = false);
+        });
+      }
+    }
+  }
+
 // Scrolls to a target context if that context is not on the screen.
   void _scrollTo(BuildContext context) {
     if (_scrolling) return;