diff --git a/frontend/app_flowy/lib/startup/deps_resolver.dart b/frontend/app_flowy/lib/startup/deps_resolver.dart index f8583046b6..10b51438e4 100644 --- a/frontend/app_flowy/lib/startup/deps_resolver.dart +++ b/frontend/app_flowy/lib/startup/deps_resolver.dart @@ -16,8 +16,10 @@ import 'package:app_flowy/user/presentation/router.dart'; import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell; import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart'; import 'package:get_it/get_it.dart'; @@ -174,25 +176,25 @@ void _resolveGridDeps(GetIt getIt) { ), ); - getIt.registerFactoryParam( + getIt.registerFactoryParam, void>( (context, _) => SelectionCellBloc( cellContext: context, ), ); - getIt.registerFactoryParam( + getIt.registerFactoryParam, void>( (context, _) => NumberCellBloc( cellContext: context, ), ); - getIt.registerFactoryParam( + getIt.registerFactoryParam, void>( (context, _) => DateCellBloc( cellContext: context, ), ); - getIt.registerFactoryParam( + getIt.registerFactoryParam, void>( (cellData, _) => CheckboxCellBloc( service: CellService(), cellContext: cellData, diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service.dart index 49a04586b3..224e8b0d3a 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/cell_service.dart @@ -9,55 +9,126 @@ import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart'; import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart'; + part 'cell_service.freezed.dart'; -class GridCellContext { - GridCell cellData; - GridCellCache cellCache; +class GridCellContext { + final GridCell gridCell; + final GridCellCache cellCache; late GridCellCacheKey _cacheKey; + final GridCellDataLoader cellDataLoader; + + final CellListener _cellListener; + final CellService _cellService = CellService(); + final ValueNotifier _cellDataNotifier = ValueNotifier(null); + GridCellContext({ - required this.cellData, + required this.gridCell, required this.cellCache, - }) { + required this.cellDataLoader, + }) : _cellListener = CellListener(rowId: gridCell.rowId, fieldId: gridCell.field.id) { + _cellListener.updateCellNotifier?.addPublishListener((result) { + result.fold( + (notification) => _loadData(), + (err) => Log.error(err), + ); + }); + + _cellListener.start(); + _cacheKey = GridCellCacheKey( objectId: "$hashCode", - fieldId: cellData.field.id, + fieldId: gridCell.field.id, ); } - String get gridId => cellData.gridId; + String get gridId => gridCell.gridId; - String get cellId => cellData.rowId + (cellData.cell?.fieldId ?? ""); + String get rowId => gridCell.rowId; - String get rowId => cellData.rowId; + String get cellId => gridCell.rowId + gridCell.field.id; - String get fieldId => cellData.field.id; + String get fieldId => gridCell.field.id; - FieldType get fieldType => cellData.field.fieldType; + Field get field => gridCell.field; - Field get field => cellData.field; + FieldType get fieldType => gridCell.field.fieldType; GridCellCacheKey get cacheKey => _cacheKey; - T? getCacheData() { - return cellCache.get(cacheKey); + T? getCellData() { + final data = cellCache.get(cacheKey); + if (data == null) { + _loadData(); + } + return data; } - void setCacheData(dynamic data) { + void setCellData(T? data) { cellCache.insert(GridCellCacheData(key: cacheKey, object: data)); } + void saveCellData(String data) { + _cellService.updateCell(gridId: gridId, fieldId: field.id, rowId: rowId, data: data); + } + + void _loadData() { + // It may trigger getCell multiple times. Use cancel operation to fix this. + cellDataLoader.loadData().then((data) { + _cellDataNotifier.value = data; + setCellData(data); + }); + } + void onFieldChanged(VoidCallback callback) { cellCache.addListener(cacheKey, callback); } + void onCellChanged(void Function(T) callback) { + _cellDataNotifier.addListener(() { + final value = _cellDataNotifier.value; + if (value is T) { + callback(value); + } + }); + } + void removeListener() { cellCache.removeListener(cacheKey); } } +abstract class GridCellDataLoader { + Future loadData(); +} + +class DefaultCellDataLoader implements GridCellDataLoader { + final CellService service = CellService(); + final GridCell gridCell; + + DefaultCellDataLoader({ + required this.gridCell, + }); + + @override + Future loadData() { + final fut = service.getCell( + gridId: gridCell.gridId, + fieldId: gridCell.field.id, + rowId: gridCell.rowId, + ); + return fut.then((result) { + return result.fold((data) => data, (err) { + Log.error(err); + return null; + }); + }); + } +} + // key: rowId -typedef CellDataMap = LinkedHashMap; +typedef GridCellMap = LinkedHashMap; class GridCellCacheData { GridCellCacheKey key; @@ -175,41 +246,41 @@ class CellService { } } -class CellCache { - final CellService _cellService; - final HashMap _cellDataMap = HashMap(); +// class CellCache { +// final CellService _cellService; +// final HashMap _cellDataMap = HashMap(); - CellCache() : _cellService = CellService(); +// CellCache() : _cellService = CellService(); - Future> getCellData(GridCell identifier) async { - final cellId = _cellId(identifier); - final Cell? data = _cellDataMap[cellId]; - if (data != null) { - return Future(() => Some(data)); - } +// Future> getCellData(GridCell identifier) async { +// final cellId = _cellId(identifier); +// final Cell? data = _cellDataMap[cellId]; +// if (data != null) { +// return Future(() => Some(data)); +// } - final result = await _cellService.getCell( - gridId: identifier.gridId, - fieldId: identifier.field.id, - rowId: identifier.rowId, - ); +// final result = await _cellService.getCell( +// gridId: identifier.gridId, +// fieldId: identifier.field.id, +// rowId: identifier.rowId, +// ); - return result.fold( - (cell) { - _cellDataMap[_cellId(identifier)] = cell; - return Some(cell); - }, - (err) { - Log.error(err); - return none(); - }, - ); - } +// return result.fold( +// (cell) { +// _cellDataMap[_cellId(identifier)] = cell; +// return Some(cell); +// }, +// (err) { +// Log.error(err); +// return none(); +// }, +// ); +// } - String _cellId(GridCell identifier) { - return "${identifier.rowId}/${identifier.field.id}"; - } -} +// String _cellId(GridCell identifier) { +// return "${identifier.rowId}/${identifier.field.id}"; +// } +// } @freezed class GridCell with _$GridCell { diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart index 5acf532af6..5a36c7f5a8 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart @@ -1,5 +1,3 @@ -import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart'; -import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -9,15 +7,13 @@ import 'cell_service.dart'; part 'checkbox_cell_bloc.freezed.dart'; class CheckboxCellBloc extends Bloc { - final CellService _service; - final CellListener _cellListener; + final GridCellContext _cellContext; CheckboxCellBloc({ required CellService service, - required GridCellContext cellContext, - }) : _service = service, - _cellListener = CellListener(rowId: cellContext.rowId, fieldId: cellContext.fieldId), - super(CheckboxCellState.initial(cellContext.cellData)) { + required GridCellContext cellContext, + }) : _cellContext = cellContext, + super(CheckboxCellState.initial(cellContext)) { on( (event, emit) async { await event.map( @@ -37,42 +33,19 @@ class CheckboxCellBloc extends Bloc { @override Future close() async { - await _cellListener.stop(); return super.close(); } void _startListening() { - _cellListener.updateCellNotifier?.addPublishListener((result) { - result.fold( - (notificationData) async => await _loadCellData(), - (err) => Log.error(err), - ); + _cellContext.onCellChanged((cell) { + if (!isClosed) { + add(CheckboxCellEvent.didReceiveCellUpdate(cell)); + } }); - _cellListener.start(); - } - - Future _loadCellData() async { - final result = await _service.getCell( - gridId: state.cellData.gridId, - fieldId: state.cellData.field.id, - rowId: state.cellData.rowId, - ); - if (isClosed) { - return; - } - result.fold( - (cell) => add(CheckboxCellEvent.didReceiveCellUpdate(cell)), - (err) => Log.error(err), - ); } void _updateCellData() { - _service.updateCell( - gridId: state.cellData.gridId, - fieldId: state.cellData.field.id, - rowId: state.cellData.rowId, - data: !state.isSelected ? "Yes" : "No", - ); + _cellContext.saveCellData(!state.isSelected ? "Yes" : "No"); } } @@ -86,12 +59,11 @@ class CheckboxCellEvent with _$CheckboxCellEvent { @freezed class CheckboxCellState with _$CheckboxCellState { const factory CheckboxCellState({ - required GridCell cellData, required bool isSelected, }) = _CheckboxCellState; - factory CheckboxCellState.initial(GridCell cellData) { - return CheckboxCellState(cellData: cellData, isSelected: _isSelected(cellData.cell)); + factory CheckboxCellState.initial(GridCellContext context) { + return CheckboxCellState(isSelected: _isSelected(context.getCellData())); } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart index 7f5f1fc808..b1eacce788 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart @@ -1,4 +1,3 @@ -import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart'; import 'package:app_flowy/workspace/application/grid/field/field_listener.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell, Field; @@ -10,15 +9,12 @@ import 'cell_service.dart'; part 'date_cell_bloc.freezed.dart'; class DateCellBloc extends Bloc { - final CellService _service; - final CellListener _cellListener; final SingleFieldListener _fieldListener; + final GridCellContext cellContext; - DateCellBloc({required GridCellContext cellContext}) - : _service = CellService(), - _cellListener = CellListener(rowId: cellContext.rowId, fieldId: cellContext.fieldId), - _fieldListener = SingleFieldListener(fieldId: cellContext.fieldId), - super(DateCellState.initial(cellContext.cellData)) { + DateCellBloc({required this.cellContext}) + : _fieldListener = SingleFieldListener(fieldId: cellContext.fieldId), + super(DateCellState.initial(cellContext)) { on( (event, emit) async { event.map( @@ -30,13 +26,11 @@ class DateCellBloc extends Bloc { }, didReceiveCellUpdate: (_DidReceiveCellUpdate value) { emit(state.copyWith( - cellData: state.cellData.copyWith(cell: value.cell), content: value.cell.content, )); }, didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) { emit(state.copyWith(field: value.field)); - _loadCellData(); }, ); }, @@ -45,19 +39,16 @@ class DateCellBloc extends Bloc { @override Future close() async { - await _cellListener.stop(); await _fieldListener.stop(); return super.close(); } void _startListening() { - _cellListener.updateCellNotifier?.addPublishListener((result) { - result.fold( - (notificationData) => _loadCellData(), - (err) => Log.error(err), - ); - }, listenWhen: () => !isClosed); - _cellListener.start(); + cellContext.onCellChanged((cell) { + if (!isClosed) { + add(DateCellEvent.didReceiveCellUpdate(cell)); + } + }); _fieldListener.updateFieldNotifier?.addPublishListener((result) { result.fold( @@ -68,29 +59,9 @@ class DateCellBloc extends Bloc { _fieldListener.start(); } - Future _loadCellData() async { - final result = await _service.getCell( - gridId: state.cellData.gridId, - fieldId: state.cellData.field.id, - rowId: state.cellData.rowId, - ); - if (isClosed) { - return; - } - result.fold( - (cell) => add(DateCellEvent.didReceiveCellUpdate(cell)), - (err) => Log.error(err), - ); - } - void _updateCellData(DateTime day) { final data = day.millisecondsSinceEpoch ~/ 1000; - _service.updateCell( - gridId: state.cellData.gridId, - fieldId: state.cellData.field.id, - rowId: state.cellData.rowId, - data: data.toString(), - ); + cellContext.saveCellData(data.toString()); } } @@ -105,15 +76,13 @@ class DateCellEvent with _$DateCellEvent { @freezed class DateCellState with _$DateCellState { const factory DateCellState({ - required GridCell cellData, required String content, required Field field, DateTime? selectedDay, }) = _DateCellState; - factory DateCellState.initial(GridCell cellData) => DateCellState( - cellData: cellData, - field: cellData.field, - content: cellData.cell?.content ?? "", + factory DateCellState.initial(GridCellContext context) => DateCellState( + field: context.field, + content: context.getCellData()?.content ?? "", ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart index b46d70cc8f..28eca05498 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart @@ -1,4 +1,3 @@ -import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart'; import 'package:app_flowy/workspace/application/grid/field/field_listener.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart'; @@ -10,16 +9,13 @@ import 'cell_service.dart'; part 'number_cell_bloc.freezed.dart'; class NumberCellBloc extends Bloc { - final CellService _service; - final CellListener _cellListener; + final GridCellContext cellContext; final SingleFieldListener _fieldListener; NumberCellBloc({ - required GridCellContext cellContext, - }) : _service = CellService(), - _cellListener = CellListener(rowId: cellContext.rowId, fieldId: cellContext.fieldId), - _fieldListener = SingleFieldListener(fieldId: cellContext.fieldId), - super(NumberCellState.initial(cellContext.cellData)) { + required this.cellContext, + }) : _fieldListener = SingleFieldListener(fieldId: cellContext.fieldId), + super(NumberCellState.initial(cellContext)) { on( (event, emit) async { await event.map( @@ -38,60 +34,31 @@ class NumberCellBloc extends Bloc { } Future _updateCellValue(_UpdateCell value, Emitter emit) async { - final result = await _service.updateCell( - gridId: state.cellData.gridId, - fieldId: state.cellData.field.id, - rowId: state.cellData.rowId, - data: value.text, - ); - result.fold( - (field) => _getCellData(), - (err) => Log.error(err), - ); + cellContext.saveCellData(value.text); + cellContext.reloadCellData(); } @override Future close() async { - await _cellListener.stop(); await _fieldListener.stop(); return super.close(); } void _startListening() { - _cellListener.updateCellNotifier?.addPublishListener((result) { - result.fold( - (notificationData) async { - await _getCellData(); - }, - (err) => Log.error(err), - ); + cellContext.onCellChanged((cell) { + if (!isClosed) { + add(NumberCellEvent.didReceiveCellUpdate(cell)); + } }); - _cellListener.start(); _fieldListener.updateFieldNotifier?.addPublishListener((result) { result.fold( - (field) => _getCellData(), + (field) => cellContext.reloadCellData(), (err) => Log.error(err), ); }); _fieldListener.start(); } - - Future _getCellData() async { - final result = await _service.getCell( - gridId: state.cellData.gridId, - fieldId: state.cellData.field.id, - rowId: state.cellData.rowId, - ); - - if (isClosed) { - return; - } - result.fold( - (cell) => add(NumberCellEvent.didReceiveCellUpdate(cell)), - (err) => Log.error(err), - ); - } } @freezed @@ -104,11 +71,10 @@ class NumberCellEvent with _$NumberCellEvent { @freezed class NumberCellState with _$NumberCellState { const factory NumberCellState({ - required GridCell cellData, required String content, }) = _NumberCellState; - factory NumberCellState.initial(GridCell cellData) { - return NumberCellState(cellData: cellData, content: cellData.cell?.content ?? ""); + factory NumberCellState.initial(GridCellContext context) { + return NumberCellState(content: context.getCellData().cell?.content ?? ""); } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart index 41c496c373..aaac04ee49 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart @@ -1,19 +1,43 @@ -import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; -import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart'; +import 'package:flowy_sdk/log.dart'; +import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; -class SelectOptionService { - SelectOptionService(); +import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart'; - Future> create({ - required String gridId, - required String fieldId, - required String rowId, - required String name, - }) { +import 'cell_service.dart'; + +class SelectOptionCellDataLoader implements GridCellDataLoader { + final SelectOptionService service; + final GridCell gridCell; + SelectOptionCellDataLoader({ + required this.gridCell, + }) : service = SelectOptionService(gridCell: gridCell); + @override + Future loadData() async { + return service.getOpitonContext().then((result) { + return result.fold( + (data) => data, + (err) { + Log.error(err); + return null; + }, + ); + }); + } +} + +class SelectOptionService { + final GridCell gridCell; + SelectOptionService({required this.gridCell}); + + String get gridId => gridCell.gridId; + String get fieldId => gridCell.field.id; + String get rowId => gridCell.rowId; + + Future> create({required String name}) { return TypeOptionService(gridId: gridId, fieldId: fieldId).newOption(name: name).then( (result) { return result.fold( @@ -34,9 +58,6 @@ class SelectOptionService { } Future> update({ - required String gridId, - required String fieldId, - required String rowId, required SelectOption option, }) { final cellIdentifier = CellIdentifierPayload.create() @@ -50,9 +71,6 @@ class SelectOptionService { } Future> delete({ - required String gridId, - required String fieldId, - required String rowId, required SelectOption option, }) { final cellIdentifier = CellIdentifierPayload.create() @@ -67,11 +85,7 @@ class SelectOptionService { return GridEventUpdateSelectOption(payload).send(); } - Future> getOpitonContext({ - required String gridId, - required String fieldId, - required String rowId, - }) { + Future> getOpitonContext() { final payload = CellIdentifierPayload.create() ..gridId = gridId ..fieldId = fieldId @@ -80,12 +94,7 @@ class SelectOptionService { return GridEventGetSelectOptionContext(payload).send(); } - Future> select({ - required String gridId, - required String fieldId, - required String rowId, - required String optionId, - }) { + Future> select({required String optionId}) { final payload = SelectOptionCellChangesetPayload.create() ..gridId = gridId ..fieldId = fieldId @@ -94,12 +103,7 @@ class SelectOptionService { return GridEventUpdateCellSelectOption(payload).send(); } - Future> unSelect({ - required String gridId, - required String fieldId, - required String rowId, - required String optionId, - }) { + Future> unSelect({required String optionId}) { final payload = SelectOptionCellChangesetPayload.create() ..gridId = gridId ..fieldId = fieldId diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/selection_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/selection_cell_bloc.dart index a10757d220..6a4a038ac4 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/selection_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/selection_cell_bloc.dart @@ -1,39 +1,31 @@ import 'dart:async'; - -import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; - -import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart'; import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart'; -import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart'; import 'package:app_flowy/workspace/application/grid/field/field_listener.dart'; part 'selection_cell_bloc.freezed.dart'; class SelectionCellBloc extends Bloc { - final SelectOptionService _service; - final CellListener _cellListener; final SingleFieldListener _fieldListener; - final GridCellContext _cellContext; + final GridCellContext cellContext; SelectionCellBloc({ - required GridCellContext cellContext, - }) : _service = SelectOptionService(), - _cellContext = cellContext, - _cellListener = CellListener(rowId: cellContext.rowId, fieldId: cellContext.fieldId), - _fieldListener = SingleFieldListener(fieldId: cellContext.fieldId), - super(SelectionCellState.initial(cellContext.cellData)) { + required this.cellContext, + }) : _fieldListener = SingleFieldListener(fieldId: cellContext.fieldId), + super(SelectionCellState.initial(cellContext)) { on( (event, emit) async { await event.map( initial: (_InitialCell value) async { _startListening(); - _loadOptions(); }, didReceiveOptions: (_DidReceiveOptions value) { - emit(state.copyWith(options: value.options, selectedOptions: value.selectedOptions)); + emit(state.copyWith( + options: value.options, + selectedOptions: value.selectedOptions, + )); }, ); }, @@ -42,57 +34,22 @@ class SelectionCellBloc extends Bloc { @override Future close() async { - await _cellListener.stop(); await _fieldListener.stop(); - _cellContext.removeListener(); + cellContext.removeListener(); return super.close(); } - void _loadOptions() async { - var selectOptionContext = _cellContext.getCacheData(); - if (selectOptionContext == null) { - final result = await _service.getOpitonContext( - gridId: state.cellData.gridId, - fieldId: state.cellData.field.id, - rowId: state.cellData.rowId, - ); - if (isClosed) { - return; - } - - result.fold( - (newSelectOptionContext) { - _cellContext.setCacheData(newSelectOptionContext); - selectOptionContext = newSelectOptionContext; - }, - (err) => Log.error(err), - ); - } - - add(SelectionCellEvent.didReceiveOptions( - selectOptionContext!.options, - selectOptionContext!.selectOptions, - )); - } - void _startListening() { - _cellListener.updateCellNotifier?.addPublishListener((result) { - result.fold( - (notificationData) => _loadOptions(), - (err) => Log.error(err), - ); + cellContext.onCellChanged((selectOptionContext) { + if (!isClosed) { + add(SelectionCellEvent.didReceiveOptions( + selectOptionContext.options, + selectOptionContext.selectOptions, + )); + } }); - _cellListener.start(); - _cellContext.onFieldChanged(() => _loadOptions()); - - // _fieldListener.updateFieldNotifier?.addPublishListener((result) { - // result.fold( - // (field) => _loadOptions(), - // (err) => Log.error(err), - // ); - // }); - // _fieldListener.start(); + cellContext.onFieldChanged(() => cellContext.reloadCellData()); } } @@ -108,14 +65,16 @@ class SelectionCellEvent with _$SelectionCellEvent { @freezed class SelectionCellState with _$SelectionCellState { const factory SelectionCellState({ - required GridCell cellData, required List options, required List selectedOptions, }) = _SelectionCellState; - factory SelectionCellState.initial(GridCell cellData) => SelectionCellState( - cellData: cellData, - options: [], - selectedOptions: [], - ); + factory SelectionCellState.initial(GridCellContext context) { + final data = context.getCellData(); + + return SelectionCellState( + options: data?.options ?? [], + selectedOptions: data?.selectOptions ?? [], + ); + } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/selection_editor_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/selection_editor_bloc.dart index 8abd379cb9..b7eb32d18e 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/selection_editor_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/selection_editor_bloc.dart @@ -13,28 +13,19 @@ part 'selection_editor_bloc.freezed.dart'; class SelectOptionEditorBloc extends Bloc { final SelectOptionService _selectOptionService; - final SingleFieldListener _fieldListener; - final CellListener _cellListener; + final GridCellContext cellContext; Timer? _delayOperation; SelectOptionEditorBloc({ - required GridCell cellData, - required List options, - required List selectedOptions, - }) : _selectOptionService = SelectOptionService(), - _fieldListener = SingleFieldListener(fieldId: cellData.field.id), - _cellListener = CellListener(rowId: cellData.rowId, fieldId: cellData.field.id), - super(SelectOptionEditorState.initial(cellData, options, selectedOptions)) { + required this.cellContext, + }) : _selectOptionService = SelectOptionService(gridCell: cellContext.gridCell), + super(SelectOptionEditorState.initial(cellContext)) { on( (event, emit) async { await event.map( initial: (_Initial value) async { _startListening(); }, - didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) { - emit(state.copyWith(field: value.field)); - _loadOptions(); - }, didReceiveOptions: (_DidReceiveOptions value) { emit(state.copyWith( options: value.options, @@ -61,26 +52,17 @@ class SelectOptionEditorBloc extends Bloc close() async { _delayOperation?.cancel(); - await _fieldListener.stop(); - await _cellListener.stop(); + cellContext.removeListener(); return super.close(); } void _createOption(String name) async { - final result = await _selectOptionService.create( - gridId: state.gridId, - fieldId: state.field.id, - rowId: state.rowId, - name: name, - ); - result.fold((l) => _loadOptions(), (err) => Log.error(err)); + final result = await _selectOptionService.create(name: name); + result.fold((l) => {}, (err) => Log.error(err)); } void _deleteOption(SelectOption option) async { final result = await _selectOptionService.delete( - gridId: state.gridId, - fieldId: state.field.id, - rowId: state.rowId, option: option, ); @@ -89,9 +71,6 @@ class SelectOptionEditorBloc extends Bloc option.id == optionId); if (hasSelected != null) { - _selectOptionService.unSelect( - gridId: state.gridId, - fieldId: state.field.id, - rowId: state.rowId, - optionId: optionId, - ); + _selectOptionService.unSelect(optionId: optionId); } else { - _selectOptionService.select( - gridId: state.gridId, - fieldId: state.field.id, - rowId: state.rowId, - optionId: optionId, - ); + _selectOptionService.select(optionId: optionId); } } - void _loadOptions() async { - _delayOperation?.cancel(); - _delayOperation = Timer( - const Duration(milliseconds: 1), - () async { - final result = await _selectOptionService.getOpitonContext( - gridId: state.gridId, - fieldId: state.field.id, - rowId: state.rowId, - ); - if (isClosed) { - return; - } + // void _loadOptions() async { + // _delayOperation?.cancel(); + // _delayOperation = Timer( + // const Duration(milliseconds: 1), + // () async { + // final result = await _selectOptionService.getOpitonContext(); + // if (isClosed) { + // return; + // } - result.fold( - (selectOptionContext) => add(SelectOptionEditorEvent.didReceiveOptions( - selectOptionContext.options, - selectOptionContext.selectOptions, - )), - (err) => Log.error(err), - ); - }, - ); - } + // result.fold( + // (selectOptionContext) => add(SelectOptionEditorEvent.didReceiveOptions( + // selectOptionContext.options, + // selectOptionContext.selectOptions, + // )), + // (err) => Log.error(err), + // ); + // }, + // ); + // } void _startListening() { - _cellListener.updateCellNotifier?.addPublishListener((result) { - result.fold( - (notificationData) => _loadOptions(), - (err) => Log.error(err), - ); + cellContext.onCellChanged((selectOptionContext) { + if (!isClosed) { + add(SelectOptionEditorEvent.didReceiveOptions( + selectOptionContext.options, + selectOptionContext.selectOptions, + )); + } }); - _cellListener.start(); - _fieldListener.updateFieldNotifier?.addPublishListener((result) { - result.fold( - (field) => add(SelectOptionEditorEvent.didReceiveFieldUpdate(field)), - (err) => Log.error(err), - ); - }, listenWhen: () => !isClosed); - _fieldListener.start(); + cellContext.onFieldChanged(() => cellContext.reloadCellData()); } } @freezed class SelectOptionEditorEvent with _$SelectOptionEditorEvent { const factory SelectOptionEditorEvent.initial() = _Initial; - const factory SelectOptionEditorEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate; const factory SelectOptionEditorEvent.didReceiveOptions( List options, List selectedOptions) = _DidReceiveOptions; const factory SelectOptionEditorEvent.newOption(String optionName) = _NewOption; @@ -176,24 +135,15 @@ class SelectOptionEditorEvent with _$SelectOptionEditorEvent { @freezed class SelectOptionEditorState with _$SelectOptionEditorState { const factory SelectOptionEditorState({ - required String gridId, - required Field field, - required String rowId, required List options, required List selectedOptions, }) = _SelectOptionEditorState; - factory SelectOptionEditorState.initial( - GridCell cellData, - List options, - List selectedOptions, - ) { + factory SelectOptionEditorState.initial(GridCellContext context) { + final data = context.getCellData(); return SelectOptionEditorState( - gridId: cellData.gridId, - field: cellData.field, - rowId: cellData.rowId, - options: options, - selectedOptions: selectedOptions, + options: data?.options ?? [], + selectedOptions: data?.selectOptions ?? [], ); } } diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart index 94cb749eb2..a051eca2d5 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart @@ -16,7 +16,7 @@ class TextCellBloc extends Bloc { required GridCellContext cellContext, }) : _service = CellService(), _cellListener = CellListener(rowId: cellContext.rowId, fieldId: cellContext.fieldId), - super(TextCellState.initial(cellContext.cellData)) { + super(TextCellState.initial(cellContext.gridCell)) { on( (event, emit) async { await event.map( diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart index fd83974d68..08634774ba 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart @@ -57,17 +57,17 @@ class RowBloc extends Bloc { class RowEvent with _$RowEvent { const factory RowEvent.initial() = _InitialRow; const factory RowEvent.createRow() = _CreateRow; - const factory RowEvent.didReceiveCellDatas(CellDataMap cellData) = _DidReceiveCellDatas; + const factory RowEvent.didReceiveCellDatas(GridCellMap cellData) = _DidReceiveCellDatas; } @freezed class RowState with _$RowState { const factory RowState({ required GridRow rowData, - required CellDataMap cellDataMap, + required GridCellMap cellDataMap, }) = _RowState; - factory RowState.initial(GridRow rowData, CellDataMap cellDataMap) => RowState( + factory RowState.initial(GridRow rowData, GridCellMap cellDataMap) => RowState( rowData: rowData, cellDataMap: cellDataMap, ); diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart index 57895888f0..96ffac61ab 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart @@ -24,7 +24,7 @@ class RowDetailBloc extends Bloc { _loadCellData(); }, didReceiveCellDatas: (_DidReceiveCellDatas value) { - emit(state.copyWith(cellDatas: value.cellDatas)); + emit(state.copyWith(gridCells: value.gridCells)); }, ); }, @@ -58,16 +58,16 @@ class RowDetailBloc extends Bloc { @freezed class RowDetailEvent with _$RowDetailEvent { const factory RowDetailEvent.initial() = _Initial; - const factory RowDetailEvent.didReceiveCellDatas(List cellDatas) = _DidReceiveCellDatas; + const factory RowDetailEvent.didReceiveCellDatas(List gridCells) = _DidReceiveCellDatas; } @freezed class RowDetailState with _$RowDetailState { const factory RowDetailState({ - required List cellDatas, + required List gridCells, }) = _RowDetailState; factory RowDetailState.initial() => RowDetailState( - cellDatas: List.empty(), + gridCells: List.empty(), ); } diff --git a/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart index f3706d2782..57f299f2ab 100644 --- a/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart +++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart @@ -84,7 +84,7 @@ class GridRowCache { RowUpdateCallback addRowListener({ required String rowId, - void Function(CellDataMap)? onUpdated, + void Function(GridCellMap)? onUpdated, bool Function()? listenWhen, }) { listenrHandler() { @@ -99,7 +99,7 @@ class GridRowCache { notify() { final row = _rowNotifier.rowDataWithId(rowId); if (row != null) { - final CellDataMap cellDataMap = _makeCellDataMap(rowId, row); + final GridCellMap cellDataMap = _makeCellDataMap(rowId, row); onUpdated(cellDataMap); } } @@ -118,8 +118,8 @@ class GridRowCache { return listenrHandler; } - CellDataMap _makeCellDataMap(String rowId, Row? row) { - var cellDataMap = CellDataMap.new(); + GridCellMap _makeCellDataMap(String rowId, Row? row) { + var cellDataMap = GridCellMap.new(); for (final field in _fieldDelegate.fields) { if (field.visibility) { cellDataMap[field.id] = GridCell( @@ -137,7 +137,7 @@ class GridRowCache { _rowNotifier.removeListener(callback); } - CellDataMap loadCellData(String rowId) { + GridCellMap loadCellData(String rowId) { final Row? data = _rowNotifier.rowDataWithId(rowId); if (data == null) { final payload = RowIdentifierPayload.create() diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart index e95fcd9a53..195449f4f9 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart @@ -1,6 +1,8 @@ import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart'; +import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; -import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType; +import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Cell, FieldType; +import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flutter/widgets.dart'; import 'checkbox_cell.dart'; import 'date_cell.dart'; @@ -8,22 +10,47 @@ import 'number_cell.dart'; import 'selection_cell/selection_cell.dart'; import 'text_cell.dart'; -GridCellWidget buildGridCell(GridCellContext cellContext, {GridCellStyle? style}) { - final key = ValueKey(cellContext.cellId); - final fieldType = cellContext.cellData.field.fieldType; - switch (fieldType) { +GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {GridCellStyle? style}) { + final key = ValueKey(gridCell.rowId + gridCell.field.id); + + final cellContext = makeCellContext(gridCell, cellCache); + + switch (gridCell.field.fieldType) { case FieldType.Checkbox: return CheckboxCell(cellContext: cellContext, key: key); case FieldType.DateTime: return DateCell(cellContext: cellContext, key: key); case FieldType.MultiSelect: - return MultiSelectCell(cellContext: cellContext, style: style, key: key); + return MultiSelectCell(cellContext: cellContext as GridCellContext, style: style, key: key); case FieldType.Number: return NumberCell(cellContext: cellContext, key: key); case FieldType.RichText: return GridTextCell(cellContext: cellContext, style: style, key: key); case FieldType.SingleSelect: - return SingleSelectCell(cellContext: cellContext, style: style, key: key); + return SingleSelectCell(cellContext: cellContext as GridCellContext, style: style, key: key); + default: + throw UnimplementedError; + } +} + +GridCellContext makeCellContext(GridCell gridCell, GridCellCache cellCache) { + switch (gridCell.field.fieldType) { + case FieldType.Checkbox: + case FieldType.DateTime: + case FieldType.Number: + case FieldType.RichText: + return GridCellContext( + gridCell: gridCell, + cellCache: cellCache, + cellDataLoader: DefaultCellDataLoader(gridCell: gridCell), + ); + case FieldType.MultiSelect: + case FieldType.SingleSelect: + return GridCellContext( + gridCell: gridCell, + cellCache: cellCache, + cellDataLoader: SelectOptionCellDataLoader(gridCell: gridCell), + ); default: throw UnimplementedError; } diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart index 41ea4b2002..5d1019d32c 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_cell.dart @@ -3,6 +3,7 @@ import 'package:app_flowy/workspace/application/grid/prelude.dart'; import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -18,7 +19,7 @@ class SelectOptionCellStyle extends GridCellStyle { } class SingleSelectCell extends GridCellWidget { - final GridCellContext cellContext; + final GridCellContext cellContext; late final SelectOptionCellStyle? cellStyle; SingleSelectCell({ @@ -66,9 +67,7 @@ class _SingleSelectCellState extends State { widget.onFocus.value = true; SelectOptionCellEditor.show( context, - state.cellData, - state.options, - state.selectedOptions, + widget.cellContext, () => widget.onFocus.value = false, ); }, @@ -89,7 +88,7 @@ class _SingleSelectCellState extends State { //---------------------------------------------------------------- class MultiSelectCell extends GridCellWidget { - final GridCellContext cellContext; + final GridCellContext cellContext; late final SelectOptionCellStyle? cellStyle; MultiSelectCell({ @@ -135,9 +134,7 @@ class _MultiSelectCellState extends State { widget.onFocus.value = true; SelectOptionCellEditor.show( context, - state.cellData, - state.options, - state.selectedOptions, + widget.cellContext, () => widget.onFocus.value = false, ); }, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart index 2f4b54cc9b..13a4a590c0 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/selection_cell/selection_editor.dart @@ -26,15 +26,11 @@ import 'text_field.dart'; const double _editorPannelWidth = 300; class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate { - final GridCell cellData; - final List options; - final List selectedOptions; + final GridCellContext cellContext; final VoidCallback onDismissed; const SelectOptionCellEditor({ - required this.cellData, - required this.options, - required this.selectedOptions, + required this.cellContext, required this.onDismissed, Key? key, }) : super(key: key); @@ -43,9 +39,7 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate { Widget build(BuildContext context) { return BlocProvider( create: (context) => SelectOptionEditorBloc( - cellData: cellData, - options: options, - selectedOptions: selectedOptions, + cellContext: cellContext, )..add(const SelectOptionEditorEvent.initial()), child: BlocBuilder( builder: (context, state) { @@ -67,16 +61,12 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate { static void show( BuildContext context, - GridCell cellData, - List options, - List selectedOptions, + GridCellContext cellContext, VoidCallback onDismissed, ) { SelectOptionCellEditor.remove(context); final editor = SelectOptionCellEditor( - cellData: cellData, - options: options, - selectedOptions: selectedOptions, + cellContext: cellContext, onDismissed: onDismissed, ); diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart index f5ebba8921..62cdc5c53f 100755 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart @@ -170,22 +170,17 @@ class _RowCells extends StatelessWidget { ); } - List _makeCells(CellDataMap cellDataMap) { - return cellDataMap.values.map( - (cellData) { + List _makeCells(GridCellMap gridCellMap) { + return gridCellMap.values.map( + (gridCell) { Widget? expander; - if (cellData.field.isPrimary) { + if (gridCell.field.isPrimary) { expander = _CellExpander(onExpand: onExpand); } - final cellContext = GridCellContext( - cellData: cellData, - cellCache: cellCache, - ); - return CellContainer( - width: cellData.field.width.toDouble(), - child: buildGridCell(cellContext), + width: gridCell.field.width.toDouble(), + child: buildGridCellWidget(gridCell, cellCache), expander: expander, ); }, diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart index e61516ba4f..ae82033320 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_detail.dart @@ -84,7 +84,7 @@ class _PropertyList extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder( - buildWhen: (previous, current) => previous.cellDatas != current.cellDatas, + buildWhen: (previous, current) => previous.gridCells != current.gridCells, builder: (context, state) { return ScrollbarListStack( axis: Axis.vertical, @@ -92,13 +92,12 @@ class _PropertyList extends StatelessWidget { barSize: GridSize.scrollBarSize, child: ListView.separated( controller: _scrollController, - itemCount: state.cellDatas.length, + itemCount: state.gridCells.length, itemBuilder: (BuildContext context, int index) { - final cellContext = GridCellContext( - cellData: state.cellDatas[index], + return _RowDetailCell( + gridCell: state.gridCells[index], cellCache: cellCache, ); - return _RowDetailCell(cellContext: cellContext); }, separatorBuilder: (BuildContext context, int index) { return const VSpace(2); @@ -111,16 +110,22 @@ class _PropertyList extends StatelessWidget { } class _RowDetailCell extends StatelessWidget { - final GridCellContext cellContext; - const _RowDetailCell({required this.cellContext, Key? key}) : super(key: key); + final GridCell gridCell; + final GridCellCache cellCache; + const _RowDetailCell({ + required this.gridCell, + required this.cellCache, + Key? key, + }) : super(key: key); @override Widget build(BuildContext context) { final theme = context.watch(); - final cell = buildGridCell( - cellContext, - style: _buildCellStyle(theme, cellContext.fieldType), + final cell = buildGridCellWidget( + gridCell, + cellCache, + style: _buildCellStyle(theme, gridCell.field.fieldType), ); return SizedBox( height: 36, @@ -130,7 +135,7 @@ class _RowDetailCell extends StatelessWidget { children: [ SizedBox( width: 150, - child: FieldCellButton(field: cellContext.field, onTap: () => _showFieldEditor(context)), + child: FieldCellButton(field: gridCell.field, onTap: () => _showFieldEditor(context)), ), const HSpace(10), Expanded( @@ -146,10 +151,10 @@ class _RowDetailCell extends StatelessWidget { void _showFieldEditor(BuildContext context) { FieldEditor( - gridId: cellContext.gridId, + gridId: gridCell.gridId, fieldContextLoader: FieldContextLoaderAdaptor( - gridId: cellContext.gridId, - field: cellContext.field, + gridId: gridCell.gridId, + field: gridCell.field, ), ).show(context); }