Kaynağa Gözat

Merge pull request #800 from AppFlowy-IO/fix/create_column

fix: create column errors
Nathan.fooo 2 yıl önce
ebeveyn
işleme
fd1bbaa6fd
20 değiştirilmiş dosya ile 500 ekleme ve 196 silme
  1. 146 2
      frontend/app_flowy/lib/plugins/board/presentation/board_page.dart
  2. 13 5
      frontend/app_flowy/lib/plugins/doc/presentation/banner.dart
  3. 4 3
      frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_field_notifier.dart
  4. 2 2
      frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/context_builder.dart
  5. 64 90
      frontend/app_flowy/lib/plugins/grid/application/grid_bloc.dart
  6. 128 0
      frontend/app_flowy/lib/plugins/grid/application/grid_data_controller.dart
  7. 15 18
      frontend/app_flowy/lib/plugins/grid/application/row/row_bloc.dart
  8. 41 0
      frontend/app_flowy/lib/plugins/grid/application/row/row_data_controller.dart
  9. 3 3
      frontend/app_flowy/lib/plugins/grid/application/row/row_service.dart
  10. 6 5
      frontend/app_flowy/lib/plugins/grid/presentation/controller/grid_scroll.dart
  11. 13 6
      frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart
  12. 30 20
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_builder.dart
  13. 0 0
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_container.dart
  14. 3 3
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/number_cell.dart
  15. 11 11
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart
  16. 3 3
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/text_cell.dart
  17. 6 6
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/url_cell.dart
  18. 8 8
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart
  19. 4 2
      frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart
  20. 0 9
      frontend/rust-lib/flowy-grid/src/entities/field_entities.rs

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

@@ -1,17 +1,161 @@
 // ignore_for_file: unused_field
 
+import 'package:appflowy_board/appflowy_board.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
 import 'package:flutter/material.dart';
 
-class BoardPage extends StatelessWidget {
+class BoardPage extends StatefulWidget {
   final ViewPB _view;
 
   const BoardPage({required ViewPB view, Key? key})
       : _view = view,
         super(key: key);
 
+  @override
+  State<BoardPage> createState() => _BoardPageState();
+}
+
+class _BoardPageState extends State<BoardPage> {
+  final BoardDataController boardDataController = BoardDataController(
+    onMoveColumn: (fromIndex, toIndex) {
+      debugPrint('Move column from $fromIndex to $toIndex');
+    },
+    onMoveColumnItem: (columnId, fromIndex, toIndex) {
+      debugPrint('Move $columnId:$fromIndex to $columnId:$toIndex');
+    },
+    onMoveColumnItemToColumn: (fromColumnId, fromIndex, toColumnId, toIndex) {
+      debugPrint('Move $fromColumnId:$fromIndex to $toColumnId:$toIndex');
+    },
+  );
+
+  @override
+  void initState() {
+    final column1 = BoardColumnData(id: "To Do", items: [
+      TextItem("Card 1"),
+      TextItem("Card 2"),
+      RichTextItem(title: "Card 3", subtitle: 'Aug 1, 2020 4:05 PM'),
+      TextItem("Card 4"),
+    ]);
+    final column2 = BoardColumnData(id: "In Progress", items: [
+      RichTextItem(title: "Card 5", subtitle: 'Aug 1, 2020 4:05 PM'),
+      TextItem("Card 6"),
+    ]);
+
+    final column3 = BoardColumnData(id: "Done", items: []);
+
+    boardDataController.addColumn(column1);
+    boardDataController.addColumn(column2);
+    boardDataController.addColumn(column3);
+    super.initState();
+  }
+
   @override
   Widget build(BuildContext context) {
-    return Container();
+    final config = BoardConfig(
+      columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
+    );
+    return Container(
+      color: Colors.white,
+      child: Padding(
+        padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
+        child: Board(
+          dataController: boardDataController,
+          footBuilder: (context, columnData) {
+            return AppFlowyColumnFooter(
+              icon: const Icon(Icons.add, size: 20),
+              title: const Text('New'),
+              height: 50,
+              margin: config.columnItemPadding,
+            );
+          },
+          headerBuilder: (context, columnData) {
+            return AppFlowyColumnHeader(
+              icon: const Icon(Icons.lightbulb_circle),
+              title: Text(columnData.id),
+              addIcon: const Icon(Icons.add, size: 20),
+              moreIcon: const Icon(Icons.more_horiz, size: 20),
+              height: 50,
+              margin: config.columnItemPadding,
+            );
+          },
+          cardBuilder: (context, item) {
+            return AppFlowyColumnItemCard(
+              key: ObjectKey(item),
+              child: _buildCard(item),
+            );
+          },
+          columnConstraints: const BoxConstraints.tightFor(width: 240),
+          config: BoardConfig(
+            columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
+          ),
+        ),
+      ),
+    );
+  }
+
+  Widget _buildCard(ColumnItem item) {
+    if (item is TextItem) {
+      return Align(
+        alignment: Alignment.centerLeft,
+        child: Padding(
+          padding: const EdgeInsets.symmetric(horizontal: 20),
+          child: Text(item.s),
+        ),
+      );
+    }
+
+    if (item is RichTextItem) {
+      return Align(
+        alignment: Alignment.centerLeft,
+        child: Padding(
+          padding: const EdgeInsets.all(20),
+          child: Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              Text(
+                item.title,
+                style: const TextStyle(fontSize: 14),
+                textAlign: TextAlign.left,
+              ),
+              const SizedBox(height: 10),
+              Text(
+                item.subtitle,
+                style: const TextStyle(fontSize: 12, color: Colors.grey),
+              )
+            ],
+          ),
+        ),
+      );
+    }
+
+    throw UnimplementedError();
+  }
+}
+
+class TextItem extends ColumnItem {
+  final String s;
+
+  TextItem(this.s);
+
+  @override
+  String get id => s;
+}
+
+class RichTextItem extends ColumnItem {
+  final String title;
+  final String subtitle;
+
+  RichTextItem({required this.title, required this.subtitle});
+
+  @override
+  String get id => title;
+}
+
+extension HexColor on Color {
+  static Color fromHex(String hexString) {
+    final buffer = StringBuffer();
+    if (hexString.length == 6 || hexString.length == 7) buffer.write('ff');
+    buffer.write(hexString.replaceFirst('#', ''));
+    return Color(int.parse(buffer.toString(), radix: 16));
   }
 }

+ 13 - 5
frontend/app_flowy/lib/plugins/doc/presentation/banner.dart

@@ -11,7 +11,9 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
 class DocumentBanner extends StatelessWidget {
   final void Function() onRestore;
   final void Function() onDelete;
-  const DocumentBanner({required this.onRestore, required this.onDelete, Key? key}) : super(key: key);
+  const DocumentBanner(
+      {required this.onRestore, required this.onDelete, Key? key})
+      : super(key: key);
 
   @override
   Widget build(BuildContext context) {
@@ -26,7 +28,8 @@ class DocumentBanner extends StatelessWidget {
           fit: BoxFit.scaleDown,
           child: Row(
             children: [
-              FlowyText.medium(LocaleKeys.deletePagePrompt_text.tr(), color: Colors.white),
+              FlowyText.medium(LocaleKeys.deletePagePrompt_text.tr(),
+                  color: Colors.white),
               const HSpace(20),
               BaseStyledButton(
                   minWidth: 160,
@@ -37,7 +40,10 @@ class DocumentBanner extends StatelessWidget {
                   downColor: theme.main1,
                   outlineColor: Colors.white,
                   borderRadius: Corners.s8Border,
-                  child: FlowyText.medium(LocaleKeys.deletePagePrompt_restore.tr(), color: Colors.white, fontSize: 14),
+                  child: FlowyText.medium(
+                      LocaleKeys.deletePagePrompt_restore.tr(),
+                      color: Colors.white,
+                      fontSize: 14),
                   onPressed: onRestore),
               const HSpace(20),
               BaseStyledButton(
@@ -49,8 +55,10 @@ class DocumentBanner extends StatelessWidget {
                   downColor: theme.main1,
                   outlineColor: Colors.white,
                   borderRadius: Corners.s8Border,
-                  child: FlowyText.medium(LocaleKeys.deletePagePrompt_deletePermanent.tr(),
-                      color: Colors.white, fontSize: 14),
+                  child: FlowyText.medium(
+                      LocaleKeys.deletePagePrompt_deletePermanent.tr(),
+                      color: Colors.white,
+                      fontSize: 14),
                   onPressed: onDelete),
             ],
           ),

+ 4 - 3
frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_field_notifier.dart

@@ -3,7 +3,7 @@ import 'package:flutter/foundation.dart';
 
 import 'cell_service.dart';
 
-abstract class GridFieldChangedNotifier {
+abstract class IGridFieldChangedNotifier {
   void onFieldChanged(void Function(GridFieldPB) callback);
   void dispose();
 }
@@ -12,9 +12,10 @@ abstract class GridFieldChangedNotifier {
 /// You Register an onFieldChanged callback to listen to the cell changes, and unregister if you don't want to listen.
 class GridCellFieldNotifier {
   /// fieldId: {objectId: callback}
-  final Map<String, Map<String, List<VoidCallback>>> _fieldListenerByFieldId = {};
+  final Map<String, Map<String, List<VoidCallback>>> _fieldListenerByFieldId =
+      {};
 
-  GridCellFieldNotifier({required GridFieldChangedNotifier notifier}) {
+  GridCellFieldNotifier({required IGridFieldChangedNotifier notifier}) {
     notifier.onFieldChanged(
       (field) {
         final map = _fieldListenerByFieldId[field.id];

+ 2 - 2
frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/context_builder.dart

@@ -246,7 +246,7 @@ class IGridCellController<T, D> extends Equatable {
   }
 
   /// Save the cell data to disk
-  /// You can set [dedeplicate] to true (default is false) to reduce the save operation.
+  /// You can set [deduplicate] to true (default is false) to reduce the save operation.
   /// It's useful when you call this method when user editing the [TextField].
   /// The default debounce interval is 300 milliseconds.
   void saveCellData(D data,
@@ -304,7 +304,7 @@ class IGridCellController<T, D> extends Equatable {
       [_cellsCache.get(_cacheKey) ?? "", cellId.rowId + cellId.field.id];
 }
 
-class _GridFieldChangedNotifierImpl extends GridFieldChangedNotifier {
+class _GridFieldChangedNotifierImpl extends IGridFieldChangedNotifier {
   final GridFieldCache _cache;
   FieldChangesetCallback? _onChangesetFn;
 

+ 64 - 90
frontend/app_flowy/lib/plugins/grid/application/grid_bloc.dart

@@ -1,40 +1,23 @@
 import 'dart:async';
 import 'package:dartz/dartz.dart';
 import 'package:equatable/equatable.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 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'block/block_cache.dart';
-import 'grid_service.dart';
+import 'grid_data_controller.dart';
 import 'row/row_service.dart';
 import 'dart:collection';
 
 part 'grid_bloc.freezed.dart';
 
 class GridBloc extends Bloc<GridEvent, GridState> {
-  final String gridId;
-  final GridService _gridService;
-  final GridFieldCache fieldCache;
-
-  // key: the block id
-  final LinkedHashMap<String, GridBlockCache> _blocks;
-
-  List<GridRowInfo> get rowInfos {
-    final List<GridRowInfo> rows = [];
-    for (var block in _blocks.values) {
-      rows.addAll(block.rows);
-    }
-    return rows;
-  }
+  final GridDataController dataController;
 
   GridBloc({required ViewPB view})
-      : gridId = view.id,
-        _blocks = LinkedHashMap.identity(),
-        _gridService = GridService(gridId: view.id),
-        fieldCache = GridFieldCache(gridId: view.id),
+      : dataController = GridDataController(view: view),
         super(GridState.initial(view.id)) {
     on<GridEvent>(
       (event, emit) async {
@@ -44,13 +27,21 @@ class GridBloc extends Bloc<GridEvent, GridState> {
             await _loadGrid(emit);
           },
           createRow: () {
-            _gridService.createRow();
+            dataController.createRow();
           },
-          didReceiveRowUpdate: (newRowInfos, reason) {
-            emit(state.copyWith(rowInfos: newRowInfos, reason: reason));
+          didReceiveGridUpdate: (grid) {
+            emit(state.copyWith(grid: Some(grid)));
           },
           didReceiveFieldUpdate: (fields) {
-            emit(state.copyWith(rowInfos: rowInfos, fields: GridFieldEquatable(fields)));
+            emit(state.copyWith(
+              fields: GridFieldEquatable(fields),
+            ));
+          },
+          didReceiveRowUpdate: (newRowInfos, reason) {
+            emit(state.copyWith(
+              rowInfos: newRowInfos,
+              reason: reason,
+            ));
           },
         );
       },
@@ -59,89 +50,63 @@ class GridBloc extends Bloc<GridEvent, GridState> {
 
   @override
   Future<void> close() async {
-    await _gridService.closeGrid();
-    await fieldCache.dispose();
-
-    for (final blockCache in _blocks.values) {
-      blockCache.dispose();
-    }
+    await dataController.dispose();
     return super.close();
   }
 
   GridRowCache? getRowCache(String blockId, String rowId) {
-    final GridBlockCache? blockCache = _blocks[blockId];
+    final GridBlockCache? blockCache = dataController.blocks[blockId];
     return blockCache?.rowCache;
   }
 
   void _startListening() {
-    fieldCache.addListener(
-      listenWhen: () => !isClosed,
-      onFields: (fields) => add(GridEvent.didReceiveFieldUpdate(fields)),
+    dataController.addListener(
+      onGridChanged: (grid) {
+        if (!isClosed) {
+          add(GridEvent.didReceiveGridUpdate(grid));
+        }
+      },
+      onRowsChanged: (rowInfos, reason) {
+        if (!isClosed) {
+          add(GridEvent.didReceiveRowUpdate(rowInfos, reason));
+        }
+      },
+      onFieldsChanged: (fields) {
+        if (!isClosed) {
+          add(GridEvent.didReceiveFieldUpdate(fields));
+        }
+      },
     );
   }
 
   Future<void> _loadGrid(Emitter<GridState> emit) async {
-    final result = await _gridService.loadGrid();
-    return Future(
-      () => result.fold(
-        (grid) async {
-          _initialBlocks(grid.blocks);
-          await _loadFields(grid, emit);
-        },
-        (err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))),
+    final result = await dataController.loadData();
+    result.fold(
+      (grid) => emit(
+        state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
       ),
-    );
-  }
-
-  Future<void> _loadFields(GridPB grid, Emitter<GridState> emit) async {
-    final result = await _gridService.getFields(fieldIds: grid.fields);
-    return Future(
-      () => result.fold(
-        (fields) {
-          fieldCache.fields = fields.items;
-
-          emit(state.copyWith(
-            grid: Some(grid),
-            fields: GridFieldEquatable(fieldCache.fields),
-            rowInfos: rowInfos,
-            loadingState: GridLoadingState.finish(left(unit)),
-          ));
-        },
-        (err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))),
+      (err) => emit(
+        state.copyWith(loadingState: GridLoadingState.finish(right(err))),
       ),
     );
   }
-
-  void _initialBlocks(List<GridBlockPB> blocks) {
-    for (final block in blocks) {
-      if (_blocks[block.id] != null) {
-        Log.warn("Intial duplicate block's cache: ${block.id}");
-        return;
-      }
-
-      final cache = GridBlockCache(
-        gridId: gridId,
-        block: block,
-        fieldCache: fieldCache,
-      );
-
-      cache.addListener(
-        listenWhen: () => !isClosed,
-        onChangeReason: (reason) => add(GridEvent.didReceiveRowUpdate(rowInfos, reason)),
-      );
-
-      _blocks[block.id] = cache;
-    }
-  }
 }
 
 @freezed
 class GridEvent with _$GridEvent {
   const factory GridEvent.initial() = InitialGrid;
   const factory GridEvent.createRow() = _CreateRow;
-  const factory GridEvent.didReceiveRowUpdate(List<GridRowInfo> rows, GridRowChangeReason listState) =
-      _DidReceiveRowUpdate;
-  const factory GridEvent.didReceiveFieldUpdate(List<GridFieldPB> fields) = _DidReceiveFieldUpdate;
+  const factory GridEvent.didReceiveRowUpdate(
+    List<GridRowInfo> rows,
+    GridRowChangeReason listState,
+  ) = _DidReceiveRowUpdate;
+  const factory GridEvent.didReceiveFieldUpdate(
+    UnmodifiableListView<GridFieldPB> fields,
+  ) = _DidReceiveFieldUpdate;
+
+  const factory GridEvent.didReceiveGridUpdate(
+    GridPB grid,
+  ) = _DidReceiveGridUpdate;
 }
 
 @freezed
@@ -156,7 +121,7 @@ class GridState with _$GridState {
   }) = _GridState;
 
   factory GridState.initial(String gridId) => GridState(
-        fields: const GridFieldEquatable([]),
+        fields: GridFieldEquatable(UnmodifiableListView([])),
         rowInfos: [],
         grid: none(),
         gridId: gridId,
@@ -168,18 +133,27 @@ class GridState with _$GridState {
 @freezed
 class GridLoadingState with _$GridLoadingState {
   const factory GridLoadingState.loading() = _Loading;
-  const factory GridLoadingState.finish(Either<Unit, FlowyError> successOrFail) = _Finish;
+  const factory GridLoadingState.finish(
+      Either<Unit, FlowyError> successOrFail) = _Finish;
 }
 
 class GridFieldEquatable extends Equatable {
-  final List<GridFieldPB> _fields;
-  const GridFieldEquatable(List<GridFieldPB> fields) : _fields = fields;
+  final UnmodifiableListView<GridFieldPB> _fields;
+  const GridFieldEquatable(
+    UnmodifiableListView<GridFieldPB> fields,
+  ) : _fields = fields;
 
   @override
   List<Object?> get props {
+    if (_fields.isEmpty) {
+      return [];
+    }
+
     return [
       _fields.length,
-      _fields.map((field) => field.width).reduce((value, element) => value + element),
+      _fields
+          .map((field) => field.width)
+          .reduce((value, element) => value + element),
     ];
   }
 

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

@@ -0,0 +1,128 @@
+import 'dart:collection';
+
+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 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
+import 'dart:async';
+import 'package:dartz/dartz.dart';
+import 'block/block_cache.dart';
+import 'prelude.dart';
+
+typedef OnFieldsChanged = void Function(UnmodifiableListView<GridFieldPB>);
+typedef OnGridChanged = void Function(GridPB);
+
+typedef OnRowsChanged = void Function(
+  List<GridRowInfo> rowInfos,
+  GridRowChangeReason,
+);
+typedef ListenONRowChangedCondition = bool Function();
+
+class GridDataController {
+  final String gridId;
+  final GridService _gridFFIService;
+  final GridFieldCache fieldCache;
+
+  // key: the block id
+  final LinkedHashMap<String, GridBlockCache> _blocks;
+  UnmodifiableMapView<String, GridBlockCache> get blocks =>
+      UnmodifiableMapView(_blocks);
+
+  OnRowsChanged? _onRowChanged;
+  OnFieldsChanged? _onFieldsChanged;
+  OnGridChanged? _onGridChanged;
+
+  List<GridRowInfo> get rowInfos {
+    final List<GridRowInfo> rows = [];
+    for (var block in _blocks.values) {
+      rows.addAll(block.rows);
+    }
+    return rows;
+  }
+
+  GridDataController({required ViewPB view})
+      : gridId = view.id,
+        _blocks = LinkedHashMap.identity(),
+        _gridFFIService = GridService(gridId: view.id),
+        fieldCache = GridFieldCache(gridId: view.id);
+
+  void addListener({
+    required OnGridChanged onGridChanged,
+    required OnRowsChanged onRowsChanged,
+    required OnFieldsChanged onFieldsChanged,
+  }) {
+    _onGridChanged = onGridChanged;
+    _onRowChanged = onRowsChanged;
+    _onFieldsChanged = onFieldsChanged;
+
+    fieldCache.addListener(onFields: (fields) {
+      _onFieldsChanged?.call(UnmodifiableListView(fields));
+    });
+  }
+
+  Future<Either<Unit, FlowyError>> loadData() async {
+    final result = await _gridFFIService.loadGrid();
+    return Future(
+      () => result.fold(
+        (grid) async {
+          _initialBlocks(grid.blocks);
+          _onGridChanged?.call(grid);
+          return await _loadFields(grid);
+        },
+        (err) => right(err),
+      ),
+    );
+  }
+
+  void createRow() {
+    _gridFFIService.createRow();
+  }
+
+  Future<void> dispose() async {
+    await _gridFFIService.closeGrid();
+    await fieldCache.dispose();
+
+    for (final blockCache in _blocks.values) {
+      blockCache.dispose();
+    }
+  }
+
+  void _initialBlocks(List<GridBlockPB> 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) {
+          _onRowChanged?.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(
+      () => result.fold(
+        (fields) {
+          fieldCache.fields = fields.items;
+          _onFieldsChanged?.call(UnmodifiableListView(fieldCache.fields));
+          return left(unit);
+        },
+        (err) => right(err),
+      ),
+    );
+  }
+}

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

@@ -5,25 +5,25 @@ 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 'row_data_controller.dart';
 import 'row_service.dart';
 
 part 'row_bloc.freezed.dart';
 
 class RowBloc extends Bloc<RowEvent, RowState> {
   final RowService _rowService;
-  final GridRowCache _rowCache;
-  void Function()? _rowListenFn;
+  final GridRowDataController _dataController;
 
   RowBloc({
     required GridRowInfo rowInfo,
-    required GridRowCache rowCache,
+    required GridRowDataController dataController,
   })  : _rowService = RowService(
           gridId: rowInfo.gridId,
           blockId: rowInfo.blockId,
           rowId: rowInfo.id,
         ),
-        _rowCache = rowCache,
-        super(RowState.initial(rowInfo, rowCache.loadGridCells(rowInfo.id))) {
+        _dataController = dataController,
+        super(RowState.initial(rowInfo, dataController.loadData())) {
     on<RowEvent>(
       (event, emit) async {
         await event.map(
@@ -33,7 +33,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
           createRow: (_CreateRow value) {
             _rowService.createRow();
           },
-          didReceiveCellDatas: (_DidReceiveCellDatas value) async {
+          didReceiveCells: (_DidReceiveCells value) async {
             final fields = value.gridCellMap.values
                 .map((e) => GridCellEquatable(e.field))
                 .toList();
@@ -51,19 +51,17 @@ class RowBloc extends Bloc<RowEvent, RowState> {
 
   @override
   Future<void> close() async {
-    if (_rowListenFn != null) {
-      _rowCache.removeRowListener(_rowListenFn!);
-    }
-
+    _dataController.dispose();
     return super.close();
   }
 
   Future<void> _startListening() async {
-    _rowListenFn = _rowCache.addListener(
-      rowId: state.rowInfo.id,
-      onCellUpdated: (cellDatas, reason) =>
-          add(RowEvent.didReceiveCellDatas(cellDatas, reason)),
-      listenWhen: () => !isClosed,
+    _dataController.addListener(
+      onRowChanged: (cells, reason) {
+        if (!isClosed) {
+          add(RowEvent.didReceiveCells(cells, reason));
+        }
+      },
     );
   }
 }
@@ -72,9 +70,8 @@ class RowBloc extends Bloc<RowEvent, RowState> {
 class RowEvent with _$RowEvent {
   const factory RowEvent.initial() = _InitialRow;
   const factory RowEvent.createRow() = _CreateRow;
-  const factory RowEvent.didReceiveCellDatas(
-          GridCellMap gridCellMap, GridRowChangeReason reason) =
-      _DidReceiveCellDatas;
+  const factory RowEvent.didReceiveCells(
+      GridCellMap gridCellMap, GridRowChangeReason reason) = _DidReceiveCells;
 }
 
 @freezed

+ 41 - 0
frontend/app_flowy/lib/plugins/grid/application/row/row_data_controller.dart

@@ -0,0 +1,41 @@
+import 'package:flutter/material.dart';
+import '../cell/cell_service/cell_service.dart';
+import '../grid_service.dart';
+import 'row_service.dart';
+
+typedef OnRowChanged = void Function(GridCellMap, GridRowChangeReason);
+
+class GridRowDataController {
+  final String rowId;
+  VoidCallback? _onRowChangedListener;
+  final GridFieldCache _fieldCache;
+  final GridRowCache _rowCache;
+
+  GridFieldCache get fieldCache => _fieldCache;
+
+  GridRowCache get rowCache => _rowCache;
+
+  GridRowDataController({
+    required this.rowId,
+    required GridFieldCache fieldCache,
+    required GridRowCache rowCache,
+  })  : _fieldCache = fieldCache,
+        _rowCache = rowCache;
+
+  GridCellMap loadData() {
+    return _rowCache.loadGridCells(rowId);
+  }
+
+  void addListener({OnRowChanged? onRowChanged}) {
+    _onRowChangedListener = _rowCache.addListener(
+      rowId: rowId,
+      onCellUpdated: onRowChanged,
+    );
+  }
+
+  void dispose() {
+    if (_onRowChangedListener != null) {
+      _rowCache.removeRowListener(_onRowChangedListener!);
+    }
+  }
+}

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

@@ -158,7 +158,7 @@ class GridRowCache {
     void Function(GridCellMap, GridRowChangeReason)? onCellUpdated,
     bool Function()? listenWhen,
   }) {
-    listenrHandler() async {
+    listenerHandler() async {
       if (listenWhen != null && listenWhen() == false) {
         return;
       }
@@ -181,8 +181,8 @@ class GridRowCache {
       );
     }
 
-    _rowChangeReasonNotifier.addListener(listenrHandler);
-    return listenrHandler;
+    _rowChangeReasonNotifier.addListener(listenerHandler);
+    return listenerHandler;
   }
 
   void removeRowListener(VoidCallback callback) {

+ 6 - 5
frontend/app_flowy/lib/plugins/grid/presentation/controller/grid_scroll.dart

@@ -2,19 +2,20 @@ import 'package:flutter/material.dart';
 import 'package:linked_scroll_controller/linked_scroll_controller.dart';
 
 class GridScrollController {
-  final LinkedScrollControllerGroup _scrollGroupContorller;
+  final LinkedScrollControllerGroup _scrollGroupController;
   final ScrollController verticalController;
   final ScrollController horizontalController;
 
   final List<ScrollController> _linkHorizontalControllers = [];
 
-  GridScrollController({required LinkedScrollControllerGroup scrollGroupContorller})
-      : _scrollGroupContorller = scrollGroupContorller,
+  GridScrollController(
+      {required LinkedScrollControllerGroup scrollGroupController})
+      : _scrollGroupController = scrollGroupController,
         verticalController = ScrollController(),
-        horizontalController = scrollGroupContorller.addAndGet();
+        horizontalController = scrollGroupController.addAndGet();
 
   ScrollController linkHorizontalController() {
-    final controller = _scrollGroupContorller.addAndGet();
+    final controller = _scrollGroupController.addAndGet();
     _linkHorizontalControllers.add(controller);
     return controller;
   }

+ 13 - 6
frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart

@@ -1,3 +1,4 @@
+import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/plugins/grid/application/grid_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
@@ -79,7 +80,7 @@ class FlowyGrid extends StatefulWidget {
 
 class _FlowyGridState extends State<FlowyGrid> {
   final _scrollController = GridScrollController(
-      scrollGroupContorller: LinkedScrollControllerGroup());
+      scrollGroupController: LinkedScrollControllerGroup());
   late ScrollController headerScrollController;
 
   @override
@@ -153,7 +154,7 @@ class _FlowyGridState extends State<FlowyGrid> {
   }
 
   Widget _gridHeader(BuildContext context, String gridId) {
-    final fieldCache = context.read<GridBloc>().fieldCache;
+    final fieldCache = context.read<GridBloc>().dataController.fieldCache;
     return GridHeaderSliverAdaptor(
       gridId: gridId,
       fieldCache: fieldCache,
@@ -169,7 +170,7 @@ class _GridToolbarAdaptor extends StatelessWidget {
   Widget build(BuildContext context) {
     return BlocSelector<GridBloc, GridState, GridToolbarContext>(
       selector: (state) {
-        final fieldCache = context.read<GridBloc>().fieldCache;
+        final fieldCache = context.read<GridBloc>().dataController.fieldCache;
         return GridToolbarContext(
           gridId: state.gridId,
           fieldCache: fieldCache,
@@ -237,14 +238,20 @@ class _GridRowsState extends State<_GridRows> {
   ) {
     final rowCache =
         context.read<GridBloc>().getRowCache(rowInfo.blockId, rowInfo.id);
-    final fieldCache = context.read<GridBloc>().fieldCache;
+
+    final fieldCache = context.read<GridBloc>().dataController.fieldCache;
     if (rowCache != null) {
+      final dataController = GridRowDataController(
+        rowId: rowInfo.id,
+        fieldCache: fieldCache,
+        rowCache: rowCache,
+      );
+
       return SizeTransition(
         sizeFactor: animation,
         child: GridRowWidget(
           rowData: rowInfo,
-          rowCache: rowCache,
-          fieldCache: fieldCache,
+          dataController: dataController,
           key: ValueKey(rowInfo.id),
         ),
       );

+ 30 - 20
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_builder.dart

@@ -27,39 +27,49 @@ class GridCellBuilder {
       cellCache: cellCache,
       fieldCache: fieldCache,
     );
+
     final key = cell.key();
     switch (cell.fieldType) {
       case FieldType.Checkbox:
         return GridCheckboxCell(
-            cellControllerBuilder: cellControllerBuilder, key: key);
+          cellControllerBuilder: cellControllerBuilder,
+          key: key,
+        );
       case FieldType.DateTime:
         return GridDateCell(
-            cellControllerBuilder: cellControllerBuilder,
-            key: key,
-            style: style);
+          cellControllerBuilder: cellControllerBuilder,
+          key: key,
+          style: style,
+        );
       case FieldType.SingleSelect:
         return GridSingleSelectCell(
-            cellContorllerBuilder: cellControllerBuilder,
-            style: style,
-            key: key);
+          cellControllerBuilder: cellControllerBuilder,
+          style: style,
+          key: key,
+        );
       case FieldType.MultiSelect:
         return GridMultiSelectCell(
-            cellContorllerBuilder: cellControllerBuilder,
-            style: style,
-            key: key);
+          cellControllerBuilder: cellControllerBuilder,
+          style: style,
+          key: key,
+        );
       case FieldType.Number:
         return GridNumberCell(
-            cellContorllerBuilder: cellControllerBuilder, key: key);
+          cellControllerBuilder: cellControllerBuilder,
+          key: key,
+        );
       case FieldType.RichText:
         return GridTextCell(
-            cellContorllerBuilder: cellControllerBuilder,
-            style: style,
-            key: key);
+          cellControllerBuilder: cellControllerBuilder,
+          style: style,
+          key: key,
+        );
       case FieldType.URL:
         return GridURLCell(
-            cellContorllerBuilder: cellControllerBuilder,
-            style: style,
-            key: key);
+          cellControllerBuilder: cellControllerBuilder,
+          style: style,
+          key: key,
+        );
     }
     throw UnimplementedError;
   }
@@ -93,7 +103,7 @@ abstract class GridCellWidget extends StatefulWidget
   @override
   final ValueNotifier<bool> onCellFocus = ValueNotifier<bool>(false);
 
-  // When the cell is focused, we assume that the accessory alse be hovered.
+  // When the cell is focused, we assume that the accessory also be hovered.
   @override
   ValueNotifier<bool> get onAccessoryHover => onCellFocus;
 
@@ -150,7 +160,7 @@ abstract class GridCellState<T extends GridCellWidget> extends State<T> {
 
 abstract class GridFocusNodeCellState<T extends GridCellWidget>
     extends GridCellState<T> {
-  SingleListenrFocusNode focusNode = SingleListenrFocusNode();
+  SingleListenerFocusNode focusNode = SingleListenerFocusNode();
 
   @override
   void initState() {
@@ -219,7 +229,7 @@ class GridCellFocusListener extends ChangeNotifier {
 
 abstract class GridCellStyle {}
 
-class SingleListenrFocusNode extends FocusNode {
+class SingleListenerFocusNode extends FocusNode {
   VoidCallback? _listener;
 
   void setListener(VoidCallback listener) {

+ 0 - 0
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_cotainer.dart → frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_container.dart


+ 3 - 3
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/number_cell.dart

@@ -7,10 +7,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'cell_builder.dart';
 
 class GridNumberCell extends GridCellWidget {
-  final GridCellControllerBuilder cellContorllerBuilder;
+  final GridCellControllerBuilder cellControllerBuilder;
 
   GridNumberCell({
-    required this.cellContorllerBuilder,
+    required this.cellControllerBuilder,
     Key? key,
   }) : super(key: key);
 
@@ -25,7 +25,7 @@ class _NumberCellState extends GridFocusNodeCellState<GridNumberCell> {
 
   @override
   void initState() {
-    final cellContext = widget.cellContorllerBuilder.build();
+    final cellContext = widget.cellControllerBuilder.build();
     _cellBloc = getIt<NumberCellBloc>(param1: cellContext)
       ..add(const NumberCellEvent.initial());
     _controller =

+ 11 - 11
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart

@@ -22,11 +22,11 @@ class SelectOptionCellStyle extends GridCellStyle {
 }
 
 class GridSingleSelectCell extends GridCellWidget {
-  final GridCellControllerBuilder cellContorllerBuilder;
+  final GridCellControllerBuilder cellControllerBuilder;
   late final SelectOptionCellStyle? cellStyle;
 
   GridSingleSelectCell({
-    required this.cellContorllerBuilder,
+    required this.cellControllerBuilder,
     GridCellStyle? style,
     Key? key,
   }) : super(key: key) {
@@ -47,7 +47,7 @@ class _SingleSelectCellState extends State<GridSingleSelectCell> {
   @override
   void initState() {
     final cellContext =
-        widget.cellContorllerBuilder.build() as GridSelectOptionCellController;
+        widget.cellControllerBuilder.build() as GridSelectOptionCellController;
     _cellBloc = getIt<SelectOptionCellBloc>(param1: cellContext)
       ..add(const SelectOptionCellEvent.initial());
     super.initState();
@@ -63,7 +63,7 @@ class _SingleSelectCellState extends State<GridSingleSelectCell> {
               selectOptions: state.selectedOptions,
               cellStyle: widget.cellStyle,
               onFocus: (value) => widget.onCellEditing.value = value,
-              cellContorllerBuilder: widget.cellContorllerBuilder);
+              cellControllerBuilder: widget.cellControllerBuilder);
         },
       ),
     );
@@ -78,11 +78,11 @@ class _SingleSelectCellState extends State<GridSingleSelectCell> {
 
 //----------------------------------------------------------------
 class GridMultiSelectCell extends GridCellWidget {
-  final GridCellControllerBuilder cellContorllerBuilder;
+  final GridCellControllerBuilder cellControllerBuilder;
   late final SelectOptionCellStyle? cellStyle;
 
   GridMultiSelectCell({
-    required this.cellContorllerBuilder,
+    required this.cellControllerBuilder,
     GridCellStyle? style,
     Key? key,
   }) : super(key: key) {
@@ -103,7 +103,7 @@ class _MultiSelectCellState extends State<GridMultiSelectCell> {
   @override
   void initState() {
     final cellContext =
-        widget.cellContorllerBuilder.build() as GridSelectOptionCellController;
+        widget.cellControllerBuilder.build() as GridSelectOptionCellController;
     _cellBloc = getIt<SelectOptionCellBloc>(param1: cellContext)
       ..add(const SelectOptionCellEvent.initial());
     super.initState();
@@ -119,7 +119,7 @@ class _MultiSelectCellState extends State<GridMultiSelectCell> {
               selectOptions: state.selectedOptions,
               cellStyle: widget.cellStyle,
               onFocus: (value) => widget.onCellEditing.value = value,
-              cellContorllerBuilder: widget.cellContorllerBuilder);
+              cellControllerBuilder: widget.cellControllerBuilder);
         },
       ),
     );
@@ -136,12 +136,12 @@ class _SelectOptionCell extends StatelessWidget {
   final List<SelectOptionPB> selectOptions;
   final void Function(bool) onFocus;
   final SelectOptionCellStyle? cellStyle;
-  final GridCellControllerBuilder cellContorllerBuilder;
+  final GridCellControllerBuilder cellControllerBuilder;
   const _SelectOptionCell({
     required this.selectOptions,
     required this.onFocus,
     required this.cellStyle,
-    required this.cellContorllerBuilder,
+    required this.cellControllerBuilder,
     Key? key,
   }) : super(key: key);
 
@@ -179,7 +179,7 @@ class _SelectOptionCell extends StatelessWidget {
           onTap: () {
             onFocus(true);
             final cellContext =
-                cellContorllerBuilder.build() as GridSelectOptionCellController;
+                cellControllerBuilder.build() as GridSelectOptionCellController;
             SelectOptionCellEditor.show(
                 context, cellContext, () => onFocus(false));
           },

+ 3 - 3
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/text_cell.dart

@@ -14,10 +14,10 @@ class GridTextCellStyle extends GridCellStyle {
 }
 
 class GridTextCell extends GridCellWidget {
-  final GridCellControllerBuilder cellContorllerBuilder;
+  final GridCellControllerBuilder cellControllerBuilder;
   late final GridTextCellStyle? cellStyle;
   GridTextCell({
-    required this.cellContorllerBuilder,
+    required this.cellControllerBuilder,
     GridCellStyle? style,
     Key? key,
   }) : super(key: key) {
@@ -39,7 +39,7 @@ class _GridTextCellState extends GridFocusNodeCellState<GridTextCell> {
 
   @override
   void initState() {
-    final cellContext = widget.cellContorllerBuilder.build();
+    final cellContext = widget.cellControllerBuilder.build();
     _cellBloc = getIt<TextCellBloc>(param1: cellContext);
     _cellBloc.add(const TextCellEvent.initial());
     _controller = TextEditingController(text: _cellBloc.state.content);

+ 6 - 6
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/url_cell.dart

@@ -31,10 +31,10 @@ enum GridURLCellAccessoryType {
 }
 
 class GridURLCell extends GridCellWidget {
-  final GridCellControllerBuilder cellContorllerBuilder;
+  final GridCellControllerBuilder cellControllerBuilder;
   late final GridURLCellStyle? cellStyle;
   GridURLCell({
-    required this.cellContorllerBuilder,
+    required this.cellControllerBuilder,
     GridCellStyle? style,
     Key? key,
   }) : super(key: key) {
@@ -53,14 +53,14 @@ class GridURLCell extends GridCellWidget {
     switch (ty) {
       case GridURLCellAccessoryType.edit:
         final cellContext =
-            cellContorllerBuilder.build() as GridURLCellController;
+            cellControllerBuilder.build() as GridURLCellController;
         return _EditURLAccessory(
             cellContext: cellContext,
             anchorContext: buildContext.anchorContext);
 
       case GridURLCellAccessoryType.copyURL:
         final cellContext =
-            cellContorllerBuilder.build() as GridURLCellController;
+            cellControllerBuilder.build() as GridURLCellController;
         return _CopyURLAccessory(cellContext: cellContext);
     }
   }
@@ -91,7 +91,7 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
   @override
   void initState() {
     final cellContext =
-        widget.cellContorllerBuilder.build() as GridURLCellController;
+        widget.cellControllerBuilder.build() as GridURLCellController;
     _cellBloc = URLCellBloc(cellContext: cellContext);
     _cellBloc.add(const URLCellEvent.initial());
     super.initState();
@@ -141,7 +141,7 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
       await launchUrl(uri);
     } else {
       final cellContext =
-          widget.cellContorllerBuilder.build() as GridURLCellController;
+          widget.cellControllerBuilder.build() as GridURLCellController;
       widget.onCellEditing.value = true;
       URLCellEditor.show(context, cellContext, () {
         widget.onCellEditing.value = false;

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

@@ -1,4 +1,5 @@
 import 'package:app_flowy/plugins/grid/application/prelude.dart';
+import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/icon_button.dart';
@@ -9,24 +10,23 @@ import 'package:provider/provider.dart';
 
 import '../../layout/sizes.dart';
 import '../cell/cell_accessory.dart';
-import '../cell/cell_cotainer.dart';
+import '../cell/cell_container.dart';
 import '../cell/prelude.dart';
 import 'row_action_sheet.dart';
 import 'row_detail.dart';
 
 class GridRowWidget extends StatefulWidget {
   final GridRowInfo rowData;
-  final GridRowCache rowCache;
+  final GridRowDataController dataController;
   final GridCellBuilder cellBuilder;
 
   GridRowWidget({
     required this.rowData,
-    required this.rowCache,
-    required GridFieldCache fieldCache,
+    required this.dataController,
     Key? key,
   })  : cellBuilder = GridCellBuilder(
-          cellCache: rowCache.cellCache,
-          fieldCache: fieldCache,
+          cellCache: dataController.rowCache.cellCache,
+          fieldCache: dataController.fieldCache,
         ),
         super(key: key);
 
@@ -41,7 +41,7 @@ class _GridRowWidgetState extends State<GridRowWidget> {
   void initState() {
     _rowBloc = RowBloc(
       rowInfo: widget.rowData,
-      rowCache: widget.rowCache,
+      dataController: widget.dataController,
     );
     _rowBloc.add(const RowEvent.initial());
     super.initState();
@@ -81,7 +81,7 @@ class _GridRowWidgetState extends State<GridRowWidget> {
   void _expandRow(BuildContext context) {
     final page = RowDetailPage(
       rowInfo: widget.rowData,
-      rowCache: widget.rowCache,
+      rowCache: widget.dataController.rowCache,
       cellBuilder: widget.cellBuilder,
     );
     page.show(context);

+ 4 - 2
frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart

@@ -62,8 +62,10 @@ class _RowDetailPageState extends State<RowDetailPage> {
   Widget build(BuildContext context) {
     return BlocProvider(
       create: (context) {
-        final bloc =
-            RowDetailBloc(rowInfo: widget.rowInfo, rowCache: widget.rowCache);
+        final bloc = RowDetailBloc(
+          rowInfo: widget.rowInfo,
+          rowCache: widget.rowCache,
+        );
         bloc.add(const RowDetailEvent.initial());
         return bloc;
       },

+ 0 - 9
frontend/rust-lib/flowy-grid/src/entities/field_entities.rs

@@ -164,18 +164,11 @@ pub struct CreateFieldPayloadPB {
     pub grid_id: String,
 
     #[pb(index = 2)]
-    pub field_id: String,
-
-    #[pb(index = 3)]
     pub field_type: FieldType,
-
-    #[pb(index = 4)]
-    pub create_if_not_exist: bool,
 }
 
 pub struct CreateFieldParams {
     pub grid_id: String,
-    pub field_id: String,
     pub field_type: FieldType,
 }
 
@@ -184,10 +177,8 @@ impl TryInto<CreateFieldParams> for CreateFieldPayloadPB {
 
     fn try_into(self) -> Result<CreateFieldParams, Self::Error> {
         let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
-        let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
         Ok(CreateFieldParams {
             grid_id: grid_id.0,
-            field_id: field_id.0,
             field_type: self.field_type,
         })
     }