From 292b90c14cec3576e4624694ff08316a21075436 Mon Sep 17 00:00:00 2001 From: appflowy Date: Tue, 9 Aug 2022 18:04:23 +0800 Subject: [PATCH] fix: create column errors --- .../board/presentation/board_page.dart | 148 ++++++++++++++++- .../lib/plugins/doc/presentation/banner.dart | 18 +- .../cell_service/cell_field_notifier.dart | 7 +- .../cell/cell_service/context_builder.dart | 4 +- .../plugins/grid/application/grid_bloc.dart | 156 ++++++++---------- .../application/grid_data_controller.dart | 128 ++++++++++++++ .../grid/application/row/row_bloc.dart | 33 ++-- .../application/row/row_data_controller.dart | 41 +++++ .../grid/application/row/row_service.dart | 6 +- .../presentation/controller/grid_scroll.dart | 11 +- .../plugins/grid/presentation/grid_page.dart | 19 ++- .../widgets/cell/cell_builder.dart | 50 +++--- ...cell_cotainer.dart => cell_container.dart} | 0 .../widgets/cell/number_cell.dart | 6 +- .../select_option_cell.dart | 22 +-- .../presentation/widgets/cell/text_cell.dart | 6 +- .../widgets/cell/url_cell/url_cell.dart | 12 +- .../presentation/widgets/row/grid_row.dart | 16 +- .../presentation/widgets/row/row_detail.dart | 6 +- .../flowy-grid/src/entities/field_entities.rs | 9 - 20 files changed, 501 insertions(+), 197 deletions(-) create mode 100644 frontend/app_flowy/lib/plugins/grid/application/grid_data_controller.dart create mode 100644 frontend/app_flowy/lib/plugins/grid/application/row/row_data_controller.dart rename frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/{cell_cotainer.dart => cell_container.dart} (100%) diff --git a/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart b/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart index 612e7c6770..953587852a 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart +++ b/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 createState() => _BoardPageState(); +} + +class _BoardPageState extends State { + 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)); } } diff --git a/frontend/app_flowy/lib/plugins/doc/presentation/banner.dart b/frontend/app_flowy/lib/plugins/doc/presentation/banner.dart index 52c422b64c..bd4b651da8 100644 --- a/frontend/app_flowy/lib/plugins/doc/presentation/banner.dart +++ b/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), ], ), diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_field_notifier.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_field_notifier.dart index 72f1bc787d..950832c674 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_field_notifier.dart +++ b/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>> _fieldListenerByFieldId = {}; + final Map>> _fieldListenerByFieldId = + {}; - GridCellFieldNotifier({required GridFieldChangedNotifier notifier}) { + GridCellFieldNotifier({required IGridFieldChangedNotifier notifier}) { notifier.onFieldChanged( (field) { final map = _fieldListenerByFieldId[field.id]; diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/context_builder.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/context_builder.dart index 8f78793a2c..526213ee4d 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/context_builder.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/context_builder.dart @@ -246,7 +246,7 @@ class IGridCellController 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 extends Equatable { [_cellsCache.get(_cacheKey) ?? "", cellId.rowId + cellId.field.id]; } -class _GridFieldChangedNotifierImpl extends GridFieldChangedNotifier { +class _GridFieldChangedNotifierImpl extends IGridFieldChangedNotifier { final GridFieldCache _cache; FieldChangesetCallback? _onChangesetFn; diff --git a/frontend/app_flowy/lib/plugins/grid/application/grid_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/grid_bloc.dart index 19c4049224..4516d01ba3 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/grid_bloc.dart +++ b/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 { - final String gridId; - final GridService _gridService; - final GridFieldCache fieldCache; - - // key: the block id - final LinkedHashMap _blocks; - - List get rowInfos { - final List 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( (event, emit) async { @@ -44,13 +27,21 @@ class GridBloc extends Bloc { 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 { @override Future 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 _loadGrid(Emitter 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))), + ), + (err) => emit( + state.copyWith(loadingState: GridLoadingState.finish(right(err))), ), ); } - - Future _loadFields(GridPB grid, Emitter 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)))), - ), - ); - } - - void _initialBlocks(List 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 rows, GridRowChangeReason listState) = - _DidReceiveRowUpdate; - const factory GridEvent.didReceiveFieldUpdate(List fields) = _DidReceiveFieldUpdate; + const factory GridEvent.didReceiveRowUpdate( + List rows, + GridRowChangeReason listState, + ) = _DidReceiveRowUpdate; + const factory GridEvent.didReceiveFieldUpdate( + UnmodifiableListView 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 successOrFail) = _Finish; + const factory GridLoadingState.finish( + Either successOrFail) = _Finish; } class GridFieldEquatable extends Equatable { - final List _fields; - const GridFieldEquatable(List fields) : _fields = fields; + final UnmodifiableListView _fields; + const GridFieldEquatable( + UnmodifiableListView fields, + ) : _fields = fields; @override List 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), ]; } diff --git a/frontend/app_flowy/lib/plugins/grid/application/grid_data_controller.dart b/frontend/app_flowy/lib/plugins/grid/application/grid_data_controller.dart new file mode 100644 index 0000000000..3563a13528 --- /dev/null +++ b/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); +typedef OnGridChanged = void Function(GridPB); + +typedef OnRowsChanged = void Function( + List rowInfos, + GridRowChangeReason, +); +typedef ListenONRowChangedCondition = bool Function(); + +class GridDataController { + final String gridId; + final GridService _gridFFIService; + final GridFieldCache fieldCache; + + // key: the block id + final LinkedHashMap _blocks; + UnmodifiableMapView get blocks => + UnmodifiableMapView(_blocks); + + OnRowsChanged? _onRowChanged; + OnFieldsChanged? _onFieldsChanged; + OnGridChanged? _onGridChanged; + + List get rowInfos { + final List 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> 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 dispose() async { + await _gridFFIService.closeGrid(); + await fieldCache.dispose(); + + for (final blockCache in _blocks.values) { + blockCache.dispose(); + } + } + + void _initialBlocks(List 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> _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), + ), + ); + } +} diff --git a/frontend/app_flowy/lib/plugins/grid/application/row/row_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/row/row_bloc.dart index 3b755d1524..b6079b6764 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/row/row_bloc.dart +++ b/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 { 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( (event, emit) async { await event.map( @@ -33,7 +33,7 @@ class RowBloc extends Bloc { 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 { @override Future close() async { - if (_rowListenFn != null) { - _rowCache.removeRowListener(_rowListenFn!); - } - + _dataController.dispose(); return super.close(); } Future _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 { 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 diff --git a/frontend/app_flowy/lib/plugins/grid/application/row/row_data_controller.dart b/frontend/app_flowy/lib/plugins/grid/application/row/row_data_controller.dart new file mode 100644 index 0000000000..fb05ff5920 --- /dev/null +++ b/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!); + } + } +} diff --git a/frontend/app_flowy/lib/plugins/grid/application/row/row_service.dart b/frontend/app_flowy/lib/plugins/grid/application/row/row_service.dart index 4fd18a1fdd..d72a05089a 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/row/row_service.dart +++ b/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) { diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/controller/grid_scroll.dart b/frontend/app_flowy/lib/plugins/grid/presentation/controller/grid_scroll.dart index dddd93d175..72b5152aea 100755 --- a/frontend/app_flowy/lib/plugins/grid/presentation/controller/grid_scroll.dart +++ b/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 _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; } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart b/frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart index ed6306a972..d267e4aca9 100755 --- a/frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart +++ b/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 { final _scrollController = GridScrollController( - scrollGroupContorller: LinkedScrollControllerGroup()); + scrollGroupController: LinkedScrollControllerGroup()); late ScrollController headerScrollController; @override @@ -153,7 +154,7 @@ class _FlowyGridState extends State { } Widget _gridHeader(BuildContext context, String gridId) { - final fieldCache = context.read().fieldCache; + final fieldCache = context.read().dataController.fieldCache; return GridHeaderSliverAdaptor( gridId: gridId, fieldCache: fieldCache, @@ -169,7 +170,7 @@ class _GridToolbarAdaptor extends StatelessWidget { Widget build(BuildContext context) { return BlocSelector( selector: (state) { - final fieldCache = context.read().fieldCache; + final fieldCache = context.read().dataController.fieldCache; return GridToolbarContext( gridId: state.gridId, fieldCache: fieldCache, @@ -237,14 +238,20 @@ class _GridRowsState extends State<_GridRows> { ) { final rowCache = context.read().getRowCache(rowInfo.blockId, rowInfo.id); - final fieldCache = context.read().fieldCache; + + final fieldCache = context.read().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), ), ); diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_builder.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_builder.dart index 1913aac786..c53bee8423 100755 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_builder.dart +++ b/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 onCellFocus = ValueNotifier(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 get onAccessoryHover => onCellFocus; @@ -150,7 +160,7 @@ abstract class GridCellState extends State { abstract class GridFocusNodeCellState extends GridCellState { - 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) { diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_cotainer.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_container.dart similarity index 100% rename from frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_cotainer.dart rename to frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_container.dart diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/number_cell.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/number_cell.dart index 8c6db0afe9..a24243ef99 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/number_cell.dart +++ b/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 { @override void initState() { - final cellContext = widget.cellContorllerBuilder.build(); + final cellContext = widget.cellControllerBuilder.build(); _cellBloc = getIt(param1: cellContext) ..add(const NumberCellEvent.initial()); _controller = diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart index 53c77c6016..f4ebfa8d4d 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart +++ b/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 { @override void initState() { final cellContext = - widget.cellContorllerBuilder.build() as GridSelectOptionCellController; + widget.cellControllerBuilder.build() as GridSelectOptionCellController; _cellBloc = getIt(param1: cellContext) ..add(const SelectOptionCellEvent.initial()); super.initState(); @@ -63,7 +63,7 @@ class _SingleSelectCellState extends State { 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 { //---------------------------------------------------------------- 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 { @override void initState() { final cellContext = - widget.cellContorllerBuilder.build() as GridSelectOptionCellController; + widget.cellControllerBuilder.build() as GridSelectOptionCellController; _cellBloc = getIt(param1: cellContext) ..add(const SelectOptionCellEvent.initial()); super.initState(); @@ -119,7 +119,7 @@ class _MultiSelectCellState extends State { 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 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)); }, diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/text_cell.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/text_cell.dart index 29471c13b6..21f9c60631 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/text_cell.dart +++ b/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 { @override void initState() { - final cellContext = widget.cellContorllerBuilder.build(); + final cellContext = widget.cellControllerBuilder.build(); _cellBloc = getIt(param1: cellContext); _cellBloc.add(const TextCellEvent.initial()); _controller = TextEditingController(text: _cellBloc.state.content); diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/url_cell.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/url_cell.dart index bb39b8f276..506cc3bc2b 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/url_cell.dart +++ b/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 { @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 { 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; diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart index effc1b188c..d37a45d210 100755 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart +++ b/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 { 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 { void _expandRow(BuildContext context) { final page = RowDetailPage( rowInfo: widget.rowData, - rowCache: widget.rowCache, + rowCache: widget.dataController.rowCache, cellBuilder: widget.cellBuilder, ); page.show(context); diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart index e7e00ead68..b45457fffd 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart @@ -62,8 +62,10 @@ class _RowDetailPageState extends State { 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; }, diff --git a/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs index dce77787c3..dd31e22a54 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs +++ b/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 for CreateFieldPayloadPB { fn try_into(self) -> Result { 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, }) }