mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
commit
591d8a3872
@ -45,5 +45,7 @@
|
||||
<array>
|
||||
<string>en</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -89,18 +89,30 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
didCreateRow: (String groupId, RowPB row, int? index) {
|
||||
didCreateRow: (group, row, int? index) {
|
||||
emit(state.copyWith(
|
||||
editingRow: Some(BoardEditingRow(
|
||||
columnId: groupId,
|
||||
group: group,
|
||||
row: row,
|
||||
index: index,
|
||||
)),
|
||||
));
|
||||
_groupItemStartEditing(group, row, true);
|
||||
},
|
||||
endEditRow: (rowId) {
|
||||
startEditingRow: (group, row) {
|
||||
emit(state.copyWith(
|
||||
editingRow: Some(BoardEditingRow(
|
||||
group: group,
|
||||
row: row,
|
||||
index: null,
|
||||
)),
|
||||
));
|
||||
_groupItemStartEditing(group, row, true);
|
||||
},
|
||||
endEditingRow: (rowId) {
|
||||
state.editingRow.fold(() => null, (editingRow) {
|
||||
assert(editingRow.row.id == rowId);
|
||||
_groupItemStartEditing(editingRow.group, editingRow.row, false);
|
||||
emit(state.copyWith(editingRow: none()));
|
||||
});
|
||||
},
|
||||
@ -122,6 +134,24 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
);
|
||||
}
|
||||
|
||||
void _groupItemStartEditing(GroupPB group, RowPB row, bool isEdit) {
|
||||
final fieldContext = fieldController.getField(group.fieldId);
|
||||
if (fieldContext == null) {
|
||||
Log.warn("FieldContext should not be null");
|
||||
return;
|
||||
}
|
||||
|
||||
boardController.enableGroupDragging(!isEdit);
|
||||
// boardController.updateGroupItem(
|
||||
// group.groupId,
|
||||
// GroupItem(
|
||||
// row: row,
|
||||
// fieldContext: fieldContext,
|
||||
// isDraggable: !isEdit,
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
|
||||
void _moveRow(RowPB? fromRow, String columnId, RowPB? toRow) {
|
||||
if (fromRow != null) {
|
||||
_rowService
|
||||
@ -136,11 +166,11 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
}
|
||||
}
|
||||
|
||||
void _moveGroup(String fromColumnId, String toColumnId) {
|
||||
void _moveGroup(String fromGroupId, String toGroupId) {
|
||||
_rowService
|
||||
.moveGroup(
|
||||
fromGroupId: fromColumnId,
|
||||
toGroupId: toColumnId,
|
||||
fromGroupId: fromGroupId,
|
||||
toGroupId: toGroupId,
|
||||
)
|
||||
.then((result) {
|
||||
result.fold((l) => null, (r) => add(BoardEvent.didReceiveError(r)));
|
||||
@ -156,7 +186,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void initializeGroups(List<GroupPB> groups) {
|
||||
void initializeGroups(List<GroupPB> groupsData) {
|
||||
for (var controller in groupControllers.values) {
|
||||
controller.dispose();
|
||||
}
|
||||
@ -164,27 +194,27 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
boardController.clear();
|
||||
|
||||
//
|
||||
List<AppFlowyGroupData> columns = groups
|
||||
List<AppFlowyGroupData> groups = groupsData
|
||||
.where((group) => fieldController.getField(group.fieldId) != null)
|
||||
.map((group) {
|
||||
return AppFlowyGroupData(
|
||||
id: group.groupId,
|
||||
name: group.desc,
|
||||
items: _buildRows(group),
|
||||
customData: BoardCustomData(
|
||||
items: _buildGroupItems(group),
|
||||
customData: GroupData(
|
||||
group: group,
|
||||
fieldContext: fieldController.getField(group.fieldId)!,
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
boardController.addGroups(columns);
|
||||
boardController.addGroups(groups);
|
||||
|
||||
for (final group in groups) {
|
||||
for (final group in groupsData) {
|
||||
final delegate = GroupControllerDelegateImpl(
|
||||
controller: boardController,
|
||||
fieldController: fieldController,
|
||||
onNewColumnItem: (groupId, row, index) {
|
||||
add(BoardEvent.didCreateRow(groupId, row, index));
|
||||
add(BoardEvent.didCreateRow(group, row, index));
|
||||
},
|
||||
);
|
||||
final controller = GroupController(
|
||||
@ -242,10 +272,13 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
);
|
||||
}
|
||||
|
||||
List<AppFlowyGroupItem> _buildRows(GroupPB group) {
|
||||
List<AppFlowyGroupItem> _buildGroupItems(GroupPB group) {
|
||||
final items = group.rows.map((row) {
|
||||
final fieldContext = fieldController.getField(group.fieldId);
|
||||
return BoardColumnItem(row: row, fieldContext: fieldContext!);
|
||||
return GroupItem(
|
||||
row: row,
|
||||
fieldContext: fieldContext!,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return <AppFlowyGroupItem>[...items];
|
||||
@ -270,11 +303,15 @@ class BoardEvent with _$BoardEvent {
|
||||
const factory BoardEvent.createBottomRow(String groupId) = _CreateBottomRow;
|
||||
const factory BoardEvent.createHeaderRow(String groupId) = _CreateHeaderRow;
|
||||
const factory BoardEvent.didCreateRow(
|
||||
String groupId,
|
||||
GroupPB group,
|
||||
RowPB row,
|
||||
int? index,
|
||||
) = _DidCreateRow;
|
||||
const factory BoardEvent.endEditRow(String rowId) = _EndEditRow;
|
||||
const factory BoardEvent.startEditingRow(
|
||||
GroupPB group,
|
||||
RowPB row,
|
||||
) = _StartEditRow;
|
||||
const factory BoardEvent.endEditingRow(String rowId) = _EndEditRow;
|
||||
const factory BoardEvent.didReceiveError(FlowyError error) = _DidReceiveError;
|
||||
const factory BoardEvent.didReceiveGridUpdate(
|
||||
GridPB grid,
|
||||
@ -334,14 +371,17 @@ class GridFieldEquatable extends Equatable {
|
||||
UnmodifiableListView<FieldPB> get value => UnmodifiableListView(_fields);
|
||||
}
|
||||
|
||||
class BoardColumnItem extends AppFlowyGroupItem {
|
||||
class GroupItem extends AppFlowyGroupItem {
|
||||
final RowPB row;
|
||||
final GridFieldContext fieldContext;
|
||||
|
||||
BoardColumnItem({
|
||||
GroupItem({
|
||||
required this.row,
|
||||
required this.fieldContext,
|
||||
});
|
||||
bool draggable = true,
|
||||
}) {
|
||||
super.draggable = draggable;
|
||||
}
|
||||
|
||||
@override
|
||||
String get id => row.id;
|
||||
@ -367,10 +407,16 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
|
||||
}
|
||||
|
||||
if (index != null) {
|
||||
final item = BoardColumnItem(row: row, fieldContext: fieldContext);
|
||||
final item = GroupItem(
|
||||
row: row,
|
||||
fieldContext: fieldContext,
|
||||
);
|
||||
controller.insertGroupItem(group.groupId, index, item);
|
||||
} else {
|
||||
final item = BoardColumnItem(row: row, fieldContext: fieldContext);
|
||||
final item = GroupItem(
|
||||
row: row,
|
||||
fieldContext: fieldContext,
|
||||
);
|
||||
controller.addGroupItem(group.groupId, item);
|
||||
}
|
||||
}
|
||||
@ -389,7 +435,10 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
|
||||
}
|
||||
controller.updateGroupItem(
|
||||
group.groupId,
|
||||
BoardColumnItem(row: row, fieldContext: fieldContext),
|
||||
GroupItem(
|
||||
row: row,
|
||||
fieldContext: fieldContext,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -400,7 +449,11 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
|
||||
Log.warn("FieldContext should not be null");
|
||||
return;
|
||||
}
|
||||
final item = BoardColumnItem(row: row, fieldContext: fieldContext);
|
||||
final item = GroupItem(
|
||||
row: row,
|
||||
fieldContext: fieldContext,
|
||||
draggable: false,
|
||||
);
|
||||
|
||||
if (index != null) {
|
||||
controller.insertGroupItem(group.groupId, index, item);
|
||||
@ -412,21 +465,21 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
|
||||
}
|
||||
|
||||
class BoardEditingRow {
|
||||
String columnId;
|
||||
GroupPB group;
|
||||
RowPB row;
|
||||
int? index;
|
||||
|
||||
BoardEditingRow({
|
||||
required this.columnId,
|
||||
required this.group,
|
||||
required this.row,
|
||||
required this.index,
|
||||
});
|
||||
}
|
||||
|
||||
class BoardCustomData {
|
||||
class GroupData {
|
||||
final GroupPB group;
|
||||
final GridFieldContext fieldContext;
|
||||
BoardCustomData({
|
||||
GroupData({
|
||||
required this.group,
|
||||
required this.fieldContext,
|
||||
});
|
||||
|
@ -87,13 +87,13 @@ class BoardDataController {
|
||||
onUpdatedGroup.call(changeset.updateGroups);
|
||||
}
|
||||
|
||||
if (changeset.insertedGroups.isNotEmpty) {
|
||||
onInsertedGroup.call(changeset.insertedGroups);
|
||||
}
|
||||
|
||||
if (changeset.deletedGroups.isNotEmpty) {
|
||||
onDeletedGroup.call(changeset.deletedGroups);
|
||||
}
|
||||
|
||||
if (changeset.insertedGroups.isNotEmpty) {
|
||||
onInsertedGroup.call(changeset.insertedGroups);
|
||||
}
|
||||
},
|
||||
(e) => _onError?.call(e),
|
||||
);
|
||||
|
@ -83,7 +83,7 @@ class _BoardContentState extends State<BoardContent> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<BoardBloc, BoardState>(
|
||||
listener: (context, state) => _handleEditState(state, context),
|
||||
listener: (context, state) => _handleEditStateChanged(state, context),
|
||||
child: BlocBuilder<BoardBloc, BoardState>(
|
||||
buildWhen: (previous, current) => previous.groupIds != current.groupIds,
|
||||
builder: (context, state) {
|
||||
@ -128,21 +128,14 @@ class _BoardContentState extends State<BoardContent> {
|
||||
);
|
||||
}
|
||||
|
||||
void _handleEditState(BoardState state, BuildContext context) {
|
||||
void _handleEditStateChanged(BoardState state, BuildContext context) {
|
||||
state.editingRow.fold(
|
||||
() => null,
|
||||
(editingRow) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (editingRow.index != null) {
|
||||
context
|
||||
.read<BoardBloc>()
|
||||
.add(BoardEvent.endEditRow(editingRow.row.id));
|
||||
} else {
|
||||
scrollManager.scrollToBottom(editingRow.columnId, (boardContext) {
|
||||
context
|
||||
.read<BoardBloc>()
|
||||
.add(BoardEvent.endEditRow(editingRow.row.id));
|
||||
});
|
||||
scrollManager.scrollToBottom(editingRow.group.groupId);
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -156,14 +149,14 @@ class _BoardContentState extends State<BoardContent> {
|
||||
|
||||
Widget _buildHeader(
|
||||
BuildContext context,
|
||||
AppFlowyGroupData columnData,
|
||||
AppFlowyGroupData groupData,
|
||||
) {
|
||||
final boardCustomData = columnData.customData as BoardCustomData;
|
||||
final boardCustomData = groupData.customData as GroupData;
|
||||
return AppFlowyGroupHeader(
|
||||
title: Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: FlowyText.medium(
|
||||
columnData.headerData.groupName,
|
||||
groupData.headerData.groupName,
|
||||
fontSize: 14,
|
||||
overflow: TextOverflow.clip,
|
||||
color: context.read<AppTheme>().textColor,
|
||||
@ -180,7 +173,7 @@ class _BoardContentState extends State<BoardContent> {
|
||||
),
|
||||
onAddButtonClick: () {
|
||||
context.read<BoardBloc>().add(
|
||||
BoardEvent.createHeaderRow(columnData.id),
|
||||
BoardEvent.createHeaderRow(groupData.id),
|
||||
);
|
||||
},
|
||||
height: 50,
|
||||
@ -218,15 +211,16 @@ class _BoardContentState extends State<BoardContent> {
|
||||
|
||||
Widget _buildCard(
|
||||
BuildContext context,
|
||||
AppFlowyGroupData group,
|
||||
AppFlowyGroupItem columnItem,
|
||||
AppFlowyGroupData afGroupData,
|
||||
AppFlowyGroupItem afGroupItem,
|
||||
) {
|
||||
final boardColumnItem = columnItem as BoardColumnItem;
|
||||
final rowPB = boardColumnItem.row;
|
||||
final groupItem = afGroupItem as GroupItem;
|
||||
final groupData = afGroupData.customData as GroupData;
|
||||
final rowPB = groupItem.row;
|
||||
final rowCache = context.read<BoardBloc>().getRowCache(rowPB.blockId);
|
||||
|
||||
/// Return placeholder widget if the rowCache is null.
|
||||
if (rowCache == null) return SizedBox(key: ObjectKey(columnItem));
|
||||
if (rowCache == null) return SizedBox(key: ObjectKey(groupItem));
|
||||
|
||||
final fieldController = context.read<BoardBloc>().fieldController;
|
||||
final gridId = context.read<BoardBloc>().gridId;
|
||||
@ -241,19 +235,19 @@ class _BoardContentState extends State<BoardContent> {
|
||||
context.read<BoardBloc>().state.editingRow.fold(
|
||||
() => null,
|
||||
(editingRow) {
|
||||
isEditing = editingRow.row.id == columnItem.row.id;
|
||||
isEditing = editingRow.row.id == groupItem.row.id;
|
||||
},
|
||||
);
|
||||
|
||||
final groupItemId = columnItem.id + group.id;
|
||||
final groupItemId = groupItem.row.id + groupData.group.groupId;
|
||||
return AppFlowyGroupCard(
|
||||
key: ValueKey(groupItemId),
|
||||
margin: config.cardPadding,
|
||||
decoration: _makeBoxDecoration(context),
|
||||
child: BoardCard(
|
||||
gridId: gridId,
|
||||
groupId: group.id,
|
||||
fieldId: boardColumnItem.fieldContext.id,
|
||||
groupId: groupData.group.groupId,
|
||||
fieldId: groupItem.fieldContext.id,
|
||||
isEditing: isEditing,
|
||||
cellBuilder: cellBuilder,
|
||||
dataController: cardController,
|
||||
@ -264,6 +258,19 @@ class _BoardContentState extends State<BoardContent> {
|
||||
rowCache,
|
||||
context,
|
||||
),
|
||||
onStartEditing: () {
|
||||
context.read<BoardBloc>().add(
|
||||
BoardEvent.startEditingRow(
|
||||
groupData.group,
|
||||
groupItem.row,
|
||||
),
|
||||
);
|
||||
},
|
||||
onEndEditing: () {
|
||||
context
|
||||
.read<BoardBloc>()
|
||||
.add(BoardEvent.endEditingRow(groupItem.row.id));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -345,7 +352,7 @@ extension HexColor on Color {
|
||||
}
|
||||
}
|
||||
|
||||
Widget? _buildHeaderIcon(BoardCustomData customData) {
|
||||
Widget? _buildHeaderIcon(GroupData customData) {
|
||||
Widget? widget;
|
||||
switch (customData.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
|
@ -76,6 +76,10 @@ class EditableRowNotifier {
|
||||
}
|
||||
|
||||
abstract class EditableCell {
|
||||
// Each cell notifier will be bind to the [EditableRowNotifier], which enable
|
||||
// the row notifier receive its cells event. For example: begin editing the
|
||||
// cell or end editing the cell.
|
||||
//
|
||||
EditableCellNotifier? get editableNotifier;
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,9 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
focusNode.requestFocus();
|
||||
}
|
||||
|
||||
// If the focusNode lost its focus, the widget's editableNotifier will
|
||||
// set to false, which will cause the [EditableRowNotifier] to receive
|
||||
// end edit event.
|
||||
focusNode.addListener(() {
|
||||
if (!focusNode.hasFocus) {
|
||||
focusWhenInit = false;
|
||||
@ -131,7 +134,11 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: BoardSizes.cardCellVPadding,
|
||||
),
|
||||
child: FlowyText.medium(state.content, fontSize: 14),
|
||||
child: FlowyText.medium(
|
||||
state.content,
|
||||
fontSize: 14,
|
||||
maxLines: null, // Enable multiple lines
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,8 @@ class BoardCard extends StatefulWidget {
|
||||
final CardDataController dataController;
|
||||
final BoardCellBuilder cellBuilder;
|
||||
final void Function(BuildContext) openCard;
|
||||
final VoidCallback onStartEditing;
|
||||
final VoidCallback onEndEditing;
|
||||
|
||||
const BoardCard({
|
||||
required this.gridId,
|
||||
@ -30,6 +32,8 @@ class BoardCard extends StatefulWidget {
|
||||
required this.dataController,
|
||||
required this.cellBuilder,
|
||||
required this.openCard,
|
||||
required this.onStartEditing,
|
||||
required this.onEndEditing,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -56,6 +60,12 @@ class _BoardCardState extends State<BoardCard> {
|
||||
rowNotifier.isEditing.addListener(() {
|
||||
if (!mounted) return;
|
||||
_cardBloc.add(BoardCardEvent.setIsEditing(rowNotifier.isEditing.value));
|
||||
|
||||
if (rowNotifier.isEditing.value) {
|
||||
widget.onStartEditing();
|
||||
} else {
|
||||
widget.onEndEditing();
|
||||
}
|
||||
});
|
||||
|
||||
popoverController = PopoverController();
|
||||
|
@ -78,7 +78,7 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
|
||||
height: 50,
|
||||
margin: config.groupItemPadding,
|
||||
onAddButtonClick: () {
|
||||
boardController.scrollToBottom(columnData.id, (p0) {});
|
||||
boardController.scrollToBottom(columnData.id);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
@ -32,4 +32,8 @@ class Log {
|
||||
'AppFlowyBoard: ❗️[Trace] - ${DateTime.now().second}=> $message');
|
||||
}
|
||||
}
|
||||
|
||||
static void error(String? message) {
|
||||
debugPrint('AppFlowyBoard: ❌[Error] - ${DateTime.now().second}=> $message');
|
||||
}
|
||||
}
|
||||
|
@ -11,10 +11,11 @@ import 'reorder_phantom/phantom_controller.dart';
|
||||
import '../rendering/board_overlay.dart';
|
||||
|
||||
class AppFlowyBoardScrollController {
|
||||
AppFlowyBoardState? _groupState;
|
||||
AppFlowyBoardState? _boardState;
|
||||
|
||||
void scrollToBottom(String groupId, void Function(BuildContext)? completed) {
|
||||
_groupState?.reorderFlexActionMap[groupId]?.scrollToBottom(completed);
|
||||
void scrollToBottom(String groupId,
|
||||
{void Function(BuildContext)? completed}) {
|
||||
_boardState?.reorderFlexActionMap[groupId]?.scrollToBottom(completed);
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,9 +40,6 @@ class AppFlowyBoardConfig {
|
||||
}
|
||||
|
||||
class AppFlowyBoard extends StatelessWidget {
|
||||
/// The direction to use as the main axis.
|
||||
final Axis direction = Axis.vertical;
|
||||
|
||||
/// The widget that will be rendered as the background of the board.
|
||||
final Widget? background;
|
||||
|
||||
@ -94,11 +92,7 @@ class AppFlowyBoard extends StatelessWidget {
|
||||
///
|
||||
final AppFlowyBoardScrollController? boardScrollController;
|
||||
|
||||
final AppFlowyBoardState _groupState = AppFlowyBoardState();
|
||||
|
||||
late final BoardPhantomController _phantomController;
|
||||
|
||||
AppFlowyBoard({
|
||||
const AppFlowyBoard({
|
||||
required this.controller,
|
||||
required this.cardBuilder,
|
||||
this.background,
|
||||
@ -109,12 +103,7 @@ class AppFlowyBoard extends StatelessWidget {
|
||||
this.groupConstraints = const BoxConstraints(maxWidth: 200),
|
||||
this.config = const AppFlowyBoardConfig(),
|
||||
Key? key,
|
||||
}) : super(key: key) {
|
||||
_phantomController = BoardPhantomController(
|
||||
delegate: controller,
|
||||
groupsState: _groupState,
|
||||
);
|
||||
}
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -122,8 +111,14 @@ class AppFlowyBoard extends StatelessWidget {
|
||||
value: controller,
|
||||
child: Consumer<AppFlowyBoardController>(
|
||||
builder: (context, notifier, child) {
|
||||
final boardState = AppFlowyBoardState();
|
||||
BoardPhantomController phantomController = BoardPhantomController(
|
||||
delegate: controller,
|
||||
groupsState: boardState,
|
||||
);
|
||||
|
||||
if (boardScrollController != null) {
|
||||
boardScrollController!._groupState = _groupState;
|
||||
boardScrollController!._boardState = boardState;
|
||||
}
|
||||
|
||||
return _AppFlowyBoardContent(
|
||||
@ -131,14 +126,14 @@ class AppFlowyBoard extends StatelessWidget {
|
||||
dataController: controller,
|
||||
scrollController: scrollController,
|
||||
scrollManager: boardScrollController,
|
||||
groupState: _groupState,
|
||||
boardState: boardState,
|
||||
background: background,
|
||||
delegate: _phantomController,
|
||||
delegate: phantomController,
|
||||
groupConstraints: groupConstraints,
|
||||
cardBuilder: cardBuilder,
|
||||
footerBuilder: footerBuilder,
|
||||
headerBuilder: headerBuilder,
|
||||
phantomController: _phantomController,
|
||||
phantomController: phantomController,
|
||||
onReorder: controller.moveGroup,
|
||||
);
|
||||
},
|
||||
@ -156,7 +151,7 @@ class _AppFlowyBoardContent extends StatefulWidget {
|
||||
final ReorderFlexConfig reorderFlexConfig;
|
||||
final BoxConstraints groupConstraints;
|
||||
final AppFlowyBoardScrollController? scrollManager;
|
||||
final AppFlowyBoardState groupState;
|
||||
final AppFlowyBoardState boardState;
|
||||
final AppFlowyBoardCardBuilder cardBuilder;
|
||||
final AppFlowyBoardHeaderBuilder? headerBuilder;
|
||||
final AppFlowyBoardFooterBuilder? footerBuilder;
|
||||
@ -169,7 +164,7 @@ class _AppFlowyBoardContent extends StatefulWidget {
|
||||
required this.delegate,
|
||||
required this.dataController,
|
||||
required this.scrollManager,
|
||||
required this.groupState,
|
||||
required this.boardState,
|
||||
this.scrollController,
|
||||
this.background,
|
||||
required this.groupConstraints,
|
||||
@ -178,7 +173,10 @@ class _AppFlowyBoardContent extends StatefulWidget {
|
||||
this.headerBuilder,
|
||||
required this.phantomController,
|
||||
Key? key,
|
||||
}) : reorderFlexConfig = const ReorderFlexConfig(),
|
||||
}) : reorderFlexConfig = const ReorderFlexConfig(
|
||||
direction: Axis.horizontal,
|
||||
dragDirection: Axis.horizontal,
|
||||
),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
@ -198,7 +196,7 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
||||
reorderFlexId: widget.dataController.identifier,
|
||||
acceptedReorderFlexId: widget.dataController.groupIds,
|
||||
delegate: widget.delegate,
|
||||
columnsState: widget.groupState,
|
||||
columnsState: widget.boardState,
|
||||
);
|
||||
|
||||
final reorderFlex = ReorderFlex(
|
||||
@ -206,9 +204,7 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
||||
scrollController: widget.scrollController,
|
||||
onReorder: widget.onReorder,
|
||||
dataSource: widget.dataController,
|
||||
direction: Axis.horizontal,
|
||||
interceptor: interceptor,
|
||||
reorderable: true,
|
||||
children: _buildColumns(),
|
||||
);
|
||||
|
||||
@ -254,7 +250,7 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
||||
);
|
||||
|
||||
final reorderFlexAction = ReorderFlexActionImpl();
|
||||
widget.groupState.reorderFlexActionMap[columnData.id] =
|
||||
widget.boardState.reorderFlexActionMap[columnData.id] =
|
||||
reorderFlexAction;
|
||||
|
||||
return ChangeNotifierProvider.value(
|
||||
@ -275,8 +271,8 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
||||
onReorder: widget.dataController.moveGroupItem,
|
||||
cornerRadius: widget.config.cornerRadius,
|
||||
backgroundColor: widget.config.groupBackgroundColor,
|
||||
dragStateStorage: widget.groupState,
|
||||
dragTargetKeys: widget.groupState,
|
||||
dragStateStorage: widget.boardState,
|
||||
dragTargetKeys: widget.boardState,
|
||||
reorderFlexAction: reorderFlexAction,
|
||||
);
|
||||
|
||||
|
@ -138,7 +138,11 @@ class AppFlowyBoardController extends ChangeNotifier
|
||||
/// groups or get ready to reinitialize the [AppFlowyBoard].
|
||||
void clear() {
|
||||
_groupDatas.clear();
|
||||
for (final group in _groupControllers.values) {
|
||||
group.dispose();
|
||||
}
|
||||
_groupControllers.clear();
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@ -202,6 +206,14 @@ class AppFlowyBoardController extends ChangeNotifier
|
||||
getGroupController(groupId)?.replaceOrInsertItem(item);
|
||||
}
|
||||
|
||||
void enableGroupDragging(bool isEnable) {
|
||||
for (var groupController in _groupControllers.values) {
|
||||
groupController.enableDragging(isEnable);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Moves the item at [fromGroupIndex] in group with id [fromGroupId] to
|
||||
/// group with id [toGroupId] at [toGroupIndex]
|
||||
@override
|
||||
@ -215,6 +227,8 @@ class AppFlowyBoardController extends ChangeNotifier
|
||||
final fromGroupController = getGroupController(fromGroupId)!;
|
||||
final toGroupController = getGroupController(toGroupId)!;
|
||||
final fromGroupItem = fromGroupController.removeAt(fromGroupIndex);
|
||||
if (fromGroupItem == null) return;
|
||||
|
||||
if (toGroupController.items.length > toGroupIndex) {
|
||||
assert(toGroupController.items[toGroupIndex] is PhantomGroupItem);
|
||||
|
||||
@ -275,7 +289,9 @@ class AppFlowyBoardController extends ChangeNotifier
|
||||
Log.trace(
|
||||
'[$BoardPhantomController] update $groupId:$index to $groupId:$newIndex');
|
||||
final item = groupController.removeAt(index, notify: false);
|
||||
groupController.insert(newIndex, item, notify: false);
|
||||
if (item != null) {
|
||||
groupController.insert(newIndex, item, notify: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -156,9 +156,9 @@ class _AppFlowyBoardGroupState extends State<AppFlowyBoardGroup> {
|
||||
widget.onDragStarted?.call(index);
|
||||
},
|
||||
onReorder: ((fromIndex, toIndex) {
|
||||
if (widget.phantomController.isFromGroup(widget.groupId)) {
|
||||
if (widget.phantomController.shouldReorder(widget.groupId)) {
|
||||
widget.onReorder(widget.groupId, fromIndex, toIndex);
|
||||
widget.phantomController.transformIndex(fromIndex, toIndex);
|
||||
widget.phantomController.updateIndex(fromIndex, toIndex);
|
||||
}
|
||||
}),
|
||||
onDragEnded: () {
|
||||
|
@ -5,6 +5,8 @@ import 'package:appflowy_board/src/widgets/reorder_flex/reorder_flex.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef IsDraggable = bool;
|
||||
|
||||
/// A item represents the generic data model of each group card.
|
||||
///
|
||||
/// Each item displayed in the group required to implement this class.
|
||||
@ -50,8 +52,17 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
||||
/// * [notify] the default value of [notify] is true, it will notify the
|
||||
/// listener. Set to false if you do not want to notify the listeners.
|
||||
///
|
||||
AppFlowyGroupItem removeAt(int index, {bool notify = true}) {
|
||||
assert(index >= 0);
|
||||
AppFlowyGroupItem? removeAt(int index, {bool notify = true}) {
|
||||
if (groupData._items.length <= index) {
|
||||
Log.error(
|
||||
'Fatal error, index is out of bounds. Index: $index, len: ${groupData._items.length}');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (index < 0) {
|
||||
Log.error('Invalid index:$index');
|
||||
return null;
|
||||
}
|
||||
|
||||
Log.debug('[$AppFlowyGroupController] $groupData remove item at $index');
|
||||
final item = groupData._items.removeAt(index);
|
||||
@ -71,12 +82,17 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
||||
/// Move the item from [fromIndex] to [toIndex]. It will do nothing if the
|
||||
/// [fromIndex] equal to the [toIndex].
|
||||
bool move(int fromIndex, int toIndex) {
|
||||
assert(fromIndex >= 0);
|
||||
assert(toIndex >= 0);
|
||||
if (groupData._items.length < fromIndex) {
|
||||
Log.error(
|
||||
'Out of bounds error. index: $fromIndex should not greater than ${groupData._items.length}');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fromIndex == toIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.debug(
|
||||
'[$AppFlowyGroupController] $groupData move item from $fromIndex to $toIndex');
|
||||
final item = groupData._items.removeAt(fromIndex);
|
||||
@ -124,7 +140,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
||||
Log.debug('[$AppFlowyGroupController] $groupData add $newItem');
|
||||
} else {
|
||||
if (index >= groupData._items.length) {
|
||||
Log.warn(
|
||||
Log.error(
|
||||
'[$AppFlowyGroupController] unexpected items length, index should less than the count of the items. Index: $index, items count: ${items.length}');
|
||||
return;
|
||||
}
|
||||
@ -155,6 +171,15 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
||||
-1;
|
||||
}
|
||||
|
||||
void enableDragging(bool isEnable) {
|
||||
groupData.draggable = isEnable;
|
||||
|
||||
for (var item in groupData._items) {
|
||||
item.draggable = isEnable;
|
||||
}
|
||||
_notify();
|
||||
}
|
||||
|
||||
void _notify() {
|
||||
notifyListeners();
|
||||
}
|
||||
|
@ -16,13 +16,13 @@ class FlexDragTargetData extends DragTargetData {
|
||||
@override
|
||||
final int draggingIndex;
|
||||
|
||||
final DraggingState _state;
|
||||
final DraggingState _draggingState;
|
||||
|
||||
Widget? get draggingWidget => _state.draggingWidget;
|
||||
Widget? get draggingWidget => _draggingState.draggingWidget;
|
||||
|
||||
Size? get feedbackSize => _state.feedbackSize;
|
||||
Size? get feedbackSize => _draggingState.feedbackSize;
|
||||
|
||||
bool get isDragging => _state.isDragging();
|
||||
bool get isDragging => _draggingState.isDragging();
|
||||
|
||||
final String dragTargetId;
|
||||
|
||||
@ -40,8 +40,8 @@ class FlexDragTargetData extends DragTargetData {
|
||||
required this.reorderFlexId,
|
||||
required this.reorderFlexItem,
|
||||
required this.dragTargetIndexKey,
|
||||
required DraggingState state,
|
||||
}) : _state = state;
|
||||
required DraggingState draggingState,
|
||||
}) : _draggingState = draggingState;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:appflowy_board/src/utils/log.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
@ -78,10 +79,12 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
|
||||
|
||||
final bool useMoveAnimation;
|
||||
|
||||
final bool draggable;
|
||||
final IsDraggable draggable;
|
||||
|
||||
final double draggingOpacity;
|
||||
|
||||
final Axis? dragDirection;
|
||||
|
||||
const ReorderDragTarget({
|
||||
Key? key,
|
||||
required this.child,
|
||||
@ -99,6 +102,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
|
||||
this.onLeave,
|
||||
this.draggableTargetBuilder,
|
||||
this.draggingOpacity = 0.3,
|
||||
this.dragDirection,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@ -115,8 +119,10 @@ class _ReorderDragTargetState<T extends DragTargetData>
|
||||
Widget dragTarget = DragTarget<T>(
|
||||
builder: _buildDraggableWidget,
|
||||
onWillAccept: (dragTargetData) {
|
||||
assert(dragTargetData != null);
|
||||
if (dragTargetData == null) return false;
|
||||
if (dragTargetData == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return widget.onWillAccept(dragTargetData);
|
||||
},
|
||||
onAccept: widget.onAccept,
|
||||
@ -140,9 +146,6 @@ class _ReorderDragTargetState<T extends DragTargetData>
|
||||
List<T?> acceptedCandidates,
|
||||
List<dynamic> rejectedCandidates,
|
||||
) {
|
||||
if (!widget.draggable) {
|
||||
return widget.child;
|
||||
}
|
||||
Widget feedbackBuilder = Builder(builder: (BuildContext context) {
|
||||
BoxConstraints contentSizeConstraints =
|
||||
BoxConstraints.loose(_draggingFeedbackSize!);
|
||||
@ -163,7 +166,8 @@ class _ReorderDragTargetState<T extends DragTargetData>
|
||||
widget.deleteAnimationController,
|
||||
) ??
|
||||
Draggable<DragTargetData>(
|
||||
maxSimultaneousDrags: 1,
|
||||
axis: widget.dragDirection,
|
||||
maxSimultaneousDrags: widget.draggable ? 1 : 0,
|
||||
data: widget.dragTargetData,
|
||||
ignoringFeedbackSemantics: false,
|
||||
feedback: feedbackBuilder,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import '../../utils/log.dart';
|
||||
@ -29,6 +30,8 @@ abstract class ReoderFlexDataSource {
|
||||
abstract class ReoderFlexItem {
|
||||
/// [id] is used to identify the item. It must be unique.
|
||||
String get id;
|
||||
|
||||
IsDraggable draggable = true;
|
||||
}
|
||||
|
||||
/// Cache each dragTarget's key.
|
||||
@ -73,8 +76,15 @@ class ReorderFlexConfig {
|
||||
|
||||
final bool useMovePlaceholder;
|
||||
|
||||
/// [direction] How to place the children, default is Axis.vertical
|
||||
final Axis direction;
|
||||
|
||||
final Axis? dragDirection;
|
||||
|
||||
const ReorderFlexConfig({
|
||||
this.useMoveAnimation = true,
|
||||
this.direction = Axis.vertical,
|
||||
this.dragDirection,
|
||||
}) : useMovePlaceholder = !useMoveAnimation;
|
||||
}
|
||||
|
||||
@ -82,8 +92,6 @@ class ReorderFlex extends StatefulWidget {
|
||||
final ReorderFlexConfig config;
|
||||
final List<Widget> children;
|
||||
|
||||
/// [direction] How to place the children, default is Axis.vertical
|
||||
final Axis direction;
|
||||
final MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start;
|
||||
|
||||
final ScrollController? scrollController;
|
||||
@ -108,8 +116,6 @@ class ReorderFlex extends StatefulWidget {
|
||||
|
||||
final ReorderFlexAction? reorderFlexAction;
|
||||
|
||||
final bool reorderable;
|
||||
|
||||
ReorderFlex({
|
||||
Key? key,
|
||||
this.scrollController,
|
||||
@ -117,14 +123,12 @@ class ReorderFlex extends StatefulWidget {
|
||||
required this.children,
|
||||
required this.config,
|
||||
required this.onReorder,
|
||||
this.reorderable = true,
|
||||
this.dragStateStorage,
|
||||
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.'),
|
||||
super(key: key);
|
||||
@ -146,8 +150,8 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
/// Whether or not we are currently scrolling this view to show a widget.
|
||||
bool _scrolling = false;
|
||||
|
||||
/// [dragState] records the dragging state including dragStartIndex, and phantomIndex, etc.
|
||||
late DraggingState dragState;
|
||||
/// [draggingState] records the dragging state including dragStartIndex, and phantomIndex, etc.
|
||||
late DraggingState draggingState;
|
||||
|
||||
/// [_animation] controls the dragging animations
|
||||
late DragTargetAnimation _animation;
|
||||
@ -158,9 +162,9 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
void initState() {
|
||||
_notifier = ReorderFlexNotifier();
|
||||
final flexId = widget.reorderFlexId;
|
||||
dragState = widget.dragStateStorage?.readState(flexId) ??
|
||||
draggingState = widget.dragStateStorage?.readState(flexId) ??
|
||||
DraggingState(widget.reorderFlexId);
|
||||
Log.trace('[DragTarget] init dragState: $dragState');
|
||||
Log.trace('[DragTarget] init dragState: $draggingState');
|
||||
|
||||
widget.dragStateStorage?.removeState(flexId);
|
||||
|
||||
@ -168,7 +172,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
reorderAnimationDuration: widget.config.reorderAnimationDuration,
|
||||
entranceAnimateStatusChanged: (status) {
|
||||
if (status == AnimationStatus.completed) {
|
||||
if (dragState.nextIndex == -1) return;
|
||||
if (draggingState.nextIndex == -1) return;
|
||||
setState(() => _requestAnimationToNextIndex());
|
||||
}
|
||||
},
|
||||
@ -225,7 +229,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
indexKey,
|
||||
);
|
||||
|
||||
children.add(_wrap(child, i, indexKey));
|
||||
children.add(_wrap(child, i, indexKey, item.draggable));
|
||||
|
||||
// if (widget.config.useMovePlaceholder) {
|
||||
// children.add(DragTargeMovePlaceholder(
|
||||
@ -256,64 +260,70 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
/// when the animation finish.
|
||||
|
||||
if (_animation.entranceController.isCompleted) {
|
||||
dragState.removePhantom();
|
||||
draggingState.removePhantom();
|
||||
|
||||
if (!isAcceptingNewTarget && dragState.didDragTargetMoveToNext()) {
|
||||
if (!isAcceptingNewTarget && draggingState.didDragTargetMoveToNext()) {
|
||||
return;
|
||||
}
|
||||
|
||||
dragState.moveDragTargetToNext();
|
||||
draggingState.moveDragTargetToNext();
|
||||
_animation.animateToNext();
|
||||
}
|
||||
}
|
||||
|
||||
/// [child]: the child will be wrapped with dartTarget
|
||||
/// [childIndex]: the index of the child in a list
|
||||
Widget _wrap(Widget child, int childIndex, GlobalObjectKey indexKey) {
|
||||
Widget _wrap(
|
||||
Widget child,
|
||||
int childIndex,
|
||||
GlobalObjectKey indexKey,
|
||||
IsDraggable draggable,
|
||||
) {
|
||||
return Builder(builder: (context) {
|
||||
final ReorderDragTarget dragTarget = _buildDragTarget(
|
||||
context,
|
||||
child,
|
||||
childIndex,
|
||||
indexKey,
|
||||
draggable,
|
||||
);
|
||||
int shiftedIndex = childIndex;
|
||||
|
||||
if (dragState.isOverlapWithPhantom()) {
|
||||
shiftedIndex = dragState.calculateShiftedIndex(childIndex);
|
||||
if (draggingState.isOverlapWithPhantom()) {
|
||||
shiftedIndex = draggingState.calculateShiftedIndex(childIndex);
|
||||
}
|
||||
|
||||
Log.trace(
|
||||
'Rebuild: Group:[${dragState.reorderFlexId}] ${dragState.toString()}, childIndex: $childIndex shiftedIndex: $shiftedIndex');
|
||||
final currentIndex = dragState.currentIndex;
|
||||
final dragPhantomIndex = dragState.phantomIndex;
|
||||
'Rebuild: Group:[${draggingState.reorderFlexId}] ${draggingState.toString()}, childIndex: $childIndex shiftedIndex: $shiftedIndex');
|
||||
final currentIndex = draggingState.currentIndex;
|
||||
final dragPhantomIndex = draggingState.phantomIndex;
|
||||
|
||||
if (shiftedIndex == currentIndex || childIndex == dragPhantomIndex) {
|
||||
Widget dragSpace;
|
||||
if (dragState.draggingWidget != null) {
|
||||
if (dragState.draggingWidget is PhantomWidget) {
|
||||
dragSpace = dragState.draggingWidget!;
|
||||
if (draggingState.draggingWidget != null) {
|
||||
if (draggingState.draggingWidget is PhantomWidget) {
|
||||
dragSpace = draggingState.draggingWidget!;
|
||||
} else {
|
||||
dragSpace = PhantomWidget(
|
||||
opacity: widget.config.draggingWidgetOpacity,
|
||||
child: dragState.draggingWidget,
|
||||
child: draggingState.draggingWidget,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
dragSpace = SizedBox.fromSize(size: dragState.dropAreaSize);
|
||||
dragSpace = SizedBox.fromSize(size: draggingState.dropAreaSize);
|
||||
}
|
||||
|
||||
/// Returns the dragTarget it is not start dragging. The size of the
|
||||
/// dragTarget is the same as the the passed in child.
|
||||
///
|
||||
if (dragState.isNotDragging()) {
|
||||
if (draggingState.isNotDragging()) {
|
||||
return _buildDraggingContainer(children: [dragTarget]);
|
||||
}
|
||||
|
||||
/// Determine the size of the drop area to show under the dragging widget.
|
||||
Size? feedbackSize = Size.zero;
|
||||
if (widget.config.useMoveAnimation) {
|
||||
feedbackSize = dragState.feedbackSize;
|
||||
feedbackSize = draggingState.feedbackSize;
|
||||
}
|
||||
|
||||
Widget appearSpace = _makeAppearSpace(dragSpace, feedbackSize);
|
||||
@ -321,7 +331,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
|
||||
/// When start dragging, the dragTarget, [ReorderDragTarget], will
|
||||
/// return a [IgnorePointerWidget] which size is zero.
|
||||
if (dragState.isPhantomAboveDragTarget()) {
|
||||
if (draggingState.isPhantomAboveDragTarget()) {
|
||||
_notifier.updateDragTargetIndex(currentIndex);
|
||||
if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) {
|
||||
return _buildDraggingContainer(children: [
|
||||
@ -343,7 +353,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
}
|
||||
|
||||
///
|
||||
if (dragState.isPhantomBelowDragTarget()) {
|
||||
if (draggingState.isPhantomBelowDragTarget()) {
|
||||
_notifier.updateDragTargetIndex(currentIndex);
|
||||
if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) {
|
||||
return _buildDraggingContainer(children: [
|
||||
@ -364,10 +374,10 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
}
|
||||
}
|
||||
|
||||
assert(!dragState.isOverlapWithPhantom());
|
||||
assert(!draggingState.isOverlapWithPhantom());
|
||||
|
||||
List<Widget> children = [];
|
||||
if (dragState.isDragTargetMovingDown()) {
|
||||
if (draggingState.isDragTargetMovingDown()) {
|
||||
children.addAll([dragTarget, appearSpace]);
|
||||
} else {
|
||||
children.addAll([appearSpace, dragTarget]);
|
||||
@ -395,15 +405,17 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
Widget child,
|
||||
int dragTargetIndex,
|
||||
GlobalObjectKey indexKey,
|
||||
IsDraggable draggable,
|
||||
) {
|
||||
final reorderFlexItem = widget.dataSource.items[dragTargetIndex];
|
||||
return ReorderDragTarget<FlexDragTargetData>(
|
||||
indexGlobalKey: indexKey,
|
||||
draggable: draggable,
|
||||
dragTargetData: FlexDragTargetData(
|
||||
draggingIndex: dragTargetIndex,
|
||||
reorderFlexId: widget.reorderFlexId,
|
||||
reorderFlexItem: reorderFlexItem,
|
||||
state: dragState,
|
||||
draggingState: draggingState,
|
||||
dragTargetId: reorderFlexItem.id,
|
||||
dragTargetIndexKey: indexKey,
|
||||
),
|
||||
@ -432,11 +444,11 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
setState(() {
|
||||
if (dragTargetData.reorderFlexId == widget.reorderFlexId) {
|
||||
_onReordered(
|
||||
dragState.dragStartIndex,
|
||||
dragState.currentIndex,
|
||||
draggingState.dragStartIndex,
|
||||
draggingState.currentIndex,
|
||||
);
|
||||
}
|
||||
dragState.endDragging();
|
||||
draggingState.endDragging();
|
||||
widget.onDragEnded?.call();
|
||||
});
|
||||
},
|
||||
@ -482,8 +494,8 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
deleteAnimationController: _animation.deleteController,
|
||||
draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder,
|
||||
useMoveAnimation: widget.config.useMoveAnimation,
|
||||
draggable: widget.reorderable,
|
||||
draggingOpacity: widget.config.draggingWidgetOpacity,
|
||||
dragDirection: widget.config.dragDirection,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
@ -506,7 +518,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
child,
|
||||
_animation.entranceController,
|
||||
feedbackSize,
|
||||
widget.direction,
|
||||
widget.config.direction,
|
||||
);
|
||||
}
|
||||
|
||||
@ -515,7 +527,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
child,
|
||||
_animation.phantomController,
|
||||
feedbackSize,
|
||||
widget.direction,
|
||||
widget.config.direction,
|
||||
);
|
||||
}
|
||||
|
||||
@ -525,7 +537,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
Size? feedbackSize,
|
||||
) {
|
||||
setState(() {
|
||||
dragState.startDragging(draggingWidget, dragIndex, feedbackSize);
|
||||
draggingState.startDragging(draggingWidget, dragIndex, feedbackSize);
|
||||
_animation.startDragging();
|
||||
});
|
||||
}
|
||||
@ -535,34 +547,34 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
return;
|
||||
}
|
||||
|
||||
dragState.setStartDraggingIndex(dragTargetIndex);
|
||||
draggingState.setStartDraggingIndex(dragTargetIndex);
|
||||
widget.dragStateStorage?.insertState(
|
||||
widget.reorderFlexId,
|
||||
dragState,
|
||||
draggingState,
|
||||
);
|
||||
}
|
||||
|
||||
bool handleOnWillAccept(BuildContext context, int dragTargetIndex) {
|
||||
final dragIndex = dragState.dragStartIndex;
|
||||
final dragIndex = draggingState.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.
|
||||
///
|
||||
|
||||
bool willAccept =
|
||||
dragState.dragStartIndex == dragIndex && dragIndex != dragTargetIndex;
|
||||
bool willAccept = draggingState.dragStartIndex == dragIndex &&
|
||||
dragIndex != dragTargetIndex;
|
||||
setState(() {
|
||||
if (willAccept) {
|
||||
int shiftedIndex = dragState.calculateShiftedIndex(dragTargetIndex);
|
||||
dragState.updateNextIndex(shiftedIndex);
|
||||
int shiftedIndex = draggingState.calculateShiftedIndex(dragTargetIndex);
|
||||
draggingState.updateNextIndex(shiftedIndex);
|
||||
} else {
|
||||
dragState.updateNextIndex(dragTargetIndex);
|
||||
draggingState.updateNextIndex(dragTargetIndex);
|
||||
}
|
||||
_requestAnimationToNextIndex(isAcceptingNewTarget: true);
|
||||
});
|
||||
|
||||
Log.trace(
|
||||
'[$ReorderDragTarget] ${widget.reorderFlexId} dragging state: $dragState}');
|
||||
'[$ReorderDragTarget] ${widget.reorderFlexId} dragging state: $draggingState}');
|
||||
|
||||
_scrollTo(context);
|
||||
|
||||
@ -587,7 +599,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
return child;
|
||||
} else {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: widget.direction,
|
||||
scrollDirection: widget.config.direction,
|
||||
controller: _scrollController,
|
||||
child: child,
|
||||
);
|
||||
@ -595,7 +607,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
}
|
||||
|
||||
Widget _wrapContainer(List<Widget> children) {
|
||||
switch (widget.direction) {
|
||||
switch (widget.config.direction) {
|
||||
case Axis.horizontal:
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -613,7 +625,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
}
|
||||
|
||||
Widget _buildDraggingContainer({required List<Widget> children}) {
|
||||
switch (widget.direction) {
|
||||
switch (widget.config.direction) {
|
||||
case Axis.horizontal:
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -660,6 +672,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
.ensureVisible(
|
||||
dragTargetRenderObject,
|
||||
alignment: 0.5,
|
||||
alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart,
|
||||
duration: const Duration(milliseconds: 120),
|
||||
)
|
||||
.then((value) {
|
||||
@ -683,9 +696,9 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
// 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
|
||||
// screen, then it is already on-screen.
|
||||
final double margin = widget.direction == Axis.horizontal
|
||||
? dragState.dropAreaSize.width
|
||||
: dragState.dropAreaSize.height / 2.0;
|
||||
final double margin = widget.config.direction == Axis.horizontal
|
||||
? draggingState.dropAreaSize.width
|
||||
: draggingState.dropAreaSize.height / 2.0;
|
||||
if (_scrollController.hasClients) {
|
||||
final double scrollOffset = _scrollController.offset;
|
||||
final double topOffset = max(
|
||||
|
@ -46,15 +46,23 @@ class BoardPhantomController extends OverlapDragTargetDelegate
|
||||
required this.groupsState,
|
||||
});
|
||||
|
||||
bool isFromGroup(String groupId) {
|
||||
/// Determines whether the group should perform reorder
|
||||
///
|
||||
/// Returns `true` if the fromGroupId and toGroupId of the phantomRecord
|
||||
/// equal to the passed in groupId.
|
||||
///
|
||||
/// Returns `true` if the phantomRecord is null
|
||||
///
|
||||
bool shouldReorder(String groupId) {
|
||||
if (phantomRecord != null) {
|
||||
return phantomRecord!.fromGroupId == groupId;
|
||||
return phantomRecord!.toGroupId == groupId &&
|
||||
phantomRecord!.fromGroupId == groupId;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void transformIndex(int fromIndex, int toIndex) {
|
||||
void updateIndex(int fromIndex, int toIndex) {
|
||||
if (phantomRecord == null) {
|
||||
return;
|
||||
}
|
||||
@ -69,7 +77,6 @@ class BoardPhantomController extends OverlapDragTargetDelegate
|
||||
/// Remove the phantom in the group when the group is end dragging.
|
||||
void groupEndDragging(String groupId) {
|
||||
phantomState.setGroupIsDragging(groupId, false);
|
||||
|
||||
if (phantomRecord == null) return;
|
||||
|
||||
final fromGroupId = phantomRecord!.fromGroupId;
|
||||
@ -246,10 +253,6 @@ class PhantomRecord {
|
||||
});
|
||||
|
||||
void updateFromGroupIndex(int index) {
|
||||
if (fromGroupIndex == index) {
|
||||
return;
|
||||
}
|
||||
|
||||
fromGroupIndex = index;
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ packages:
|
||||
path: "packages/appflowy_board"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.7"
|
||||
version: "0.0.8"
|
||||
appflowy_editor:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -623,7 +623,7 @@ impl GridRevisionEditor {
|
||||
self.view_manager
|
||||
.move_group_row(row_rev, to_group_id, to_row_id.clone(), |row_changeset| {
|
||||
wrap_future(async move {
|
||||
tracing::trace!("Move group row cause row data changed: {:?}", row_changeset);
|
||||
tracing::trace!("Row data changed: {:?}", row_changeset);
|
||||
let cell_changesets = row_changeset
|
||||
.cell_by_field_id
|
||||
.into_iter()
|
||||
|
@ -79,7 +79,7 @@ impl GridViewRevisionEditor {
|
||||
Ok(json_str)
|
||||
}
|
||||
|
||||
pub(crate) async fn will_create_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) {
|
||||
pub(crate) async fn will_create_view_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) {
|
||||
if params.group_id.is_none() {
|
||||
return;
|
||||
}
|
||||
@ -92,7 +92,7 @@ impl GridViewRevisionEditor {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub(crate) async fn did_create_row(&self, row_pb: &RowPB, params: &CreateRowParams) {
|
||||
pub(crate) async fn did_create_view_row(&self, row_pb: &RowPB, params: &CreateRowParams) {
|
||||
// Send the group notification if the current view has groups
|
||||
match params.group_id.as_ref() {
|
||||
None => {}
|
||||
@ -115,7 +115,7 @@ impl GridViewRevisionEditor {
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
pub(crate) async fn did_delete_row(&self, row_rev: &RowRevision) {
|
||||
pub(crate) async fn did_delete_view_row(&self, row_rev: &RowRevision) {
|
||||
// Send the group notification if the current view has groups;
|
||||
let changesets = self
|
||||
.mut_group_controller(|group_controller, field_rev| group_controller.did_delete_row(row_rev, &field_rev))
|
||||
@ -129,7 +129,7 @@ impl GridViewRevisionEditor {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn did_update_row(&self, row_rev: &RowRevision) {
|
||||
pub(crate) async fn did_update_view_row(&self, row_rev: &RowRevision) {
|
||||
let changesets = self
|
||||
.mut_group_controller(|group_controller, field_rev| group_controller.did_update_row(row_rev, &field_rev))
|
||||
.await;
|
||||
@ -141,7 +141,7 @@ impl GridViewRevisionEditor {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn move_group_row(
|
||||
pub(crate) async fn move_view_group_row(
|
||||
&self,
|
||||
row_rev: &RowRevision,
|
||||
row_changeset: &mut RowChangeset,
|
||||
@ -167,14 +167,14 @@ impl GridViewRevisionEditor {
|
||||
}
|
||||
/// Only call once after grid view editor initialized
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
pub(crate) async fn load_groups(&self) -> FlowyResult<Vec<GroupPB>> {
|
||||
pub(crate) async fn load_view_groups(&self) -> FlowyResult<Vec<GroupPB>> {
|
||||
let groups = self.group_controller.read().await.groups();
|
||||
tracing::trace!("Number of groups: {}", groups.len());
|
||||
Ok(groups.into_iter().map(GroupPB::from).collect())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||
pub(crate) async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> {
|
||||
pub(crate) async fn move_view_group(&self, params: MoveGroupParams) -> FlowyResult<()> {
|
||||
let _ = self
|
||||
.group_controller
|
||||
.write()
|
||||
@ -206,13 +206,13 @@ impl GridViewRevisionEditor {
|
||||
self.group_controller.read().await.field_id().to_owned()
|
||||
}
|
||||
|
||||
pub(crate) async fn get_setting(&self) -> GridSettingPB {
|
||||
pub(crate) async fn get_view_setting(&self) -> GridSettingPB {
|
||||
let field_revs = self.field_delegate.get_field_revs().await;
|
||||
let grid_setting = make_grid_setting(&*self.pad.read().await, &field_revs);
|
||||
grid_setting
|
||||
}
|
||||
|
||||
pub(crate) async fn get_filters(&self) -> Vec<GridFilterConfigurationPB> {
|
||||
pub(crate) async fn get_view_filters(&self) -> Vec<GridFilterConfigurationPB> {
|
||||
let field_revs = self.field_delegate.get_field_revs().await;
|
||||
match self.pad.read().await.get_all_filters(&field_revs) {
|
||||
None => vec![],
|
||||
@ -245,7 +245,7 @@ impl GridViewRevisionEditor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> {
|
||||
pub(crate) async fn delete_view_group(&self, params: DeleteGroupParams) -> FlowyResult<()> {
|
||||
self.modify(|pad| {
|
||||
let changeset = pad.delete_filter(¶ms.field_id, ¶ms.field_type_rev, ¶ms.group_id)?;
|
||||
Ok(changeset)
|
||||
@ -253,7 +253,7 @@ impl GridViewRevisionEditor {
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_filter(&self, params: InsertFilterParams) -> FlowyResult<()> {
|
||||
pub(crate) async fn insert_view_filter(&self, params: InsertFilterParams) -> FlowyResult<()> {
|
||||
self.modify(|pad| {
|
||||
let filter_rev = FilterConfigurationRevision {
|
||||
id: gen_grid_filter_id(),
|
||||
@ -267,7 +267,7 @@ impl GridViewRevisionEditor {
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_filter(&self, delete_filter: DeleteFilterParams) -> FlowyResult<()> {
|
||||
pub(crate) async fn delete_view_filter(&self, delete_filter: DeleteFilterParams) -> FlowyResult<()> {
|
||||
self.modify(|pad| {
|
||||
let changeset = pad.delete_filter(
|
||||
&delete_filter.field_id,
|
||||
@ -324,7 +324,7 @@ impl GridViewRevisionEditor {
|
||||
}
|
||||
|
||||
async fn notify_did_update_setting(&self) {
|
||||
let setting = self.get_setting().await;
|
||||
let setting = self.get_view_setting().await;
|
||||
send_dart_notification(&self.view_id, GridNotification::DidUpdateGridSetting)
|
||||
.payload(setting)
|
||||
.send();
|
||||
|
@ -65,14 +65,14 @@ impl GridViewManager {
|
||||
/// When the row was created, we may need to modify the [RowRevision] according to the [CreateRowParams].
|
||||
pub(crate) async fn will_create_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) {
|
||||
for view_editor in self.view_editors.iter() {
|
||||
view_editor.will_create_row(row_rev, params).await;
|
||||
view_editor.will_create_view_row(row_rev, params).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Notify the view that the row was created. For the moment, the view is just sending notifications.
|
||||
pub(crate) async fn did_create_row(&self, row_pb: &RowPB, params: &CreateRowParams) {
|
||||
for view_editor in self.view_editors.iter() {
|
||||
view_editor.did_create_row(row_pb, params).await;
|
||||
view_editor.did_create_view_row(row_pb, params).await;
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,7 +84,7 @@ impl GridViewManager {
|
||||
}
|
||||
Some(row_rev) => {
|
||||
for view_editor in self.view_editors.iter() {
|
||||
view_editor.did_update_row(&row_rev).await;
|
||||
view_editor.did_update_view_row(&row_rev).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -102,33 +102,33 @@ impl GridViewManager {
|
||||
|
||||
pub(crate) async fn did_delete_row(&self, row_rev: Arc<RowRevision>) {
|
||||
for view_editor in self.view_editors.iter() {
|
||||
view_editor.did_delete_row(&row_rev).await;
|
||||
view_editor.did_delete_view_row(&row_rev).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_setting(&self) -> FlowyResult<GridSettingPB> {
|
||||
let view_editor = self.get_default_view_editor().await?;
|
||||
Ok(view_editor.get_setting().await)
|
||||
Ok(view_editor.get_view_setting().await)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_filters(&self) -> FlowyResult<Vec<GridFilterConfigurationPB>> {
|
||||
let view_editor = self.get_default_view_editor().await?;
|
||||
Ok(view_editor.get_filters().await)
|
||||
Ok(view_editor.get_view_filters().await)
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_or_update_filter(&self, params: InsertFilterParams) -> FlowyResult<()> {
|
||||
let view_editor = self.get_default_view_editor().await?;
|
||||
view_editor.insert_filter(params).await
|
||||
view_editor.insert_view_filter(params).await
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> {
|
||||
let view_editor = self.get_default_view_editor().await?;
|
||||
view_editor.delete_filter(params).await
|
||||
view_editor.delete_view_filter(params).await
|
||||
}
|
||||
|
||||
pub(crate) async fn load_groups(&self) -> FlowyResult<RepeatedGridGroupPB> {
|
||||
let view_editor = self.get_default_view_editor().await?;
|
||||
let groups = view_editor.load_groups().await?;
|
||||
let groups = view_editor.load_view_groups().await?;
|
||||
Ok(RepeatedGridGroupPB { items: groups })
|
||||
}
|
||||
|
||||
@ -139,12 +139,12 @@ impl GridViewManager {
|
||||
|
||||
pub(crate) async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> {
|
||||
let view_editor = self.get_default_view_editor().await?;
|
||||
view_editor.delete_group(params).await
|
||||
view_editor.delete_view_group(params).await
|
||||
}
|
||||
|
||||
pub(crate) async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> {
|
||||
let view_editor = self.get_default_view_editor().await?;
|
||||
let _ = view_editor.move_group(params).await?;
|
||||
let _ = view_editor.move_view_group(params).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -161,7 +161,7 @@ impl GridViewManager {
|
||||
let mut row_changeset = RowChangeset::new(row_rev.id.clone());
|
||||
let view_editor = self.get_default_view_editor().await?;
|
||||
let group_changesets = view_editor
|
||||
.move_group_row(&row_rev, &mut row_changeset, &to_group_id, to_row_id.clone())
|
||||
.move_view_group_row(&row_rev, &mut row_changeset, &to_group_id, to_row_id.clone())
|
||||
.await;
|
||||
|
||||
if !row_changeset.is_empty() {
|
||||
|
@ -119,12 +119,20 @@ 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::info!("Configuration groups: {:?} ", configuration.groups);
|
||||
if let (Some(from), Some(to)) = &(from_index, to_index) {
|
||||
tracing::trace!("Move group from index:{:?} to index:{:?}", from_index, to_index);
|
||||
let group = configuration.groups.remove(*from);
|
||||
configuration.groups.insert(*to, group);
|
||||
}
|
||||
tracing::debug!(
|
||||
"Group order: {:?} ",
|
||||
configuration
|
||||
.groups
|
||||
.iter()
|
||||
.map(|group| group.name.clone())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",")
|
||||
);
|
||||
|
||||
from_index.is_some() && to_index.is_some()
|
||||
})?;
|
||||
|
@ -80,9 +80,9 @@ pub fn move_group_row(group: &mut Group, context: &mut MoveGroupRowContext) -> O
|
||||
};
|
||||
|
||||
// Remove the row in which group contains it
|
||||
if from_index.is_some() {
|
||||
if let Some(from_index) = &from_index {
|
||||
changeset.deleted_rows.push(row_rev.id.clone());
|
||||
tracing::debug!("Group:{} remove row:{}", group.id, row_rev.id);
|
||||
tracing::debug!("Group:{} remove {} at {}", group.id, row_rev.id, from_index);
|
||||
group.remove_row(&row_rev.id);
|
||||
}
|
||||
|
||||
@ -97,10 +97,11 @@ pub fn move_group_row(group: &mut Group, context: &mut MoveGroupRowContext) -> O
|
||||
}
|
||||
Some(to_index) => {
|
||||
if to_index < group.number_of_row() {
|
||||
tracing::debug!("Group:{} insert row:{} at {} ", group.id, row_rev.id, to_index);
|
||||
tracing::debug!("Group:{} insert {} at {} ", group.id, row_rev.id, to_index);
|
||||
inserted_row.index = Some(to_index as i32);
|
||||
group.insert_row(to_index, row_pb);
|
||||
} else {
|
||||
tracing::warn!("Mote to index: {} is out of bounds", to_index);
|
||||
tracing::debug!("Group:{} append row:{}", group.id, row_rev.id);
|
||||
group.add_row(row_pb);
|
||||
}
|
||||
|
@ -350,6 +350,36 @@ async fn group_move_from_default_group_test() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_move_group_test() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let group_0 = test.group_at_index(0).await;
|
||||
let group_1 = test.group_at_index(1).await;
|
||||
let scripts = vec![
|
||||
MoveGroup {
|
||||
from_group_index: 0,
|
||||
to_group_index: 1,
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 0,
|
||||
row_count: 2,
|
||||
},
|
||||
AssertGroup {
|
||||
group_index: 0,
|
||||
expected_group: group_1,
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 1,
|
||||
row_count: 2,
|
||||
},
|
||||
AssertGroup {
|
||||
group_index: 1,
|
||||
expected_group: group_0,
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn group_move_group_row_after_move_group_test() {
|
||||
let mut test = GridGroupTest::new().await;
|
||||
let group_0 = test.group_at_index(0).await;
|
||||
let group_1 = test.group_at_index(1).await;
|
||||
@ -366,6 +396,20 @@ async fn group_move_group_test() {
|
||||
group_index: 1,
|
||||
expected_group: group_0,
|
||||
},
|
||||
MoveRow {
|
||||
from_group_index: 0,
|
||||
from_row_index: 0,
|
||||
to_group_index: 1,
|
||||
to_row_index: 0,
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 0,
|
||||
row_count: 1,
|
||||
},
|
||||
AssertGroupRowCount {
|
||||
group_index: 1,
|
||||
row_count: 3,
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user