fix: invalid drag start index

This commit is contained in:
appflowy 2022-08-05 10:29:39 +08:00
parent c25237c767
commit a0d7f114c0
4 changed files with 108 additions and 74 deletions

View File

@ -17,21 +17,18 @@ class FlexDragTargetData extends DragTargetData {
Size? get feedbackSize => state.feedbackSize; Size? get feedbackSize => state.feedbackSize;
/// Indicate the dragTarget come from which [ReorderFlex].
final DraggingReorderFlex draggingReorderFlex;
final String dragTargetId; final String dragTargetId;
final ReoderFlexItem reorderFlexItem; final String reorderFlexId;
String get reorderFlexId => draggingReorderFlex.reorderFlexId; final ReoderFlexItem reorderFlexItem;
FlexDragTargetData({ FlexDragTargetData({
required this.dragTargetId, required this.dragTargetId,
required this.draggingIndex, required this.draggingIndex,
required this.reorderFlexId,
required this.reorderFlexItem, required this.reorderFlexItem,
required this.state, required this.state,
required this.draggingReorderFlex,
}); });
@override @override
@ -40,11 +37,6 @@ class FlexDragTargetData extends DragTargetData {
} }
} }
abstract class DraggingReorderFlex {
String get reorderFlexId;
ReoderFlexItem itemAtIndex(int index);
}
class DraggingState { class DraggingState {
final String id; final String id;
@ -85,10 +77,12 @@ class DraggingState {
if (_draggingFeedbackSize == null) { if (_draggingFeedbackSize == null) {
return Size.zero; return Size.zero;
} }
return _draggingFeedbackSize! + const Offset(_dropAreaMargin, _dropAreaMargin); return _draggingFeedbackSize! +
const Offset(_dropAreaMargin, _dropAreaMargin);
} }
void startDragging(Widget draggingWidget, int draggingWidgetIndex, Size? draggingWidgetSize) { void startDragging(Widget draggingWidget, int draggingWidgetIndex,
Size? draggingWidgetSize) {
/// ///
assert(draggingWidgetIndex >= 0); assert(draggingWidgetIndex >= 0);

View File

@ -25,7 +25,8 @@ abstract class ReorderFlexDragTargetInterceptor {
abstract class OverlapReorderFlexDragTargetDelegate {} abstract class OverlapReorderFlexDragTargetDelegate {}
class OverlapReorderFlexDragTargetInteceptor extends ReorderFlexDragTargetInterceptor { class OverlapReorderFlexDragTargetInteceptor
extends ReorderFlexDragTargetInterceptor {
final String reorderFlexId; final String reorderFlexId;
final List<String> acceptedReorderFlexId; final List<String> acceptedReorderFlexId;
final OverlapReorderFlexDragTargetDelegate delegate; final OverlapReorderFlexDragTargetDelegate delegate;
@ -49,7 +50,7 @@ class OverlapReorderFlexDragTargetInteceptor extends ReorderFlexDragTargetInterc
required String dragTargetId, required String dragTargetId,
required int dragTargetIndex}) { required int dragTargetIndex}) {
if (dragTargetId == dragTargetData.reorderFlexId) { if (dragTargetId == dragTargetData.reorderFlexId) {
Log.debug('remove all phantom'); // Log.debug('remove all phantom');
} }
return true; return true;
@ -60,7 +61,7 @@ abstract class CrossReorderFlexDragTargetDelegate {
bool acceptNewDragTargetData( bool acceptNewDragTargetData(
String reorderFlexId, String reorderFlexId,
FlexDragTargetData dragTargetData, FlexDragTargetData dragTargetData,
int index, int dragTargetIndex,
); );
void updateDragTargetData( void updateDragTargetData(
@ -70,7 +71,8 @@ abstract class CrossReorderFlexDragTargetDelegate {
); );
} }
class CrossReorderFlexDragTargetInterceptor extends ReorderFlexDragTargetInterceptor { class CrossReorderFlexDragTargetInterceptor
extends ReorderFlexDragTargetInterceptor {
final String reorderFlexId; final String reorderFlexId;
final List<String> acceptedReorderFlexIds; final List<String> acceptedReorderFlexIds;
final CrossReorderFlexDragTargetDelegate delegate; final CrossReorderFlexDragTargetDelegate delegate;
@ -102,21 +104,24 @@ class CrossReorderFlexDragTargetInterceptor extends ReorderFlexDragTargetInterce
@override @override
void onAccept(FlexDragTargetData dragTargetData) { void onAccept(FlexDragTargetData dragTargetData) {
Log.trace('[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on onAccept'); Log.trace(
'[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on onAccept');
} }
@override @override
void onLeave(FlexDragTargetData dragTargetData) { void onLeave(FlexDragTargetData dragTargetData) {
Log.trace('[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on leave'); Log.trace(
'[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on leave');
} }
@override @override
bool onWillAccept( bool onWillAccept({
{required BuildContext context, required BuildContext context,
required ReorderFlexState reorderFlexState, required ReorderFlexState reorderFlexState,
required FlexDragTargetData dragTargetData, required FlexDragTargetData dragTargetData,
required String dragTargetId, required String dragTargetId,
required int dragTargetIndex}) { required int dragTargetIndex,
}) {
final isNewDragTarget = delegate.acceptNewDragTargetData( final isNewDragTarget = delegate.acceptNewDragTargetData(
reorderFlexId, reorderFlexId,
dragTargetData, dragTargetData,
@ -132,7 +137,6 @@ class CrossReorderFlexDragTargetInterceptor extends ReorderFlexDragTargetInterce
reorderFlexState.handleOnWillAccept( reorderFlexState.handleOnWillAccept(
context, context,
dragTargetData.draggingIndex,
dragTargetIndex, dragTargetIndex,
); );
} }

View File

@ -13,7 +13,8 @@ typedef OnDragEnded = void Function();
typedef OnReorder = void Function(int fromIndex, int toIndex); typedef OnReorder = void Function(int fromIndex, int toIndex);
typedef OnDeleted = void Function(int deletedIndex); typedef OnDeleted = void Function(int deletedIndex);
typedef OnInserted = void Function(int insertedIndex); typedef OnInserted = void Function(int insertedIndex);
typedef OnReveivePassedInPhantom = void Function(FlexDragTargetData dragTargetData, int phantomIndex); typedef OnReveivePassedInPhantom = void Function(
FlexDragTargetData dragTargetData, int phantomIndex);
abstract class ReoderFlextDataSource { abstract class ReoderFlextDataSource {
String get identifier; String get identifier;
@ -32,7 +33,7 @@ class ReorderFlexConfig {
const ReorderFlexConfig(); const ReorderFlexConfig();
} }
class ReorderFlex extends StatefulWidget with DraggingReorderFlex { class ReorderFlex extends StatefulWidget {
final ReorderFlexConfig config; final ReorderFlexConfig config;
final List<Widget> children; final List<Widget> children;
@ -68,16 +69,11 @@ class ReorderFlex extends StatefulWidget with DraggingReorderFlex {
@override @override
State<ReorderFlex> createState() => ReorderFlexState(); State<ReorderFlex> createState() => ReorderFlexState();
@override
String get reorderFlexId => dataSource.identifier; String get reorderFlexId => dataSource.identifier;
@override
ReoderFlexItem itemAtIndex(int index) {
return dataSource.items[index];
}
} }
class ReorderFlexState extends State<ReorderFlex> with ReorderFlexMinxi, TickerProviderStateMixin<ReorderFlex> { class ReorderFlexState extends State<ReorderFlex>
with ReorderFlexMinxi, TickerProviderStateMixin<ReorderFlex> {
/// Controls scrolls and measures scroll progress. /// Controls scrolls and measures scroll progress.
late ScrollController _scrollController; late ScrollController _scrollController;
ScrollPosition? _attachedScrollPosition; ScrollPosition? _attachedScrollPosition;
@ -113,7 +109,9 @@ class ReorderFlexState extends State<ReorderFlex> with ReorderFlexMinxi, TickerP
_attachedScrollPosition = null; _attachedScrollPosition = null;
} }
_scrollController = widget.scrollController ?? PrimaryScrollController.of(context) ?? ScrollController(); _scrollController = widget.scrollController ??
PrimaryScrollController.of(context) ??
ScrollController();
if (_scrollController.hasClients) { if (_scrollController.hasClients) {
_attachedScrollPosition = Scrollable.of(context)?.position; _attachedScrollPosition = Scrollable.of(context)?.position;
@ -235,7 +233,9 @@ class ReorderFlexState extends State<ReorderFlex> with ReorderFlexMinxi, TickerP
]); ]);
} else if (childIndex == dragPhantomIndex) { } else if (childIndex == dragPhantomIndex) {
return _buildDraggingContainer( return _buildDraggingContainer(
children: shiftedIndex <= childIndex ? [dragTarget, disappearSpace] : [disappearSpace, dragTarget]); children: shiftedIndex <= childIndex
? [dragTarget, disappearSpace]
: [disappearSpace, dragTarget]);
} }
} }
@ -256,7 +256,9 @@ class ReorderFlexState extends State<ReorderFlex> with ReorderFlexMinxi, TickerP
]); ]);
} else if (childIndex == dragPhantomIndex) { } else if (childIndex == dragPhantomIndex) {
return _buildDraggingContainer( return _buildDraggingContainer(
children: shiftedIndex >= childIndex ? [disappearSpace, dragTarget] : [dragTarget, disappearSpace]); children: shiftedIndex >= childIndex
? [disappearSpace, dragTarget]
: [dragTarget, disappearSpace]);
} }
} }
@ -282,22 +284,25 @@ class ReorderFlexState extends State<ReorderFlex> with ReorderFlexMinxi, TickerP
Widget child, Widget child,
int dragTargetIndex, int dragTargetIndex,
) { ) {
final ReoderFlexItem reorderFlexItem = widget.dataSource.items[dragTargetIndex]; final ReoderFlexItem reorderFlexItem =
widget.dataSource.items[dragTargetIndex];
return ReorderDragTarget<FlexDragTargetData>( return ReorderDragTarget<FlexDragTargetData>(
dragTargetData: FlexDragTargetData( dragTargetData: FlexDragTargetData(
draggingIndex: dragTargetIndex, draggingIndex: dragTargetIndex,
reorderFlexId: widget.reorderFlexId,
reorderFlexItem: reorderFlexItem, reorderFlexItem: reorderFlexItem,
state: dragState, state: dragState,
draggingReorderFlex: widget,
dragTargetId: reorderFlexItem.id, dragTargetId: reorderFlexItem.id,
), ),
onDragStarted: (draggingWidget, draggingIndex, size) { onDragStarted: (draggingWidget, draggingIndex, size) {
Log.debug("[DragTarget] Column${widget.dataSource.identifier} start dragging"); Log.debug(
"[DragTarget] Column${widget.dataSource.identifier} start dragging");
_startDragging(draggingWidget, draggingIndex, size); _startDragging(draggingWidget, draggingIndex, size);
widget.onDragStarted?.call(draggingIndex); widget.onDragStarted?.call(draggingIndex);
}, },
onDragEnded: (dragTargetData) { onDragEnded: (dragTargetData) {
Log.debug("[DragTarget]: Column${widget.dataSource.identifier} end dragging"); Log.debug(
"[DragTarget]: Column${widget.dataSource.identifier} end dragging");
setState(() { setState(() {
_onReordered( _onReordered(
@ -323,8 +328,7 @@ class ReorderFlexState extends State<ReorderFlex> with ReorderFlexMinxi, TickerP
)) { )) {
return true; return true;
} else { } else {
final dragIndex = dragTargetData.draggingIndex; return handleOnWillAccept(builderContext, dragTargetIndex);
return handleOnWillAccept(builderContext, dragIndex, dragTargetIndex);
} }
}, },
onAccept: (dragTargetData) { onAccept: (dragTargetData) {
@ -386,14 +390,17 @@ class ReorderFlexState extends State<ReorderFlex> with ReorderFlexMinxi, TickerP
}); });
} }
bool handleOnWillAccept(BuildContext context, int? dragIndex, int dragTargetIndex) { bool handleOnWillAccept(BuildContext context, int dragTargetIndex) {
final dragIndex = dragState.dragStartIndex;
/// The [willAccept] will be true if the dargTarget is the widget that gets /// The [willAccept] will be true if the dargTarget is the widget that gets
/// dragged and it is dragged on top of the other dragTargets. /// dragged and it is dragged on top of the other dragTargets.
/// ///
Log.debug( Log.debug(
'[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, dragIndex:$dragIndex, dragTargetIndex:$dragTargetIndex, count: ${widget.dataSource.items.length}'); '[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, dragIndex:$dragIndex, dragTargetIndex:$dragTargetIndex, count: ${widget.dataSource.items.length}');
bool willAccept = dragState.dragStartIndex == dragIndex && dragIndex != dragTargetIndex; bool willAccept =
dragState.dragStartIndex == dragIndex && dragIndex != dragTargetIndex;
setState(() { setState(() {
if (willAccept) { if (willAccept) {
int shiftedIndex = dragState.calculateShiftedIndex(dragTargetIndex); int shiftedIndex = dragState.calculateShiftedIndex(dragTargetIndex);
@ -420,7 +427,8 @@ class ReorderFlexState extends State<ReorderFlex> with ReorderFlexMinxi, TickerP
} }
Widget _wrapScrollView({required Widget child}) { Widget _wrapScrollView({required Widget child}) {
if (widget.scrollController != null && PrimaryScrollController.of(context) == null) { if (widget.scrollController != null &&
PrimaryScrollController.of(context) == null) {
return child; return child;
} else { } else {
return SingleChildScrollView( return SingleChildScrollView(
@ -474,12 +482,14 @@ class ReorderFlexState extends State<ReorderFlex> with ReorderFlexMinxi, TickerP
void _scrollTo(BuildContext context) { void _scrollTo(BuildContext context) {
if (_scrolling) return; if (_scrolling) return;
final RenderObject contextObject = context.findRenderObject()!; final RenderObject contextObject = context.findRenderObject()!;
final RenderAbstractViewport viewport = RenderAbstractViewport.of(contextObject)!; final RenderAbstractViewport viewport =
RenderAbstractViewport.of(contextObject)!;
// If and only if the current scroll offset falls in-between the offsets // If and only if the current scroll offset falls in-between the offsets
// necessary to reveal the selected context at the top or bottom of the // necessary to reveal the selected context at the top or bottom of the
// screen, then it is already on-screen. // screen, then it is already on-screen.
final double margin = final double margin = widget.direction == Axis.horizontal
widget.direction == Axis.horizontal ? dragState.dropAreaSize.width : dragState.dropAreaSize.height; ? dragState.dropAreaSize.width
: dragState.dropAreaSize.height;
if (_scrollController.hasClients) { if (_scrollController.hasClients) {
final double scrollOffset = _scrollController.offset; final double scrollOffset = _scrollController.offset;
final double topOffset = max( final double topOffset = max(
@ -490,7 +500,8 @@ class ReorderFlexState extends State<ReorderFlex> with ReorderFlexMinxi, TickerP
_scrollController.position.maxScrollExtent, _scrollController.position.maxScrollExtent,
viewport.getOffsetToReveal(contextObject, 1.0).offset + margin, viewport.getOffsetToReveal(contextObject, 1.0).offset + margin,
); );
final bool onScreen = scrollOffset <= topOffset && scrollOffset >= bottomOffset; final bool onScreen =
scrollOffset <= topOffset && scrollOffset >= bottomOffset;
// If the context is off screen, then we request a scroll to make it visible. // If the context is off screen, then we request a scroll to make it visible.
if (!onScreen) { if (!onScreen) {

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import '../../../flowy_board.dart'; import '../../../flowy_board.dart';
import '../../utils/log.dart'; import '../../utils/log.dart';
import '../flex/drag_state.dart'; import '../flex/drag_state.dart';
@ -15,7 +14,8 @@ mixin ColumnDataPhantomMixim {
BoardColumnDataController? get; BoardColumnDataController? get;
} }
class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate with CrossReorderFlexDragTargetDelegate { class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate
with CrossReorderFlexDragTargetDelegate {
final BoardPhantomControllerDelegate delegate; final BoardPhantomControllerDelegate delegate;
PhantomRecord? phantomRecord; PhantomRecord? phantomRecord;
@ -64,10 +64,16 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate with C
if (columnsState.isDragging(phantomRecord!.fromColumnId) == false) { if (columnsState.isDragging(phantomRecord!.fromColumnId) == false) {
return; return;
} }
final item = delegate.controller(phantomRecord!.fromColumnId)?.removeAt(phantomRecord!.fromColumnIndex); final item = delegate
.controller(phantomRecord!.fromColumnId)
?.removeAt(phantomRecord!.fromColumnIndex);
assert(item != null); assert(item != null);
assert(delegate.controller(phantomRecord!.toColumnId)?.items[phantomRecord!.toColumnIndex] is PhantomColumnItem); assert(delegate
delegate.controller(phantomRecord!.toColumnId)?.replace(phantomRecord!.toColumnIndex, item!); .controller(phantomRecord!.toColumnId)
?.items[phantomRecord!.toColumnIndex] is PhantomColumnItem);
delegate
.controller(phantomRecord!.toColumnId)
?.replace(phantomRecord!.toColumnIndex, item!);
Log.debug("[$BoardPhantomController] did move ${phantomRecord.toString()}"); Log.debug("[$BoardPhantomController] did move ${phantomRecord.toString()}");
phantomRecord = null; phantomRecord = null;
@ -76,24 +82,28 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate with C
void _updatePhantom( void _updatePhantom(
String toColumnId, String toColumnId,
FlexDragTargetData dragTargetData, FlexDragTargetData dragTargetData,
int phantomIndex, int dragTargetIndex,
) { ) {
final columnDataController = delegate.controller(toColumnId); final columnDataController = delegate.controller(toColumnId);
final index = columnDataController?.items.indexWhere((item) => item.isPhantom); final index =
columnDataController?.items.indexWhere((item) => item.isPhantom);
if (index == null) return; if (index == null) return;
assert(index != -1); assert(index != -1);
if (index != -1) { if (index != -1) {
if (index != phantomIndex) { if (index != dragTargetIndex) {
// Log.debug('[$BoardPhantomController] update $toColumnId:$index to $toColumnId:$phantomIndex'); // Log.debug('[$BoardPhantomController] update $toColumnId:$index to $toColumnId:$phantomIndex');
final item = columnDataController!.removeAt(index, notify: false); final item = columnDataController!.removeAt(index, notify: false);
columnDataController.insert(phantomIndex, item, notify: false); columnDataController.insert(dragTargetIndex, item, notify: false);
} }
} }
} }
void _removePhantom(String columnId) { void _removePhantom(String columnId) {
final index = delegate.controller(columnId)?.items.indexWhere((item) => item.isPhantom); final index = delegate
.controller(columnId)
?.items
.indexWhere((item) => item.isPhantom);
if (index == null) return; if (index == null) return;
@ -124,7 +134,9 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate with C
); );
columnsState.addColumnListener(toColumnId, phantomContext); columnsState.addColumnListener(toColumnId, phantomContext);
Log.debug('$phantomContext'); Log.debug('$phantomContext');
delegate.controller(toColumnId)?.insert(phantomIndex, PhantomColumnItem(phantomContext)); delegate
.controller(toColumnId)
?.insert(phantomIndex, PhantomColumnItem(phantomContext));
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
Future.delayed(const Duration(milliseconds: 100), () { Future.delayed(const Duration(milliseconds: 100), () {
@ -152,10 +164,14 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate with C
} }
@override @override
bool acceptNewDragTargetData(String reorderFlexId, FlexDragTargetData dragTargetData, int index) { bool acceptNewDragTargetData(
String reorderFlexId,
FlexDragTargetData dragTargetData,
int dragTargetIndex,
) {
if (phantomRecord == null) { if (phantomRecord == null) {
_updatePhantomRecord(reorderFlexId, dragTargetData, index); _updatePhantomRecord(reorderFlexId, dragTargetData, dragTargetIndex);
_insertPhantom(reorderFlexId, dragTargetData, index); _insertPhantom(reorderFlexId, dragTargetData, dragTargetIndex);
return false; return false;
} }
@ -165,21 +181,26 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate with C
_removePhantom(phantomRecord!.toColumnId); _removePhantom(phantomRecord!.toColumnId);
/// Update the record and insert the phantom to new column. /// Update the record and insert the phantom to new column.
_updatePhantomRecord(reorderFlexId, dragTargetData, index); _updatePhantomRecord(reorderFlexId, dragTargetData, dragTargetIndex);
_insertPhantom(reorderFlexId, dragTargetData, index); _insertPhantom(reorderFlexId, dragTargetData, dragTargetIndex);
} }
return isNewDragTarget; return isNewDragTarget;
} }
@override @override
void updateDragTargetData(String reorderFlexId, FlexDragTargetData dragTargetData, int dragTargetIndex) { void updateDragTargetData(
String reorderFlexId,
FlexDragTargetData dragTargetData,
int dragTargetIndex,
) {
phantomRecord?.updateInsertedIndex(dragTargetIndex); phantomRecord?.updateInsertedIndex(dragTargetIndex);
assert(phantomRecord != null); assert(phantomRecord != null);
if (phantomRecord!.toColumnId == reorderFlexId) { if (phantomRecord!.toColumnId == reorderFlexId) {
/// Update the existing phantom index /// Update the existing phantom index
_updatePhantom(phantomRecord!.toColumnId, dragTargetData, dragTargetIndex); _updatePhantom(
phantomRecord!.toColumnId, dragTargetData, dragTargetIndex);
} }
} }
} }
@ -204,7 +225,8 @@ class PhantomRecord {
if (fromColumnIndex == index) { if (fromColumnIndex == index) {
return; return;
} }
Log.debug('[$PhantomRecord] Update Column$fromColumnId remove position to $index'); Log.debug(
'[$PhantomRecord] Update Column$fromColumnId remove position to $index');
fromColumnIndex = index; fromColumnIndex = index;
} }
@ -213,7 +235,8 @@ class PhantomRecord {
return; return;
} }
Log.debug('[$PhantomRecord] Column$toColumnId update position $toColumnIndex -> $index'); Log.debug(
'[$PhantomRecord] Column$toColumnId update position $toColumnIndex -> $index');
toColumnIndex = index; toColumnIndex = index;
} }
@ -226,7 +249,8 @@ class PhantomRecord {
class PhantomColumnItem extends ColumnItem { class PhantomColumnItem extends ColumnItem {
final PassthroughPhantomContext phantomContext; final PassthroughPhantomContext phantomContext;
PhantomColumnItem(PassthroughPhantomContext insertedPhantom) : phantomContext = insertedPhantom; PhantomColumnItem(PassthroughPhantomContext insertedPhantom)
: phantomContext = insertedPhantom;
@override @override
bool get isPhantom => true; bool get isPhantom => true;
@ -236,8 +260,9 @@ class PhantomColumnItem extends ColumnItem {
Size? get feedbackSize => phantomContext.feedbackSize; Size? get feedbackSize => phantomContext.feedbackSize;
Widget get draggingWidget => Widget get draggingWidget => phantomContext.draggingWidget == null
phantomContext.draggingWidget == null ? const SizedBox() : phantomContext.draggingWidget!; ? const SizedBox()
: phantomContext.draggingWidget!;
} }
class PassthroughPhantomContext extends FakeDragTargetEventTrigger class PassthroughPhantomContext extends FakeDragTargetEventTrigger