Browse Source

fix: optimize insert card

appflowy 2 years ago
parent
commit
60cf97969c

+ 1 - 1
frontend/app_flowy/packages/appflowy_board/lib/src/utils/log.dart

@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
 const DART_LOG = "Dart_LOG";
 
 class Log {
-  static const enableLog = true;
+  static const enableLog = false;
 
   static void info(String? message) {
     if (enableLog) {

+ 70 - 21
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart

@@ -1,22 +1,24 @@
-import 'dart:collection';
-
+import 'package:appflowy_board/src/utils/log.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 import 'board_column/board_column.dart';
 import 'board_column/board_column_data.dart';
 import 'board_data.dart';
+import 'reorder_flex/drag_state.dart';
 import 'reorder_flex/drag_target_interceptor.dart';
 import 'reorder_flex/reorder_flex.dart';
 import 'reorder_phantom/phantom_controller.dart';
 import '../rendering/board_overlay.dart';
 
 class AFBoardScrollManager {
-  BoardColumnState? _columnState;
+  BoardColumnsState? _columnState;
 
   // AFBoardScrollManager();
 
   void scrollToBottom(String columnId, VoidCallback? completed) {
-    _columnState?.reorderFlexStateAtColumn(columnId)?.scrollToBottom(completed);
+    _columnState
+        ?.getReorderFlexState(columnId: columnId)
+        ?.scrollToBottom(completed);
   }
 }
 
@@ -70,7 +72,7 @@ class AFBoard extends StatelessWidget {
 
   final AFBoardScrollManager? scrollManager;
 
-  final BoardColumnState _columnState = BoardColumnState();
+  final BoardColumnsState _columnState = BoardColumnsState();
 
   AFBoard({
     required this.dataController,
@@ -101,7 +103,7 @@ class AFBoard extends StatelessWidget {
             dataController: dataController,
             scrollController: scrollController,
             scrollManager: scrollManager,
-            columnState: _columnState,
+            columnsState: _columnState,
             background: background,
             delegate: phantomController,
             columnConstraints: columnConstraints,
@@ -128,7 +130,7 @@ class AFBoardContent extends StatefulWidget {
   final ReorderFlexConfig reorderFlexConfig;
   final BoxConstraints columnConstraints;
   final AFBoardScrollManager? scrollManager;
-  final BoardColumnState columnState;
+  final BoardColumnsState columnsState;
 
   ///
   final AFBoardColumnCardBuilder cardBuilder;
@@ -149,7 +151,7 @@ class AFBoardContent extends StatefulWidget {
     required this.delegate,
     required this.dataController,
     required this.scrollManager,
-    required this.columnState,
+    required this.columnsState,
     this.onDragStarted,
     this.onDragEnded,
     this.scrollController,
@@ -180,11 +182,10 @@ class _AFBoardContentState extends State<AFBoardContent> {
           reorderFlexId: widget.dataController.identifier,
           acceptedReorderFlexId: widget.dataController.columnIds,
           delegate: widget.delegate,
-          columnKeys: UnmodifiableMapView(widget.columnState.columnKeys),
+          columnsState: widget.columnsState,
         );
 
         final reorderFlex = ReorderFlex(
-          key: const PageStorageKey<String>('AFBoardContent'),
           config: widget.reorderFlexConfig,
           scrollController: widget.scrollController,
           onDragStarted: widget.onDragStarted,
@@ -243,7 +244,8 @@ class _AFBoardContentState extends State<AFBoardContent> {
           child: Consumer<AFBoardColumnDataController>(
             builder: (context, value, child) {
               final boardColumn = AFBoardColumnWidget(
-                key: const PageStorageKey<String>('AFBoardColumnWidget'),
+                key: PageStorageKey<String>(columnData.id),
+                // key: GlobalObjectKey(columnData.id),
                 margin: _marginFromIndex(columnIndex),
                 itemMargin: widget.config.columnItemPadding,
                 headerBuilder: _buildHeader,
@@ -255,10 +257,11 @@ class _AFBoardContentState extends State<AFBoardContent> {
                 onReorder: widget.dataController.moveColumnItem,
                 cornerRadius: widget.config.cornerRadius,
                 backgroundColor: widget.config.columnBackgroundColor,
+                dragStateStorage: widget.columnsState,
+                dragTargetIndexKeyStorage: widget.columnsState,
               );
 
-              widget.columnState
-                  .addColumn(columnData.id, boardColumn.globalKey);
+              widget.columnsState.addColumn(columnData.id, boardColumn);
 
               return ConstrainedBox(
                 constraints: widget.columnConstraints,
@@ -320,18 +323,23 @@ class _BoardColumnDataSourceImpl extends AFBoardColumnDataDataSource {
   List<String> get acceptedColumnIds => dataController.columnIds;
 }
 
-class BoardColumnState {
+class BoardColumnContext {
+  GlobalKey? columnKey;
+  DraggingState? draggingState;
+}
+
+class BoardColumnsState extends DraggingStateStorage
+    with ReorderDragTargerIndexKeyStorage {
   /// Quick access to the [AFBoardColumnWidget]
   final Map<String, GlobalKey> columnKeys = {};
+  final Map<String, DraggingState> columnDragStates = {};
+  final Map<String, Map<String, GlobalObjectKey>> columnDragDragTargets = {};
 
-  /// Records the position of the [AFBoardColumnWidget]
-  final Map<String, ScrollPosition> columnScrollPositions = {};
-
-  void addColumn(String columnId, GlobalKey key) {
-    columnKeys[columnId] = key;
+  void addColumn(String columnId, AFBoardColumnWidget columnWidget) {
+    columnKeys[columnId] = columnWidget.globalKey;
   }
 
-  ReorderFlexState? reorderFlexStateAtColumn(String columnId) {
+  ReorderFlexState? getReorderFlexState({required String columnId}) {
     final flexGlobalKey = columnKeys[columnId];
     if (flexGlobalKey == null) return null;
     if (flexGlobalKey.currentState is! ReorderFlexState) return null;
@@ -339,11 +347,52 @@ class BoardColumnState {
     return state;
   }
 
-  ReorderFlex? reorderFlexAtColumn(String columnId) {
+  ReorderFlex? getReorderFlex({required 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;
   }
+
+  @override
+  DraggingState? read(String reorderFlexId) {
+    return columnDragStates[reorderFlexId];
+  }
+
+  @override
+  void write(String reorderFlexId, DraggingState state) {
+    Log.trace('$reorderFlexId Write dragging state: $state');
+    columnDragStates[reorderFlexId] = state;
+  }
+
+  @override
+  void remove(String reorderFlexId) {
+    columnDragStates.remove(reorderFlexId);
+  }
+
+  @override
+  void addKey(
+    String reorderFlexId,
+    String key,
+    GlobalObjectKey<State<StatefulWidget>> value,
+  ) {
+    Map<String, GlobalObjectKey>? column = columnDragDragTargets[reorderFlexId];
+    if (column == null) {
+      column = {};
+      columnDragDragTargets[reorderFlexId] = column;
+    }
+    column[key] = value;
+  }
+
+  @override
+  GlobalObjectKey<State<StatefulWidget>>? readKey(
+      String reorderFlexId, String key) {
+    Map<String, GlobalObjectKey>? column = columnDragDragTargets[reorderFlexId];
+    if (column != null) {
+      return column[key];
+    } else {
+      return null;
+    }
+  }
 }

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

@@ -1,5 +1,6 @@
 import 'dart:collection';
 
+import 'package:appflowy_board/src/widgets/reorder_flex/drag_state.dart';
 import 'package:flutter/material.dart';
 import '../../rendering/board_overlay.dart';
 import '../../utils/log.dart';
@@ -87,6 +88,10 @@ class AFBoardColumnWidget extends StatefulWidget {
 
   final Color backgroundColor;
 
+  final DraggingStateStorage? dragStateStorage;
+
+  final ReorderDragTargerIndexKeyStorage? dragTargetIndexKeyStorage;
+
   final GlobalKey globalKey;
 
   AFBoardColumnWidget({
@@ -97,6 +102,8 @@ class AFBoardColumnWidget extends StatefulWidget {
     required this.onReorder,
     required this.dataSource,
     required this.phantomController,
+    this.dragStateStorage,
+    this.dragTargetIndexKeyStorage,
     this.scrollController,
     this.onDragStarted,
     this.onDragEnded,
@@ -140,6 +147,8 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
 
         Widget reorderFlex = ReorderFlex(
           key: widget.globalKey,
+          dragStateStorage: widget.dragStateStorage,
+          dragTargetIndexKeyStorage: widget.dragTargetIndexKeyStorage,
           scrollController: widget.scrollController,
           config: widget.config,
           onDragStarted: (index) {

+ 58 - 0
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_state.dart

@@ -24,6 +24,10 @@ class FlexDragTargetData extends DragTargetData {
 
   final String dragTargetId;
 
+  Offset dragTargetOffset = Offset.zero;
+
+  final GlobalObjectKey dragTargetIndexKey;
+
   final String reorderFlexId;
 
   final ReoderFlexItem reorderFlexItem;
@@ -33,6 +37,7 @@ class FlexDragTargetData extends DragTargetData {
     required this.draggingIndex,
     required this.reorderFlexId,
     required this.reorderFlexItem,
+    required this.dragTargetIndexKey,
     required DraggingState state,
   }) : _state = state;
 
@@ -40,6 +45,50 @@ class FlexDragTargetData extends DragTargetData {
   String toString() {
     return 'ReorderFlexId: $reorderFlexId, dragTargetId: $dragTargetId';
   }
+
+  bool isOverlapWithWidgets(List<GlobalObjectKey> widgetKeys) {
+    final renderBox = dragTargetIndexKey.currentContext?.findRenderObject();
+
+    if (renderBox == null) return false;
+    if (renderBox is! RenderBox) return false;
+    final size = feedbackSize ?? Size.zero;
+    final Rect rect = dragTargetOffset & size;
+
+    for (final widgetKey in widgetKeys) {
+      final renderObject = widgetKey.currentContext?.findRenderObject();
+      if (renderObject != null && renderObject is RenderBox) {
+        Rect widgetRect =
+            renderObject.localToGlobal(Offset.zero) & renderObject.size;
+        // return rect.overlaps(widgetRect);
+        if (rect.right <= widgetRect.left || widgetRect.right <= rect.left) {
+          return false;
+        }
+
+        if (rect.bottom <= widgetRect.top || widgetRect.bottom <= rect.top) {
+          return false;
+        }
+        return true;
+      }
+    }
+
+    // final HitTestResult result = HitTestResult();
+    // WidgetsBinding.instance.hitTest(result, position);
+    // for (final HitTestEntry entry in result.path) {
+    //   final HitTestTarget target = entry.target;
+    //   if (target is RenderMetaData) {
+    //     print(target.metaData);
+    //   }
+    //   print(target);
+    // }
+
+    return false;
+  }
+}
+
+abstract class DraggingStateStorage {
+  void write(String reorderFlexId, DraggingState state);
+  void remove(String reorderFlexId);
+  DraggingState? read(String reorderFlexId);
 }
 
 class DraggingState {
@@ -128,6 +177,7 @@ class DraggingState {
 
   /// Set the currentIndex to nextIndex
   void moveDragTargetToNext() {
+    Log.debug('$reorderFlexId updateCurrentIndex: $nextIndex');
     currentIndex = nextIndex;
   }
 
@@ -136,6 +186,14 @@ class DraggingState {
     nextIndex = index;
   }
 
+  void setStartDragggingIndex(int index) {
+    Log.debug('$reorderFlexId setDragIndex: $index');
+    dragStartIndex = index;
+    phantomIndex = index;
+    currentIndex = index;
+    nextIndex = index;
+  }
+
   bool isNotDragging() {
     return dragStartIndex == -1;
   }

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

@@ -26,6 +26,11 @@ typedef DragTargetWillAccepted<T extends DragTargetData> = bool Function(
 ///
 typedef DragTargetOnStarted = void Function(Widget, int, Size?);
 
+typedef DragTargetOnMove<T extends DragTargetData> = void Function(
+  T dragTargetData,
+  Offset offset,
+);
+
 ///
 typedef DragTargetOnEnded<T extends DragTargetData> = void Function(
     T dragTargetData);
@@ -46,6 +51,8 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
 
   final DragTargetOnEnded<T> onDragEnded;
 
+  final DragTargetOnMove<T> onDragMoved;
+
   /// Called to determine whether this widget is interested in receiving a given
   /// piece of data being dragged over this drag target.
   ///
@@ -75,6 +82,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
     required this.indexGlobalKey,
     required this.dragTargetData,
     required this.onDragStarted,
+    required this.onDragMoved,
     required this.onDragEnded,
     required this.onWillAccept,
     required this.insertAnimationController,
@@ -104,6 +112,9 @@ class _ReorderDragTargetState<T extends DragTargetData>
         return widget.onWillAccept(dragTargetData);
       },
       onAccept: widget.onAccept,
+      onMove: (detail) {
+        widget.onDragMoved(detail.data, detail.offset);
+      },
       onLeave: (dragTargetData) {
         assert(dragTargetData != null);
         if (dragTargetData != null) {

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

@@ -1,8 +1,7 @@
 import 'dart:async';
-import 'dart:collection';
 
+import 'package:appflowy_board/src/widgets/board.dart';
 import 'package:flutter/material.dart';
-
 import '../../utils/log.dart';
 import 'drag_state.dart';
 import 'drag_target.dart';
@@ -42,7 +41,7 @@ abstract class OverlapDragTargetDelegate {
     int dragTargetIndex,
   );
 
-  int canMoveTo(String dragTargetId);
+  int getInsertedIndex(String dragTargetId);
 }
 
 /// [OverlappingDragTargetInterceptor] is used to receive the overlapping
@@ -56,14 +55,14 @@ class OverlappingDragTargetInterceptor extends DragTargetInterceptor {
   final String reorderFlexId;
   final List<String> acceptedReorderFlexId;
   final OverlapDragTargetDelegate delegate;
-  final UnmodifiableMapView<String, GlobalKey> columnKeys;
+  final BoardColumnsState columnsState;
   Timer? _delayOperation;
 
   OverlappingDragTargetInterceptor({
     required this.delegate,
     required this.reorderFlexId,
     required this.acceptedReorderFlexId,
-    required this.columnKeys,
+    required this.columnsState,
   });
 
   @override
@@ -81,24 +80,30 @@ class OverlappingDragTargetInterceptor extends DragTargetInterceptor {
     if (dragTargetId == dragTargetData.reorderFlexId) {
       delegate.cancel();
     } else {
+      // Ignore the event if the dragTarget overlaps with the other column's dragTargets.
+      final columnKeys = columnsState.columnDragDragTargets[dragTargetId];
+      if (columnKeys != null) {
+        final keys = columnKeys.values.toList();
+        if (dragTargetData.isOverlapWithWidgets(keys)) {
+          _delayOperation?.cancel();
+          return true;
+        }
+      }
+
       /// The priority of the column interactions is high than the cross column.
       /// Workaround: delay 100 milliseconds to lower the cross column event priority.
+      ///
       _delayOperation?.cancel();
       _delayOperation = Timer(const Duration(milliseconds: 100), () {
-        final index = delegate.canMoveTo(dragTargetId);
+        final index = delegate.getInsertedIndex(dragTargetId);
         if (index != -1) {
           Log.trace(
               '[$OverlappingDragTargetInterceptor] move to $dragTargetId at $index');
           delegate.moveTo(dragTargetId, dragTargetData, index);
 
-          // final columnIndex = columnKeys
-          //     .indexWhere((element) => element.columnId == dragTargetId);
-          // if (columnIndex != -1) {
-          //   final state = columnKeys[columnIndex].key.currentState;
-          //   if (state is ReorderFlexState) {
-          //     state.handleOnWillAccept(context, index);
-          //   }
-          // }
+          columnsState
+              .getReorderFlexState(columnId: dragTargetId)
+              ?.resetDragTargetIndex(index);
         }
       });
     }

+ 52 - 18
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart

@@ -31,6 +31,11 @@ abstract class ReoderFlexItem {
   String get id;
 }
 
+abstract class ReorderDragTargerIndexKeyStorage {
+  void addKey(String reorderFlexId, String key, GlobalObjectKey value);
+  GlobalObjectKey? readKey(String reorderFlexId, String key);
+}
+
 class ReorderFlexConfig {
   /// The opacity of the dragging widget
   final double draggingWidgetOpacity = 0.3;
@@ -52,7 +57,6 @@ class ReorderFlexConfig {
 
 class ReorderFlex extends StatefulWidget {
   final ReorderFlexConfig config;
-
   final List<Widget> children;
 
   /// [direction] How to place the children, default is Axis.vertical
@@ -74,6 +78,10 @@ class ReorderFlex extends StatefulWidget {
 
   final DragTargetInterceptor? interceptor;
 
+  final DraggingStateStorage? dragStateStorage;
+
+  final ReorderDragTargerIndexKeyStorage? dragTargetIndexKeyStorage;
+
   ReorderFlex({
     Key? key,
     this.scrollController,
@@ -81,6 +89,8 @@ class ReorderFlex extends StatefulWidget {
     required this.children,
     required this.config,
     required this.onReorder,
+    this.dragStateStorage,
+    this.dragTargetIndexKeyStorage,
     this.onDragStarted,
     this.onDragEnded,
     this.interceptor,
@@ -114,13 +124,15 @@ class ReorderFlexState extends State<ReorderFlex>
 
   late ReorderFlexNotifier _notifier;
 
-  late Map<String, GlobalObjectKey> _childKeys;
-
   @override
   void initState() {
     _notifier = ReorderFlexNotifier();
-    dragState = DraggingState(widget.reorderFlexId);
-    _childKeys = {};
+    final flexId = widget.reorderFlexId;
+    dragState = widget.dragStateStorage?.read(flexId) ??
+        DraggingState(widget.reorderFlexId);
+    Log.trace('[DragTarget] init dragState: $dragState');
+
+    widget.dragStateStorage?.remove(flexId);
 
     _animation = DragTargetAnimation(
       reorderAnimationDuration: widget.config.reorderAnimationDuration,
@@ -164,11 +176,17 @@ class ReorderFlexState extends State<ReorderFlex>
 
     for (int i = 0; i < widget.children.length; i += 1) {
       Widget child = widget.children[i];
-      final item = widget.dataSource.items[i];
+      final ReoderFlexItem item = widget.dataSource.items[i];
+
+      final indexKey = GlobalObjectKey(child.key!);
+      // Save the index key for quick access
+      widget.dragTargetIndexKeyStorage?.addKey(
+        widget.reorderFlexId,
+        item.id,
+        indexKey,
+      );
 
-      final indexGlobalKey = GlobalObjectKey(child.key!);
-      _childKeys[item.id] = indexGlobalKey;
-      children.add(_wrap(child, i, indexGlobalKey));
+      children.add(_wrap(child, i, indexKey));
 
       // if (widget.config.useMovePlaceholder) {
       //   children.add(DragTargeMovePlaceholder(
@@ -212,10 +230,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, GlobalObjectKey indexGlobalKey) {
+  Widget _wrap(Widget child, int childIndex, GlobalObjectKey indexKey) {
     return Builder(builder: (context) {
       final ReorderDragTarget dragTarget =
-          _buildDragTarget(context, child, childIndex, indexGlobalKey);
+          _buildDragTarget(context, child, childIndex, indexKey);
       int shiftedIndex = childIndex;
 
       if (dragState.isOverlapWithPhantom()) {
@@ -324,24 +342,28 @@ class ReorderFlexState extends State<ReorderFlex>
     BuildContext builderContext,
     Widget child,
     int dragTargetIndex,
-    GlobalObjectKey indexGlobalKey,
+    GlobalObjectKey indexKey,
   ) {
-    final ReoderFlexItem reorderFlexItem =
-        widget.dataSource.items[dragTargetIndex];
+    final reorderFlexItem = widget.dataSource.items[dragTargetIndex];
     return ReorderDragTarget<FlexDragTargetData>(
-      indexGlobalKey: indexGlobalKey,
+      indexGlobalKey: indexKey,
       dragTargetData: FlexDragTargetData(
         draggingIndex: dragTargetIndex,
         reorderFlexId: widget.reorderFlexId,
         reorderFlexItem: reorderFlexItem,
         state: dragState,
         dragTargetId: reorderFlexItem.id,
+        dragTargetIndexKey: indexKey,
       ),
       onDragStarted: (draggingWidget, draggingIndex, size) {
         Log.debug(
             "[DragTarget] Column:[${widget.dataSource.identifier}] start dragging item at $draggingIndex");
         _startDragging(draggingWidget, draggingIndex, size);
         widget.onDragStarted?.call(draggingIndex);
+        widget.dragStateStorage?.remove(widget.reorderFlexId);
+      },
+      onDragMoved: (dragTargetData, offset) {
+        dragTargetData.dragTargetOffset = offset;
       },
       onDragEnded: (dragTargetData) {
         if (!mounted) return;
@@ -445,14 +467,20 @@ class ReorderFlexState extends State<ReorderFlex>
     });
   }
 
+  void resetDragTargetIndex(int dragTargetIndex) {
+    dragState.setStartDragggingIndex(dragTargetIndex);
+    widget.dragStateStorage?.write(
+      widget.reorderFlexId,
+      dragState,
+    );
+  }
+
   bool handleOnWillAccept(BuildContext context, int dragTargetIndex) {
     final dragIndex = dragState.dragStartIndex;
 
     /// The [willAccept] will be true if the dargTarget is the widget that gets
     /// dragged and it is dragged on top of the other dragTargets.
     ///
-    Log.trace(
-        '[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, dragIndex:$dragIndex, dragTargetIndex:$dragTargetIndex, count: ${widget.dataSource.items.length}');
 
     bool willAccept =
         dragState.dragStartIndex == dragIndex && dragIndex != dragTargetIndex;
@@ -466,6 +494,9 @@ class ReorderFlexState extends State<ReorderFlex>
       _requestAnimationToNextIndex(isAcceptingNewTarget: true);
     });
 
+    Log.trace(
+        '[$ReorderDragTarget] ${widget.reorderFlexId} dragging state: $dragState}');
+
     _scrollTo(context);
 
     /// If the target is not the original starting point, then we will accept the drop.
@@ -537,7 +568,10 @@ class ReorderFlexState extends State<ReorderFlex>
 
     if (widget.dataSource.items.isNotEmpty) {
       final item = widget.dataSource.items.last;
-      final indexKey = _childKeys[item.id];
+      final indexKey = widget.dragTargetIndexKeyStorage?.readKey(
+        widget.reorderFlexId,
+        item.id,
+      );
       if (indexKey == null) {
         completed?.call();
         return;

+ 1 - 1
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart

@@ -203,7 +203,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate
   }
 
   @override
-  int canMoveTo(String dragTargetId) {
+  int getInsertedIndex(String dragTargetId) {
     if (columnsState.isDragging(dragTargetId)) {
       return -1;
     }