浏览代码

chore: card ui

appflowy 2 年之前
父节点
当前提交
800fb82211
共有 21 个文件被更改,包括 480 次插入47 次删除
  1. 23 11
      frontend/app_flowy/lib/plugins/board/application/board_bloc.dart
  2. 43 3
      frontend/app_flowy/lib/plugins/board/application/board_data_controller.dart
  3. 0 0
      frontend/app_flowy/lib/plugins/board/application/card/board_date_cell_bloc.dart
  4. 111 0
      frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart
  5. 49 0
      frontend/app_flowy/lib/plugins/board/application/card/card_data_controller.dart
  6. 1 1
      frontend/app_flowy/lib/plugins/board/board.dart
  7. 24 2
      frontend/app_flowy/lib/plugins/board/presentation/board_page.dart
  8. 21 0
      frontend/app_flowy/lib/plugins/board/presentation/card/board_checkbox_cell.dart
  9. 21 0
      frontend/app_flowy/lib/plugins/board/presentation/card/board_date_cell.dart
  10. 21 0
      frontend/app_flowy/lib/plugins/board/presentation/card/board_number_cell.dart
  11. 21 0
      frontend/app_flowy/lib/plugins/board/presentation/card/board_url_cell.dart
  12. 55 5
      frontend/app_flowy/lib/plugins/board/presentation/card/card.dart
  13. 69 0
      frontend/app_flowy/lib/plugins/board/presentation/card/card_cell_builder.dart
  14. 1 1
      frontend/app_flowy/lib/plugins/grid/application/grid_data_controller.dart
  15. 2 2
      frontend/app_flowy/lib/plugins/grid/application/row/row_action_sheet_bloc.dart
  16. 8 8
      frontend/app_flowy/lib/plugins/grid/application/row/row_bloc.dart
  17. 1 5
      frontend/app_flowy/lib/plugins/grid/application/row/row_data_controller.dart
  18. 2 2
      frontend/app_flowy/lib/plugins/grid/application/row/row_service.dart
  19. 1 1
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart
  20. 1 1
      frontend/app_flowy/lib/workspace/presentation/home/menu/menu_user.dart
  21. 5 5
      frontend/rust-lib/flowy-folder/tests/workspace/script.rs

+ 23 - 11
frontend/app_flowy/lib/plugins/board/application/board_bloc.dart

@@ -1,5 +1,6 @@
 import 'dart:async';
 import 'package:app_flowy/plugins/grid/application/block/block_cache.dart';
+import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
 import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
 import 'package:appflowy_board/appflowy_board.dart';
 import 'package:dartz/dartz.dart';
@@ -20,6 +21,9 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
   final BoardDataController _dataController;
   late final AFBoardDataController boardDataController;
 
+  GridFieldCache get fieldCache => _dataController.fieldCache;
+  String get gridId => _dataController.gridId;
+
   BoardBloc({required ViewPB view})
       : _dataController = BoardDataController(view: view),
         super(BoardState.initial(view.id)) {
@@ -57,6 +61,9 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
           didReceiveGroups: (List<GroupPB> groups) {
             emit(state.copyWith(groups: groups));
           },
+          didReceiveRows: (List<RowInfo> rowInfos) {
+            emit(state.copyWith(rowInfos: rowInfos));
+          },
         );
       },
     );
@@ -68,7 +75,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
     return super.close();
   }
 
-  GridRowCache? getRowCache(String blockId, String rowId) {
+  GridRowCache? getRowCache(String blockId) {
     final GridBlockCache? blockCache = _dataController.blocks[blockId];
     return blockCache?.rowCache;
   }
@@ -92,6 +99,9 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
 
         boardDataController.addColumns(columns);
       },
+      onRowsChanged: (List<RowInfo> rowInfos, RowChangeReason reason) {
+        add(BoardEvent.didReceiveRows(rowInfos));
+      },
       onError: (err) {
         Log.error(err);
       },
@@ -100,15 +110,15 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
 
   List<BoardColumnItem> _buildRows(List<RowPB> rows) {
     return 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: rowInfo);
+      // 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();
   }
 
@@ -131,6 +141,8 @@ class BoardEvent with _$BoardEvent {
   const factory BoardEvent.createRow() = _CreateRow;
   const factory BoardEvent.didReceiveGroups(List<GroupPB> groups) =
       _DidReceiveGroup;
+  const factory BoardEvent.didReceiveRows(List<RowInfo> rowInfos) =
+      _DidReceiveRows;
   const factory BoardEvent.didReceiveGridUpdate(
     GridPB grid,
   ) = _DidReceiveGridUpdate;
@@ -186,7 +198,7 @@ class GridFieldEquatable extends Equatable {
 }
 
 class BoardColumnItem extends AFColumnItem {
-  final RowInfo row;
+  final RowPB row;
 
   BoardColumnItem({required this.row});
 

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

@@ -3,6 +3,8 @@ import 'dart:collection';
 import 'package:app_flowy/plugins/grid/application/block/block_cache.dart';
 import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
 import 'package:app_flowy/plugins/grid/application/grid_service.dart';
+import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
+import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
 import 'dart:async';
@@ -12,6 +14,10 @@ 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 OnRowsChanged = void Function(
+  List<RowInfo> rowInfos,
+  RowChangeReason,
+);
 typedef OnError = void Function(FlowyError);
 
 class BoardDataController {
@@ -21,17 +27,25 @@ class BoardDataController {
 
   // key: the block id
   final LinkedHashMap<String, GridBlockCache> _blocks;
-  UnmodifiableMapView<String, GridBlockCache> get blocks =>
-      UnmodifiableMapView(_blocks);
+  LinkedHashMap<String, GridBlockCache> get blocks => _blocks;
 
   OnFieldsChanged? _onFieldsChanged;
   OnGridChanged? _onGridChanged;
   OnGroupChanged? _onGroupChanged;
+  OnRowsChanged? _onRowsChanged;
   OnError? _onError;
 
+  List<RowInfo> get rowInfos {
+    final List<RowInfo> rows = [];
+    for (var block in _blocks.values) {
+      rows.addAll(block.rows);
+    }
+    return rows;
+  }
+
   BoardDataController({required ViewPB view})
       : gridId = view.id,
-        _blocks = LinkedHashMap.identity(),
+        _blocks = LinkedHashMap.new(),
         _gridFFIService = GridService(gridId: view.id),
         fieldCache = GridFieldCache(gridId: view.id);
 
@@ -39,11 +53,13 @@ class BoardDataController {
     OnGridChanged? onGridChanged,
     OnFieldsChanged? onFieldsChanged,
     OnGroupChanged? onGroupChanged,
+    OnRowsChanged? onRowsChanged,
     OnError? onError,
   }) {
     _onGridChanged = onGridChanged;
     _onFieldsChanged = onFieldsChanged;
     _onGroupChanged = onGroupChanged;
+    _onRowsChanged = onRowsChanged;
     _onError = onError;
 
     fieldCache.addListener(onFields: (fields) {
@@ -57,6 +73,7 @@ class BoardDataController {
       () => result.fold(
         (grid) async {
           _onGridChanged?.call(grid);
+          _initialBlocks(grid.blocks);
           return await _loadFields(grid).then((result) {
             return result.fold(
               (l) {
@@ -85,6 +102,29 @@ class BoardDataController {
     }
   }
 
+  void _initialBlocks(List<BlockPB> blocks) {
+    for (final block in blocks) {
+      if (_blocks[block.id] != null) {
+        Log.warn("Initial duplicate block's cache: ${block.id}");
+        return;
+      }
+
+      final cache = GridBlockCache(
+        gridId: gridId,
+        block: block,
+        fieldCache: fieldCache,
+      );
+
+      cache.addListener(
+        onChangeReason: (reason) {
+          _onRowsChanged?.call(rowInfos, reason);
+        },
+      );
+
+      _blocks[block.id] = cache;
+    }
+  }
+
   Future<Either<Unit, FlowyError>> _loadFields(GridPB grid) async {
     final result = await _gridFFIService.getFields(fieldIds: grid.fields);
     return Future(

+ 0 - 0
frontend/app_flowy/lib/plugins/board/application/card/board_date_cell_bloc.dart


+ 111 - 0
frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart

@@ -0,0 +1,111 @@
+import 'dart:collection';
+import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
+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';
+
+import 'card_data_controller.dart';
+
+part 'card_bloc.freezed.dart';
+
+class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
+  final RowFFIService _rowService;
+  final CardDataController _dataController;
+
+  BoardCardBloc({
+    required String gridId,
+    required CardDataController dataController,
+  })  : _rowService = RowFFIService(
+          gridId: gridId,
+          blockId: dataController.rowPB.blockId,
+          rowId: dataController.rowPB.id,
+        ),
+        _dataController = dataController,
+        super(BoardCardState.initial(
+            dataController.rowPB, dataController.loadData())) {
+    on<BoardCardEvent>(
+      (event, emit) async {
+        await event.map(
+          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))
+                .toList();
+            emit(state.copyWith(
+              gridCellMap: value.gridCellMap,
+              cells: UnmodifiableListView(cells),
+              changeReason: value.reason,
+            ));
+          },
+        );
+      },
+    );
+  }
+
+  @override
+  Future<void> close() async {
+    _dataController.dispose();
+    return super.close();
+  }
+
+  Future<void> _startListening() async {
+    _dataController.addListener(
+      onRowChanged: (cells, reason) {
+        if (!isClosed) {
+          add(BoardCardEvent.didReceiveCells(cells, reason));
+        }
+      },
+    );
+  }
+}
+
+@freezed
+class BoardCardEvent with _$BoardCardEvent {
+  const factory BoardCardEvent.initial() = _InitialRow;
+  const factory BoardCardEvent.createRow() = _CreateRow;
+  const factory BoardCardEvent.didReceiveCells(
+      GridCellMap gridCellMap, RowChangeReason reason) = _DidReceiveCells;
+}
+
+@freezed
+class BoardCardState with _$BoardCardState {
+  const factory BoardCardState({
+    required RowPB rowPB,
+    required GridCellMap gridCellMap,
+    required UnmodifiableListView<GridCellEquatable> cells,
+    RowChangeReason? changeReason,
+  }) = _BoardCardState;
+
+  factory BoardCardState.initial(RowPB rowPB, GridCellMap cellDataMap) =>
+      BoardCardState(
+        rowPB: rowPB,
+        gridCellMap: cellDataMap,
+        cells: UnmodifiableListView(
+          cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList(),
+        ),
+      );
+}
+
+class GridCellEquatable extends Equatable {
+  final FieldPB _field;
+
+  const GridCellEquatable(FieldPB field) : _field = field;
+
+  @override
+  List<Object?> get props => [
+        _field.id,
+        _field.fieldType,
+        _field.visibility,
+        _field.width,
+      ];
+}

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

@@ -0,0 +1,49 @@
+import 'package:app_flowy/plugins/board/presentation/card/card_cell_builder.dart';
+import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
+import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_field_notifier.dart';
+import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
+import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
+import 'package:flutter/foundation.dart';
+
+typedef OnCardChanged = void Function(GridCellMap, RowChangeReason);
+
+class CardDataController extends BoardCellBuilderDelegate {
+  final RowPB rowPB;
+  final GridFieldCache _fieldCache;
+  final GridRowCache _rowCache;
+  final List<VoidCallback> _onCardChangedListeners = [];
+
+  CardDataController({
+    required this.rowPB,
+    required GridFieldCache fieldCache,
+    required GridRowCache rowCache,
+  })  : _fieldCache = fieldCache,
+        _rowCache = rowCache;
+
+  GridCellMap loadData() {
+    return _rowCache.loadGridCells(rowPB.id);
+  }
+
+  void addListener({OnCardChanged? onRowChanged}) {
+    _onCardChangedListeners.add(_rowCache.addListener(
+      rowId: rowPB.id,
+      onCellUpdated: onRowChanged,
+    ));
+  }
+
+  void dispose() {
+    for (final fn in _onCardChangedListeners) {
+      _rowCache.removeRowListener(fn);
+    }
+  }
+
+  @override
+  GridCellFieldNotifier buildFieldNotifier() {
+    return GridCellFieldNotifier(
+        notifier: GridCellFieldNotifierImpl(_fieldCache));
+  }
+
+  @override
+  GridCellCache get cellCache => _rowCache.cellCache;
+}

+ 1 - 1
frontend/app_flowy/lib/plugins/board/board.dart

@@ -31,7 +31,7 @@ class BoardPluginBuilder implements PluginBuilder {
 
 class BoardPluginConfig implements PluginConfig {
   @override
-  bool get creatable => false;
+  bool get creatable => true;
 }
 
 class BoardPlugin extends Plugin {

+ 24 - 2
frontend/app_flowy/lib/plugins/board/presentation/board_page.dart

@@ -1,5 +1,6 @@
 // ignore_for_file: unused_field
 
+import 'package:app_flowy/plugins/board/application/card/card_data_controller.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';
@@ -7,6 +8,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import '../application/board_bloc.dart';
 import 'card/card.dart';
+import 'card/card_cell_builder.dart';
 
 class BoardPage extends StatelessWidget {
   final ViewPB view;
@@ -51,6 +53,7 @@ class BoardContent extends StatelessWidget {
           child: Padding(
             padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
             child: AFBoard(
+              key: UniqueKey(),
               dataController: context.read<BoardBloc>().boardDataController,
               headerBuilder: _buildHeader,
               footBuilder: _buildFooter,
@@ -87,10 +90,29 @@ class BoardContent extends StatelessWidget {
   }
 
   Widget _buildCard(BuildContext context, AFColumnItem item) {
-    final rowInfo = (item as BoardColumnItem).row;
+    final rowPB = (item as BoardColumnItem).row;
+    final rowCache = context.read<BoardBloc>().getRowCache(rowPB.blockId);
+
+    /// Return placeholder widget if the rowCache is null.
+    if (rowCache == null) return SizedBox(key: ObjectKey(item));
+
+    final fieldCache = context.read<BoardBloc>().fieldCache;
+    final gridId = context.read<BoardBloc>().gridId;
+    final cardController = CardDataController(
+      fieldCache: fieldCache,
+      rowCache: rowCache,
+      rowPB: rowPB,
+    );
+
+    final cellBuilder = BoardCellBuilder(cardController);
+
     return AppFlowyColumnItemCard(
       key: ObjectKey(item),
-      child: BoardCard(rowInfo: rowInfo),
+      child: BoardCard(
+        cellBuilder: cellBuilder,
+        dataController: cardController,
+        gridId: gridId,
+      ),
     );
   }
 }

+ 21 - 0
frontend/app_flowy/lib/plugins/board/presentation/card/board_checkbox_cell.dart

@@ -0,0 +1,21 @@
+import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
+import 'package:flutter/material.dart';
+
+class BoardCheckboxCell extends StatefulWidget {
+  final GridCellControllerBuilder cellControllerBuilder;
+
+  const BoardCheckboxCell({
+    required this.cellControllerBuilder,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  State<BoardCheckboxCell> createState() => _BoardCheckboxCellState();
+}
+
+class _BoardCheckboxCellState extends State<BoardCheckboxCell> {
+  @override
+  Widget build(BuildContext context) {
+    return Container();
+  }
+}

+ 21 - 0
frontend/app_flowy/lib/plugins/board/presentation/card/board_date_cell.dart

@@ -0,0 +1,21 @@
+import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
+import 'package:flutter/material.dart';
+
+class BoardDateCell extends StatefulWidget {
+  final GridCellControllerBuilder cellControllerBuilder;
+
+  const BoardDateCell({
+    required this.cellControllerBuilder,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  State<BoardDateCell> createState() => _BoardDateCellState();
+}
+
+class _BoardDateCellState extends State<BoardDateCell> {
+  @override
+  Widget build(BuildContext context) {
+    return Container();
+  }
+}

+ 21 - 0
frontend/app_flowy/lib/plugins/board/presentation/card/board_number_cell.dart

@@ -0,0 +1,21 @@
+import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
+import 'package:flutter/material.dart';
+
+class BoardNumberCell extends StatefulWidget {
+  final GridCellControllerBuilder cellControllerBuilder;
+
+  const BoardNumberCell({
+    required this.cellControllerBuilder,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  State<BoardNumberCell> createState() => _BoardNumberCellState();
+}
+
+class _BoardNumberCellState extends State<BoardNumberCell> {
+  @override
+  Widget build(BuildContext context) {
+    return Container();
+  }
+}

+ 21 - 0
frontend/app_flowy/lib/plugins/board/presentation/card/board_url_cell.dart

@@ -0,0 +1,21 @@
+import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
+import 'package:flutter/material.dart';
+
+class BoardUrlCell extends StatefulWidget {
+  final GridCellControllerBuilder cellControllerBuilder;
+
+  const BoardUrlCell({
+    required this.cellControllerBuilder,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  State<BoardUrlCell> createState() => _BoardUrlCellState();
+}
+
+class _BoardUrlCellState extends State<BoardUrlCell> {
+  @override
+  Widget build(BuildContext context) {
+    return Container();
+  }
+}

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

@@ -1,13 +1,63 @@
-import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
+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:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
 
-class BoardCard extends StatelessWidget {
-  final RowInfo rowInfo;
+import 'card_cell_builder.dart';
 
-  const BoardCard({required this.rowInfo, Key? key}) : super(key: key);
+class BoardCard extends StatefulWidget {
+  final String gridId;
+  final CardDataController dataController;
+  final BoardCellBuilder cellBuilder;
+
+  const BoardCard({
+    required this.gridId,
+    required this.dataController,
+    required this.cellBuilder,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  State<BoardCard> createState() => _BoardCardState();
+}
+
+class _BoardCardState extends State<BoardCard> {
+  late BoardCardBloc _cardBloc;
+
+  @override
+  void initState() {
+    _cardBloc = BoardCardBloc(
+      gridId: widget.gridId,
+      dataController: widget.dataController,
+    );
+    super.initState();
+  }
 
   @override
   Widget build(BuildContext context) {
-    return const SizedBox(height: 20, child: Text('1234'));
+    return BlocProvider.value(
+      value: _cardBloc,
+      child: BlocBuilder<BoardCardBloc, BoardCardState>(
+        builder: (context, state) {
+          return SizedBox(
+            height: 100,
+            child: Column(
+              children: _makeCells(context, state.gridCellMap),
+            ),
+          );
+        },
+      ),
+    );
+  }
+
+  List<Widget> _makeCells(BuildContext context, GridCellMap cellMap) {
+    return cellMap.values.map(
+      (cellId) {
+        final child = widget.cellBuilder.buildCell(cellId);
+
+        return SizedBox(height: 39, child: child);
+      },
+    ).toList();
   }
 }

+ 69 - 0
frontend/app_flowy/lib/plugins/board/presentation/card/card_cell_builder.dart

@@ -0,0 +1,69 @@
+import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:flutter/material.dart';
+
+import 'board_checkbox_cell.dart';
+import 'board_date_cell.dart';
+import 'board_number_cell.dart';
+import 'board_select_option_cell.dart';
+import 'board_text_cell.dart';
+import 'board_url_cell.dart';
+
+abstract class BoardCellBuilderDelegate
+    extends GridCellControllerBuilderDelegate {
+  GridCellCache get cellCache;
+}
+
+class BoardCellBuilder {
+  final BoardCellBuilderDelegate delegate;
+
+  BoardCellBuilder(this.delegate);
+
+  Widget buildCell(GridCellIdentifier cellId) {
+    final cellControllerBuilder = GridCellControllerBuilder(
+      delegate: delegate,
+      cellId: cellId,
+      cellCache: delegate.cellCache,
+    );
+
+    final key = cellId.key();
+    switch (cellId.fieldType) {
+      case FieldType.Checkbox:
+        return BoardCheckboxCell(
+          cellControllerBuilder: cellControllerBuilder,
+          key: key,
+        );
+      case FieldType.DateTime:
+        return BoardDateCell(
+          cellControllerBuilder: cellControllerBuilder,
+          key: key,
+        );
+      case FieldType.SingleSelect:
+        return BoardSelectOptionCell(
+          cellControllerBuilder: cellControllerBuilder,
+          key: key,
+        );
+      case FieldType.MultiSelect:
+        return BoardSelectOptionCell(
+          cellControllerBuilder: cellControllerBuilder,
+          key: key,
+        );
+      case FieldType.Number:
+        return BoardNumberCell(
+          cellControllerBuilder: cellControllerBuilder,
+          key: key,
+        );
+      case FieldType.RichText:
+        return BoardTextCell(
+          cellControllerBuilder: cellControllerBuilder,
+          key: key,
+        );
+      case FieldType.URL:
+        return BoardUrlCell(
+          cellControllerBuilder: cellControllerBuilder,
+          key: key,
+        );
+    }
+    throw UnimplementedError;
+  }
+}

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/application/grid_data_controller.dart

@@ -46,7 +46,7 @@ class GridDataController {
 
   GridDataController({required ViewPB view})
       : gridId = view.id,
-        _blocks = LinkedHashMap.identity(),
+        _blocks = LinkedHashMap.new(),
         _gridFFIService = GridService(gridId: view.id),
         fieldCache = GridFieldCache(gridId: view.id);
 

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

@@ -12,10 +12,10 @@ part 'row_action_sheet_bloc.freezed.dart';
 
 class RowActionSheetBloc
     extends Bloc<RowActionSheetEvent, RowActionSheetState> {
-  final RowService _rowService;
+  final RowFFIService _rowService;
 
   RowActionSheetBloc({required RowInfo rowData})
-      : _rowService = RowService(
+      : _rowService = RowFFIService(
           gridId: rowData.gridId,
           blockId: rowData.blockId,
           rowId: rowData.id,

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

@@ -12,13 +12,13 @@ import 'row_service.dart';
 part 'row_bloc.freezed.dart';
 
 class RowBloc extends Bloc<RowEvent, RowState> {
-  final RowService _rowService;
+  final RowFFIService _rowService;
   final GridRowDataController _dataController;
 
   RowBloc({
     required RowInfo rowInfo,
     required GridRowDataController dataController,
-  })  : _rowService = RowService(
+  })  : _rowService = RowFFIService(
           gridId: rowInfo.gridId,
           blockId: rowInfo.blockId,
           rowId: rowInfo.id,
@@ -35,13 +35,12 @@ class RowBloc extends Bloc<RowEvent, RowState> {
             _rowService.createRow();
           },
           didReceiveCells: (_DidReceiveCells value) async {
-            final fields = value.gridCellMap.values
+            final cells = value.gridCellMap.values
                 .map((e) => GridCellEquatable(e.field))
                 .toList();
-            final snapshots = UnmodifiableListView(fields);
             emit(state.copyWith(
               gridCellMap: value.gridCellMap,
-              snapshots: snapshots,
+              cells: UnmodifiableListView(cells),
               changeReason: value.reason,
             ));
           },
@@ -80,7 +79,7 @@ class RowState with _$RowState {
   const factory RowState({
     required RowInfo rowInfo,
     required GridCellMap gridCellMap,
-    required UnmodifiableListView<GridCellEquatable> snapshots,
+    required UnmodifiableListView<GridCellEquatable> cells,
     RowChangeReason? changeReason,
   }) = _RowState;
 
@@ -88,8 +87,9 @@ class RowState with _$RowState {
       RowState(
         rowInfo: rowInfo,
         gridCellMap: cellDataMap,
-        snapshots: UnmodifiableListView(
-            cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList()),
+        cells: UnmodifiableListView(
+          cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList(),
+        ),
       );
 }
 

+ 1 - 5
frontend/app_flowy/lib/plugins/grid/application/row/row_data_controller.dart

@@ -13,10 +13,6 @@ class GridRowDataController extends GridCellBuilderDelegate {
   final GridFieldCache _fieldCache;
   final GridRowCache _rowCache;
 
-  GridFieldCache get fieldCache => _fieldCache;
-
-  GridRowCache get rowCache => _rowCache;
-
   GridRowDataController({
     required this.rowInfo,
     required GridFieldCache fieldCache,
@@ -49,5 +45,5 @@ class GridRowDataController extends GridCellBuilderDelegate {
   }
 
   @override
-  GridCellCache get cellCache => rowCache.cellCache;
+  GridCellCache get cellCache => _rowCache.cellCache;
 }

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

@@ -5,12 +5,12 @@ 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';
 
-class RowService {
+class RowFFIService {
   final String gridId;
   final String blockId;
   final String rowId;
 
-  RowService(
+  RowFFIService(
       {required this.gridId, required this.blockId, required this.rowId});
 
   Future<Either<RowPB, FlowyError>> createRow() {

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart

@@ -164,7 +164,7 @@ class RowContent extends StatelessWidget {
   Widget build(BuildContext context) {
     return BlocBuilder<RowBloc, RowState>(
       buildWhen: (previous, current) =>
-          !listEquals(previous.snapshots, current.snapshots),
+          !listEquals(previous.cells, current.cells),
       builder: (context, state) {
         return IntrinsicHeight(
             child: Row(

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/home/menu/menu_user.dart

@@ -53,7 +53,7 @@ class MenuUser extends StatelessWidget {
           borderRadius: Corners.s5Border,
           child: CircleAvatar(
             backgroundColor: Colors.transparent,
-            child: svgWidget('emoji/$iconUrl'),
+            child: Container(),
           )),
     );
   }

+ 5 - 5
frontend/rust-lib/flowy-folder/tests/workspace/script.rs

@@ -150,7 +150,7 @@ impl FolderTest {
             //     assert_eq!(json, expected_json);
             // }
             FolderScript::AssertWorkspace(workspace) => {
-                assert_eq!(self.workspace, workspace);
+                assert_eq!(self.workspace, workspace, "Workspace not equal");
             }
             FolderScript::ReadWorkspace(workspace_id) => {
                 let workspace = read_workspace(sdk, workspace_id).await.pop().unwrap();
@@ -166,7 +166,7 @@ impl FolderTest {
             //     assert_eq!(json, expected_json);
             // }
             FolderScript::AssertApp(app) => {
-                assert_eq!(self.app, app);
+                assert_eq!(self.app, app, "App not equal");
             }
             FolderScript::ReadApp(app_id) => {
                 let app = read_app(sdk, &app_id).await;
@@ -184,7 +184,7 @@ impl FolderTest {
                 self.view = view;
             }
             FolderScript::AssertView(view) => {
-                assert_eq!(self.view, view);
+                assert_eq!(self.view, view, "View not equal");
             }
             FolderScript::ReadView(view_id) => {
                 let view = read_view(sdk, &view_id).await;
@@ -215,7 +215,7 @@ impl FolderTest {
             }
             FolderScript::AssertRevisionState { rev_id, state } => {
                 let record = cache.get(rev_id).await.unwrap();
-                assert_eq!(record.state, state);
+                assert_eq!(record.state, state, "Revision state is not match");
                 if let RevisionState::Ack = state {
                     // There is a defer action that writes the revisions to disk, so we wait here.
                     // Make sure everything is written.
@@ -235,7 +235,7 @@ impl FolderTest {
                     .unwrap_or_else(|| panic!("Expected Next revision is {}, but receive None", rev_id.unwrap()));
                 let mut notify = rev_manager.ack_notify();
                 let _ = notify.recv().await;
-                assert_eq!(next_revision.rev_id, rev_id.unwrap());
+                assert_eq!(next_revision.rev_id, rev_id.unwrap(), "Revision id not match");
             }
         }
     }