fix: optimize insert card

This commit is contained in:
appflowy 2022-08-29 09:41:40 +08:00
parent 4ead583f6d
commit 60cf97969c
8 changed files with 221 additions and 55 deletions

View File

@ -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) {

View File

@ -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;
}
}
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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) {

View File

@ -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);
}
});
}

View File

@ -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 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<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;

View File

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