Kaynağa Gözat

Merge branch 'AppFlowy-IO:main' into documentation/flowy_editor

Lucas.Xu 2 yıl önce
ebeveyn
işleme
713e6abaa6
47 değiştirilmiş dosya ile 607 ekleme ve 293 silme
  1. 46 19
      frontend/app_flowy/lib/plugins/board/application/board_bloc.dart
  2. 5 5
      frontend/app_flowy/lib/plugins/board/application/board_data_controller.dart
  3. 10 5
      frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart
  4. 49 0
      frontend/app_flowy/lib/plugins/board/application/group_controller.dart
  5. 51 0
      frontend/app_flowy/lib/plugins/board/application/group_listener.dart
  6. 37 1
      frontend/app_flowy/lib/plugins/board/presentation/board_page.dart
  7. 10 2
      frontend/app_flowy/lib/plugins/board/presentation/card/card.dart
  8. 10 5
      frontend/app_flowy/lib/plugins/board/presentation/card/card_container.dart
  9. 3 4
      frontend/app_flowy/lib/plugins/grid/application/field/field_service.dart
  10. 2 2
      frontend/app_flowy/lib/plugins/grid/application/grid_service.dart
  11. 4 4
      frontend/app_flowy/lib/plugins/grid/application/row/row_action_sheet_bloc.dart
  12. 2 3
      frontend/app_flowy/lib/plugins/grid/application/row/row_bloc.dart
  13. 0 2
      frontend/app_flowy/lib/plugins/grid/application/row/row_cache.dart
  14. 27 16
      frontend/app_flowy/lib/plugins/grid/application/row/row_service.dart
  15. 4 2
      frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart
  16. 5 2
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_action_sheet.dart
  17. 2 2
      frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart
  18. 2 2
      frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart
  19. 5 2
      frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart
  20. 1 1
      frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart
  21. 5 5
      frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart
  22. 3 3
      frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_interceptor.dart
  23. 8 8
      frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart
  24. 5 4
      frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart
  25. 1 1
      frontend/rust-lib/flowy-grid/src/dart_notification.rs
  26. 60 26
      frontend/rust-lib/flowy-grid/src/entities/grid_entities.rs
  27. 2 41
      frontend/rust-lib/flowy-grid/src/entities/group_entities/board_card.rs
  28. 43 0
      frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs
  29. 2 0
      frontend/rust-lib/flowy-grid/src/entities/group_entities/mod.rs
  30. 6 2
      frontend/rust-lib/flowy-grid/src/entities/row_entities.rs
  31. 14 14
      frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs
  32. 17 6
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  33. 10 6
      frontend/rust-lib/flowy-grid/src/event_map.rs
  34. 5 1
      frontend/rust-lib/flowy-grid/src/services/block_editor.rs
  35. 11 2
      frontend/rust-lib/flowy-grid/src/services/block_manager.rs
  36. 13 16
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  37. 64 18
      frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs
  38. 39 15
      frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs
  39. 1 1
      frontend/rust-lib/flowy-grid/src/services/group/group_generator/checkbox_group.rs
  40. 1 1
      frontend/rust-lib/flowy-grid/src/services/group/group_generator/generator.rs
  41. 2 2
      frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs
  42. 7 29
      frontend/rust-lib/flowy-grid/src/services/group/group_service.rs
  43. 3 3
      frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs
  44. 2 1
      frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs
  45. 3 3
      frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs
  46. 3 0
      shared-lib/flowy-error-code/src/code.rs
  47. 2 6
      shared-lib/flowy-sync/src/client_grid/block_revision_pad.rs

+ 46 - 19
frontend/app_flowy/lib/plugins/board/application/board_bloc.dart

@@ -14,12 +14,14 @@ import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:collection';
 
 import 'board_data_controller.dart';
+import 'group_controller.dart';
 
 part 'board_bloc.freezed.dart';
 
 class BoardBloc extends Bloc<BoardEvent, BoardState> {
   final BoardDataController _dataController;
-  late final AFBoardDataController boardDataController;
+  late final AFBoardDataController afBoardDataController;
+  List<GroupController> groupControllers = [];
 
   GridFieldCache get fieldCache => _dataController.fieldCache;
   String get gridId => _dataController.gridId;
@@ -27,7 +29,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
   BoardBloc({required ViewPB view})
       : _dataController = BoardDataController(view: view),
         super(BoardState.initial(view.id)) {
-    boardDataController = AFBoardDataController(
+    afBoardDataController = AFBoardDataController(
       onMoveColumn: (
         fromIndex,
         toIndex,
@@ -71,9 +73,6 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
           didReceiveGridUpdate: (GridPB grid) {
             emit(state.copyWith(grid: Some(grid)));
           },
-          didReceiveGroups: (List<GroupPB> groups) {
-            emit(state.copyWith(groups: groups));
-          },
           didReceiveRows: (List<RowInfo> rowInfos) {
             emit(state.copyWith(rowInfos: rowInfos));
           },
@@ -85,9 +84,24 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
   @override
   Future<void> close() async {
     await _dataController.dispose();
+    for (final controller in groupControllers) {
+      controller.dispose();
+    }
     return super.close();
   }
 
+  void initializeGroups(List<GroupPB> groups) {
+    for (final group in groups) {
+      final delegate = GroupControllerDelegateImpl(afBoardDataController);
+      final controller = GroupController(
+        group: group,
+        delegate: delegate,
+      );
+      controller.startListening();
+      groupControllers.add(controller);
+    }
+  }
+
   GridRowCache? getRowCache(String blockId) {
     final GridBlockCache? blockCache = _dataController.blocks[blockId];
     return blockCache?.rowCache;
@@ -100,7 +114,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
           add(BoardEvent.didReceiveGridUpdate(grid));
         }
       },
-      onGroupChanged: (groups) {
+      didLoadGroups: (groups) {
         List<AFBoardColumnData> columns = groups.map((group) {
           return AFBoardColumnData(
             id: group.groupId,
@@ -110,7 +124,8 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
           );
         }).toList();
 
-        boardDataController.addColumns(columns);
+        afBoardDataController.addColumns(columns);
+        initializeGroups(groups);
       },
       onRowsChanged: (List<RowInfo> rowInfos, RowsChangedReason reason) {
         add(BoardEvent.didReceiveRows(rowInfos));
@@ -123,14 +138,6 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
 
   List<AFColumnItem> _buildRows(List<RowPB> rows) {
     final items = rows.map((row) {
-      // final rowInfo = RowInfo(
-      //   gridId: _dataController.gridId,
-      //   blockId: row.blockId,
-      //   id: row.id,
-      //   fields: _dataController.fieldCache.unmodifiableFields,
-      //   height: row.height.toDouble(),
-      //   rawRow: row,
-      // );
       return BoardColumnItem(row: row);
     }).toList();
 
@@ -155,8 +162,6 @@ class BoardEvent with _$BoardEvent {
   const factory BoardEvent.initial() = InitialGrid;
   const factory BoardEvent.createRow(String groupId) = _CreateRow;
   const factory BoardEvent.endEditRow(String rowId) = _EndEditRow;
-  const factory BoardEvent.didReceiveGroups(List<GroupPB> groups) =
-      _DidReceiveGroup;
   const factory BoardEvent.didReceiveRows(List<RowInfo> rowInfos) =
       _DidReceiveRows;
   const factory BoardEvent.didReceiveGridUpdate(
@@ -169,7 +174,6 @@ class BoardState with _$BoardState {
   const factory BoardState({
     required String gridId,
     required Option<GridPB> grid,
-    required List<GroupPB> groups,
     required Option<RowPB> editingRow,
     required List<RowInfo> rowInfos,
     required GridLoadingState loadingState,
@@ -177,7 +181,6 @@ class BoardState with _$BoardState {
 
   factory BoardState.initial(String gridId) => BoardState(
         rowInfos: [],
-        groups: [],
         grid: none(),
         gridId: gridId,
         editingRow: none(),
@@ -228,3 +231,27 @@ class CreateCardItem extends AFColumnItem {
   @override
   String get id => '$CreateCardItem';
 }
+
+class GroupControllerDelegateImpl extends GroupControllerDelegate {
+  final AFBoardDataController controller;
+
+  GroupControllerDelegateImpl(this.controller);
+
+  @override
+  void insertRow(String groupId, RowPB row, int? index) {
+    final item = BoardColumnItem(row: row);
+    if (index != null) {
+      controller.insertColumnItem(groupId, index, item);
+    } else {
+      controller.addColumnItem(groupId, item);
+    }
+  }
+
+  @override
+  void removeRow(String groupId, String rowId) {
+    controller.removeColumnItem(groupId, rowId);
+  }
+
+  @override
+  void updateRow(String groupId, RowPB row) {}
+}

+ 5 - 5
frontend/app_flowy/lib/plugins/board/application/board_data_controller.dart

@@ -12,7 +12,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
 
 typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldPB>);
 typedef OnGridChanged = void Function(GridPB);
-typedef OnGroupChanged = void Function(List<GroupPB>);
+typedef DidLoadGroups = void Function(List<GroupPB>);
 typedef OnRowsChanged = void Function(
   List<RowInfo>,
   RowsChangedReason,
@@ -30,7 +30,7 @@ class BoardDataController {
 
   OnFieldsChanged? _onFieldsChanged;
   OnGridChanged? _onGridChanged;
-  OnGroupChanged? _onGroupChanged;
+  DidLoadGroups? _didLoadGroup;
   OnRowsChanged? _onRowsChanged;
   OnError? _onError;
 
@@ -51,13 +51,13 @@ class BoardDataController {
   void addListener({
     OnGridChanged? onGridChanged,
     OnFieldsChanged? onFieldsChanged,
-    OnGroupChanged? onGroupChanged,
+    DidLoadGroups? didLoadGroups,
     OnRowsChanged? onRowsChanged,
     OnError? onError,
   }) {
     _onGridChanged = onGridChanged;
     _onFieldsChanged = onFieldsChanged;
-    _onGroupChanged = onGroupChanged;
+    _didLoadGroup = didLoadGroups;
     _onRowsChanged = onRowsChanged;
     _onError = onError;
 
@@ -133,7 +133,7 @@ class BoardDataController {
     return Future(
       () => result.fold(
         (groups) {
-          _onGroupChanged?.call(groups.items);
+          _didLoadGroup?.call(groups.items);
         },
         (err) => _onError?.call(err),
       ),

+ 10 - 5
frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart

@@ -23,7 +23,6 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
   })  : _rowService = RowFFIService(
           gridId: gridId,
           blockId: dataController.rowPB.blockId,
-          rowId: dataController.rowPB.id,
         ),
         _dataController = dataController,
         super(BoardCardState.initial(
@@ -34,9 +33,6 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
           initial: (_InitialRow value) async {
             await _startListening();
           },
-          createRow: (_CreateRow value) {
-            _rowService.createRow();
-          },
           didReceiveCells: (_DidReceiveCells value) async {
             final cells = value.gridCellMap.values
                 .map((e) => GridCellEquatable(e.field))
@@ -58,6 +54,16 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
     return super.close();
   }
 
+  RowInfo rowInfo() {
+    return RowInfo(
+      gridId: _rowService.gridId,
+      fields: UnmodifiableListView(
+        state.cells.map((cell) => cell._field).toList(),
+      ),
+      rowPB: state.rowPB,
+    );
+  }
+
   Future<void> _startListening() async {
     _dataController.addListener(
       onRowChanged: (cells, reason) {
@@ -72,7 +78,6 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
 @freezed
 class BoardCardEvent with _$BoardCardEvent {
   const factory BoardCardEvent.initial() = _InitialRow;
-  const factory BoardCardEvent.createRow() = _CreateRow;
   const factory BoardCardEvent.didReceiveCells(
       GridCellMap gridCellMap, RowsChangedReason reason) = _DidReceiveCells;
 }

+ 49 - 0
frontend/app_flowy/lib/plugins/board/application/group_controller.dart

@@ -0,0 +1,49 @@
+import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
+
+import 'group_listener.dart';
+
+abstract class GroupControllerDelegate {
+  void removeRow(String groupId, String rowId);
+  void insertRow(String groupId, RowPB row, int? index);
+  void updateRow(String groupId, RowPB row);
+}
+
+class GroupController {
+  final GroupPB group;
+  final GroupListener _listener;
+  final GroupControllerDelegate delegate;
+
+  GroupController({required this.group, required this.delegate})
+      : _listener = GroupListener(group);
+
+  void startListening() {
+    _listener.start(onGroupChanged: (result) {
+      result.fold(
+        (GroupRowsChangesetPB changeset) {
+          for (final insertedRow in changeset.insertedRows) {
+            final index = insertedRow.hasIndex() ? insertedRow.index : null;
+            delegate.insertRow(
+              group.groupId,
+              insertedRow.row,
+              index,
+            );
+          }
+
+          for (final deletedRow in changeset.deletedRows) {
+            delegate.removeRow(group.groupId, deletedRow);
+          }
+
+          for (final updatedRow in changeset.updatedRows) {
+            delegate.updateRow(group.groupId, updatedRow);
+          }
+        },
+        (err) => Log.error(err),
+      );
+    });
+  }
+
+  Future<void> dispose() async {
+    _listener.stop();
+  }
+}

+ 51 - 0
frontend/app_flowy/lib/plugins/board/application/group_listener.dart

@@ -0,0 +1,51 @@
+import 'dart:typed_data';
+
+import 'package:app_flowy/core/grid_notification.dart';
+import 'package:flowy_infra/notifier.dart';
+import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart';
+
+typedef UpdateGroupNotifiedValue = Either<GroupRowsChangesetPB, FlowyError>;
+
+class GroupListener {
+  final GroupPB group;
+  PublishNotifier<UpdateGroupNotifiedValue>? _groupNotifier = PublishNotifier();
+  GridNotificationListener? _listener;
+  GroupListener(this.group);
+
+  void start({
+    required void Function(UpdateGroupNotifiedValue) onGroupChanged,
+  }) {
+    _groupNotifier?.addPublishListener(onGroupChanged);
+    _listener = GridNotificationListener(
+      objectId: group.groupId,
+      handler: _handler,
+    );
+  }
+
+  void _handler(
+    GridNotification ty,
+    Either<Uint8List, FlowyError> result,
+  ) {
+    switch (ty) {
+      case GridNotification.DidUpdateGroup:
+        result.fold(
+          (payload) => _groupNotifier?.value =
+              left(GroupRowsChangesetPB.fromBuffer(payload)),
+          (error) => _groupNotifier?.value = right(error),
+        );
+        break;
+      default:
+        break;
+    }
+  }
+
+  Future<void> stop() async {
+    await _listener?.stop();
+    _groupNotifier?.dispose();
+    _groupNotifier = null;
+  }
+}

+ 37 - 1
frontend/app_flowy/lib/plugins/board/presentation/board_page.dart

@@ -1,11 +1,20 @@
 // ignore_for_file: unused_field
 
+import 'dart:collection';
+
 import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
+import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
+import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
+import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
+import 'package:app_flowy/plugins/grid/presentation/widgets/cell/cell_builder.dart';
+import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_detail.dart';
 import 'package:appflowy_board/appflowy_board.dart';
 import 'package:flowy_infra_ui/widget/error_page.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
+import '../../grid/application/row/row_cache.dart';
 import '../application/board_bloc.dart';
 import 'card/card.dart';
 import 'card/card_cell_builder.dart';
@@ -55,7 +64,7 @@ class BoardContent extends StatelessWidget {
             child: AFBoard(
               // key: UniqueKey(),
               scrollController: ScrollController(),
-              dataController: context.read<BoardBloc>().boardDataController,
+              dataController: context.read<BoardBloc>().afBoardDataController,
               headerBuilder: _buildHeader,
               footBuilder: _buildFooter,
               cardBuilder: (_, data) => _buildCard(context, data),
@@ -123,9 +132,36 @@ class BoardContent extends StatelessWidget {
         onEditEditing: (rowId) {
           context.read<BoardBloc>().add(BoardEvent.endEditRow(rowId));
         },
+        openCard: (context) => _openCard(
+          gridId,
+          fieldCache,
+          rowPB,
+          rowCache,
+          context,
+        ),
       ),
     );
   }
+
+  void _openCard(String gridId, GridFieldCache fieldCache, RowPB rowPB,
+      GridRowCache rowCache, BuildContext context) {
+    final rowInfo = RowInfo(
+      gridId: gridId,
+      fields: UnmodifiableListView(fieldCache.fields),
+      rowPB: rowPB,
+    );
+
+    final dataController = GridRowDataController(
+      rowInfo: rowInfo,
+      fieldCache: fieldCache,
+      rowCache: rowCache,
+    );
+
+    RowDetailPage(
+      cellBuilder: GridCellBuilder(delegate: dataController),
+      dataController: dataController,
+    ).show(context);
+  }
 }
 
 extension HexColor on Color {

+ 10 - 2
frontend/app_flowy/lib/plugins/board/presentation/card/card.dart

@@ -1,9 +1,10 @@
 import 'package:app_flowy/plugins/board/application/card/card_bloc.dart';
 import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
+import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_action_sheet.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
-import 'package:flowy_sdk/log.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'card_cell_builder.dart';
@@ -17,6 +18,7 @@ class BoardCard extends StatefulWidget {
   final CardDataController dataController;
   final BoardCellBuilder cellBuilder;
   final OnEndEditing onEditEditing;
+  final void Function(BuildContext) openCard;
 
   const BoardCard({
     required this.gridId,
@@ -24,6 +26,7 @@ class BoardCard extends StatefulWidget {
     required this.dataController,
     required this.cellBuilder,
     required this.onEditEditing,
+    required this.openCard,
     Key? key,
   }) : super(key: key);
 
@@ -53,6 +56,9 @@ class _BoardCardState extends State<BoardCard> {
             accessoryBuilder: (context) {
               return [const _CardMoreOption()];
             },
+            onTap: (context) {
+              widget.openCard(context);
+            },
             child: Column(
               children: _makeCells(context, state.gridCellMap),
             ),
@@ -85,6 +91,8 @@ class _CardMoreOption extends StatelessWidget with CardAccessory {
 
   @override
   void onTap(BuildContext context) {
-    Log.debug('show options');
+    GridRowActionSheet(
+      rowData: context.read<BoardCardBloc>().rowInfo(),
+    ).show(context, direction: AnchorDirection.bottomWithCenterAligned);
   }
 }

+ 10 - 5
frontend/app_flowy/lib/plugins/board/presentation/card/card_container.dart

@@ -7,8 +7,10 @@ import 'package:styled_widget/styled_widget.dart';
 class BoardCardContainer extends StatelessWidget {
   final Widget child;
   final CardAccessoryBuilder? accessoryBuilder;
+  final void Function(BuildContext) onTap;
   const BoardCardContainer({
     required this.child,
+    required this.onTap,
     this.accessoryBuilder,
     Key? key,
   }) : super(key: key);
@@ -30,11 +32,14 @@ class BoardCardContainer extends StatelessWidget {
             }
           }
 
-          return Padding(
-            padding: const EdgeInsets.all(8),
-            child: ConstrainedBox(
-              constraints: const BoxConstraints(minHeight: 30),
-              child: container,
+          return GestureDetector(
+            onTap: () => onTap(context),
+            child: Padding(
+              padding: const EdgeInsets.all(8),
+              child: ConstrainedBox(
+                constraints: const BoxConstraints(minHeight: 30),
+                child: container,
+              ),
             ),
           );
         },

+ 3 - 4
frontend/app_flowy/lib/plugins/grid/application/field/field_service.dart

@@ -18,14 +18,13 @@ class FieldService {
   FieldService({required this.gridId, required this.fieldId});
 
   Future<Either<Unit, FlowyError>> moveField(int fromIndex, int toIndex) {
-    final payload = MoveItemPayloadPB.create()
+    final payload = MoveFieldPayloadPB.create()
       ..gridId = gridId
-      ..itemId = fieldId
-      ..ty = MoveItemTypePB.MoveField
+      ..fieldId = fieldId
       ..fromIndex = fromIndex
       ..toIndex = toIndex;
 
-    return GridEventMoveItem(payload).send();
+    return GridEventMoveField(payload).send();
   }
 
   Future<Either<Unit, FlowyError>> updateField({

+ 2 - 2
frontend/app_flowy/lib/plugins/grid/application/grid_service.dart

@@ -23,9 +23,9 @@ class GridFFIService {
   }
 
   Future<Either<RowPB, FlowyError>> createRow({Option<String>? startRowId}) {
-    CreateRowPayloadPB payload = CreateRowPayloadPB.create()..gridId = gridId;
+    var payload = CreateTableRowPayloadPB.create()..gridId = gridId;
     startRowId?.fold(() => null, (id) => payload.startRowId = id);
-    return GridEventCreateRow(payload).send();
+    return GridEventCreateTableRow(payload).send();
   }
 
   Future<Either<RowPB, FlowyError>> createBoardCard(String groupId) {

+ 4 - 4
frontend/app_flowy/lib/plugins/grid/application/row/row_action_sheet_bloc.dart

@@ -17,19 +17,19 @@ class RowActionSheetBloc
   RowActionSheetBloc({required RowInfo rowInfo})
       : _rowService = RowFFIService(
           gridId: rowInfo.gridId,
-          blockId: rowInfo.blockId,
-          rowId: rowInfo.rowPB.id,
+          blockId: rowInfo.rowPB.blockId,
         ),
         super(RowActionSheetState.initial(rowInfo)) {
     on<RowActionSheetEvent>(
       (event, emit) async {
         await event.map(
           deleteRow: (_DeleteRow value) async {
-            final result = await _rowService.deleteRow();
+            final result = await _rowService.deleteRow(state.rowData.rowPB.id);
             logResult(result);
           },
           duplicateRow: (_DuplicateRow value) async {
-            final result = await _rowService.duplicateRow();
+            final result =
+                await _rowService.duplicateRow(state.rowData.rowPB.id);
             logResult(result);
           },
         );

+ 2 - 3
frontend/app_flowy/lib/plugins/grid/application/row/row_bloc.dart

@@ -20,8 +20,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
     required GridRowDataController dataController,
   })  : _rowService = RowFFIService(
           gridId: rowInfo.gridId,
-          blockId: rowInfo.blockId,
-          rowId: rowInfo.rowPB.id,
+          blockId: rowInfo.rowPB.blockId,
         ),
         _dataController = dataController,
         super(RowState.initial(rowInfo, dataController.loadData())) {
@@ -32,7 +31,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
             await _startListening();
           },
           createRow: (_CreateRow value) {
-            _rowService.createRow();
+            _rowService.createRow(rowInfo.rowPB.id);
           },
           didReceiveCells: (_DidReceiveCells value) async {
             final cells = value.gridCellMap.values

+ 0 - 2
frontend/app_flowy/lib/plugins/grid/application/row/row_cache.dart

@@ -255,7 +255,6 @@ class GridRowCache {
   RowInfo buildGridRow(RowPB rowPB) {
     return RowInfo(
       gridId: gridId,
-      blockId: block.id,
       fields: _fieldNotifier.fields,
       rowPB: rowPB,
     );
@@ -283,7 +282,6 @@ class _RowChangesetNotifier extends ChangeNotifier {
 class RowInfo with _$RowInfo {
   const factory RowInfo({
     required String gridId,
-    required String blockId,
     required UnmodifiableListView<FieldPB> fields,
     required RowPB rowPB,
   }) = _RowInfo;

+ 27 - 16
frontend/app_flowy/lib/plugins/grid/application/row/row_service.dart

@@ -4,36 +4,47 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/setting_entities.pb.dart';
 
 class RowFFIService {
   final String gridId;
   final String blockId;
-  final String rowId;
 
-  RowFFIService(
-      {required this.gridId, required this.blockId, required this.rowId});
+  RowFFIService({
+    required this.gridId,
+    required this.blockId,
+  });
 
-  Future<Either<RowPB, FlowyError>> createRow() {
-    CreateRowPayloadPB payload = CreateRowPayloadPB.create()
+  Future<Either<RowPB, FlowyError>> createRow(String rowId) {
+    final payload = CreateTableRowPayloadPB.create()
       ..gridId = gridId
       ..startRowId = rowId;
 
-    return GridEventCreateRow(payload).send();
+    return GridEventCreateTableRow(payload).send();
   }
 
-  Future<Either<Unit, FlowyError>> moveRow(
-      String rowId, int fromIndex, int toIndex) {
-    final payload = MoveItemPayloadPB.create()
-      ..gridId = gridId
-      ..itemId = rowId
-      ..ty = MoveItemTypePB.MoveRow
+  Future<Either<Unit, FlowyError>> moveRow({
+    required String rowId,
+    required int fromIndex,
+    required int toIndex,
+    required GridLayout layout,
+    String? upperRowId,
+  }) {
+    var payload = MoveRowPayloadPB.create()
+      ..viewId = gridId
+      ..rowId = rowId
+      ..layout = layout
       ..fromIndex = fromIndex
       ..toIndex = toIndex;
 
-    return GridEventMoveItem(payload).send();
+    if (upperRowId != null) {
+      payload.upperRowId = upperRowId;
+    }
+
+    return GridEventMoveRow(payload).send();
   }
 
-  Future<Either<OptionalRowPB, FlowyError>> getRow() {
+  Future<Either<OptionalRowPB, FlowyError>> getRow(String rowId) {
     final payload = RowIdPB.create()
       ..gridId = gridId
       ..blockId = blockId
@@ -42,7 +53,7 @@ class RowFFIService {
     return GridEventGetRow(payload).send();
   }
 
-  Future<Either<Unit, FlowyError>> deleteRow() {
+  Future<Either<Unit, FlowyError>> deleteRow(String rowId) {
     final payload = RowIdPB.create()
       ..gridId = gridId
       ..blockId = blockId
@@ -51,7 +62,7 @@ class RowFFIService {
     return GridEventDeleteRow(payload).send();
   }
 
-  Future<Either<Unit, FlowyError>> duplicateRow() {
+  Future<Either<Unit, FlowyError>> duplicateRow(String rowId) {
     final payload = RowIdPB.create()
       ..gridId = gridId
       ..blockId = blockId

+ 4 - 2
frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart

@@ -239,8 +239,10 @@ class _GridRowsState extends State<_GridRows> {
     RowInfo rowInfo,
     Animation<double> animation,
   ) {
-    final rowCache =
-        context.read<GridBloc>().getRowCache(rowInfo.blockId, rowInfo.rowPB.id);
+    final rowCache = context.read<GridBloc>().getRowCache(
+          rowInfo.rowPB.blockId,
+          rowInfo.rowPB.id,
+        );
 
     /// Return placeholder widget if the rowCache is null.
     if (rowCache == null) return const SizedBox();

+ 5 - 2
frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_action_sheet.dart

@@ -53,7 +53,10 @@ class GridRowActionSheet extends StatelessWidget {
     );
   }
 
-  void show(BuildContext overlayContext) {
+  void show(
+    BuildContext overlayContext, {
+    AnchorDirection direction = AnchorDirection.leftWithCenterAligned,
+  }) {
     FlowyOverlay.of(overlayContext).insertWithAnchor(
       widget: OverlayContainer(
         child: this,
@@ -61,7 +64,7 @@ class GridRowActionSheet extends StatelessWidget {
       ),
       identifier: GridRowActionSheet.identifier(),
       anchorContext: overlayContext,
-      anchorDirection: AnchorDirection.leftWithCenterAligned,
+      anchorDirection: direction,
     );
   }
 

+ 2 - 2
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart

@@ -3,7 +3,7 @@ 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_target_inteceptor.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';
@@ -143,7 +143,7 @@ class _BoardContentState extends State<BoardContent> {
   void initState() {
     _overlayEntry = BoardOverlayEntry(
       builder: (BuildContext context) {
-        final interceptor = OverlappingDragTargetInteceptor(
+        final interceptor = OverlappingDragTargetInterceptor(
           reorderFlexId: widget.dataController.identifier,
           acceptedReorderFlexId: widget.dataController.columnIds,
           delegate: widget.delegate,

+ 2 - 2
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart

@@ -5,7 +5,7 @@ import '../../rendering/board_overlay.dart';
 import '../../utils/log.dart';
 import '../reorder_phantom/phantom_controller.dart';
 import '../reorder_flex/reorder_flex.dart';
-import '../reorder_flex/drag_target_inteceptor.dart';
+import '../reorder_flex/drag_target_interceptor.dart';
 import 'board_column_data.dart';
 
 typedef OnColumnDragStarted = void Function(int index);
@@ -37,7 +37,7 @@ typedef AFBoardColumnFooterBuilder = Widget Function(
   AFBoardColumnData columnData,
 );
 
-abstract class AFBoardColumnDataDataSource extends ReoderFlextDataSource {
+abstract class AFBoardColumnDataDataSource extends ReoderFlexDataSource {
   AFBoardColumnData get columnData;
 
   List<String> get acceptedColumnIds;

+ 5 - 2
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart

@@ -51,8 +51,11 @@ class AFBoardColumnDataController extends ChangeNotifier with EquatableMixin {
     return item;
   }
 
-  int removeWhere(bool Function(AFColumnItem) condition) {
-    return items.indexWhere(condition);
+  void removeWhere(bool Function(AFColumnItem) condition) {
+    final index = items.indexWhere(condition);
+    if (index != -1) {
+      removeAt(index);
+    }
   }
 
   /// Move the item from [fromIndex] to [toIndex]. It will do nothing if the

+ 1 - 1
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart

@@ -24,7 +24,7 @@ typedef OnMoveColumnItemToColumn = void Function(
 );
 
 class AFBoardDataController extends ChangeNotifier
-    with EquatableMixin, BoardPhantomControllerDelegate, ReoderFlextDataSource {
+    with EquatableMixin, BoardPhantomControllerDelegate, ReoderFlexDataSource {
   final List<AFBoardColumnData> _columnDatas = [];
   final OnMoveColumn? onMoveColumn;
   final OnMoveColumnItem? onMoveColumnItem;

+ 5 - 5
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart

@@ -13,14 +13,14 @@ abstract class ReorderFlexDraggableTargetBuilder {
     Widget child,
     DragTargetOnStarted onDragStarted,
     DragTargetOnEnded<T> onDragEnded,
-    DragTargetWillAccpet<T> onWillAccept,
+    DragTargetWillAccepted<T> onWillAccept,
     AnimationController insertAnimationController,
     AnimationController deleteAnimationController,
   );
 }
 
 ///
-typedef DragTargetWillAccpet<T extends DragTargetData> = bool Function(
+typedef DragTargetWillAccepted<T extends DragTargetData> = bool Function(
     T dragTargetData);
 
 ///
@@ -51,7 +51,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
   ///
   /// [toAccept] represents the dragTarget index, which is the value passed in
   /// when creating the [ReorderDragTarget].
-  final DragTargetWillAccpet<T> onWillAccept;
+  final DragTargetWillAccepted<T> onWillAccept;
 
   /// Called when an acceptable piece of data was dropped over this drag target.
   ///
@@ -228,7 +228,7 @@ class DragTargetAnimation {
         value: 0.0, vsync: vsync, duration: const Duration(milliseconds: 10));
   }
 
-  void startDargging() {
+  void startDragging() {
     entranceController.value = 1.0;
   }
 
@@ -386,7 +386,7 @@ class FakeDragTarget<T extends DragTargetData> extends StatefulWidget {
   final FakeDragTargetEventData eventData;
   final DragTargetOnStarted onDragStarted;
   final DragTargetOnEnded<T> onDragEnded;
-  final DragTargetWillAccpet<T> onWillAccept;
+  final DragTargetWillAccepted<T> onWillAccept;
   final Widget child;
   final AnimationController insertAnimationController;
   final AnimationController deleteAnimationController;

+ 3 - 3
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_inteceptor.dart → frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_interceptor.dart

@@ -40,18 +40,18 @@ abstract class OverlapDragTargetDelegate {
   bool canMoveTo(String dragTargetId);
 }
 
-/// [OverlappingDragTargetInteceptor] is used to receive the overlapping
+/// [OverlappingDragTargetInterceptor] is used to receive the overlapping
 /// [DragTarget] event. If a [DragTarget] child is [DragTarget], it will
 /// receive the [DragTarget] event when being dragged.
 ///
 /// Receive the [DragTarget] event if the [acceptedReorderFlexId] contains
 /// the passed in dragTarget' reorderFlexId.
-class OverlappingDragTargetInteceptor extends DragTargetInterceptor {
+class OverlappingDragTargetInterceptor extends DragTargetInterceptor {
   final String reorderFlexId;
   final List<String> acceptedReorderFlexId;
   final OverlapDragTargetDelegate delegate;
 
-  OverlappingDragTargetInteceptor({
+  OverlappingDragTargetInterceptor({
     required this.delegate,
     required this.reorderFlexId,
     required this.acceptedReorderFlexId,

+ 8 - 8
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart

@@ -7,25 +7,25 @@ import '../../utils/log.dart';
 import 'reorder_mixin.dart';
 import 'drag_target.dart';
 import 'drag_state.dart';
-import 'drag_target_inteceptor.dart';
+import 'drag_target_interceptor.dart';
 
 typedef OnDragStarted = void Function(int index);
 typedef OnDragEnded = void Function();
 typedef OnReorder = void Function(int fromIndex, int toIndex);
 typedef OnDeleted = void Function(int deletedIndex);
 typedef OnInserted = void Function(int insertedIndex);
-typedef OnReveivePassedInPhantom = void Function(
+typedef OnReceivePassedInPhantom = void Function(
     FlexDragTargetData dragTargetData, int phantomIndex);
 
-abstract class ReoderFlextDataSource {
+abstract class ReoderFlexDataSource {
   /// [identifier] represents the id the [ReorderFlex]. It must be unique.
   String get identifier;
 
-  /// The number of [ReoderFlexItem]s will be displaied in the [ReorderFlex].
+  /// The number of [ReoderFlexItem]s will be displayed in the [ReorderFlex].
   UnmodifiableListView<ReoderFlexItem> get items;
 }
 
-/// Each item displaied in the [ReorderFlex] required to implement the [ReoderFlexItem].
+/// Each item displayed in the [ReorderFlex] required to implement the [ReoderFlexItem].
 abstract class ReoderFlexItem {
   /// [id] is used to identify the item. It must be unique.
   String get id;
@@ -70,7 +70,7 @@ class ReorderFlex extends StatefulWidget {
   /// [onDragEnded] is called when dragTarget did end dragging
   final OnDragEnded? onDragEnded;
 
-  final ReoderFlextDataSource dataSource;
+  final ReoderFlexDataSource dataSource;
 
   final DragTargetInterceptor? interceptor;
 
@@ -187,7 +187,7 @@ class ReorderFlexState extends State<ReorderFlex>
   void _requestAnimationToNextIndex({bool isAcceptingNewTarget = false}) {
     /// Update the dragState and animate to the next index if the current
     /// dragging animation is completed. Otherwise, it will get called again
-    /// when the animation finishs.
+    /// when the animation finish.
 
     if (_animation.entranceController.isCompleted) {
       dragState.removePhantom();
@@ -425,7 +425,7 @@ class ReorderFlexState extends State<ReorderFlex>
   ) {
     setState(() {
       dragState.startDragging(draggingWidget, dragIndex, feedbackSize);
-      _animation.startDargging();
+      _animation.startDragging();
     });
   }
 

+ 5 - 4
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart

@@ -1,9 +1,10 @@
-import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+
 import '../../utils/log.dart';
 import '../board_column/board_column_data.dart';
 import '../reorder_flex/drag_state.dart';
 import '../reorder_flex/drag_target.dart';
-import '../reorder_flex/drag_target_inteceptor.dart';
+import '../reorder_flex/drag_target_interceptor.dart';
 import 'phantom_state.dart';
 
 abstract class BoardPhantomControllerDelegate {
@@ -61,7 +62,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate
     columnsState.setColumnIsDragging(columnId, false);
   }
 
-  /// Remove the phanton in the column when the column is end dragging.
+  /// Remove the phantom in the column when the column is end dragging.
   void columnEndDragging(String columnId) {
     columnsState.setColumnIsDragging(columnId, true);
     if (phantomRecord == null) return;
@@ -331,7 +332,7 @@ class PhantomDraggableBuilder extends ReorderFlexDraggableTargetBuilder {
     Widget child,
     DragTargetOnStarted onDragStarted,
     DragTargetOnEnded<T> onDragEnded,
-    DragTargetWillAccpet<T> onWillAccept,
+    DragTargetWillAccepted<T> onWillAccept,
     AnimationController insertAnimationController,
     AnimationController deleteAnimationController,
   ) {

+ 1 - 1
frontend/rust-lib/flowy-grid/src/dart_notification.rs

@@ -11,7 +11,7 @@ pub enum GridNotification {
     DidUpdateRow = 30,
     DidUpdateCell = 40,
     DidUpdateField = 50,
-    DidUpdateBoard = 60,
+    DidUpdateGroup = 60,
 }
 
 impl std::default::Default for GridNotification {

+ 60 - 26
frontend/rust-lib/flowy-grid/src/entities/grid_entities.rs

@@ -1,5 +1,5 @@
-use crate::entities::{BlockPB, FieldIdPB};
-use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use crate::entities::{BlockPB, FieldIdPB, GridLayout};
+use flowy_derive::ProtoBuf;
 use flowy_error::ErrorCode;
 use flowy_grid_data_model::parser::NotEmptyStr;
 
@@ -52,25 +52,51 @@ impl std::convert::From<&str> for GridBlockIdPB {
     }
 }
 
-#[derive(Debug, Clone, ProtoBuf_Enum)]
-pub enum MoveItemTypePB {
-    MoveField = 0,
-    MoveRow = 1,
+#[derive(Debug, Clone, Default, ProtoBuf)]
+pub struct MoveFieldPayloadPB {
+    #[pb(index = 1)]
+    pub grid_id: String,
+
+    #[pb(index = 2)]
+    pub field_id: String,
+
+    #[pb(index = 3)]
+    pub from_index: i32,
+
+    #[pb(index = 4)]
+    pub to_index: i32,
+}
+
+#[derive(Clone)]
+pub struct MoveFieldParams {
+    pub grid_id: String,
+    pub field_id: String,
+    pub from_index: i32,
+    pub to_index: i32,
 }
 
-impl std::default::Default for MoveItemTypePB {
-    fn default() -> Self {
-        MoveItemTypePB::MoveField
+impl TryInto<MoveFieldParams> for MoveFieldPayloadPB {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<MoveFieldParams, Self::Error> {
+        let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
+        let item_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::InvalidData)?;
+        Ok(MoveFieldParams {
+            grid_id: grid_id.0,
+            field_id: item_id.0,
+            from_index: self.from_index,
+            to_index: self.to_index,
+        })
     }
 }
 
 #[derive(Debug, Clone, Default, ProtoBuf)]
-pub struct MoveItemPayloadPB {
+pub struct MoveRowPayloadPB {
     #[pb(index = 1)]
-    pub grid_id: String,
+    pub view_id: String,
 
     #[pb(index = 2)]
-    pub item_id: String,
+    pub row_id: String,
 
     #[pb(index = 3)]
     pub from_index: i32,
@@ -79,30 +105,38 @@ pub struct MoveItemPayloadPB {
     pub to_index: i32,
 
     #[pb(index = 5)]
-    pub ty: MoveItemTypePB,
+    pub layout: GridLayout,
+
+    #[pb(index = 6, one_of)]
+    pub upper_row_id: Option<String>,
 }
 
-#[derive(Clone)]
-pub struct MoveItemParams {
-    pub grid_id: String,
-    pub item_id: String,
+pub struct MoveRowParams {
+    pub view_id: String,
+    pub row_id: String,
     pub from_index: i32,
     pub to_index: i32,
-    pub ty: MoveItemTypePB,
+    pub layout: GridLayout,
+    pub upper_row_id: Option<String>,
 }
 
-impl TryInto<MoveItemParams> for MoveItemPayloadPB {
+impl TryInto<MoveRowParams> for MoveRowPayloadPB {
     type Error = ErrorCode;
 
-    fn try_into(self) -> Result<MoveItemParams, Self::Error> {
-        let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
-        let item_id = NotEmptyStr::parse(self.item_id).map_err(|_| ErrorCode::InvalidData)?;
-        Ok(MoveItemParams {
-            grid_id: grid_id.0,
-            item_id: item_id.0,
+    fn try_into(self) -> Result<MoveRowParams, Self::Error> {
+        let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::GridViewIdIsEmpty)?;
+        let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
+        let upper_row_id = match self.upper_row_id {
+            None => None,
+            Some(upper_row_id) => Some(NotEmptyStr::parse(upper_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?.0),
+        };
+        Ok(MoveRowParams {
+            view_id: view_id.0,
+            row_id: row_id.0,
             from_index: self.from_index,
             to_index: self.to_index,
-            ty: self.ty,
+            layout: self.layout,
+            upper_row_id,
         })
     }
 }

+ 2 - 41
frontend/rust-lib/flowy-grid/src/entities/group_entities/board_card.rs

@@ -1,4 +1,4 @@
-use crate::entities::{CreateRowParams, RowPB};
+use crate::entities::{CreateRowParams, GridLayout};
 use flowy_derive::ProtoBuf;
 use flowy_error::ErrorCode;
 use flowy_grid_data_model::parser::NotEmptyStr;
@@ -22,46 +22,7 @@ impl TryInto<CreateRowParams> for CreateBoardCardPayloadPB {
             grid_id: grid_id.0,
             start_row_id: None,
             group_id: Some(group_id.0),
+            layout: GridLayout::Board,
         })
     }
 }
-
-#[derive(Debug, Default, ProtoBuf)]
-pub struct BoardCardChangesetPB {
-    #[pb(index = 1)]
-    pub group_id: String,
-
-    #[pb(index = 2)]
-    pub inserted_cards: Vec<RowPB>,
-
-    #[pb(index = 3)]
-    pub deleted_cards: Vec<String>,
-
-    #[pb(index = 4)]
-    pub updated_cards: Vec<RowPB>,
-}
-impl BoardCardChangesetPB {
-    pub fn insert(group_id: String, inserted_cards: Vec<RowPB>) -> Self {
-        Self {
-            group_id,
-            inserted_cards,
-            ..Default::default()
-        }
-    }
-
-    pub fn delete(group_id: String, deleted_cards: Vec<String>) -> Self {
-        Self {
-            group_id,
-            deleted_cards,
-            ..Default::default()
-        }
-    }
-
-    pub fn update(group_id: String, updated_cards: Vec<RowPB>) -> Self {
-        Self {
-            group_id,
-            updated_cards,
-            ..Default::default()
-        }
-    }
-}

+ 43 - 0
frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs

@@ -0,0 +1,43 @@
+use crate::entities::{InsertedRowPB, RowPB};
+use flowy_derive::ProtoBuf;
+
+#[derive(Debug, Default, ProtoBuf)]
+pub struct GroupRowsChangesetPB {
+    #[pb(index = 1)]
+    pub group_id: String,
+
+    #[pb(index = 2)]
+    pub inserted_rows: Vec<InsertedRowPB>,
+
+    #[pb(index = 3)]
+    pub deleted_rows: Vec<String>,
+
+    #[pb(index = 4)]
+    pub updated_rows: Vec<RowPB>,
+}
+
+impl GroupRowsChangesetPB {
+    pub fn insert(group_id: String, inserted_rows: Vec<InsertedRowPB>) -> Self {
+        Self {
+            group_id,
+            inserted_rows,
+            ..Default::default()
+        }
+    }
+
+    pub fn delete(group_id: String, deleted_rows: Vec<String>) -> Self {
+        Self {
+            group_id,
+            deleted_rows,
+            ..Default::default()
+        }
+    }
+
+    pub fn update(group_id: String, updated_rows: Vec<RowPB>) -> Self {
+        Self {
+            group_id,
+            updated_rows,
+            ..Default::default()
+        }
+    }
+}

+ 2 - 0
frontend/rust-lib/flowy-grid/src/entities/group_entities/mod.rs

@@ -1,7 +1,9 @@
 mod board_card;
 mod configuration;
 mod group;
+mod group_changeset;
 
 pub use board_card::*;
 pub use configuration::*;
 pub use group::*;
+pub use group_changeset::*;

+ 6 - 2
frontend/rust-lib/flowy-grid/src/entities/row_entities.rs

@@ -1,3 +1,4 @@
+use crate::entities::GridLayout;
 use flowy_derive::ProtoBuf;
 use flowy_error::ErrorCode;
 use flowy_grid_data_model::parser::NotEmptyStr;
@@ -46,7 +47,7 @@ pub struct BlockRowIdPB {
 }
 
 #[derive(ProtoBuf, Default)]
-pub struct CreateRowPayloadPB {
+pub struct CreateTableRowPayloadPB {
     #[pb(index = 1)]
     pub grid_id: String,
 
@@ -59,17 +60,20 @@ pub struct CreateRowParams {
     pub grid_id: String,
     pub start_row_id: Option<String>,
     pub group_id: Option<String>,
+    pub layout: GridLayout,
 }
 
-impl TryInto<CreateRowParams> for CreateRowPayloadPB {
+impl TryInto<CreateRowParams> for CreateTableRowPayloadPB {
     type Error = ErrorCode;
 
     fn try_into(self) -> Result<CreateRowParams, Self::Error> {
         let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
+
         Ok(CreateRowParams {
             grid_id: grid_id.0,
             start_row_id: self.start_row_id,
             group_id: None,
+            layout: GridLayout::Table,
         })
     }
 }

+ 14 - 14
frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs

@@ -19,7 +19,7 @@ pub struct GridSettingPB {
     pub layouts: Vec<GridLayoutPB>,
 
     #[pb(index = 2)]
-    pub current_layout_type: Layout,
+    pub current_layout_type: GridLayout,
 
     #[pb(index = 3)]
     pub filter_configuration_by_field_id: HashMap<String, RepeatedGridConfigurationFilterPB>,
@@ -34,13 +34,13 @@ pub struct GridSettingPB {
 #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
 pub struct GridLayoutPB {
     #[pb(index = 1)]
-    ty: Layout,
+    ty: GridLayout,
 }
 
 impl GridLayoutPB {
     pub fn all() -> Vec<GridLayoutPB> {
         let mut layouts = vec![];
-        for layout_ty in Layout::iter() {
+        for layout_ty in GridLayout::iter() {
             layouts.push(GridLayoutPB { ty: layout_ty })
         }
 
@@ -50,31 +50,31 @@ impl GridLayoutPB {
 
 #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum, EnumIter)]
 #[repr(u8)]
-pub enum Layout {
+pub enum GridLayout {
     Table = 0,
     Board = 1,
 }
 
-impl std::default::Default for Layout {
+impl std::default::Default for GridLayout {
     fn default() -> Self {
-        Layout::Table
+        GridLayout::Table
     }
 }
 
-impl std::convert::From<LayoutRevision> for Layout {
+impl std::convert::From<LayoutRevision> for GridLayout {
     fn from(rev: LayoutRevision) -> Self {
         match rev {
-            LayoutRevision::Table => Layout::Table,
-            LayoutRevision::Board => Layout::Board,
+            LayoutRevision::Table => GridLayout::Table,
+            LayoutRevision::Board => GridLayout::Board,
         }
     }
 }
 
-impl std::convert::From<Layout> for LayoutRevision {
-    fn from(layout: Layout) -> Self {
+impl std::convert::From<GridLayout> for LayoutRevision {
+    fn from(layout: GridLayout) -> Self {
         match layout {
-            Layout::Table => LayoutRevision::Table,
-            Layout::Board => LayoutRevision::Board,
+            GridLayout::Table => LayoutRevision::Table,
+            GridLayout::Board => LayoutRevision::Board,
         }
     }
 }
@@ -85,7 +85,7 @@ pub struct GridSettingChangesetPayloadPB {
     pub grid_id: String,
 
     #[pb(index = 2)]
-    pub layout_type: Layout,
+    pub layout_type: GridLayout,
 
     #[pb(index = 3, one_of)]
     pub insert_filter: Option<CreateGridFilterPayloadPB>,

+ 17 - 6
frontend/rust-lib/flowy-grid/src/event_handler.rs

@@ -203,13 +203,13 @@ pub(crate) async fn create_field_type_option_data_handler(
 }
 
 #[tracing::instrument(level = "trace", skip(data, manager), err)]
-pub(crate) async fn move_item_handler(
-    data: Data<MoveItemPayloadPB>,
+pub(crate) async fn move_field_handler(
+    data: Data<MoveFieldPayloadPB>,
     manager: AppData<Arc<GridManager>>,
 ) -> Result<(), FlowyError> {
-    let params: MoveItemParams = data.into_inner().try_into()?;
+    let params: MoveFieldParams = data.into_inner().try_into()?;
     let editor = manager.get_grid_editor(&params.grid_id)?;
-    let _ = editor.move_item(params).await?;
+    let _ = editor.move_field(params).await?;
     Ok(())
 }
 
@@ -260,8 +260,19 @@ pub(crate) async fn duplicate_row_handler(
 }
 
 #[tracing::instrument(level = "debug", skip(data, manager), err)]
-pub(crate) async fn create_row_handler(
-    data: Data<CreateRowPayloadPB>,
+pub(crate) async fn move_row_handler(
+    data: Data<MoveRowPayloadPB>,
+    manager: AppData<Arc<GridManager>>,
+) -> Result<(), FlowyError> {
+    let params: MoveRowParams = data.into_inner().try_into()?;
+    let editor = manager.get_grid_editor(&params.view_id)?;
+    let _ = editor.move_row(params).await?;
+    Ok(())
+}
+
+#[tracing::instrument(level = "debug", skip(data, manager), err)]
+pub(crate) async fn create_table_row_handler(
+    data: Data<CreateTableRowPayloadPB>,
     manager: AppData<Arc<GridManager>>,
 ) -> DataResult<RowPB, FlowyError> {
     let params: CreateRowParams = data.into_inner().try_into()?;

+ 10 - 6
frontend/rust-lib/flowy-grid/src/event_map.rs

@@ -20,14 +20,15 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
         .event(GridEvent::DeleteField, delete_field_handler)
         .event(GridEvent::SwitchToField, switch_to_field_handler)
         .event(GridEvent::DuplicateField, duplicate_field_handler)
-        .event(GridEvent::MoveItem, move_item_handler)
+        .event(GridEvent::MoveField, move_field_handler)
         .event(GridEvent::GetFieldTypeOption, get_field_type_option_data_handler)
         .event(GridEvent::CreateFieldTypeOption, create_field_type_option_data_handler)
         // Row
-        .event(GridEvent::CreateRow, create_row_handler)
+        .event(GridEvent::CreateTableRow, create_table_row_handler)
         .event(GridEvent::GetRow, get_row_handler)
         .event(GridEvent::DeleteRow, delete_row_handler)
         .event(GridEvent::DuplicateRow, duplicate_row_handler)
+        .event(GridEvent::MoveRow, move_row_handler)
         // Cell
         .event(GridEvent::GetCell, get_cell_handler)
         .event(GridEvent::UpdateCell, update_cell_handler)
@@ -130,8 +131,8 @@ pub enum GridEvent {
 
     /// [MoveItem] event is used to move an item. For the moment, Item has two types defined in
     /// [MoveItemTypePB].
-    #[event(input = "MoveItemPayloadPB")]
-    MoveItem = 22,
+    #[event(input = "MoveFieldPayloadPB")]
+    MoveField = 22,
 
     /// [FieldTypeOptionIdPB] event is used to get the FieldTypeOption data for a specific field type.
     ///
@@ -166,8 +167,8 @@ pub enum GridEvent {
     #[event(input = "SelectOptionChangesetPayloadPB")]
     UpdateSelectOption = 32,
 
-    #[event(input = "CreateRowPayloadPB", output = "RowPB")]
-    CreateRow = 50,
+    #[event(input = "CreateTableRowPayloadPB", output = "RowPB")]
+    CreateTableRow = 50,
 
     /// [GetRow] event is used to get the row data,[RowPB]. [OptionalRowPB] is a wrapper that enables
     /// to return a nullable row data.
@@ -180,6 +181,9 @@ pub enum GridEvent {
     #[event(input = "RowIdPB")]
     DuplicateRow = 53,
 
+    #[event(input = "MoveRowPayloadPB")]
+    MoveRow = 54,
+
     #[event(input = "GridCellIdPB", output = "GridCellPB")]
     GetCell = 70,
 

+ 5 - 1
frontend/rust-lib/flowy-grid/src/services/block_editor.rs

@@ -59,7 +59,7 @@ impl GridBlockRevisionEditor {
                 if let Some(start_row_id) = prev_row_id.as_ref() {
                     match block_pad.index_of_row(start_row_id) {
                         None => {}
-                        Some(index) => row_index = Some(index + 1),
+                        Some(index) => row_index = Some(index as i32 + 1),
                     }
                 }
 
@@ -100,6 +100,10 @@ impl GridBlockRevisionEditor {
         Ok(())
     }
 
+    pub async fn index_of_row(&self, row_id: &str) -> Option<usize> {
+        self.pad.read().await.index_of_row(row_id)
+    }
+
     pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult<Option<Arc<RowRevision>>> {
         let row_ids = vec![Cow::Borrowed(row_id)];
         let row_rev = self.get_row_revs(Some(row_ids)).await?.pop();

+ 11 - 2
frontend/rust-lib/flowy-grid/src/services/block_manager.rs

@@ -52,7 +52,7 @@ impl GridBlockManager {
         }
     }
 
-    async fn get_editor_from_row_id(&self, row_id: &str) -> FlowyResult<Arc<GridBlockRevisionEditor>> {
+    pub(crate) async fn get_editor_from_row_id(&self, row_id: &str) -> FlowyResult<Arc<GridBlockRevisionEditor>> {
         let block_id = self.persistence.get_block_id(row_id)?;
         Ok(self.get_block_editor(&block_id).await?)
     }
@@ -122,6 +122,7 @@ impl GridBlockManager {
         Ok(())
     }
 
+    #[tracing::instrument(level = "trace", skip_all, err)]
     pub async fn delete_row(&self, row_id: &str) -> FlowyResult<()> {
         let row_id = row_id.to_owned();
         let block_id = self.persistence.get_block_id(&row_id)?;
@@ -155,7 +156,7 @@ impl GridBlockManager {
 
         Ok(changesets)
     }
-
+    // This function will be moved to GridViewRevisionEditor
     pub(crate) async fn move_row(&self, row_rev: Arc<RowRevision>, from: usize, to: usize) -> FlowyResult<()> {
         let editor = self.get_editor_from_row_id(&row_rev.id).await?;
         let _ = editor.move_row(&row_rev.id, from, to).await?;
@@ -180,6 +181,14 @@ impl GridBlockManager {
         Ok(())
     }
 
+    // This function will be moved to GridViewRevisionEditor.
+    pub async fn index_of_row(&self, row_id: &str) -> Option<usize> {
+        match self.get_editor_from_row_id(row_id).await {
+            Ok(editor) => editor.index_of_row(row_id).await,
+            Err(_) => None,
+        }
+    }
+
     pub async fn update_cell<F>(&self, changeset: CellChangesetPB, row_builder: F) -> FlowyResult<()>
     where
         F: FnOnce(Arc<RowRevision>) -> RowPB,

+ 13 - 16
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -342,7 +342,7 @@ impl GridRevisionEditor {
 
     pub async fn delete_row(&self, row_id: &str) -> FlowyResult<()> {
         let _ = self.block_manager.delete_row(row_id).await?;
-        self.view_manager.delete_row(row_id).await;
+        self.view_manager.did_delete_row(row_id).await;
         Ok(())
     }
 
@@ -484,21 +484,22 @@ impl GridRevisionEditor {
         Ok(snapshots)
     }
 
-    pub async fn move_item(&self, params: MoveItemParams) -> FlowyResult<()> {
-        match params.ty {
-            MoveItemTypePB::MoveField => {
-                self.move_field(&params.item_id, params.from_index, params.to_index)
-                    .await
-            }
-            MoveItemTypePB::MoveRow => self.move_row(&params.item_id, params.from_index, params.to_index).await,
-        }
+    pub async fn move_row(&self, params: MoveRowParams) -> FlowyResult<()> {
+        self.view_manager.move_row(params).await
     }
 
-    pub async fn move_field(&self, field_id: &str, from: i32, to: i32) -> FlowyResult<()> {
+    pub async fn move_field(&self, params: MoveFieldParams) -> FlowyResult<()> {
+        let MoveFieldParams {
+            grid_id: _,
+            field_id,
+            from_index,
+            to_index,
+        } = params;
+
         let _ = self
-            .modify(|grid_pad| Ok(grid_pad.move_field(field_id, from as usize, to as usize)?))
+            .modify(|grid_pad| Ok(grid_pad.move_field(&field_id, from_index as usize, to_index as usize)?))
             .await?;
-        if let Some((index, field_rev)) = self.grid_pad.read().await.get_field_rev(field_id) {
+        if let Some((index, field_rev)) = self.grid_pad.read().await.get_field_rev(&field_id) {
             let delete_field_order = FieldIdPB::from(field_id);
             let insert_field = IndexFieldPB::from_field_rev(field_rev, index);
             let notified_changeset = FieldChangesetPB {
@@ -513,10 +514,6 @@ impl GridRevisionEditor {
         Ok(())
     }
 
-    pub async fn move_row(&self, row_id: &str, from: i32, to: i32) -> FlowyResult<()> {
-        self.view_manager.move_row(row_id, from, to).await
-    }
-
     pub async fn delta_bytes(&self) -> Bytes {
         self.grid_pad.read().await.delta_bytes()
     }

+ 64 - 18
frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs

@@ -1,13 +1,17 @@
 use flowy_error::{FlowyError, FlowyResult};
 
-use crate::entities::{CreateRowParams, GridFilterConfiguration, GridSettingPB, GroupPB, RowPB};
+use crate::entities::{
+    CreateRowParams, GridFilterConfiguration, GridLayout, GridSettingPB, GroupPB, GroupRowsChangesetPB, InsertedRowPB,
+    RowPB,
+};
 use crate::services::grid_editor_task::GridServiceTaskScheduler;
-use crate::services::group::{default_group_configuration, GroupConfigurationDelegate, GroupService};
+use crate::services::group::{default_group_configuration, Group, GroupConfigurationDelegate, GroupService};
 use flowy_grid_data_model::revision::{FieldRevision, GroupConfigurationRevision, RowRevision};
 use flowy_revision::{RevisionCloudService, RevisionManager, RevisionObjectBuilder};
 use flowy_sync::client_grid::{GridViewRevisionChangeset, GridViewRevisionPad};
 use flowy_sync::entities::revision::Revision;
 
+use crate::dart_notification::{send_dart_notification, GridNotification};
 use crate::services::setting::make_grid_setting;
 use flowy_sync::entities::grid::GridSettingChangesetParams;
 use lib_infra::future::{wrap_future, AFFuture, FutureResult};
@@ -19,7 +23,7 @@ pub trait GridViewRevisionDelegate: Send + Sync + 'static {
     fn get_field_rev(&self, field_id: &str) -> AFFuture<Option<Arc<FieldRevision>>>;
 }
 
-pub trait GridViewRevisionDataSource: Send + Sync + 'static {
+pub trait GridViewRevisionRowDataSource: Send + Sync + 'static {
     fn row_revs(&self) -> AFFuture<Vec<Arc<RowRevision>>>;
 }
 
@@ -30,8 +34,9 @@ pub struct GridViewRevisionEditor {
     pad: Arc<RwLock<GridViewRevisionPad>>,
     rev_manager: Arc<RevisionManager>,
     delegate: Arc<dyn GridViewRevisionDelegate>,
-    data_source: Arc<dyn GridViewRevisionDataSource>,
+    data_source: Arc<dyn GridViewRevisionRowDataSource>,
     group_service: Arc<RwLock<GroupService>>,
+    groups: Arc<RwLock<Vec<Group>>>,
     scheduler: Arc<dyn GridServiceTaskScheduler>,
 }
 
@@ -47,7 +52,7 @@ impl GridViewRevisionEditor {
     ) -> FlowyResult<Self>
     where
         Delegate: GridViewRevisionDelegate,
-        DataSource: GridViewRevisionDataSource,
+        DataSource: GridViewRevisionRowDataSource,
     {
         let cloud = Arc::new(GridViewRevisionCloudService {
             token: token.to_owned(),
@@ -57,52 +62,87 @@ impl GridViewRevisionEditor {
         let rev_manager = Arc::new(rev_manager);
         let group_service = GroupService::new(Box::new(pad.clone())).await;
         let user_id = user_id.to_owned();
+        let groups = Arc::new(RwLock::new(vec![]));
         Ok(Self {
             pad,
             user_id,
             view_id,
             rev_manager,
             scheduler,
+            groups,
             delegate: Arc::new(delegate),
             data_source: Arc::new(data_source),
             group_service: Arc::new(RwLock::new(group_service)),
         })
     }
 
-    pub(crate) async fn create_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) {
+    pub(crate) async fn update_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) {
+        match params.layout {
+            GridLayout::Table => {
+                // Table can be grouped too
+            }
+            GridLayout::Board => match params.group_id.as_ref() {
+                None => {}
+                Some(group_id) => {
+                    self.group_service
+                        .read()
+                        .await
+                        .update_row(row_rev, group_id, |field_id| self.delegate.get_field_rev(&field_id))
+                        .await;
+                }
+            },
+        }
+    }
+
+    pub(crate) async fn did_create_row(&self, row_pb: &RowPB, params: &CreateRowParams) {
+        // Send the group notification if the current view has groups
         match params.group_id.as_ref() {
             None => {}
             Some(group_id) => {
-                self.group_service
-                    .read()
-                    .await
-                    .update_row(row_rev, group_id, |field_id| self.delegate.get_field_rev(&field_id))
-                    .await;
+                let inserted_row = InsertedRowPB {
+                    row: row_pb.clone(),
+                    index: None,
+                };
+                let changeset = GroupRowsChangesetPB::insert(group_id.clone(), vec![inserted_row]);
+                self.notify_did_update_group(changeset).await;
             }
         }
-        todo!()
     }
 
-    pub(crate) async fn did_create_row(&self, row_pb: &RowPB, params: &CreateRowParams) {
-        match params.group_id.as_ref() {
+    pub(crate) async fn did_delete_row(&self, row_id: &str) {
+        // Send the group notification if the current view has groups;
+        match self.group_id_of_row(row_id).await {
             None => {}
             Some(group_id) => {
-                self.group_service.read().await.did_create_row(group_id, row_pb).await;
+                let changeset = GroupRowsChangesetPB::delete(group_id, vec![row_id.to_owned()]);
+                self.notify_did_update_group(changeset).await;
             }
         }
     }
 
-    pub(crate) async fn delete_row(&self, row_id: &str) {
-        self.group_service.read().await.did_delete_card(row_id.to_owned()).await;
+    async fn group_id_of_row(&self, row_id: &str) -> Option<String> {
+        let read_guard = self.groups.read().await;
+        for group in read_guard.iter() {
+            if group.rows.iter().any(|row| row.id == row_id) {
+                return Some(group.id.clone());
+            }
+        }
+
+        None
     }
 
     pub(crate) async fn load_groups(&self) -> FlowyResult<Vec<GroupPB>> {
         let field_revs = self.delegate.get_field_revs().await;
         let row_revs = self.data_source.row_revs().await;
+
+        //
         let mut write_guard = self.group_service.write().await;
         match write_guard.load_groups(&field_revs, row_revs).await {
             None => Ok(vec![]),
-            Some(groups) => Ok(groups),
+            Some(groups) => {
+                *self.groups.write().await = groups.clone();
+                Ok(groups.into_iter().map(GroupPB::from).collect())
+            }
         }
     }
 
@@ -129,6 +169,12 @@ impl GridViewRevisionEditor {
         }
     }
 
+    async fn notify_did_update_group(&self, changeset: GroupRowsChangesetPB) {
+        send_dart_notification(&changeset.group_id, GridNotification::DidUpdateGroup)
+            .payload(changeset)
+            .send();
+    }
+
     async fn modify<F>(&self, f: F) -> FlowyResult<()>
     where
         F: for<'a> FnOnce(&'a mut GridViewRevisionPad) -> FlowyResult<Option<GridViewRevisionChangeset>>,

+ 39 - 15
frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs

@@ -1,8 +1,12 @@
-use crate::entities::{CreateRowParams, GridFilterConfiguration, GridSettingPB, RepeatedGridGroupPB, RowPB};
+use crate::entities::{
+    CreateRowParams, GridFilterConfiguration, GridLayout, GridSettingPB, MoveRowParams, RepeatedGridGroupPB, RowPB,
+};
 use crate::manager::GridUser;
 use crate::services::block_manager::GridBlockManager;
 use crate::services::grid_editor_task::GridServiceTaskScheduler;
-use crate::services::grid_view_editor::{GridViewRevisionDataSource, GridViewRevisionDelegate, GridViewRevisionEditor};
+use crate::services::grid_view_editor::{
+    GridViewRevisionDelegate, GridViewRevisionEditor, GridViewRevisionRowDataSource,
+};
 use bytes::Bytes;
 use dashmap::DashMap;
 use flowy_error::FlowyResult;
@@ -45,7 +49,7 @@ impl GridViewManager {
 
     pub(crate) async fn update_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) {
         for view_editor in self.view_editors.iter() {
-            view_editor.create_row(row_rev, params).await;
+            view_editor.update_row(row_rev, params).await;
         }
     }
 
@@ -55,9 +59,9 @@ impl GridViewManager {
         }
     }
 
-    pub(crate) async fn delete_row(&self, row_id: &str) {
+    pub(crate) async fn did_delete_row(&self, row_id: &str) {
         for view_editor in self.view_editors.iter() {
-            view_editor.delete_row(row_id).await;
+            view_editor.did_delete_row(row_id).await;
         }
     }
 
@@ -83,15 +87,35 @@ impl GridViewManager {
         Ok(RepeatedGridGroupPB { items: groups })
     }
 
-    pub(crate) async fn move_row(&self, row_id: &str, from: i32, to: i32) -> FlowyResult<()> {
-        match self.block_manager.get_row_rev(row_id).await? {
+    pub(crate) async fn move_row(&self, params: MoveRowParams) -> FlowyResult<()> {
+        let MoveRowParams {
+            view_id: _,
+            row_id,
+            from_index,
+            to_index,
+            layout,
+            upper_row_id,
+        } = params;
+
+        let from_index = from_index as usize;
+
+        match self.block_manager.get_row_rev(&row_id).await? {
             None => tracing::warn!("Move row failed, can not find the row:{}", row_id),
-            Some(row_rev) => {
-                let _ = self
-                    .block_manager
-                    .move_row(row_rev.clone(), from as usize, to as usize)
-                    .await?;
-            }
+            Some(row_rev) => match layout {
+                GridLayout::Table => {
+                    tracing::trace!("Move row from {} to {}", from_index, to_index);
+                    let to_index = to_index as usize;
+                    let _ = self.block_manager.move_row(row_rev, from_index, to_index).await?;
+                }
+                GridLayout::Board => {
+                    if let Some(upper_row_id) = upper_row_id {
+                        if let Some(to_index) = self.block_manager.index_of_row(&upper_row_id).await {
+                            tracing::trace!("Move row from {} to {}", from_index, to_index);
+                            let _ = self.block_manager.move_row(row_rev, from_index, to_index).await?;
+                        }
+                    }
+                }
+            },
         }
         Ok(())
     }
@@ -132,7 +156,7 @@ async fn make_view_editor<Delegate, DataSource>(
 ) -> FlowyResult<GridViewRevisionEditor>
 where
     Delegate: GridViewRevisionDelegate,
-    DataSource: GridViewRevisionDataSource,
+    DataSource: GridViewRevisionRowDataSource,
 {
     tracing::trace!("Open view:{} editor", view_id);
 
@@ -170,7 +194,7 @@ impl RevisionCompactor for GridViewRevisionCompactor {
     }
 }
 
-impl GridViewRevisionDataSource for Arc<GridBlockManager> {
+impl GridViewRevisionRowDataSource for Arc<GridBlockManager> {
     fn row_revs(&self) -> AFFuture<Vec<Arc<RowRevision>>> {
         let block_manager = self.clone();
 

+ 1 - 1
frontend/rust-lib/flowy-grid/src/services/group/group_generator/checkbox_group.rs

@@ -22,7 +22,7 @@ impl GroupActionHandler for CheckboxGroupController {
         &self.field_id
     }
 
-    fn get_groups(&self) -> Vec<Group> {
+    fn build_groups(&self) -> Vec<Group> {
         self.make_groups()
     }
 

+ 1 - 1
frontend/rust-lib/flowy-grid/src/services/group/group_generator/generator.rs

@@ -28,7 +28,7 @@ pub trait Groupable {
 
 pub trait GroupActionHandler: Send + Sync {
     fn field_id(&self) -> &str;
-    fn get_groups(&self) -> Vec<Group>;
+    fn build_groups(&self) -> Vec<Group>;
     fn group_rows(&mut self, row_revs: &[Arc<RowRevision>], field_rev: &FieldRevision) -> FlowyResult<()>;
     fn update_card(&self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str);
 }

+ 2 - 2
frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs

@@ -30,7 +30,7 @@ impl GroupActionHandler for SingleSelectGroupController {
         &self.field_id
     }
 
-    fn get_groups(&self) -> Vec<Group> {
+    fn build_groups(&self) -> Vec<Group> {
         self.make_groups()
     }
 
@@ -94,7 +94,7 @@ impl GroupActionHandler for MultiSelectGroupController {
         &self.field_id
     }
 
-    fn get_groups(&self) -> Vec<Group> {
+    fn build_groups(&self) -> Vec<Group> {
         self.make_groups()
     }
 

+ 7 - 29
frontend/rust-lib/flowy-grid/src/services/group/group_service.rs

@@ -1,11 +1,9 @@
-use crate::dart_notification::{send_dart_notification, GridNotification};
 use crate::entities::{
-    BoardCardChangesetPB, CheckboxGroupConfigurationPB, DateGroupConfigurationPB, FieldType, GroupPB,
-    NumberGroupConfigurationPB, RowPB, SelectOptionGroupConfigurationPB, TextGroupConfigurationPB,
-    UrlGroupConfigurationPB,
+    CheckboxGroupConfigurationPB, DateGroupConfigurationPB, FieldType, NumberGroupConfigurationPB,
+    SelectOptionGroupConfigurationPB, TextGroupConfigurationPB, UrlGroupConfigurationPB,
 };
 use crate::services::group::{
-    CheckboxGroupController, GroupActionHandler, MultiSelectGroupController, SingleSelectGroupController,
+    CheckboxGroupController, Group, GroupActionHandler, MultiSelectGroupController, SingleSelectGroupController,
 };
 use bytes::Bytes;
 use flowy_error::FlowyResult;
@@ -36,7 +34,7 @@ impl GroupService {
         &mut self,
         field_revs: &[Arc<FieldRevision>],
         row_revs: Vec<Arc<RowRevision>>,
-    ) -> Option<Vec<GroupPB>> {
+    ) -> Option<Vec<Group>> {
         let field_rev = find_group_field(field_revs).unwrap();
         let field_type: FieldType = field_rev.field_type_rev.into();
         let configuration = self.delegate.get_group_configuration(field_rev.clone()).await;
@@ -79,26 +77,6 @@ impl GroupService {
         // let row_pb = make_row_from_row_rev(row_rev);
         todo!()
     }
-    #[allow(dead_code)]
-    pub async fn did_delete_card(&self, _row_id: String) {
-        // let changeset = BoardCardChangesetPB::delete(group_id.to_owned(), vec![row_id]);
-        // self.notify_did_update_board(changeset).await;
-        todo!()
-    }
-
-    pub async fn did_create_row(&self, group_id: &str, row_pb: &RowPB) {
-        let changeset = BoardCardChangesetPB::insert(group_id.to_owned(), vec![row_pb.clone()]);
-        self.notify_did_update_board(changeset).await;
-    }
-
-    pub async fn notify_did_update_board(&self, changeset: BoardCardChangesetPB) {
-        if self.action_handler.is_none() {
-            return;
-        }
-        send_dart_notification(&changeset.group_id, GridNotification::DidUpdateBoard)
-            .payload(changeset)
-            .send();
-    }
 
     #[tracing::instrument(level = "trace", skip_all, err)]
     async fn build_groups(
@@ -107,7 +85,7 @@ impl GroupService {
         field_rev: &Arc<FieldRevision>,
         row_revs: Vec<Arc<RowRevision>>,
         configuration: GroupConfigurationRevision,
-    ) -> FlowyResult<Vec<GroupPB>> {
+    ) -> FlowyResult<Vec<Group>> {
         match field_type {
             FieldType::RichText => {
                 // let generator = GroupGenerator::<TextGroupConfigurationPB>::from_configuration(configuration);
@@ -139,11 +117,11 @@ impl GroupService {
         if let Some(group_action_handler) = self.action_handler.as_ref() {
             let mut write_guard = group_action_handler.write().await;
             let _ = write_guard.group_rows(&row_revs, field_rev)?;
-            groups = write_guard.get_groups();
+            groups = write_guard.build_groups();
             drop(write_guard);
         }
 
-        Ok(groups.into_iter().map(GroupPB::from).collect())
+        Ok(groups)
     }
 }
 

+ 3 - 3
frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs

@@ -1,5 +1,5 @@
 use crate::entities::{
-    GridLayoutPB, GridSettingPB, Layout, RepeatedGridConfigurationFilterPB, RepeatedGridGroupConfigurationPB,
+    GridLayout, GridLayoutPB, GridSettingPB, RepeatedGridConfigurationFilterPB, RepeatedGridGroupConfigurationPB,
     RepeatedGridSortPB,
 };
 use flowy_grid_data_model::revision::{FieldRevision, SettingRevision};
@@ -12,7 +12,7 @@ pub struct GridSettingChangesetBuilder {
 }
 
 impl GridSettingChangesetBuilder {
-    pub fn new(grid_id: &str, layout_type: &Layout) -> Self {
+    pub fn new(grid_id: &str, layout_type: &GridLayout) -> Self {
         let params = GridSettingChangesetParams {
             grid_id: grid_id.to_string(),
             layout_type: layout_type.clone().into(),
@@ -42,7 +42,7 @@ impl GridSettingChangesetBuilder {
 }
 
 pub fn make_grid_setting(grid_setting_rev: &SettingRevision, field_revs: &[Arc<FieldRevision>]) -> GridSettingPB {
-    let current_layout_type: Layout = grid_setting_rev.layout.clone().into();
+    let current_layout_type: GridLayout = grid_setting_rev.layout.clone().into();
     let filters_by_field_id = grid_setting_rev
         .get_all_filters(field_revs)
         .map(|filters_by_field_id| {

+ 2 - 1
frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs

@@ -2,7 +2,7 @@ use crate::grid::block_test::script::RowScript::{AssertCell, CreateRow};
 use crate::grid::block_test::util::GridRowTestBuilder;
 use crate::grid::grid_editor::GridEditorTest;
 
-use flowy_grid::entities::{CreateRowParams, FieldType, GridCellIdParams, RowPB};
+use flowy_grid::entities::{CreateRowParams, FieldType, GridCellIdParams, GridLayout, RowPB};
 use flowy_grid::services::field::*;
 use flowy_grid_data_model::revision::{
     GridBlockMetaRevision, GridBlockMetaRevisionChangeset, RowMetaChangeset, RowRevision,
@@ -81,6 +81,7 @@ impl GridRowTest {
                     grid_id: self.editor.grid_id.clone(),
                     start_row_id: None,
                     group_id: None,
+                    layout: GridLayout::Table,
                 };
                 let row_order = self.editor.create_row(params).await.unwrap();
                 self.row_order_by_row_id

+ 3 - 3
frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs

@@ -3,7 +3,7 @@
 #![allow(dead_code)]
 #![allow(unused_imports)]
 
-use flowy_grid::entities::{CreateGridFilterPayloadPB, Layout, GridSettingPB};
+use flowy_grid::entities::{CreateGridFilterPayloadPB, GridLayout, GridSettingPB};
 use flowy_grid::services::setting::GridSettingChangesetBuilder;
 use flowy_grid_data_model::revision::{FieldRevision, FieldTypeRevision};
 use flowy_sync::entities::grid::{CreateGridFilterParams, DeleteFilterParams, GridSettingChangesetParams};
@@ -55,7 +55,7 @@ impl GridFilterTest {
             }
             FilterScript::InsertGridTableFilter { payload } => {
                 let params: CreateGridFilterParams = payload.try_into().unwrap();
-                let layout_type = Layout::Table;
+                let layout_type = GridLayout::Table;
                 let params = GridSettingChangesetBuilder::new(&self.grid_id, &layout_type)
                     .insert_filter(params)
                     .build();
@@ -66,7 +66,7 @@ impl GridFilterTest {
                 assert_eq!(count as usize, filters.len());
             }
             FilterScript::DeleteGridTableFilter { filter_id, field_rev} => {
-                let layout_type = Layout::Table;
+                let layout_type = GridLayout::Table;
                 let params = GridSettingChangesetBuilder::new(&self.grid_id, &layout_type)
                     .delete_filter(DeleteFilterParams { field_id: field_rev.id, filter_id, field_type_rev: field_rev.field_type_rev })
                     .build();

+ 3 - 0
shared-lib/flowy-error-code/src/code.rs

@@ -91,6 +91,9 @@ pub enum ErrorCode {
 
     #[display(fmt = "Grid id is empty")]
     GridIdIsEmpty = 410,
+    #[display(fmt = "Grid view id is empty")]
+    GridViewIdIsEmpty = 411,
+
     #[display(fmt = "Grid block id is empty")]
     BlockIdIsEmpty = 420,
     #[display(fmt = "Row id is empty")]

+ 2 - 6
shared-lib/flowy-sync/src/client_grid/block_revision_pad.rs

@@ -139,12 +139,8 @@ impl GridBlockRevisionPad {
         self.block.rows.len() as i32
     }
 
-    pub fn index_of_row(&self, row_id: &str) -> Option<i32> {
-        self.block
-            .rows
-            .iter()
-            .position(|row| row.id == row_id)
-            .map(|index| index as i32)
+    pub fn index_of_row(&self, row_id: &str) -> Option<usize> {
+        self.block.rows.iter().position(|row| row.id == row_id)
     }
 
     pub fn update_row(&mut self, changeset: RowMetaChangeset) -> CollaborateResult<Option<GridBlockRevisionChangeset>> {