diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart index 44a177ab8f..4ba0f01d18 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart @@ -1,25 +1,35 @@ import 'package:flutter/material.dart'; +import '../transitions.dart'; abstract class DragTargetData { int get draggingIndex; } abstract class ReorderFlexDraggableTargetBuilder { - Widget? build(BuildContext context, Widget child, DragTargetOnStarted onDragStarted, - DragTargetOnEnded onDragEnded, DragTargetWillAccpet onWillAccept); + Widget? build( + BuildContext context, + Widget child, + DragTargetOnStarted onDragStarted, + DragTargetOnEnded onDragEnded, + DragTargetWillAccpet onWillAccept, + AnimationController insertAnimationController, + AnimationController deleteAnimationController, + ); } /// -typedef DragTargetWillAccpet = bool Function(T dragTargetData); +typedef DragTargetWillAccpet = bool Function( + T dragTargetData); /// typedef DragTargetOnStarted = void Function(Widget, int, Size?); /// -typedef DragTargetOnEnded = void Function(T dragTargetData); +typedef DragTargetOnEnded = void Function( + T dragTargetData); /// [ReorderDragTarget] is a [DragTarget] that carries the index information of -/// the child. +/// the child. You could check out this link for more information. /// /// The size of the [ReorderDragTarget] will become zero when it start dragging. /// @@ -52,6 +62,9 @@ class ReorderDragTarget extends StatefulWidget { final ReorderFlexDraggableTargetBuilder? draggableTargetBuilder; + final AnimationController insertAnimationController; + final AnimationController deleteAnimationController; + ReorderDragTarget({ Key? key, required this.child, @@ -59,6 +72,8 @@ class ReorderDragTarget extends StatefulWidget { required this.onDragStarted, required this.onDragEnded, required this.onWillAccept, + required this.insertAnimationController, + required this.deleteAnimationController, this.onAccept, this.onLeave, this.draggableTargetBuilder, @@ -69,7 +84,8 @@ class ReorderDragTarget extends StatefulWidget { State> createState() => _ReorderDragTargetState(); } -class _ReorderDragTargetState extends State> { +class _ReorderDragTargetState + extends State> { /// Returns the dragTarget's size Size? _draggingFeedbackSize = Size.zero; @@ -101,7 +117,8 @@ class _ReorderDragTargetState extends State rejectedCandidates, ) { Widget feedbackBuilder = Builder(builder: (BuildContext context) { - BoxConstraints contentSizeConstraints = BoxConstraints.loose(_draggingFeedbackSize!); + BoxConstraints contentSizeConstraints = + BoxConstraints.loose(_draggingFeedbackSize!); return _buildDraggableFeedback( context, contentSizeConstraints, @@ -115,6 +132,8 @@ class _ReorderDragTargetState extends State( maxSimultaneousDrags: 1, @@ -141,14 +160,16 @@ class _ReorderDragTargetState extends State widget.onDragEnded(widget.dragTargetData), + onDraggableCanceled: (Velocity velocity, Offset offset) => + widget.onDragEnded(widget.dragTargetData), child: widget.child, ); return draggableWidget; } - Widget _buildDraggableFeedback(BuildContext context, BoxConstraints constraints, Widget child) { + Widget _buildDraggableFeedback( + BuildContext context, BoxConstraints constraints, Widget child) { return Transform( transform: Matrix4.rotationZ(0), alignment: FractionalOffset.topLeft, @@ -163,7 +184,7 @@ class _ReorderDragTargetState extends State entranceController.isCompleted; + phantomController = AnimationController( + value: 0, vsync: vsync, duration: reorderAnimationDuration); + + insertController = AnimationController( + value: 0.0, vsync: vsync, duration: reorderAnimationDuration); + + deleteController = AnimationController( + value: 0.0, vsync: vsync, duration: reorderAnimationDuration); + } void startDargging() { entranceController.value = 1.0; @@ -203,6 +235,8 @@ class DragAnimationController { void dispose() { entranceController.dispose(); phantomController.dispose(); + insertController.dispose(); + deleteController.dispose(); } } @@ -217,7 +251,9 @@ class IgnorePointerWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final sizedChild = useIntrinsicSize ? child : SizedBox(width: 0.0, height: 0.0, child: child); + final sizedChild = useIntrinsicSize + ? child + : SizedBox(width: 0.0, height: 0.0, child: child); return IgnorePointer( ignoring: true, child: Opacity( @@ -246,42 +282,7 @@ class PhantomWidget extends StatelessWidget { } } -class PhantomAnimateContorller { - // How long an animation to reorder an element in the list takes. - final Duration reorderAnimationDuration; - late AnimationController appearController; - late AnimationController disappearController; - - PhantomAnimateContorller({ - required TickerProvider vsync, - required this.reorderAnimationDuration, - required void Function(AnimationStatus) appearAnimateStatusChanged, - }) { - appearController = AnimationController(value: 1.0, vsync: vsync, duration: reorderAnimationDuration); - disappearController = AnimationController(value: 0, vsync: vsync, duration: reorderAnimationDuration); - appearController.addStatusListener(appearAnimateStatusChanged); - } - - bool get isAppearAnimationCompleted => appearController.isCompleted; - - void animateToNext() { - disappearController.reverse(from: 1.0); - appearController.forward(from: 0.0); - } - - void performReorderAnimation() { - disappearController.reverse(from: 0.1); - appearController.reverse(from: 0.0); - } - - void dispose() { - appearController.dispose(); - disappearController.dispose(); - } -} - abstract class FakeDragTargetEventTrigger { - void fakeOnDragStarted(VoidCallback callback); void fakeOnDragEnded(VoidCallback callback); } @@ -292,12 +293,15 @@ abstract class FakeDragTargetEventData { } class FakeDragTarget extends StatefulWidget { + final Duration animationDuration; final FakeDragTargetEventTrigger eventTrigger; final FakeDragTargetEventData eventData; final DragTargetOnStarted onDragStarted; final DragTargetOnEnded onDragEnded; final DragTargetWillAccpet onWillAccept; final Widget child; + final AnimationController insertAnimationController; + final AnimationController deleteAnimationController; const FakeDragTarget({ Key? key, required this.eventTrigger, @@ -305,34 +309,43 @@ class FakeDragTarget extends StatefulWidget { required this.onDragStarted, required this.onDragEnded, required this.onWillAccept, + required this.insertAnimationController, + required this.deleteAnimationController, required this.child, + this.animationDuration = const Duration(milliseconds: 250), }) : super(key: key); @override State> createState() => _FakeDragTargetState(); } -class _FakeDragTargetState extends State> { - bool isDragging = false; +class _FakeDragTargetState + extends State> + with TickerProviderStateMixin> { + bool simulateDragging = false; @override void initState() { - widget.eventTrigger.fakeOnDragStarted(() { - if (mounted) { - setState(() { - widget.onWillAccept(widget.eventData.dragTargetData as T); - - widget.onDragStarted( - widget.child, - widget.eventData.index, - widget.eventData.feedbackSize, - ); - - isDragging = true; + widget.insertAnimationController.addStatusListener( + (status) { + if (status != AnimationStatus.completed) return; + if (!mounted) return; + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + simulateDragging = true; + widget.deleteAnimationController.reverse(from: 1.0); + widget.onWillAccept(widget.eventData.dragTargetData as T); + widget.onDragStarted( + widget.child, + widget.eventData.index, + widget.eventData.feedbackSize, + ); + }); }); - } - }); + }, + ); + widget.insertAnimationController.forward(from: 0.0); widget.eventTrigger.fakeOnDragEnded(() { if (mounted) { widget.onDragEnded(widget.eventData.dragTargetData as T); @@ -344,10 +357,17 @@ class _FakeDragTargetState extends State /// [dragState] records the dragging state including dragStartIndex, and phantomIndex, etc. late DraggingState dragState; - /// [_dragAnimationController] controls the dragging animations - late DragAnimationController _dragAnimationController; + /// [_animation] controls the dragging animations + late DragTargetAnimation _animation; @override void initState() { dragState = DraggingState(widget.reorderFlexId); - _dragAnimationController = DragAnimationController( + _animation = DragTargetAnimation( reorderAnimationDuration: widget.config.reorderAnimationDuration, entranceAnimateStatusChanged: (status) { if (status == AnimationStatus.completed) { @@ -174,7 +174,7 @@ class ReorderFlexState extends State _attachedScrollPosition = null; } - _dragAnimationController.dispose(); + _animation.dispose(); super.dispose(); } @@ -183,7 +183,7 @@ class ReorderFlexState extends State /// dragging animation is completed. Otherwise, it will get called again /// when the animation finishs. - if (_dragAnimationController.isEntranceAnimationCompleted) { + if (_animation.entranceController.isCompleted) { dragState.removePhantom(); if (!isAcceptingNewTarget && dragState.didDragTargetMoveToNext()) { @@ -191,7 +191,7 @@ class ReorderFlexState extends State } dragState.moveDragTargetToNext(); - _dragAnimationController.animateToNext(); + _animation.animateToNext(); } } @@ -238,7 +238,7 @@ class ReorderFlexState extends State Widget appearSpace = _makeAppearSpace(dragSpace, feedbackSize); Widget disappearSpace = _makeDisappearSpace(dragSpace, feedbackSize); - /// When start dragging, the dragTarget, [BoardDragTarget], will + /// When start dragging, the dragTarget, [ReorderDragTarget], will /// return a [IgnorePointerWidget] which size is zero. if (dragState.isPhantomAboveDragTarget()) { //the phantom is moving down, i.e. the tile below the phantom is moving up @@ -337,6 +337,12 @@ class ReorderFlexState extends State }); }, onWillAccept: (FlexDragTargetData dragTargetData) { + Log.debug('Insert animation: ${_animation.deleteController.status}'); + + if (_animation.deleteController.isAnimating) { + return false; + } + assert(widget.dataSource.items.length > dragTargetIndex); if (_interceptDragTarget( @@ -366,6 +372,8 @@ class ReorderFlexState extends State (interceptor) => interceptor.onLeave(dragTargetData), ); }, + insertAnimationController: _animation.insertController, + deleteAnimationController: _animation.deleteController, draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder, child: child, ); @@ -387,7 +395,7 @@ class ReorderFlexState extends State Widget _makeAppearSpace(Widget child, Size? feedbackSize) { return makeAppearingWidget( child, - _dragAnimationController.entranceController, + _animation.entranceController, feedbackSize, widget.direction, ); @@ -396,7 +404,7 @@ class ReorderFlexState extends State Widget _makeDisappearSpace(Widget child, Size? feedbackSize) { return makeDisappearingWidget( child, - _dragAnimationController.phantomController, + _animation.phantomController, feedbackSize, widget.direction, ); @@ -409,7 +417,7 @@ class ReorderFlexState extends State ) { setState(() { dragState.startDragging(draggingWidget, dragIndex, feedbackSize); - _dragAnimationController.startDargging(); + _animation.startDargging(); }); } @@ -446,7 +454,7 @@ class ReorderFlexState extends State widget.onReorder.call(fromIndex, toIndex); } - _dragAnimationController.reverseAnimation(); + _animation.reverseAnimation(); } Widget _wrapScrollView({required Widget child}) { diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart index aa2d9550d3..6a5b91bdd1 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart @@ -6,6 +6,7 @@ import '../flex/drag_target.dart'; import '../flex/drag_target_inteceptor.dart'; import 'phantom_state.dart'; +@protected abstract class BoardPhantomControllerDelegate { BoardColumnDataController? controller(String columnId); @@ -34,7 +35,9 @@ abstract class BoardPhantomControllerDelegate { ); } -class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorderFlexDragTargetDelegate { +@protected +class BoardPhantomController extends OverlapDragTargetDelegate + with CrossReorderFlexDragTargetDelegate { PhantomRecord? phantomRecord; final BoardPhantomControllerDelegate delegate; final columnsState = ColumnPhantomStateController(); @@ -60,6 +63,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorder columnsState.setColumnIsDragging(columnId, false); } + /// Remove the phanton in the column when the column is end dragging. void columnEndDragging(String columnId) { columnsState.setColumnIsDragging(columnId, true); if (phantomRecord != null) { @@ -91,6 +95,8 @@ class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorder /// Remove the phantom in the column if it contains phantom void _removePhantom(String columnId) { + // columnsState.notifyDidRemovePhantom(columnId); + // columnsState.removeColumnListener(columnId); if (delegate.removePhantom(columnId)) { columnsState.notifyDidRemovePhantom(columnId); columnsState.removeColumnListener(columnId); @@ -114,12 +120,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorder PhantomColumnItem(phantomContext), ); - WidgetsBinding.instance.addPostFrameCallback((_) { - Future.delayed(const Duration(milliseconds: 100), () { - Log.debug('[$BoardPhantomController] notify $toColumnId to insert phantom'); - columnsState.notifyDidInsertPhantom(toColumnId); - }); - }); + columnsState.notifyDidInsertPhantom(toColumnId); } /// Reset or initial the [PhantomRecord] @@ -160,6 +161,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorder final isNewDragTarget = phantomRecord!.toColumnId != reorderFlexId; if (isNewDragTarget) { + /// Remove the phantom when the dragTarget is moved from one column to another column. _removePhantom(phantomRecord!.toColumnId); _resetPhantomRecord(reorderFlexId, dragTargetData, dragTargetIndex); _insertPhantom(reorderFlexId, dragTargetData, dragTargetIndex); @@ -188,6 +190,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorder return; } + /// Remove the phantom when the dragTarge is go back to the original column. _removePhantom(phantomRecord!.toColumnId); phantomRecord = null; } @@ -214,6 +217,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorder /// [toColumnId] the column that the phantom moves into /// [toColumnIndex] the index of the phantom moves into the column /// +@protected class PhantomRecord { final String fromColumnId; int fromColumnIndex; @@ -232,7 +236,8 @@ class PhantomRecord { if (fromColumnIndex == index) { return; } - Log.debug('[$PhantomRecord] Update Column$fromColumnId remove position to $index'); + Log.debug( + '[$PhantomRecord] Update Column$fromColumnId remove position to $index'); fromColumnIndex = index; } @@ -241,7 +246,8 @@ class PhantomRecord { return; } - Log.debug('[$PhantomRecord] Column$toColumnId update position $toColumnIndex -> $index'); + Log.debug( + '[$PhantomRecord] Column$toColumnId update position $toColumnIndex -> $index'); toColumnIndex = index; } @@ -254,7 +260,8 @@ class PhantomRecord { class PhantomColumnItem extends ColumnItem { final PassthroughPhantomContext phantomContext; - PhantomColumnItem(PassthroughPhantomContext insertedPhantom) : phantomContext = insertedPhantom; + PhantomColumnItem(PassthroughPhantomContext insertedPhantom) + : phantomContext = insertedPhantom; @override bool get isPhantom => true; @@ -264,8 +271,9 @@ class PhantomColumnItem extends ColumnItem { Size? get feedbackSize => phantomContext.feedbackSize; - Widget get draggingWidget => - phantomContext.draggingWidget == null ? const SizedBox() : phantomContext.draggingWidget!; + Widget get draggingWidget => phantomContext.draggingWidget == null + ? const SizedBox() + : phantomContext.draggingWidget!; @override String toString() { @@ -303,13 +311,9 @@ class PassthroughPhantomContext extends FakeDragTargetEventTrigger void fakeOnDragEnded(VoidCallback callback) { onDragEnded = callback; } - - @override - void fakeOnDragStarted(VoidCallback callback) { - onInserted = callback; - } } +@protected class PassthroughPhantomWidget extends PhantomWidget { final PassthroughPhantomContext passthroughPhantomContext; @@ -324,7 +328,9 @@ class PassthroughPhantomWidget extends PhantomWidget { ); } +@protected class PhantomDraggableBuilder extends ReorderFlexDraggableTargetBuilder { + PhantomDraggableBuilder(); @override Widget? build( BuildContext context, @@ -332,6 +338,8 @@ class PhantomDraggableBuilder extends ReorderFlexDraggableTargetBuilder { DragTargetOnStarted onDragStarted, DragTargetOnEnded onDragEnded, DragTargetWillAccpet onWillAccept, + AnimationController insertAnimationController, + AnimationController deleteAnimationController, ) { if (child is PassthroughPhantomWidget) { return FakeDragTarget( @@ -340,6 +348,8 @@ class PhantomDraggableBuilder extends ReorderFlexDraggableTargetBuilder { onDragStarted: onDragStarted, onDragEnded: onDragEnded, onWillAccept: onWillAccept, + insertAnimationController: insertAnimationController, + deleteAnimationController: deleteAnimationController, child: child, ); } else {