Merge pull request #920 from AppFlowy-IO/feat/edit_create_card

Feat/edit create card
This commit is contained in:
Nathan.fooo 2022-08-29 10:34:50 +08:00 committed by GitHub
commit fcb144e514
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 616 additions and 229 deletions

View File

@ -72,28 +72,33 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
createRow: (groupId) async {
final result = await _gridDataController.createBoardCard(groupId);
result.fold(
(rowPB) {
emit(state.copyWith(editingRow: some(rowPB)));
},
(_) {},
(err) => Log.error(err),
);
},
didCreateRow: (String groupId, RowPB row) {
emit(state.copyWith(
editingRow: Some(BoardEditingRow(columnId: groupId, row: row)),
));
},
endEditRow: (rowId) {
assert(state.editingRow.isSome());
state.editingRow.fold(() => null, (row) {
assert(row.id == rowId);
state.editingRow.fold(() => null, (editingRow) {
assert(editingRow.row.id == rowId);
emit(state.copyWith(editingRow: none()));
});
},
didReceiveGridUpdate: (GridPB grid) {
emit(state.copyWith(grid: Some(grid)));
},
didReceiveRows: (List<RowInfo> rowInfos) {
emit(state.copyWith(rowInfos: rowInfos));
},
didReceiveError: (FlowyError error) {
emit(state.copyWith(noneOrError: some(error)));
},
didReceiveGroups: (List<GroupPB> groups) {
emit(state.copyWith(
groupIds: groups.map((group) => group.groupId).toList(),
));
},
);
},
);
@ -135,7 +140,12 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
void initializeGroups(List<GroupPB> groups) {
for (final group in groups) {
final delegate = GroupControllerDelegateImpl(boardController);
final delegate = GroupControllerDelegateImpl(
controller: boardController,
didAddColumnItem: (groupId, row) {
add(BoardEvent.didCreateRow(groupId, row));
},
);
final controller = GroupController(
gridId: state.gridId,
group: group,
@ -163,16 +173,14 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
return AFBoardColumnData(
id: group.groupId,
name: group.desc,
items: _buildRows(group.rows),
items: _buildRows(group),
customData: group,
);
}).toList();
boardController.addColumns(columns);
initializeGroups(groups);
},
onRowsChanged: (List<RowInfo> rowInfos, RowsChangedReason reason) {
add(BoardEvent.didReceiveRows(rowInfos));
add(BoardEvent.didReceiveGroups(groups));
},
onDeletedGroup: (groupIds) {
//
@ -196,9 +204,12 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
);
}
List<AFColumnItem> _buildRows(List<RowPB> rows) {
final items = rows.map((row) {
return BoardColumnItem(row: row);
List<AFColumnItem> _buildRows(GroupPB group) {
final items = group.rows.map((row) {
return BoardColumnItem(
row: row,
fieldId: group.fieldId,
);
}).toList();
return <AFColumnItem>[...items];
@ -219,15 +230,17 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
@freezed
class BoardEvent with _$BoardEvent {
const factory BoardEvent.initial() = InitialGrid;
const factory BoardEvent.initial() = _InitialBoard;
const factory BoardEvent.createRow(String groupId) = _CreateRow;
const factory BoardEvent.didCreateRow(String groupId, RowPB row) =
_DidCreateRow;
const factory BoardEvent.endEditRow(String rowId) = _EndEditRow;
const factory BoardEvent.didReceiveError(FlowyError error) = _DidReceiveError;
const factory BoardEvent.didReceiveRows(List<RowInfo> rowInfos) =
_DidReceiveRows;
const factory BoardEvent.didReceiveGridUpdate(
GridPB grid,
) = _DidReceiveGridUpdate;
const factory BoardEvent.didReceiveGroups(List<GroupPB> groups) =
_DidReceiveGroups;
}
@freezed
@ -235,16 +248,16 @@ class BoardState with _$BoardState {
const factory BoardState({
required String gridId,
required Option<GridPB> grid,
required Option<RowPB> editingRow,
required List<RowInfo> rowInfos,
required List<String> groupIds,
required Option<BoardEditingRow> editingRow,
required GridLoadingState loadingState,
required Option<FlowyError> noneOrError,
}) = _BoardState;
factory BoardState.initial(String gridId) => BoardState(
rowInfos: [],
grid: none(),
gridId: gridId,
groupIds: [],
editingRow: none(),
noneOrError: none(),
loadingState: const _Loading(),
@ -284,39 +297,68 @@ class GridFieldEquatable extends Equatable {
class BoardColumnItem extends AFColumnItem {
final RowPB row;
BoardColumnItem({required this.row});
final String fieldId;
final bool requestFocus;
BoardColumnItem({
required this.row,
required this.fieldId,
this.requestFocus = false,
});
@override
String get id => row.id;
}
class CreateCardItem extends AFColumnItem {
@override
String get id => '$CreateCardItem';
}
class GroupControllerDelegateImpl extends GroupControllerDelegate {
final AFBoardDataController controller;
final void Function(String, RowPB) didAddColumnItem;
GroupControllerDelegateImpl(this.controller);
GroupControllerDelegateImpl({
required this.controller,
required this.didAddColumnItem,
});
@override
void insertRow(String groupId, RowPB row, int? index) {
final item = BoardColumnItem(row: row);
void insertRow(GroupPB group, RowPB row, int? index) {
if (index != null) {
controller.insertColumnItem(groupId, index, item);
final item = BoardColumnItem(row: row, fieldId: group.fieldId);
controller.insertColumnItem(group.groupId, index, item);
} else {
controller.addColumnItem(groupId, item);
final item = BoardColumnItem(
row: row,
fieldId: group.fieldId,
requestFocus: true,
);
controller.addColumnItem(group.groupId, item);
didAddColumnItem(group.groupId, row);
}
}
@override
void removeRow(String groupId, String rowId) {
controller.removeColumnItem(groupId, rowId);
void removeRow(GroupPB group, String rowId) {
controller.removeColumnItem(group.groupId, rowId);
}
@override
void updateRow(String groupId, RowPB row) {
controller.updateColumnItem(groupId, BoardColumnItem(row: row));
void updateRow(GroupPB group, RowPB row) {
controller.updateColumnItem(
group.groupId,
BoardColumnItem(
row: row,
fieldId: group.fieldId,
),
);
}
}
class BoardEditingRow {
String columnId;
RowPB row;
BoardEditingRow({
required this.columnId,
required this.row,
});
}

View File

@ -60,7 +60,7 @@ class BoardDataController {
required OnGridChanged onGridChanged,
OnFieldsChanged? onFieldsChanged,
required DidLoadGroups didLoadGroups,
required OnRowsChanged onRowsChanged,
OnRowsChanged? onRowsChanged,
required OnUpdatedGroup onUpdatedGroup,
required OnDeletedGroup onDeletedGroup,
required OnInsertedGroup onInsertedGroup,

View File

@ -4,7 +4,6 @@ import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
import 'package:equatable/equatable.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
@ -14,10 +13,12 @@ import 'card_data_controller.dart';
part 'card_bloc.freezed.dart';
class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
final String fieldId;
final RowFFIService _rowService;
final CardDataController _dataController;
BoardCardBloc({
required this.fieldId,
required String gridId,
required CardDataController dataController,
}) : _rowService = RowFFIService(
@ -25,22 +26,22 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
blockId: dataController.rowPB.blockId,
),
_dataController = dataController,
super(BoardCardState.initial(
dataController.rowPB, dataController.loadData())) {
super(
BoardCardState.initial(
dataController.rowPB,
_makeCells(fieldId, dataController.loadData()),
),
) {
on<BoardCardEvent>(
(event, emit) async {
await event.map(
initial: (_InitialRow value) async {
await event.when(
initial: () async {
await _startListening();
},
didReceiveCells: (_DidReceiveCells value) async {
final cells = value.gridCellMap.values
.map((e) => GridCellEquatable(e.field))
.toList();
didReceiveCells: (cells, reason) async {
emit(state.copyWith(
gridCellMap: value.gridCellMap,
cells: UnmodifiableListView(cells),
changeReason: value.reason,
cells: cells,
changeReason: reason,
));
},
);
@ -58,7 +59,7 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
return RowInfo(
gridId: _rowService.gridId,
fields: UnmodifiableListView(
state.cells.map((cell) => cell._field).toList(),
state.cells.map((cell) => cell.identifier.field).toList(),
),
rowPB: state.rowPB,
);
@ -66,8 +67,9 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
Future<void> _startListening() async {
_dataController.addListener(
onRowChanged: (cells, reason) {
onRowChanged: (cellMap, reason) {
if (!isClosed) {
final cells = _makeCells(fieldId, cellMap);
add(BoardCardEvent.didReceiveCells(cells, reason));
}
},
@ -75,42 +77,49 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
}
}
UnmodifiableListView<BoardCellEquatable> _makeCells(
String fieldId, GridCellMap originalCellMap) {
List<BoardCellEquatable> cells = [];
for (final entry in originalCellMap.entries) {
if (entry.value.fieldId != fieldId) {
cells.add(BoardCellEquatable(entry.value));
}
}
return UnmodifiableListView(cells);
}
@freezed
class BoardCardEvent with _$BoardCardEvent {
const factory BoardCardEvent.initial() = _InitialRow;
const factory BoardCardEvent.didReceiveCells(
GridCellMap gridCellMap, RowsChangedReason reason) = _DidReceiveCells;
UnmodifiableListView<BoardCellEquatable> cells,
RowsChangedReason reason,
) = _DidReceiveCells;
}
@freezed
class BoardCardState with _$BoardCardState {
const factory BoardCardState({
required RowPB rowPB,
required GridCellMap gridCellMap,
required UnmodifiableListView<GridCellEquatable> cells,
required UnmodifiableListView<BoardCellEquatable> cells,
RowsChangedReason? changeReason,
}) = _BoardCardState;
factory BoardCardState.initial(RowPB rowPB, GridCellMap cellDataMap) =>
BoardCardState(
rowPB: rowPB,
gridCellMap: cellDataMap,
cells: UnmodifiableListView(
cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList(),
),
);
factory BoardCardState.initial(
RowPB rowPB, UnmodifiableListView<BoardCellEquatable> cells) =>
BoardCardState(rowPB: rowPB, cells: cells);
}
class GridCellEquatable extends Equatable {
final FieldPB _field;
class BoardCellEquatable extends Equatable {
final GridCellIdentifier identifier;
const GridCellEquatable(FieldPB field) : _field = field;
const BoardCellEquatable(this.identifier);
@override
List<Object?> get props => [
_field.id,
_field.fieldType,
_field.visibility,
_field.width,
identifier.field.id,
identifier.field.fieldType,
identifier.field.visibility,
identifier.field.width,
];
}

View File

@ -1,15 +1,14 @@
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
import 'group_listener.dart';
typedef OnGroupError = void Function(FlowyError);
abstract class GroupControllerDelegate {
void removeRow(String groupId, String rowId);
void insertRow(String groupId, RowPB row, int? index);
void updateRow(String groupId, RowPB row);
void removeRow(GroupPB group, String rowId);
void insertRow(GroupPB group, RowPB row, int? index);
void updateRow(GroupPB group, RowPB row);
}
class GroupController {
@ -37,12 +36,11 @@ class GroupController {
(GroupChangesetPB changeset) {
for (final deletedRow in changeset.deletedRows) {
group.rows.removeWhere((rowPB) => rowPB.id == deletedRow);
delegate.removeRow(group.groupId, deletedRow);
delegate.removeRow(group, deletedRow);
}
for (final insertedRow in changeset.insertedRows) {
final index = insertedRow.hasIndex() ? insertedRow.index : null;
if (insertedRow.hasIndex() &&
group.rows.length > insertedRow.index) {
group.rows.insert(insertedRow.index, insertedRow.row);
@ -50,11 +48,7 @@ class GroupController {
group.rows.add(insertedRow.row);
}
delegate.insertRow(
group.groupId,
insertedRow.row,
index,
);
delegate.insertRow(group, insertedRow.row, index);
}
for (final updatedRow in changeset.updatedRows) {
@ -66,7 +60,7 @@ class GroupController {
group.rows[index] = updatedRow;
}
delegate.updateRow(group.groupId, updatedRow);
delegate.updateRow(group, updatedRow);
}
},
(err) => Log.error(err),
@ -74,6 +68,29 @@ class GroupController {
});
}
// GroupChangesetPB _transformChangeset(GroupChangesetPB changeset) {
// final insertedRows = changeset.insertedRows
// .where(
// (delete) => !changeset.deletedRows.contains(delete.row.id),
// )
// .toList();
// final deletedRows = changeset.deletedRows
// .where((deletedRowId) =>
// changeset.insertedRows
// .indexWhere((insert) => insert.row.id == deletedRowId) ==
// -1)
// .toList();
// return changeset.rebuild((rebuildChangeset) {
// rebuildChangeset.insertedRows.clear();
// rebuildChangeset.insertedRows.addAll(insertedRows);
// rebuildChangeset.deletedRows.clear();
// rebuildChangeset.deletedRows.addAll(deletedRows);
// });
// }
Future<void> dispose() async {
_listener.stop();
}

View File

@ -32,13 +32,15 @@ class BoardPage extends StatelessWidget {
create: (context) =>
BoardBloc(view: view)..add(const BoardEvent.initial()),
child: BlocBuilder<BoardBloc, BoardState>(
buildWhen: (previous, current) =>
previous.loadingState != current.loadingState,
builder: (context, state) {
return state.loadingState.map(
loading: (_) =>
const Center(child: CircularProgressIndicator.adaptive()),
finish: (result) {
return result.successOrFail.fold(
(_) => BoardContent(),
(_) => const BoardContent(),
(err) => FlowyErrorPage(err.toString()),
);
},
@ -49,42 +51,82 @@ class BoardPage extends StatelessWidget {
}
}
class BoardContent extends StatelessWidget {
class BoardContent extends StatefulWidget {
const BoardContent({Key? key}) : super(key: key);
@override
State<BoardContent> createState() => _BoardContentState();
}
class _BoardContentState extends State<BoardContent> {
late ScrollController scrollController;
late AFBoardScrollManager scrollManager;
final config = AFBoardConfig(
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
);
BoardContent({Key? key}) : super(key: key);
@override
void initState() {
scrollController = ScrollController();
scrollManager = AFBoardScrollManager();
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<BoardBloc, BoardState>(
builder: (context, state) {
return Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
child: AFBoard(
scrollController: ScrollController(),
dataController: context.read<BoardBloc>().boardController,
headerBuilder: _buildHeader,
footBuilder: _buildFooter,
cardBuilder: (_, column, columnItem) => _buildCard(
context,
column,
columnItem,
),
columnConstraints: const BoxConstraints.tightFor(width: 240),
config: AFBoardConfig(
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
),
),
),
return BlocListener<BoardBloc, BoardState>(
listener: (context, state) {
state.editingRow.fold(
() => null,
(editingRow) {
WidgetsBinding.instance.addPostFrameCallback((_) {
scrollManager.scrollToBottom(editingRow.columnId, () {
context
.read<BoardBloc>()
.add(BoardEvent.endEditRow(editingRow.row.id));
});
});
},
);
},
child: BlocBuilder<BoardBloc, BoardState>(
buildWhen: (previous, current) =>
previous.groupIds.length != current.groupIds.length,
builder: (context, state) {
return Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
child: AFBoard(
scrollManager: scrollManager,
scrollController: scrollController,
dataController: context.read<BoardBloc>().boardController,
headerBuilder: _buildHeader,
footBuilder: _buildFooter,
cardBuilder: (_, column, columnItem) => _buildCard(
context,
column,
columnItem,
),
columnConstraints: const BoxConstraints.tightFor(width: 240),
config: AFBoardConfig(
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
),
),
),
);
},
),
);
}
@override
void dispose() {
scrollController.dispose();
super.dispose();
}
Widget _buildHeader(
BuildContext context, AFBoardColumnHeaderData headerData) {
return AppFlowyColumnHeader(
@ -138,7 +180,8 @@ class BoardContent extends StatelessWidget {
AFBoardColumnData column,
AFColumnItem columnItem,
) {
final rowPB = (columnItem as BoardColumnItem).row;
final boardColumnItem = columnItem as BoardColumnItem;
final rowPB = boardColumnItem.row;
final rowCache = context.read<BoardBloc>().getRowCache(rowPB.blockId);
/// Return placeholder widget if the rowCache is null.
@ -155,16 +198,17 @@ class BoardContent extends StatelessWidget {
final cellBuilder = BoardCellBuilder(cardController);
final isEditing = context.read<BoardBloc>().state.editingRow.fold(
() => false,
(editingRow) => editingRow.id == rowPB.id,
(editingRow) => editingRow.row.id == rowPB.id,
);
return AppFlowyColumnItemCard(
key: ObjectKey(columnItem),
key: ValueKey(columnItem.id),
margin: config.cardPadding,
decoration: _makeBoxDecoration(context),
child: BoardCard(
gridId: gridId,
groupId: column.id,
fieldId: boardColumnItem.fieldId,
isEditing: isEditing,
cellBuilder: cellBuilder,
dataController: cardController,

View File

@ -35,8 +35,9 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
return BlocProvider.value(
value: _cellBloc,
child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
buildWhen: (previous, current) =>
previous.selectedOptions != current.selectedOptions,
buildWhen: (previous, current) {
return previous.selectedOptions != current.selectedOptions;
},
builder: (context, state) {
if (state.selectedOptions
.where((element) => element.id == widget.groupId)

View File

@ -15,6 +15,7 @@ typedef OnEndEditing = void Function(String rowId);
class BoardCard extends StatefulWidget {
final String gridId;
final String groupId;
final String fieldId;
final bool isEditing;
final CardDataController dataController;
final BoardCellBuilder cellBuilder;
@ -24,6 +25,7 @@ class BoardCard extends StatefulWidget {
const BoardCard({
required this.gridId,
required this.groupId,
required this.fieldId,
required this.isEditing,
required this.dataController,
required this.cellBuilder,
@ -43,6 +45,7 @@ class _BoardCardState extends State<BoardCard> {
void initState() {
_cardBloc = BoardCardBloc(
gridId: widget.gridId,
fieldId: widget.fieldId,
dataController: widget.dataController,
)..add(const BoardCardEvent.initial());
super.initState();
@ -53,6 +56,9 @@ class _BoardCardState extends State<BoardCard> {
return BlocProvider.value(
value: _cardBloc,
child: BlocBuilder<BoardCardBloc, BoardCardState>(
buildWhen: (previous, current) {
return previous.cells.length != current.cells.length;
},
builder: (context, state) {
return BoardCardContainer(
accessoryBuilder: (context) {
@ -62,7 +68,10 @@ class _BoardCardState extends State<BoardCard> {
widget.openCard(context);
},
child: Column(
children: _makeCells(context, state.gridCellMap),
children: _makeCells(
context,
state.cells.map((cell) => cell.identifier).toList(),
),
),
);
},
@ -70,9 +79,12 @@ class _BoardCardState extends State<BoardCard> {
);
}
List<Widget> _makeCells(BuildContext context, GridCellMap cellMap) {
return cellMap.values.map(
(cellId) {
List<Widget> _makeCells(
BuildContext context,
List<GridCellIdentifier> cells,
) {
return cells.map(
(GridCellIdentifier cellId) {
final child = widget.cellBuilder.buildCell(widget.groupId, cellId);
return Padding(
padding: const EdgeInsets.only(left: 4, right: 4, top: 6),

View File

@ -35,7 +35,7 @@ class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
void show(BuildContext context) async {
final windowSize = MediaQuery.of(context).size;
final size = windowSize * 0.7;
final size = windowSize * 0.5;
FlowyOverlay.of(context).insertWithRect(
widget: OverlayContainer(
child: this,

View File

@ -114,7 +114,7 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
return Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 60),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
child: Text(item.s),
),
);
@ -124,7 +124,7 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
return Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 60),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [

View File

@ -1,13 +1,27 @@
import 'package:appflowy_board/src/utils/log.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'board_column/board_column.dart';
import 'board_column/board_column_data.dart';
import 'board_data.dart';
import 'reorder_flex/drag_state.dart';
import 'reorder_flex/drag_target_interceptor.dart';
import 'reorder_flex/reorder_flex.dart';
import 'reorder_phantom/phantom_controller.dart';
import '../rendering/board_overlay.dart';
class AFBoardScrollManager {
BoardColumnsState? _columnState;
// AFBoardScrollManager();
void scrollToBottom(String columnId, VoidCallback? completed) {
_columnState
?.getReorderFlexState(columnId: columnId)
?.scrollToBottom(completed);
}
}
class AFBoardConfig {
final double cornerRadius;
final EdgeInsets columnPadding;
@ -56,6 +70,10 @@ class AFBoard extends StatelessWidget {
final AFBoardConfig config;
final AFBoardScrollManager? scrollManager;
final BoardColumnsState _columnState = BoardColumnsState();
AFBoard({
required this.dataController,
required this.cardBuilder,
@ -63,6 +81,7 @@ class AFBoard extends StatelessWidget {
this.footBuilder,
this.headerBuilder,
this.scrollController,
this.scrollManager,
this.columnConstraints = const BoxConstraints(maxWidth: 200),
this.config = const AFBoardConfig(),
Key? key,
@ -75,10 +94,16 @@ class AFBoard extends StatelessWidget {
value: dataController,
child: Consumer<AFBoardDataController>(
builder: (context, notifier, child) {
return BoardContent(
if (scrollManager != null) {
scrollManager!._columnState = _columnState;
}
return AFBoardContent(
config: config,
dataController: dataController,
scrollController: scrollController,
scrollManager: scrollManager,
columnsState: _columnState,
background: background,
delegate: phantomController,
columnConstraints: columnConstraints,
@ -94,7 +119,7 @@ class AFBoard extends StatelessWidget {
}
}
class BoardContent extends StatefulWidget {
class AFBoardContent extends StatefulWidget {
final ScrollController? scrollController;
final OnDragStarted? onDragStarted;
final OnReorder onReorder;
@ -104,6 +129,8 @@ class BoardContent extends StatefulWidget {
final AFBoardConfig config;
final ReorderFlexConfig reorderFlexConfig;
final BoxConstraints columnConstraints;
final AFBoardScrollManager? scrollManager;
final BoardColumnsState columnsState;
///
final AFBoardColumnCardBuilder cardBuilder;
@ -118,11 +145,13 @@ class BoardContent extends StatefulWidget {
final BoardPhantomController phantomController;
const BoardContent({
const AFBoardContent({
required this.config,
required this.onReorder,
required this.delegate,
required this.dataController,
required this.scrollManager,
required this.columnsState,
this.onDragStarted,
this.onDragEnded,
this.scrollController,
@ -137,12 +166,12 @@ class BoardContent extends StatefulWidget {
super(key: key);
@override
State<BoardContent> createState() => _BoardContentState();
State<AFBoardContent> createState() => _AFBoardContentState();
}
class _BoardContentState extends State<BoardContent> {
final GlobalKey _columnContainerOverlayKey =
GlobalKey(debugLabel: '$BoardContent overlay key');
class _AFBoardContentState extends State<AFBoardContent> {
final GlobalKey _boardContentKey =
GlobalKey(debugLabel: '$AFBoardContent overlay key');
late BoardOverlayEntry _overlayEntry;
@override
@ -153,10 +182,10 @@ class _BoardContentState extends State<BoardContent> {
reorderFlexId: widget.dataController.identifier,
acceptedReorderFlexId: widget.dataController.columnIds,
delegate: widget.delegate,
columnsState: widget.columnsState,
);
final reorderFlex = ReorderFlex(
key: widget.key,
config: widget.reorderFlexConfig,
scrollController: widget.scrollController,
onDragStarted: widget.onDragStarted,
@ -165,7 +194,7 @@ class _BoardContentState extends State<BoardContent> {
dataSource: widget.dataController,
direction: Axis.horizontal,
interceptor: interceptor,
children: _buildColumns(interceptor.columnKeys),
children: _buildColumns(),
);
return Stack(
@ -192,12 +221,12 @@ class _BoardContentState extends State<BoardContent> {
@override
Widget build(BuildContext context) {
return BoardOverlay(
key: _columnContainerOverlayKey,
key: _boardContentKey,
initialEntries: [_overlayEntry],
);
}
List<Widget> _buildColumns(List<ColumnKey> columnKeys) {
List<Widget> _buildColumns() {
final List<Widget> children =
widget.dataController.columnDatas.asMap().entries.map(
(item) {
@ -215,6 +244,8 @@ class _BoardContentState extends State<BoardContent> {
child: Consumer<AFBoardColumnDataController>(
builder: (context, value, child) {
final boardColumn = AFBoardColumnWidget(
key: PageStorageKey<String>(columnData.id),
// key: GlobalObjectKey(columnData.id),
margin: _marginFromIndex(columnIndex),
itemMargin: widget.config.columnItemPadding,
headerBuilder: _buildHeader,
@ -226,16 +257,11 @@ class _BoardContentState extends State<BoardContent> {
onReorder: widget.dataController.moveColumnItem,
cornerRadius: widget.config.cornerRadius,
backgroundColor: widget.config.columnBackgroundColor,
dragStateStorage: widget.columnsState,
dragTargetIndexKeyStorage: widget.columnsState,
);
// columnKeys
// .removeWhere((element) => element.columnId == columnData.id);
// columnKeys.add(
// ColumnKey(
// columnId: columnData.id,
// key: boardColumn.columnGlobalKey,
// ),
// );
widget.columnsState.addColumn(columnData.id, boardColumn);
return ConstrainedBox(
constraints: widget.columnConstraints,
@ -296,3 +322,77 @@ class _BoardColumnDataSourceImpl extends AFBoardColumnDataDataSource {
@override
List<String> get acceptedColumnIds => dataController.columnIds;
}
class BoardColumnContext {
GlobalKey? columnKey;
DraggingState? draggingState;
}
class BoardColumnsState extends DraggingStateStorage
with ReorderDragTargetIndexKeyStorage {
/// Quick access to the [AFBoardColumnWidget]
final Map<String, GlobalKey> columnKeys = {};
final Map<String, DraggingState> columnDragStates = {};
final Map<String, Map<String, GlobalObjectKey>> columnDragDragTargets = {};
void addColumn(String columnId, AFBoardColumnWidget columnWidget) {
columnKeys[columnId] = columnWidget.globalKey;
}
ReorderFlexState? getReorderFlexState({required String columnId}) {
final flexGlobalKey = columnKeys[columnId];
if (flexGlobalKey == null) return null;
if (flexGlobalKey.currentState is! ReorderFlexState) return null;
final state = flexGlobalKey.currentState as ReorderFlexState;
return state;
}
ReorderFlex? getReorderFlex({required String columnId}) {
final flexGlobalKey = columnKeys[columnId];
if (flexGlobalKey == null) return null;
if (flexGlobalKey.currentWidget is! ReorderFlex) return null;
final widget = flexGlobalKey.currentWidget as ReorderFlex;
return widget;
}
@override
DraggingState? read(String reorderFlexId) {
return columnDragStates[reorderFlexId];
}
@override
void write(String reorderFlexId, DraggingState state) {
Log.trace('$reorderFlexId Write dragging state: $state');
columnDragStates[reorderFlexId] = state;
}
@override
void remove(String reorderFlexId) {
columnDragStates.remove(reorderFlexId);
}
@override
void addKey(
String reorderFlexId,
String key,
GlobalObjectKey<State<StatefulWidget>> value,
) {
Map<String, GlobalObjectKey>? column = columnDragDragTargets[reorderFlexId];
if (column == null) {
column = {};
columnDragDragTargets[reorderFlexId] = column;
}
column[key] = value;
}
@override
GlobalObjectKey<State<StatefulWidget>>? readKey(
String reorderFlexId, String key) {
Map<String, GlobalObjectKey>? column = columnDragDragTargets[reorderFlexId];
if (column != null) {
return column[key];
} else {
return null;
}
}
}

View File

@ -1,5 +1,6 @@
import 'dart:collection';
import 'package:appflowy_board/src/widgets/reorder_flex/drag_state.dart';
import 'package:flutter/material.dart';
import '../../rendering/board_overlay.dart';
import '../../utils/log.dart';
@ -65,7 +66,6 @@ class AFBoardColumnWidget extends StatefulWidget {
final AFBoardColumnDataDataSource dataSource;
final ScrollController? scrollController;
final ReorderFlexConfig config;
final OnColumnDragStarted? onDragStarted;
final OnColumnReorder onReorder;
final OnColumnDragEnded? onDragEnded;
@ -88,7 +88,11 @@ class AFBoardColumnWidget extends StatefulWidget {
final Color backgroundColor;
final GlobalKey columnGlobalKey = GlobalKey();
final DraggingStateStorage? dragStateStorage;
final ReorderDragTargetIndexKeyStorage? dragTargetIndexKeyStorage;
final GlobalKey globalKey;
AFBoardColumnWidget({
Key? key,
@ -98,14 +102,17 @@ class AFBoardColumnWidget extends StatefulWidget {
required this.onReorder,
required this.dataSource,
required this.phantomController,
this.onDragStarted,
this.dragStateStorage,
this.dragTargetIndexKeyStorage,
this.scrollController,
this.onDragStarted,
this.onDragEnded,
this.margin = EdgeInsets.zero,
this.itemMargin = EdgeInsets.zero,
this.cornerRadius = 0.0,
this.backgroundColor = Colors.transparent,
}) : config = const ReorderFlexConfig(),
}) : globalKey = GlobalKey(),
config = const ReorderFlexConfig(),
super(key: key);
@override
@ -115,7 +122,6 @@ class AFBoardColumnWidget extends StatefulWidget {
class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
final GlobalKey _columnOverlayKey =
GlobalKey(debugLabel: '$AFBoardColumnWidget overlay key');
late BoardOverlayEntry _overlayEntry;
@override
@ -140,7 +146,9 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
);
Widget reorderFlex = ReorderFlex(
key: widget.columnGlobalKey,
key: widget.globalKey,
dragStateStorage: widget.dragStateStorage,
dragTargetIndexKeyStorage: widget.dragTargetIndexKeyStorage,
scrollController: widget.scrollController,
config: widget.config,
onDragStarted: (index) {
@ -163,9 +171,6 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
children: children,
);
// reorderFlex =
// KeyedSubtree(key: widget.columnGlobalKey, child: reorderFlex);
return Container(
margin: widget.margin,
clipBehavior: Clip.hardEdge,
@ -177,10 +182,7 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
children: [
if (header != null) header,
Expanded(
child: Padding(
padding: widget.itemMargin,
child: reorderFlex,
),
child: Padding(padding: widget.itemMargin, child: reorderFlex),
),
if (footer != null) footer,
],

View File

@ -196,7 +196,6 @@ class AFBoardDataController extends ChangeNotifier
final index =
columnDataController.items.indexWhere((item) => item.isPhantom);
assert(index != -1);
if (index != -1) {
if (index != newIndex) {
Log.trace(

View File

@ -24,6 +24,10 @@ class FlexDragTargetData extends DragTargetData {
final String dragTargetId;
Offset dragTargetOffset = Offset.zero;
final GlobalObjectKey dragTargetIndexKey;
final String reorderFlexId;
final ReoderFlexItem reorderFlexItem;
@ -33,6 +37,7 @@ class FlexDragTargetData extends DragTargetData {
required this.draggingIndex,
required this.reorderFlexId,
required this.reorderFlexItem,
required this.dragTargetIndexKey,
required DraggingState state,
}) : _state = state;
@ -40,6 +45,50 @@ class FlexDragTargetData extends DragTargetData {
String toString() {
return 'ReorderFlexId: $reorderFlexId, dragTargetId: $dragTargetId';
}
bool isOverlapWithWidgets(List<GlobalObjectKey> widgetKeys) {
final renderBox = dragTargetIndexKey.currentContext?.findRenderObject();
if (renderBox == null) return false;
if (renderBox is! RenderBox) return false;
final size = feedbackSize ?? Size.zero;
final Rect rect = dragTargetOffset & size;
for (final widgetKey in widgetKeys) {
final renderObject = widgetKey.currentContext?.findRenderObject();
if (renderObject != null && renderObject is RenderBox) {
Rect widgetRect =
renderObject.localToGlobal(Offset.zero) & renderObject.size;
// return rect.overlaps(widgetRect);
if (rect.right <= widgetRect.left || widgetRect.right <= rect.left) {
return false;
}
if (rect.bottom <= widgetRect.top || widgetRect.bottom <= rect.top) {
return false;
}
return true;
}
}
// final HitTestResult result = HitTestResult();
// WidgetsBinding.instance.hitTest(result, position);
// for (final HitTestEntry entry in result.path) {
// final HitTestTarget target = entry.target;
// if (target is RenderMetaData) {
// print(target.metaData);
// }
// print(target);
// }
return false;
}
}
abstract class DraggingStateStorage {
void write(String reorderFlexId, DraggingState state);
void remove(String reorderFlexId);
DraggingState? read(String reorderFlexId);
}
class DraggingState {
@ -128,6 +177,7 @@ class DraggingState {
/// Set the currentIndex to nextIndex
void moveDragTargetToNext() {
Log.debug('$reorderFlexId updateCurrentIndex: $nextIndex');
currentIndex = nextIndex;
}
@ -136,6 +186,14 @@ class DraggingState {
nextIndex = index;
}
void setStartDraggingIndex(int index) {
Log.debug('$reorderFlexId setDragIndex: $index');
dragStartIndex = index;
phantomIndex = index;
currentIndex = index;
nextIndex = index;
}
bool isNotDragging() {
return dragStartIndex == -1;
}

View File

@ -26,6 +26,11 @@ typedef DragTargetWillAccepted<T extends DragTargetData> = bool Function(
///
typedef DragTargetOnStarted = void Function(Widget, int, Size?);
typedef DragTargetOnMove<T extends DragTargetData> = void Function(
T dragTargetData,
Offset offset,
);
///
typedef DragTargetOnEnded<T extends DragTargetData> = void Function(
T dragTargetData);
@ -39,13 +44,15 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
final Widget child;
final T dragTargetData;
final GlobalObjectKey _indexGlobalKey;
final GlobalObjectKey indexGlobalKey;
/// Called when dragTarget is being dragging.
final DragTargetOnStarted onDragStarted;
final DragTargetOnEnded<T> onDragEnded;
final DragTargetOnMove<T> onDragMoved;
/// Called to determine whether this widget is interested in receiving a given
/// piece of data being dragged over this drag target.
///
@ -69,11 +76,13 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
final bool useMoveAnimation;
ReorderDragTarget({
const ReorderDragTarget({
Key? key,
required this.child,
required this.indexGlobalKey,
required this.dragTargetData,
required this.onDragStarted,
required this.onDragMoved,
required this.onDragEnded,
required this.onWillAccept,
required this.insertAnimationController,
@ -82,8 +91,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
this.onAccept,
this.onLeave,
this.draggableTargetBuilder,
}) : _indexGlobalKey = GlobalObjectKey(child.key!),
super(key: key);
}) : super(key: key);
@override
State<ReorderDragTarget<T>> createState() => _ReorderDragTargetState<T>();
@ -104,6 +112,9 @@ class _ReorderDragTargetState<T extends DragTargetData>
return widget.onWillAccept(dragTargetData);
},
onAccept: widget.onAccept,
onMove: (detail) {
widget.onDragMoved(detail.data, detail.offset);
},
onLeave: (dragTargetData) {
assert(dragTargetData != null);
if (dragTargetData != null) {
@ -112,7 +123,7 @@ class _ReorderDragTargetState<T extends DragTargetData>
},
);
dragTarget = KeyedSubtree(key: widget._indexGlobalKey, child: dragTarget);
dragTarget = KeyedSubtree(key: widget.indexGlobalKey, child: dragTarget);
return dragTarget;
}
@ -150,7 +161,7 @@ class _ReorderDragTargetState<T extends DragTargetData>
child: widget.child,
),
onDragStarted: () {
_draggingFeedbackSize = widget._indexGlobalKey.currentContext?.size;
_draggingFeedbackSize = widget.indexGlobalKey.currentContext?.size;
widget.onDragStarted(
widget.child,
widget.dragTargetData.draggingIndex,

View File

@ -1,7 +1,7 @@
import 'dart:async';
import 'package:appflowy_board/src/widgets/board.dart';
import 'package:flutter/material.dart';
import '../../utils/log.dart';
import 'drag_state.dart';
import 'drag_target.dart';
@ -41,7 +41,7 @@ abstract class OverlapDragTargetDelegate {
int dragTargetIndex,
);
int canMoveTo(String dragTargetId);
int getInsertedIndex(String dragTargetId);
}
/// [OverlappingDragTargetInterceptor] is used to receive the overlapping
@ -55,13 +55,14 @@ class OverlappingDragTargetInterceptor extends DragTargetInterceptor {
final String reorderFlexId;
final List<String> acceptedReorderFlexId;
final OverlapDragTargetDelegate delegate;
final List<ColumnKey> columnKeys = [];
final BoardColumnsState columnsState;
Timer? _delayOperation;
OverlappingDragTargetInterceptor({
required this.delegate,
required this.reorderFlexId,
required this.acceptedReorderFlexId,
required this.columnsState,
});
@override
@ -79,24 +80,30 @@ class OverlappingDragTargetInterceptor extends DragTargetInterceptor {
if (dragTargetId == dragTargetData.reorderFlexId) {
delegate.cancel();
} else {
// Ignore the event if the dragTarget overlaps with the other column's dragTargets.
final columnKeys = columnsState.columnDragDragTargets[dragTargetId];
if (columnKeys != null) {
final keys = columnKeys.values.toList();
if (dragTargetData.isOverlapWithWidgets(keys)) {
_delayOperation?.cancel();
return true;
}
}
/// The priority of the column interactions is high than the cross column.
/// Workaround: delay 100 milliseconds to lower the cross column event priority.
///
_delayOperation?.cancel();
_delayOperation = Timer(const Duration(milliseconds: 100), () {
final index = delegate.canMoveTo(dragTargetId);
final index = delegate.getInsertedIndex(dragTargetId);
if (index != -1) {
Log.trace(
'[$OverlappingDragTargetInterceptor] move to $dragTargetId at $index');
delegate.moveTo(dragTargetId, dragTargetData, index);
// final columnIndex = columnKeys
// .indexWhere((element) => element.columnId == dragTargetId);
// if (columnIndex != -1) {
// final state = columnKeys[columnIndex].key.currentState;
// if (state is ReorderFlexState) {
// state.handleOnWillAccept(context, index);
// }
// }
columnsState
.getReorderFlexState(columnId: dragTargetId)
?.resetDragTargetIndex(index);
}
});
}
@ -105,12 +112,6 @@ class OverlappingDragTargetInterceptor extends DragTargetInterceptor {
}
}
class ColumnKey {
String columnId;
GlobalKey key;
ColumnKey({required this.columnId, required this.key});
}
abstract class CrossReorderFlexDragTargetDelegate {
/// * [reorderFlexId] is the id that the [ReorderFlex] passed in.
bool acceptNewDragTargetData(

View File

@ -31,6 +31,11 @@ abstract class ReoderFlexItem {
String get id;
}
abstract class ReorderDragTargetIndexKeyStorage {
void addKey(String reorderFlexId, String key, GlobalObjectKey value);
GlobalObjectKey? readKey(String reorderFlexId, String key);
}
class ReorderFlexConfig {
/// The opacity of the dragging widget
final double draggingWidgetOpacity = 0.3;
@ -52,7 +57,6 @@ class ReorderFlexConfig {
class ReorderFlex extends StatefulWidget {
final ReorderFlexConfig config;
final List<Widget> children;
/// [direction] How to place the children, default is Axis.vertical
@ -74,18 +78,26 @@ class ReorderFlex extends StatefulWidget {
final DragTargetInterceptor? interceptor;
const ReorderFlex({
final DraggingStateStorage? dragStateStorage;
final ReorderDragTargetIndexKeyStorage? dragTargetIndexKeyStorage;
ReorderFlex({
Key? key,
this.scrollController,
required this.dataSource,
required this.children,
required this.config,
required this.onReorder,
this.dragStateStorage,
this.dragTargetIndexKeyStorage,
this.onDragStarted,
this.onDragEnded,
this.interceptor,
this.direction = Axis.vertical,
}) : super(key: key);
}) : assert(children.every((Widget w) => w.key != null),
'All child must have a key.'),
super(key: key);
@override
State<ReorderFlex> createState() => ReorderFlexState();
@ -115,7 +127,12 @@ class ReorderFlexState extends State<ReorderFlex>
@override
void initState() {
_notifier = ReorderFlexNotifier();
dragState = DraggingState(widget.reorderFlexId);
final flexId = widget.reorderFlexId;
dragState = widget.dragStateStorage?.read(flexId) ??
DraggingState(widget.reorderFlexId);
Log.trace('[DragTarget] init dragState: $dragState');
widget.dragStateStorage?.remove(flexId);
_animation = DragTargetAnimation(
reorderAnimationDuration: widget.config.reorderAnimationDuration,
@ -159,7 +176,17 @@ class ReorderFlexState extends State<ReorderFlex>
for (int i = 0; i < widget.children.length; i += 1) {
Widget child = widget.children[i];
children.add(_wrap(child, i));
final ReoderFlexItem item = widget.dataSource.items[i];
final indexKey = GlobalObjectKey(child.key!);
// Save the index key for quick access
widget.dragTargetIndexKeyStorage?.addKey(
widget.reorderFlexId,
item.id,
indexKey,
);
children.add(_wrap(child, i, indexKey));
// if (widget.config.useMovePlaceholder) {
// children.add(DragTargeMovePlaceholder(
@ -203,10 +230,10 @@ class ReorderFlexState extends State<ReorderFlex>
/// [child]: the child will be wrapped with dartTarget
/// [childIndex]: the index of the child in a list
Widget _wrap(Widget child, int childIndex) {
Widget _wrap(Widget child, int childIndex, GlobalObjectKey indexKey) {
return Builder(builder: (context) {
final ReorderDragTarget dragTarget =
_buildDragTarget(context, child, childIndex);
_buildDragTarget(context, child, childIndex, indexKey);
int shiftedIndex = childIndex;
if (dragState.isOverlapWithPhantom()) {
@ -312,22 +339,31 @@ class ReorderFlexState extends State<ReorderFlex>
}
ReorderDragTarget _buildDragTarget(
BuildContext builderContext, Widget child, int dragTargetIndex) {
final ReoderFlexItem reorderFlexItem =
widget.dataSource.items[dragTargetIndex];
BuildContext builderContext,
Widget child,
int dragTargetIndex,
GlobalObjectKey indexKey,
) {
final reorderFlexItem = widget.dataSource.items[dragTargetIndex];
return ReorderDragTarget<FlexDragTargetData>(
indexGlobalKey: indexKey,
dragTargetData: FlexDragTargetData(
draggingIndex: dragTargetIndex,
reorderFlexId: widget.reorderFlexId,
reorderFlexItem: reorderFlexItem,
state: dragState,
dragTargetId: reorderFlexItem.id,
dragTargetIndexKey: indexKey,
),
onDragStarted: (draggingWidget, draggingIndex, size) {
Log.debug(
"[DragTarget] Column:[${widget.dataSource.identifier}] start dragging item at $draggingIndex");
_startDragging(draggingWidget, draggingIndex, size);
widget.onDragStarted?.call(draggingIndex);
widget.dragStateStorage?.remove(widget.reorderFlexId);
},
onDragMoved: (dragTargetData, offset) {
dragTargetData.dragTargetOffset = offset;
},
onDragEnded: (dragTargetData) {
if (!mounted) return;
@ -431,14 +467,20 @@ class ReorderFlexState extends State<ReorderFlex>
});
}
void resetDragTargetIndex(int dragTargetIndex) {
dragState.setStartDraggingIndex(dragTargetIndex);
widget.dragStateStorage?.write(
widget.reorderFlexId,
dragState,
);
}
bool handleOnWillAccept(BuildContext context, int dragTargetIndex) {
final dragIndex = dragState.dragStartIndex;
/// The [willAccept] will be true if the dargTarget is the widget that gets
/// dragged and it is dragged on top of the other dragTargets.
///
Log.trace(
'[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, dragIndex:$dragIndex, dragTargetIndex:$dragTargetIndex, count: ${widget.dataSource.items.length}');
bool willAccept =
dragState.dragStartIndex == dragIndex && dragIndex != dragTargetIndex;
@ -452,6 +494,9 @@ class ReorderFlexState extends State<ReorderFlex>
_requestAnimationToNextIndex(isAcceptingNewTarget: true);
});
Log.trace(
'[$ReorderDragTarget] ${widget.reorderFlexId} dragging state: $dragState}');
_scrollTo(context);
/// If the target is not the original starting point, then we will accept the drop.
@ -515,6 +560,50 @@ class ReorderFlexState extends State<ReorderFlex>
}
}
void scrollToBottom(VoidCallback? completed) {
if (_scrolling) {
completed?.call();
return;
}
if (widget.dataSource.items.isNotEmpty) {
final item = widget.dataSource.items.last;
final indexKey = widget.dragTargetIndexKeyStorage?.readKey(
widget.reorderFlexId,
item.id,
);
if (indexKey == null) {
completed?.call();
return;
}
final indexContext = indexKey.currentContext;
if (indexContext == null || _scrollController.hasClients == false) {
completed?.call();
return;
}
final renderObject = indexContext.findRenderObject();
if (renderObject != null) {
_scrolling = true;
_scrollController.position
.ensureVisible(
renderObject,
alignment: 0.5,
duration: const Duration(milliseconds: 120),
)
.then((value) {
setState(() {
_scrolling = false;
completed?.call();
});
});
} else {
completed?.call();
}
}
}
// Scrolls to a target context if that context is not on the screen.
void _scrollTo(BuildContext context) {
if (_scrolling) return;

View File

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

View File

@ -18,7 +18,7 @@ use flowy_sync::client_grid::{GridRevisionChangeset, GridRevisionPad, JsonDeseri
use flowy_sync::entities::revision::Revision;
use flowy_sync::errors::CollaborateResult;
use flowy_sync::util::make_text_delta_from_revisions;
use lib_infra::future::FutureResult;
use lib_infra::future::{wrap_future, FutureResult};
use std::collections::HashMap;
use std::sync::Arc;
@ -591,34 +591,33 @@ impl GridRevisionEditor {
match self.block_manager.get_row_rev(&from_row_id).await? {
None => tracing::warn!("Move row failed, can not find the row:{}", from_row_id),
Some(row_rev) => {
if let Some(row_changeset) = self
.view_manager
.move_group_row(row_rev, to_group_id, to_row_id.clone())
.await
{
tracing::trace!("Move group row cause row data changed: {:?}", row_changeset);
let block_manager = self.block_manager.clone();
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);
let cell_changesets = row_changeset
.cell_by_field_id
.into_iter()
.map(|(field_id, cell_rev)| CellChangesetPB {
grid_id: view_id.clone(),
row_id: row_changeset.row_id.clone(),
field_id,
content: cell_rev.data,
})
.collect::<Vec<CellChangesetPB>>();
let cell_changesets = row_changeset
.cell_by_field_id
.into_iter()
.map(|(field_id, cell_rev)| CellChangesetPB {
grid_id: view_id.clone(),
row_id: row_changeset.row_id.clone(),
field_id,
content: cell_rev.data,
for cell_changeset in cell_changesets {
match block_manager.update_cell(cell_changeset).await {
Ok(_) => {}
Err(e) => tracing::error!("Apply cell changeset error:{:?}", e),
}
}
})
.collect::<Vec<CellChangesetPB>>();
for cell_changeset in cell_changesets {
match self.block_manager.update_cell(cell_changeset).await {
Ok(_) => {}
Err(e) => tracing::error!("Apply cell changeset error:{:?}", e),
}
}
}
})
.await?;
}
}
Ok(())
}

View File

@ -140,8 +140,8 @@ impl GridViewRevisionEditor {
row_changeset: &mut RowChangeset,
to_group_id: &str,
to_row_id: Option<String>,
) {
if let Some(changesets) = self
) -> Vec<GroupChangesetPB> {
match self
.group_service
.write()
.await
@ -150,9 +150,8 @@ impl GridViewRevisionEditor {
})
.await
{
for changeset in changesets {
self.notify_did_update_group(changeset).await;
}
None => vec![],
Some(changesets) => changesets,
}
}
/// Only call once after grid view editor initialized
@ -266,7 +265,7 @@ impl GridViewRevisionEditor {
Ok(())
}
async fn notify_did_update_group(&self, changeset: GroupChangesetPB) {
pub async fn notify_did_update_group(&self, changeset: GroupChangesetPB) {
send_dart_notification(&changeset.group_id, GridNotification::DidUpdateGroup)
.payload(changeset)
.send();

View File

@ -134,19 +134,23 @@ impl GridViewManager {
row_rev: Arc<RowRevision>,
to_group_id: String,
to_row_id: Option<String>,
) -> Option<RowChangeset> {
with_row_changeset: impl FnOnce(RowChangeset) -> AFFuture<()>,
) -> FlowyResult<()> {
let mut row_changeset = RowChangeset::new(row_rev.id.clone());
for view_editor in self.view_editors.iter() {
view_editor
.move_group_row(&row_rev, &mut row_changeset, &to_group_id, to_row_id.clone())
.await;
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())
.await;
if row_changeset.is_empty() == false {
with_row_changeset(row_changeset).await;
}
if row_changeset.is_empty() {
None
} else {
Some(row_changeset)
for group_changeset in group_changesets {
view_editor.notify_did_update_group(group_changeset).await;
}
Ok(())
}
pub(crate) async fn did_update_field(&self, field_id: &str) -> FlowyResult<()> {