From 60cf97969c37c6df7b632acb2ed3a240cc6489ba Mon Sep 17 00:00:00 2001 From: appflowy Date: Mon, 29 Aug 2022 09:41:40 +0800 Subject: [PATCH] fix: optimize insert card --- .../appflowy_board/lib/src/utils/log.dart | 2 +- .../appflowy_board/lib/src/widgets/board.dart | 91 ++++++++++++++----- .../widgets/board_column/board_column.dart | 9 ++ .../src/widgets/reorder_flex/drag_state.dart | 58 ++++++++++++ .../src/widgets/reorder_flex/drag_target.dart | 11 +++ .../reorder_flex/drag_target_interceptor.dart | 33 ++++--- .../widgets/reorder_flex/reorder_flex.dart | 70 ++++++++++---- .../reorder_phantom/phantom_controller.dart | 2 +- 8 files changed, 221 insertions(+), 55 deletions(-) diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/utils/log.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/utils/log.dart index 6f35ae4195..9c23060b26 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/utils/log.dart +++ b/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) { 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 bf8cfa5313..6c2f55f2af 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 @@ -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 { 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('AFBoardContent'), config: widget.reorderFlexConfig, scrollController: widget.scrollController, onDragStarted: widget.onDragStarted, @@ -243,7 +244,8 @@ class _AFBoardContentState extends State { child: Consumer( builder: (context, value, child) { final boardColumn = AFBoardColumnWidget( - key: const PageStorageKey('AFBoardColumnWidget'), + key: PageStorageKey(columnData.id), + // key: GlobalObjectKey(columnData.id), margin: _marginFromIndex(columnIndex), itemMargin: widget.config.columnItemPadding, headerBuilder: _buildHeader, @@ -255,10 +257,11 @@ class _AFBoardContentState extends State { 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 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 columnKeys = {}; + final Map columnDragStates = {}; + final Map> columnDragDragTargets = {}; - /// Records the position of the [AFBoardColumnWidget] - final Map 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> value, + ) { + Map? column = columnDragDragTargets[reorderFlexId]; + if (column == null) { + column = {}; + columnDragDragTargets[reorderFlexId] = column; + } + column[key] = value; + } + + @override + GlobalObjectKey>? readKey( + String reorderFlexId, String key) { + Map? column = columnDragDragTargets[reorderFlexId]; + if (column != null) { + return column[key]; + } else { + return null; + } + } } 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 150e3f8d6f..ac4158a3e3 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 @@ -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 { Widget reorderFlex = ReorderFlex( key: widget.globalKey, + dragStateStorage: widget.dragStateStorage, + dragTargetIndexKeyStorage: widget.dragTargetIndexKeyStorage, scrollController: widget.scrollController, config: widget.config, onDragStarted: (index) { diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_state.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_state.dart index 592277afbc..ef2c89418c 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_state.dart +++ b/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 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; } diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart index 80c1b7b744..92b71cd1f3 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart @@ -26,6 +26,11 @@ typedef DragTargetWillAccepted = bool Function( /// typedef DragTargetOnStarted = void Function(Widget, int, Size?); +typedef DragTargetOnMove = void Function( + T dragTargetData, + Offset offset, +); + /// typedef DragTargetOnEnded = void Function( T dragTargetData); @@ -46,6 +51,8 @@ class ReorderDragTarget extends StatefulWidget { final DragTargetOnEnded onDragEnded; + final DragTargetOnMove 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 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 return widget.onWillAccept(dragTargetData); }, onAccept: widget.onAccept, + onMove: (detail) { + widget.onDragMoved(detail.data, detail.offset); + }, onLeave: (dragTargetData) { assert(dragTargetData != null); if (dragTargetData != null) { diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_interceptor.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_interceptor.dart index d1eb4b5b8b..a2d2cf9c6c 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_interceptor.dart +++ b/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 acceptedReorderFlexId; final OverlapDragTargetDelegate delegate; - final UnmodifiableMapView 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); } }); } 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 d90b7ef3d9..0850b5f030 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 @@ -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 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 late ReorderFlexNotifier _notifier; - late Map _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 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 indexGlobalKey = GlobalObjectKey(child.key!); - _childKeys[item.id] = indexGlobalKey; - children.add(_wrap(child, i, indexGlobalKey)); + final indexKey = GlobalObjectKey(child.key!); + // Save the index key for quick access + widget.dragTargetIndexKeyStorage?.addKey( + widget.reorderFlexId, + item.id, + indexKey, + ); + + children.add(_wrap(child, i, indexKey)); // if (widget.config.useMovePlaceholder) { // children.add(DragTargeMovePlaceholder( @@ -212,10 +230,10 @@ class ReorderFlexState extends State /// [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 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( - 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 }); } + 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 _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 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; diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart index 4dd4f05a74..14b525d67d 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart +++ b/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; }