mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: support moving group
This commit is contained in:
parent
6c3978c3ba
commit
5763e289ef
@ -69,7 +69,6 @@ class BoardContent extends StatefulWidget {
|
||||
|
||||
class _BoardContentState extends State<BoardContent> {
|
||||
late AppFlowyBoardScrollController scrollManager;
|
||||
final Map<String, ValueKey> cardKeysCache = {};
|
||||
|
||||
final config = AppFlowyBoardConfig(
|
||||
groupBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
@ -139,7 +138,7 @@ class _BoardContentState extends State<BoardContent> {
|
||||
.read<BoardBloc>()
|
||||
.add(BoardEvent.endEditRow(editingRow.row.id));
|
||||
} else {
|
||||
scrollManager.scrollToBottom(editingRow.columnId, () {
|
||||
scrollManager.scrollToBottom(editingRow.columnId, (boardContext) {
|
||||
context
|
||||
.read<BoardBloc>()
|
||||
.add(BoardEvent.endEditRow(editingRow.row.id));
|
||||
@ -247,15 +246,8 @@ class _BoardContentState extends State<BoardContent> {
|
||||
);
|
||||
|
||||
final groupItemId = columnItem.id + group.id;
|
||||
ValueKey? key = cardKeysCache[groupItemId];
|
||||
if (key == null) {
|
||||
final newKey = ValueKey(groupItemId);
|
||||
cardKeysCache[groupItemId] = newKey;
|
||||
key = newKey;
|
||||
}
|
||||
|
||||
return AppFlowyGroupCard(
|
||||
key: key,
|
||||
key: ValueKey(groupItemId),
|
||||
margin: config.cardPadding,
|
||||
decoration: _makeBoxDecoration(context),
|
||||
child: BoardCard(
|
||||
|
@ -34,7 +34,7 @@ class _MyAppState extends State<MyApp> {
|
||||
appBar: AppBar(
|
||||
title: const Text('AppFlowy Board'),
|
||||
),
|
||||
body: _examples[_currentIndex],
|
||||
body: Container(color: Colors.white, child: _examples[_currentIndex]),
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
fixedColor: _bottomNavigationColor,
|
||||
showSelectedLabels: true,
|
||||
|
@ -21,8 +21,11 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
|
||||
},
|
||||
);
|
||||
|
||||
late AppFlowyBoardScrollController boardController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
boardController = AppFlowyBoardScrollController();
|
||||
final group1 = AppFlowyGroupData(id: "To Do", name: "To Do", items: [
|
||||
TextItem("Card 1"),
|
||||
TextItem("Card 2"),
|
||||
@ -67,12 +70,16 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
|
||||
child: _buildCard(groupItem),
|
||||
);
|
||||
},
|
||||
boardScrollController: boardController,
|
||||
footerBuilder: (context, columnData) {
|
||||
return AppFlowyGroupFooter(
|
||||
icon: const Icon(Icons.add, size: 20),
|
||||
title: const Text('New'),
|
||||
height: 50,
|
||||
margin: config.groupItemPadding,
|
||||
onAddButtonClick: () {
|
||||
boardController.scrollToBottom(columnData.id, (p0) {});
|
||||
},
|
||||
);
|
||||
},
|
||||
headerBuilder: (context, columnData) {
|
||||
|
@ -13,10 +13,8 @@ import '../rendering/board_overlay.dart';
|
||||
class AppFlowyBoardScrollController {
|
||||
AppFlowyBoardState? _groupState;
|
||||
|
||||
void scrollToBottom(String groupId, VoidCallback? completed) {
|
||||
_groupState
|
||||
?.getReorderFlexState(groupId: groupId)
|
||||
?.scrollToBottom(completed);
|
||||
void scrollToBottom(String groupId, void Function(BuildContext)? completed) {
|
||||
_groupState?.reorderFlexActionMap[groupId]?.scrollToBottom(completed);
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,7 +131,7 @@ class AppFlowyBoard extends StatelessWidget {
|
||||
dataController: controller,
|
||||
scrollController: scrollController,
|
||||
scrollManager: boardScrollController,
|
||||
columnsState: _groupState,
|
||||
groupState: _groupState,
|
||||
background: background,
|
||||
delegate: _phantomController,
|
||||
groupConstraints: groupConstraints,
|
||||
@ -158,7 +156,7 @@ class _AppFlowyBoardContent extends StatefulWidget {
|
||||
final ReorderFlexConfig reorderFlexConfig;
|
||||
final BoxConstraints groupConstraints;
|
||||
final AppFlowyBoardScrollController? scrollManager;
|
||||
final AppFlowyBoardState columnsState;
|
||||
final AppFlowyBoardState groupState;
|
||||
final AppFlowyBoardCardBuilder cardBuilder;
|
||||
final AppFlowyBoardHeaderBuilder? headerBuilder;
|
||||
final AppFlowyBoardFooterBuilder? footerBuilder;
|
||||
@ -171,7 +169,7 @@ class _AppFlowyBoardContent extends StatefulWidget {
|
||||
required this.delegate,
|
||||
required this.dataController,
|
||||
required this.scrollManager,
|
||||
required this.columnsState,
|
||||
required this.groupState,
|
||||
this.scrollController,
|
||||
this.background,
|
||||
required this.groupConstraints,
|
||||
@ -192,8 +190,6 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
||||
GlobalKey(debugLabel: '$_AppFlowyBoardContent overlay key');
|
||||
late BoardOverlayEntry _overlayEntry;
|
||||
|
||||
final Map<String, GlobalObjectKey> _reorderFlexKeys = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_overlayEntry = BoardOverlayEntry(
|
||||
@ -202,7 +198,7 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
||||
reorderFlexId: widget.dataController.identifier,
|
||||
acceptedReorderFlexId: widget.dataController.groupIds,
|
||||
delegate: widget.delegate,
|
||||
columnsState: widget.columnsState,
|
||||
columnsState: widget.groupState,
|
||||
);
|
||||
|
||||
final reorderFlex = ReorderFlex(
|
||||
@ -212,7 +208,7 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
||||
dataSource: widget.dataController,
|
||||
direction: Axis.horizontal,
|
||||
interceptor: interceptor,
|
||||
reorderable: false,
|
||||
reorderable: true,
|
||||
children: _buildColumns(),
|
||||
);
|
||||
|
||||
@ -257,18 +253,16 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
||||
dataController: widget.dataController,
|
||||
);
|
||||
|
||||
if (_reorderFlexKeys[columnData.id] == null) {
|
||||
_reorderFlexKeys[columnData.id] = GlobalObjectKey(columnData.id);
|
||||
}
|
||||
final reorderFlexAction = ReorderFlexActionImpl();
|
||||
widget.groupState.reorderFlexActionMap[columnData.id] =
|
||||
reorderFlexAction;
|
||||
|
||||
GlobalObjectKey reorderFlexKey = _reorderFlexKeys[columnData.id]!;
|
||||
return ChangeNotifierProvider.value(
|
||||
key: ValueKey(columnData.id),
|
||||
value: widget.dataController.getGroupController(columnData.id),
|
||||
child: Consumer<AppFlowyGroupController>(
|
||||
builder: (context, value, child) {
|
||||
final boardColumn = AppFlowyBoardGroup(
|
||||
reorderFlexKey: reorderFlexKey,
|
||||
// key: PageStorageKey<String>(columnData.id),
|
||||
margin: _marginFromIndex(columnIndex),
|
||||
itemMargin: widget.config.groupItemPadding,
|
||||
@ -281,11 +275,11 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
||||
onReorder: widget.dataController.moveGroupItem,
|
||||
cornerRadius: widget.config.cornerRadius,
|
||||
backgroundColor: widget.config.groupBackgroundColor,
|
||||
dragStateStorage: widget.columnsState,
|
||||
dragTargetIndexKeyStorage: widget.columnsState,
|
||||
dragStateStorage: widget.groupState,
|
||||
dragTargetKeys: widget.groupState,
|
||||
reorderFlexAction: reorderFlexAction,
|
||||
);
|
||||
|
||||
widget.columnsState.addGroup(columnData.id, boardColumn);
|
||||
return ConstrainedBox(
|
||||
constraints: widget.groupConstraints,
|
||||
child: boardColumn,
|
||||
@ -356,71 +350,61 @@ class AppFlowyGroupContext {
|
||||
}
|
||||
|
||||
class AppFlowyBoardState extends DraggingStateStorage
|
||||
with ReorderDragTargetIndexKeyStorage {
|
||||
with ReorderDragTargeKeys {
|
||||
final Map<String, DraggingState> groupDragStates = {};
|
||||
final Map<String, Map<String, GlobalObjectKey>> groupDragTargetKeys = {};
|
||||
|
||||
/// Quick access to the [AppFlowyBoardGroup], the [GlobalKey] is bind to the
|
||||
/// AppFlowyBoardGroup's [ReorderFlex] widget.
|
||||
final Map<String, GlobalKey> groupReorderFlexKeys = {};
|
||||
final Map<String, DraggingState> groupDragStates = {};
|
||||
final Map<String, Map<String, GlobalObjectKey>> groupDragDragTargets = {};
|
||||
|
||||
void addGroup(String groupId, AppFlowyBoardGroup groupWidget) {
|
||||
groupReorderFlexKeys[groupId] = groupWidget.reorderFlexKey;
|
||||
}
|
||||
|
||||
ReorderFlexState? getReorderFlexState({required String groupId}) {
|
||||
final flexGlobalKey = groupReorderFlexKeys[groupId];
|
||||
if (flexGlobalKey == null) return null;
|
||||
if (flexGlobalKey.currentState is! ReorderFlexState) return null;
|
||||
final state = flexGlobalKey.currentState as ReorderFlexState;
|
||||
return state;
|
||||
}
|
||||
|
||||
ReorderFlex? getReorderFlex({required String groupId}) {
|
||||
final flexGlobalKey = groupReorderFlexKeys[groupId];
|
||||
if (flexGlobalKey == null) return null;
|
||||
if (flexGlobalKey.currentWidget is! ReorderFlex) return null;
|
||||
final widget = flexGlobalKey.currentWidget as ReorderFlex;
|
||||
return widget;
|
||||
}
|
||||
final Map<String, ReorderFlexActionImpl> reorderFlexActionMap = {};
|
||||
|
||||
@override
|
||||
DraggingState? read(String reorderFlexId) {
|
||||
DraggingState? readState(String reorderFlexId) {
|
||||
return groupDragStates[reorderFlexId];
|
||||
}
|
||||
|
||||
@override
|
||||
void write(String reorderFlexId, DraggingState state) {
|
||||
void insertState(String reorderFlexId, DraggingState state) {
|
||||
Log.trace('$reorderFlexId Write dragging state: $state');
|
||||
groupDragStates[reorderFlexId] = state;
|
||||
}
|
||||
|
||||
@override
|
||||
void remove(String reorderFlexId) {
|
||||
void removeState(String reorderFlexId) {
|
||||
groupDragStates.remove(reorderFlexId);
|
||||
}
|
||||
|
||||
@override
|
||||
void addKey(
|
||||
void insertDragTarget(
|
||||
String reorderFlexId,
|
||||
String key,
|
||||
GlobalObjectKey<State<StatefulWidget>> value,
|
||||
) {
|
||||
Map<String, GlobalObjectKey>? group = groupDragDragTargets[reorderFlexId];
|
||||
Map<String, GlobalObjectKey>? group = groupDragTargetKeys[reorderFlexId];
|
||||
if (group == null) {
|
||||
group = {};
|
||||
groupDragDragTargets[reorderFlexId] = group;
|
||||
groupDragTargetKeys[reorderFlexId] = group;
|
||||
}
|
||||
group[key] = value;
|
||||
}
|
||||
|
||||
@override
|
||||
GlobalObjectKey<State<StatefulWidget>>? readKey(
|
||||
String reorderFlexId, String key) {
|
||||
Map<String, GlobalObjectKey>? group = groupDragDragTargets[reorderFlexId];
|
||||
GlobalObjectKey<State<StatefulWidget>>? getDragTarget(
|
||||
String reorderFlexId,
|
||||
String key,
|
||||
) {
|
||||
Map<String, GlobalObjectKey>? group = groupDragTargetKeys[reorderFlexId];
|
||||
if (group != null) {
|
||||
return group[key];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void removeDragTarget(String reorderFlexId) {
|
||||
groupDragTargetKeys.remove(reorderFlexId);
|
||||
}
|
||||
}
|
||||
|
||||
class ReorderFlexActionImpl extends ReorderFlexAction {}
|
||||
|
@ -90,21 +90,21 @@ class AppFlowyBoardGroup extends StatefulWidget {
|
||||
|
||||
final DraggingStateStorage? dragStateStorage;
|
||||
|
||||
final ReorderDragTargetIndexKeyStorage? dragTargetIndexKeyStorage;
|
||||
final ReorderDragTargeKeys? dragTargetKeys;
|
||||
|
||||
final GlobalObjectKey reorderFlexKey;
|
||||
final ReorderFlexAction? reorderFlexAction;
|
||||
|
||||
const AppFlowyBoardGroup({
|
||||
Key? key,
|
||||
required this.reorderFlexKey,
|
||||
this.headerBuilder,
|
||||
this.footerBuilder,
|
||||
required this.cardBuilder,
|
||||
required this.onReorder,
|
||||
required this.dataSource,
|
||||
required this.phantomController,
|
||||
this.reorderFlexAction,
|
||||
this.dragStateStorage,
|
||||
this.dragTargetIndexKeyStorage,
|
||||
this.dragTargetKeys,
|
||||
this.scrollController,
|
||||
this.onDragStarted,
|
||||
this.onDragEnded,
|
||||
@ -146,9 +146,9 @@ class _AppFlowyBoardGroupState extends State<AppFlowyBoardGroup> {
|
||||
);
|
||||
|
||||
Widget reorderFlex = ReorderFlex(
|
||||
key: widget.reorderFlexKey,
|
||||
key: ValueKey(widget.groupId),
|
||||
dragStateStorage: widget.dragStateStorage,
|
||||
dragTargetIndexKeyStorage: widget.dragTargetIndexKeyStorage,
|
||||
dragTargetKeys: widget.dragTargetKeys,
|
||||
scrollController: widget.scrollController,
|
||||
config: widget.config,
|
||||
onDragStarted: (index) {
|
||||
@ -168,6 +168,7 @@ class _AppFlowyBoardGroupState extends State<AppFlowyBoardGroup> {
|
||||
},
|
||||
dataSource: widget.dataSource,
|
||||
interceptor: interceptor,
|
||||
reorderFlexAction: widget.reorderFlexAction,
|
||||
children: children,
|
||||
);
|
||||
|
||||
|
@ -41,7 +41,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
||||
void updateGroupName(String newName) {
|
||||
if (groupData.headerData.groupName != newName) {
|
||||
groupData.headerData.groupName = newName;
|
||||
notifyListeners();
|
||||
_notify();
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
||||
Log.debug('[$AppFlowyGroupController] $groupData remove item at $index');
|
||||
final item = groupData._items.removeAt(index);
|
||||
if (notify) {
|
||||
notifyListeners();
|
||||
_notify();
|
||||
}
|
||||
return item;
|
||||
}
|
||||
@ -81,7 +81,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
||||
'[$AppFlowyGroupController] $groupData move item from $fromIndex to $toIndex');
|
||||
final item = groupData._items.removeAt(fromIndex);
|
||||
groupData._items.insert(toIndex, item);
|
||||
notifyListeners();
|
||||
_notify();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -102,7 +102,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
||||
groupData._items.add(item);
|
||||
}
|
||||
|
||||
if (notify) notifyListeners();
|
||||
if (notify) _notify();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -112,7 +112,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
||||
return false;
|
||||
} else {
|
||||
groupData._items.add(item);
|
||||
if (notify) notifyListeners();
|
||||
if (notify) _notify();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -135,7 +135,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
||||
'[$AppFlowyGroupController] $groupData replace $removedItem with $newItem at $index');
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
_notify();
|
||||
}
|
||||
|
||||
void replaceOrInsertItem(AppFlowyGroupItem newItem) {
|
||||
@ -143,10 +143,10 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
||||
if (index != -1) {
|
||||
groupData._items.removeAt(index);
|
||||
groupData._items.insert(index, newItem);
|
||||
notifyListeners();
|
||||
_notify();
|
||||
} else {
|
||||
groupData._items.add(newItem);
|
||||
notifyListeners();
|
||||
_notify();
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,6 +154,10 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
||||
return groupData._items.indexWhere((element) => element.id == item.id) !=
|
||||
-1;
|
||||
}
|
||||
|
||||
void _notify() {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// [AppFlowyGroupData] represents the data of each group of the Board.
|
||||
|
@ -69,9 +69,9 @@ class FlexDragTargetData extends DragTargetData {
|
||||
}
|
||||
|
||||
abstract class DraggingStateStorage {
|
||||
void write(String reorderFlexId, DraggingState state);
|
||||
void remove(String reorderFlexId);
|
||||
DraggingState? read(String reorderFlexId);
|
||||
void insertState(String reorderFlexId, DraggingState state);
|
||||
void removeState(String reorderFlexId);
|
||||
DraggingState? readState(String reorderFlexId);
|
||||
}
|
||||
|
||||
class DraggingState {
|
||||
|
@ -73,11 +73,15 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
|
||||
final ReorderFlexDraggableTargetBuilder? draggableTargetBuilder;
|
||||
|
||||
final AnimationController insertAnimationController;
|
||||
|
||||
final AnimationController deleteAnimationController;
|
||||
|
||||
final bool useMoveAnimation;
|
||||
|
||||
final bool draggable;
|
||||
|
||||
final double draggingOpacity;
|
||||
|
||||
const ReorderDragTarget({
|
||||
Key? key,
|
||||
required this.child,
|
||||
@ -94,6 +98,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
|
||||
this.onAccept,
|
||||
this.onLeave,
|
||||
this.draggableTargetBuilder,
|
||||
this.draggingOpacity = 0.3,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@ -164,6 +169,7 @@ class _ReorderDragTargetState<T extends DragTargetData>
|
||||
feedback: feedbackBuilder,
|
||||
childWhenDragging: IgnorePointerWidget(
|
||||
useIntrinsicSize: !widget.useMoveAnimation,
|
||||
opacity: widget.draggingOpacity,
|
||||
child: widget.child,
|
||||
),
|
||||
onDragStarted: () {
|
||||
@ -195,7 +201,10 @@ class _ReorderDragTargetState<T extends DragTargetData>
|
||||
}
|
||||
|
||||
Widget _buildDraggableFeedback(
|
||||
BuildContext context, BoxConstraints constraints, Widget child) {
|
||||
BuildContext context,
|
||||
BoxConstraints constraints,
|
||||
Widget child,
|
||||
) {
|
||||
return Transform(
|
||||
transform: Matrix4.rotationZ(0),
|
||||
alignment: FractionalOffset.topLeft,
|
||||
@ -205,7 +214,7 @@ class _ReorderDragTargetState<T extends DragTargetData>
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: ConstrainedBox(
|
||||
constraints: constraints,
|
||||
child: Opacity(opacity: 0.3, child: child),
|
||||
child: Opacity(opacity: widget.draggingOpacity, child: child),
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -274,8 +283,11 @@ class DragTargetAnimation {
|
||||
class IgnorePointerWidget extends StatelessWidget {
|
||||
final Widget? child;
|
||||
final bool useIntrinsicSize;
|
||||
final double opacity;
|
||||
|
||||
const IgnorePointerWidget({
|
||||
required this.child,
|
||||
required this.opacity,
|
||||
this.useIntrinsicSize = false,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
@ -286,11 +298,10 @@ class IgnorePointerWidget extends StatelessWidget {
|
||||
? child
|
||||
: SizedBox(width: 0.0, height: 0.0, child: child);
|
||||
|
||||
final opacity = useIntrinsicSize ? 0.3 : 0.0;
|
||||
return IgnorePointer(
|
||||
ignoring: true,
|
||||
child: Opacity(
|
||||
opacity: opacity,
|
||||
opacity: useIntrinsicSize ? opacity : 0.0,
|
||||
child: sizedChild,
|
||||
),
|
||||
);
|
||||
@ -300,8 +311,10 @@ class IgnorePointerWidget extends StatelessWidget {
|
||||
class AbsorbPointerWidget extends StatelessWidget {
|
||||
final Widget? child;
|
||||
final bool useIntrinsicSize;
|
||||
final double opacity;
|
||||
const AbsorbPointerWidget({
|
||||
required this.child,
|
||||
required this.opacity,
|
||||
this.useIntrinsicSize = false,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
@ -312,10 +325,9 @@ class AbsorbPointerWidget extends StatelessWidget {
|
||||
? child
|
||||
: SizedBox(width: 0.0, height: 0.0, child: child);
|
||||
|
||||
final opacity = useIntrinsicSize ? 0.3 : 0.0;
|
||||
return AbsorbPointer(
|
||||
child: Opacity(
|
||||
opacity: opacity,
|
||||
opacity: useIntrinsicSize ? opacity : 0.0,
|
||||
child: sizedChild,
|
||||
),
|
||||
);
|
||||
@ -494,6 +506,7 @@ class _FakeDragTargetState<T extends DragTargetData>
|
||||
sizeFactor: widget.deleteAnimationController,
|
||||
axis: Axis.vertical,
|
||||
child: AbsorbPointerWidget(
|
||||
opacity: 0.3,
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
@ -503,6 +516,7 @@ class _FakeDragTargetState<T extends DragTargetData>
|
||||
axis: Axis.vertical,
|
||||
child: AbsorbPointerWidget(
|
||||
useIntrinsicSize: true,
|
||||
opacity: 0.3,
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
|
@ -81,7 +81,7 @@ class OverlappingDragTargetInterceptor extends DragTargetInterceptor {
|
||||
delegate.cancel();
|
||||
} else {
|
||||
// Ignore the event if the dragTarget overlaps with the other column's dragTargets.
|
||||
final columnKeys = columnsState.groupDragDragTargets[dragTargetId];
|
||||
final columnKeys = columnsState.groupDragTargetKeys[dragTargetId];
|
||||
if (columnKeys != null) {
|
||||
final keys = columnKeys.values.toList();
|
||||
if (dragTargetData.isOverlapWithWidgets(keys)) {
|
||||
@ -102,8 +102,7 @@ class OverlappingDragTargetInterceptor extends DragTargetInterceptor {
|
||||
delegate.dragTargetDidMoveToReorderFlex(
|
||||
dragTargetId, dragTargetData, index);
|
||||
|
||||
columnsState
|
||||
.getReorderFlexState(groupId: dragTargetId)
|
||||
columnsState.reorderFlexActionMap[dragTargetId]
|
||||
?.resetDragTargetIndex(index);
|
||||
}
|
||||
});
|
||||
|
@ -31,9 +31,32 @@ abstract class ReoderFlexItem {
|
||||
String get id;
|
||||
}
|
||||
|
||||
abstract class ReorderDragTargetIndexKeyStorage {
|
||||
void addKey(String reorderFlexId, String key, GlobalObjectKey value);
|
||||
GlobalObjectKey? readKey(String reorderFlexId, String key);
|
||||
/// Cache each dragTarget's key.
|
||||
/// For the moment, the key is used to locate the render object that will
|
||||
/// be passed in the [ScrollPosition]'s [ensureVisible] function.
|
||||
///
|
||||
abstract class ReorderDragTargeKeys {
|
||||
void insertDragTarget(
|
||||
String reorderFlexId,
|
||||
String key,
|
||||
GlobalObjectKey value,
|
||||
);
|
||||
|
||||
GlobalObjectKey? getDragTarget(
|
||||
String reorderFlexId,
|
||||
String key,
|
||||
);
|
||||
|
||||
void removeDragTarget(String reorderFlexId);
|
||||
}
|
||||
|
||||
abstract class ReorderFlexAction {
|
||||
void Function(void Function(BuildContext)?)? _scrollToBottom;
|
||||
void Function(void Function(BuildContext)?) get scrollToBottom =>
|
||||
_scrollToBottom!;
|
||||
|
||||
void Function(int)? _resetDragTargetIndex;
|
||||
void Function(int) get resetDragTargetIndex => _resetDragTargetIndex!;
|
||||
}
|
||||
|
||||
class ReorderFlexConfig {
|
||||
@ -78,9 +101,12 @@ class ReorderFlex extends StatefulWidget {
|
||||
|
||||
final DragTargetInterceptor? interceptor;
|
||||
|
||||
/// Save the [DraggingState] if the current [ReorderFlex] get reinitialize.
|
||||
final DraggingStateStorage? dragStateStorage;
|
||||
|
||||
final ReorderDragTargetIndexKeyStorage? dragTargetIndexKeyStorage;
|
||||
final ReorderDragTargeKeys? dragTargetKeys;
|
||||
|
||||
final ReorderFlexAction? reorderFlexAction;
|
||||
|
||||
final bool reorderable;
|
||||
|
||||
@ -93,10 +119,11 @@ class ReorderFlex extends StatefulWidget {
|
||||
required this.onReorder,
|
||||
this.reorderable = true,
|
||||
this.dragStateStorage,
|
||||
this.dragTargetIndexKeyStorage,
|
||||
this.dragTargetKeys,
|
||||
this.onDragStarted,
|
||||
this.onDragEnded,
|
||||
this.interceptor,
|
||||
this.reorderFlexAction,
|
||||
this.direction = Axis.vertical,
|
||||
}) : assert(children.every((Widget w) => w.key != null),
|
||||
'All child must have a key.'),
|
||||
@ -109,7 +136,7 @@ class ReorderFlex extends StatefulWidget {
|
||||
}
|
||||
|
||||
class ReorderFlexState extends State<ReorderFlex>
|
||||
with ReorderFlexMinxi, TickerProviderStateMixin<ReorderFlex> {
|
||||
with ReorderFlexMixin, TickerProviderStateMixin<ReorderFlex> {
|
||||
/// Controls scrolls and measures scroll progress.
|
||||
late ScrollController _scrollController;
|
||||
|
||||
@ -131,11 +158,11 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
void initState() {
|
||||
_notifier = ReorderFlexNotifier();
|
||||
final flexId = widget.reorderFlexId;
|
||||
dragState = widget.dragStateStorage?.read(flexId) ??
|
||||
dragState = widget.dragStateStorage?.readState(flexId) ??
|
||||
DraggingState(widget.reorderFlexId);
|
||||
Log.trace('[DragTarget] init dragState: $dragState');
|
||||
|
||||
widget.dragStateStorage?.remove(flexId);
|
||||
widget.dragStateStorage?.removeState(flexId);
|
||||
|
||||
_animation = DragTargetAnimation(
|
||||
reorderAnimationDuration: widget.config.reorderAnimationDuration,
|
||||
@ -148,6 +175,14 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
widget.reorderFlexAction?._scrollToBottom = (fn) {
|
||||
scrollToBottom(fn);
|
||||
};
|
||||
|
||||
widget.reorderFlexAction?._resetDragTargetIndex = (index) {
|
||||
resetDragTargetIndex(index);
|
||||
};
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -184,7 +219,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
|
||||
final indexKey = GlobalObjectKey(child.key!);
|
||||
// Save the index key for quick access
|
||||
widget.dragTargetIndexKeyStorage?.addKey(
|
||||
widget.dragTargetKeys?.insertDragTarget(
|
||||
widget.reorderFlexId,
|
||||
item.id,
|
||||
indexKey,
|
||||
@ -212,6 +247,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
}
|
||||
|
||||
_animation.dispose();
|
||||
widget.dragTargetKeys?.removeDragTarget(widget.reorderFlexId);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -236,8 +272,12 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
/// [childIndex]: the index of the child in a list
|
||||
Widget _wrap(Widget child, int childIndex, GlobalObjectKey indexKey) {
|
||||
return Builder(builder: (context) {
|
||||
final ReorderDragTarget dragTarget =
|
||||
_buildDragTarget(context, child, childIndex, indexKey);
|
||||
final ReorderDragTarget dragTarget = _buildDragTarget(
|
||||
context,
|
||||
child,
|
||||
childIndex,
|
||||
indexKey,
|
||||
);
|
||||
int shiftedIndex = childIndex;
|
||||
|
||||
if (dragState.isOverlapWithPhantom()) {
|
||||
@ -342,6 +382,15 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
});
|
||||
}
|
||||
|
||||
static ReorderFlexState of(BuildContext context) {
|
||||
if (context is StatefulElement && context.state is ReorderFlexState) {
|
||||
return context.state as ReorderFlexState;
|
||||
}
|
||||
final ReorderFlexState? result =
|
||||
context.findAncestorStateOfType<ReorderFlexState>();
|
||||
return result!;
|
||||
}
|
||||
|
||||
ReorderDragTarget _buildDragTarget(
|
||||
BuildContext builderContext,
|
||||
Widget child,
|
||||
@ -364,7 +413,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
"[DragTarget] Group:[${widget.dataSource.identifier}] start dragging item at $draggingIndex");
|
||||
_startDragging(draggingWidget, draggingIndex, size);
|
||||
widget.onDragStarted?.call(draggingIndex);
|
||||
widget.dragStateStorage?.remove(widget.reorderFlexId);
|
||||
widget.dragStateStorage?.removeState(widget.reorderFlexId);
|
||||
},
|
||||
onDragMoved: (dragTargetData, offset) {
|
||||
dragTargetData.dragTargetOffset = offset;
|
||||
@ -435,6 +484,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder,
|
||||
useMoveAnimation: widget.config.useMoveAnimation,
|
||||
draggable: widget.reorderable,
|
||||
draggingOpacity: widget.config.draggingWidgetOpacity,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
@ -487,7 +537,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
}
|
||||
|
||||
dragState.setStartDraggingIndex(dragTargetIndex);
|
||||
widget.dragStateStorage?.write(
|
||||
widget.dragStateStorage?.insertState(
|
||||
widget.reorderFlexId,
|
||||
dragState,
|
||||
);
|
||||
@ -581,46 +631,46 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
}
|
||||
}
|
||||
|
||||
void scrollToBottom(VoidCallback? completed) {
|
||||
void scrollToBottom(void Function(BuildContext)? completed) {
|
||||
if (_scrolling) {
|
||||
completed?.call();
|
||||
completed?.call(context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (widget.dataSource.items.isNotEmpty) {
|
||||
final item = widget.dataSource.items.last;
|
||||
final indexKey = widget.dragTargetIndexKeyStorage?.readKey(
|
||||
final dragTargetKey = widget.dragTargetKeys?.getDragTarget(
|
||||
widget.reorderFlexId,
|
||||
item.id,
|
||||
);
|
||||
if (indexKey == null) {
|
||||
completed?.call();
|
||||
if (dragTargetKey == null) {
|
||||
completed?.call(context);
|
||||
return;
|
||||
}
|
||||
|
||||
final indexContext = indexKey.currentContext;
|
||||
if (indexContext == null || _scrollController.hasClients == false) {
|
||||
completed?.call();
|
||||
final dragTargetContext = dragTargetKey.currentContext;
|
||||
if (dragTargetContext == null || _scrollController.hasClients == false) {
|
||||
completed?.call(context);
|
||||
return;
|
||||
}
|
||||
|
||||
final renderObject = indexContext.findRenderObject();
|
||||
if (renderObject != null) {
|
||||
final dragTargetRenderObject = dragTargetContext.findRenderObject();
|
||||
if (dragTargetRenderObject != null) {
|
||||
_scrolling = true;
|
||||
_scrollController.position
|
||||
.ensureVisible(
|
||||
renderObject,
|
||||
dragTargetRenderObject,
|
||||
alignment: 0.5,
|
||||
duration: const Duration(milliseconds: 120),
|
||||
)
|
||||
.then((value) {
|
||||
setState(() {
|
||||
_scrolling = false;
|
||||
completed?.call();
|
||||
completed?.call(context);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
completed?.call();
|
||||
completed?.call(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import 'package:flutter/widgets.dart';
|
||||
import '../transitions.dart';
|
||||
import 'drag_target.dart';
|
||||
|
||||
mixin ReorderFlexMinxi {
|
||||
mixin ReorderFlexMixin {
|
||||
@protected
|
||||
Widget makeAppearingWidget(
|
||||
Widget child,
|
||||
|
@ -98,6 +98,7 @@ pub struct MoveGroupPayloadPB {
|
||||
pub to_group_id: String,
|
||||
}
|
||||
|
||||
#[derive(debug, Debug)]
|
||||
pub struct MoveGroupParams {
|
||||
pub view_id: String,
|
||||
pub from_group_id: String,
|
||||
|
@ -173,6 +173,7 @@ impl GridViewRevisionEditor {
|
||||
Ok(groups.into_iter().map(GroupPB::from).collect())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", err)]
|
||||
pub(crate) async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> {
|
||||
let _ = self
|
||||
.group_controller
|
||||
@ -180,7 +181,7 @@ impl GridViewRevisionEditor {
|
||||
.await
|
||||
.move_group(¶ms.from_group_id, ¶ms.to_group_id)?;
|
||||
match self.group_controller.read().await.get_group(¶ms.from_group_id) {
|
||||
None => {}
|
||||
None => tracing::warn!("Can not find the group with id: {}", params.from_group_id),
|
||||
Some((index, group)) => {
|
||||
let inserted_group = InsertedGroupPB {
|
||||
group: GroupPB::from(group),
|
||||
|
@ -135,6 +135,7 @@ where
|
||||
self.mut_configuration(|configuration| {
|
||||
let from_index = configuration.groups.iter().position(|group| group.id == from_id);
|
||||
let to_index = configuration.groups.iter().position(|group| group.id == to_id);
|
||||
tracing::trace!("Swap group index:{:?} with index:{:?}", from_index, to_index);
|
||||
if let (Some(from), Some(to)) = (from_index, to_index) {
|
||||
configuration.groups.swap(from, to);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user