chore: clone cell context

This commit is contained in:
appflowy 2022-04-25 21:20:08 +08:00
parent 0ac17fa6df
commit 8b4c46f75b
10 changed files with 158 additions and 63 deletions

View File

@ -11,13 +11,13 @@ import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.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';
import 'package:equatable/equatable.dart';
part 'cell_service.freezed.dart';
typedef GridDefaultCellContext = GridCellContext<Cell>;
typedef GridSelectOptionCellContext = GridCellContext<SelectOptionContext>;
class GridCellContext<T> {
class GridCellContext<T> extends Equatable {
final GridCell gridCell;
final GridCellCache cellCache;
final GridCellCacheKey _cacheKey;
@ -27,6 +27,7 @@ class GridCellContext<T> {
late final CellListener _cellListener;
late final ValueNotifier<T?> _cellDataNotifier;
bool isListening = false;
VoidCallback? _onFieldChangedFn;
Timer? _delayOperation;
GridCellContext({
@ -35,6 +36,14 @@ class GridCellContext<T> {
required this.cellDataLoader,
}) : _cacheKey = GridCellCacheKey(objectId: gridCell.rowId, fieldId: gridCell.field.id);
GridCellContext<T> clone() {
return GridCellContext(
gridCell: gridCell,
cellDataLoader: cellDataLoader,
cellCache: cellCache,
);
}
String get gridId => gridCell.gridId;
String get rowId => gridCell.rowId;
@ -49,29 +58,47 @@ class GridCellContext<T> {
GridCellCacheKey get cacheKey => _cacheKey;
void startListening({required void Function(T) onCellChanged}) {
if (!isListening) {
isListening = true;
_cellDataNotifier = ValueNotifier(cellCache.get(cacheKey));
_cellListener = CellListener(rowId: gridCell.rowId, fieldId: gridCell.field.id);
_cellListener.start(onCellChanged: (result) {
result.fold(
(_) => _loadData(),
(err) => Log.error(err),
);
});
if (cellDataLoader.reloadOnFieldChanged) {
cellCache.addListener(cacheKey, () => reloadCellData());
}
VoidCallback? startListening({required void Function(T) onCellChanged}) {
if (isListening) {
Log.error("Already started. It seems like you should call clone first");
return null;
}
_cellDataNotifier.addListener(() {
isListening = true;
_cellDataNotifier = ValueNotifier(cellCache.get(cacheKey));
_cellListener = CellListener(rowId: gridCell.rowId, fieldId: gridCell.field.id);
_cellListener.start(onCellChanged: (result) {
result.fold(
(_) => _loadData(),
(err) => Log.error(err),
);
});
if (cellDataLoader.reloadOnFieldChanged) {
_onFieldChangedFn = () {
Log.info("reloadCellData ");
_loadData();
};
cellCache.addListener(cacheKey, _onFieldChangedFn!);
}
onCellChangedFn() {
final value = _cellDataNotifier.value;
if (value is T) {
onCellChanged(value);
}
});
if (cellDataLoader.reloadOnCellChanged) {
_loadData();
}
}
_cellDataNotifier.addListener(onCellChangedFn);
return onCellChangedFn;
}
void removeListener(VoidCallback fn) {
_cellDataNotifier.removeListener(fn);
}
T? getCellData() {
@ -82,47 +109,56 @@ class GridCellContext<T> {
return 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).then((result) {
result.fold((l) => null, (err) => Log.error(err));
});
}
void reloadCellData() {
_loadData();
}
void _loadData() {
_delayOperation?.cancel();
_delayOperation = Timer(const Duration(milliseconds: 10), () {
cellDataLoader.loadData().then((data) {
_cellDataNotifier.value = data;
setCellData(data);
cellCache.insert(GridCellCacheData(key: cacheKey, object: data));
});
});
}
void dispose() {
_delayOperation?.cancel();
if (_onFieldChangedFn != null) {
cellCache.removeListener(cacheKey, _onFieldChangedFn!);
_onFieldChangedFn = null;
}
}
@override
List<Object> get props => [cellCache.get(cacheKey) ?? "", cellId];
}
abstract class GridCellDataLoader<T> {
Future<T?> loadData();
bool get reloadOnFieldChanged => true;
bool get reloadOnCellChanged => false;
}
class DefaultCellDataLoader implements GridCellDataLoader<Cell> {
abstract class GridCellDataConfig {
bool get reloadOnFieldChanged => true;
bool get reloadOnCellChanged => false;
}
class DefaultCellDataLoader extends GridCellDataLoader<Cell> {
final CellService service = CellService();
final GridCell gridCell;
@override
final bool reloadOnCellChanged;
DefaultCellDataLoader({
required this.gridCell,
this.reloadOnCellChanged = false,
});
@override
@ -139,9 +175,6 @@ class DefaultCellDataLoader implements GridCellDataLoader<Cell> {
});
});
}
@override
bool get reloadOnFieldChanged => true;
}
// key: rowId
@ -174,51 +207,63 @@ class GridCellCache {
final GridCellFieldDelegate fieldDelegate;
/// fieldId: {objectId: callback}
final Map<String, Map<String, VoidCallback>> _cellListenerByFieldId = {};
final Map<String, Map<String, List<VoidCallback>>> _listenerByFieldId = {};
/// fieldId: {cacheKey: cacheData}
final Map<String, Map<String, dynamic>> _cellCacheByFieldId = {};
final Map<String, Map<String, dynamic>> _cellDataByFieldId = {};
GridCellCache({
required this.gridId,
required this.fieldDelegate,
}) {
fieldDelegate.onFieldChanged((fieldId) {
_cellCacheByFieldId.remove(fieldId);
final map = _cellListenerByFieldId[fieldId];
_cellDataByFieldId.remove(fieldId);
final map = _listenerByFieldId[fieldId];
if (map != null) {
for (final callback in map.values) {
callback();
for (final callbacks in map.values) {
for (final callback in callbacks) {
callback();
}
}
}
});
}
void addListener(GridCellCacheKey cacheKey, VoidCallback callback) {
var map = _cellListenerByFieldId[cacheKey.fieldId];
var map = _listenerByFieldId[cacheKey.fieldId];
if (map == null) {
_cellListenerByFieldId[cacheKey.fieldId] = {};
map = _cellListenerByFieldId[cacheKey.fieldId];
_listenerByFieldId[cacheKey.fieldId] = {};
map = _listenerByFieldId[cacheKey.fieldId];
map![cacheKey.objectId] = [callback];
} else {
var objects = map[cacheKey.objectId];
if (objects == null) {
map[cacheKey.objectId] = [callback];
} else {
objects.add(callback);
}
}
map![cacheKey.objectId] = callback;
}
void removeListener(GridCellCacheKey cacheKey) {
_cellListenerByFieldId[cacheKey.fieldId]?.remove(cacheKey.objectId);
void removeListener(GridCellCacheKey cacheKey, VoidCallback fn) {
var callbacks = _listenerByFieldId[cacheKey.fieldId]?[cacheKey.objectId];
final index = callbacks?.indexWhere((callback) => callback == fn);
if (index != null && index != -1) {
callbacks?.removeAt(index);
}
}
void insert<T extends GridCellCacheData>(T item) {
var map = _cellCacheByFieldId[item.key.fieldId];
var map = _cellDataByFieldId[item.key.fieldId];
if (map == null) {
_cellCacheByFieldId[item.key.fieldId] = {};
map = _cellCacheByFieldId[item.key.fieldId];
_cellDataByFieldId[item.key.fieldId] = {};
map = _cellDataByFieldId[item.key.fieldId];
}
map![item.key.objectId] = item.object;
}
T? get<T>(GridCellCacheKey key) {
final map = _cellCacheByFieldId[key.fieldId];
final map = _cellDataByFieldId[key.fieldId];
if (map == null) {
return null;
} else {
@ -226,7 +271,10 @@ class GridCellCache {
if (object is T) {
return object;
} else {
Log.error("Cache data type does not match the cache data type");
if (object != null) {
Log.error("Cache data type does not match the cache data type");
}
return null;
}
}

View File

@ -8,6 +8,7 @@ part 'checkbox_cell_bloc.freezed.dart';
class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
final GridDefaultCellContext cellContext;
void Function()? _onCellChangedFn;
CheckboxCellBloc({
required CellService service,
@ -32,12 +33,17 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellContext.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellContext.dispose();
return super.close();
}
void _startListening() {
cellContext.startListening(onCellChanged: ((cell) {
_onCellChangedFn = cellContext.startListening(onCellChanged: ((cell) {
if (!isClosed) {
add(CheckboxCellEvent.didReceiveCellUpdate(cell));
}

View File

@ -8,6 +8,7 @@ part 'date_cell_bloc.freezed.dart';
class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
final GridDefaultCellContext cellContext;
void Function()? _onCellChangedFn;
DateCellBloc({required this.cellContext}) : super(DateCellState.initial(cellContext)) {
on<DateCellEvent>(
@ -34,12 +35,16 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellContext.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellContext.dispose();
return super.close();
}
void _startListening() {
cellContext.startListening(
_onCellChangedFn = cellContext.startListening(
onCellChanged: ((cell) {
if (!isClosed) {
add(DateCellEvent.didReceiveCellUpdate(cell));

View File

@ -8,6 +8,7 @@ part 'number_cell_bloc.freezed.dart';
class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
final GridDefaultCellContext cellContext;
void Function()? _onCellChangedFn;
NumberCellBloc({
required this.cellContext,
@ -31,17 +32,20 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
Future<void> _updateCellValue(_UpdateCell value, Emitter<NumberCellState> emit) async {
cellContext.saveCellData(value.text);
cellContext.reloadCellData();
}
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellContext.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellContext.dispose();
return super.close();
}
void _startListening() {
cellContext.startListening(
_onCellChangedFn = cellContext.startListening(
onCellChanged: ((cell) {
if (!isClosed) {
add(NumberCellEvent.didReceiveCellUpdate(cell));

View File

@ -9,7 +9,7 @@ import 'package:app_flowy/workspace/application/grid/field/type_option/type_opti
import 'cell_service.dart';
class SelectOptionCellDataLoader implements GridCellDataLoader<SelectOptionContext> {
class SelectOptionCellDataLoader extends GridCellDataLoader<SelectOptionContext> {
final SelectOptionService service;
final GridCell gridCell;
SelectOptionCellDataLoader({
@ -27,9 +27,6 @@ class SelectOptionCellDataLoader implements GridCellDataLoader<SelectOptionConte
);
});
}
@override
bool get reloadOnFieldChanged => true;
}
class SelectOptionService {

View File

@ -8,6 +8,7 @@ part 'selection_cell_bloc.freezed.dart';
class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
final GridSelectOptionCellContext cellContext;
void Function()? _onCellChangedFn;
SelectionCellBloc({
required this.cellContext,
@ -31,12 +32,16 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellContext.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellContext.dispose();
return super.close();
}
void _startListening() {
cellContext.startListening(
_onCellChangedFn = cellContext.startListening(
onCellChanged: ((selectOptionContext) {
if (!isClosed) {
add(SelectionCellEvent.didReceiveOptions(

View File

@ -11,6 +11,7 @@ part 'selection_editor_bloc.freezed.dart';
class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
final SelectOptionService _selectOptionService;
final GridSelectOptionCellContext cellContext;
void Function()? _onCellChangedFn;
SelectOptionEditorBloc({
required this.cellContext,
@ -47,6 +48,10 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellContext.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellContext.dispose();
return super.close();
}
@ -82,7 +87,7 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
}
void _startListening() {
cellContext.startListening(
_onCellChangedFn = cellContext.startListening(
onCellChanged: ((selectOptionContext) {
if (!isClosed) {
add(SelectOptionEditorEvent.didReceiveOptions(

View File

@ -8,6 +8,7 @@ part 'text_cell_bloc.freezed.dart';
class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
final GridDefaultCellContext cellContext;
void Function()? _onCellChangedFn;
TextCellBloc({
required this.cellContext,
}) : super(TextCellState.initial(cellContext)) {
@ -36,12 +37,16 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellContext.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellContext.dispose();
return super.close();
}
void _startListening() {
cellContext.startListening(
_onCellChangedFn = cellContext.startListening(
onCellChanged: ((cell) {
if (!isClosed) {
add(TextCellEvent.didReceiveCellUpdate(cell));

View File

@ -37,6 +37,11 @@ GridCellContext makeCellContext(GridCell gridCell, GridCellCache cellCache) {
case FieldType.Checkbox:
case FieldType.DateTime:
case FieldType.Number:
return GridDefaultCellContext(
gridCell: gridCell,
cellCache: cellCache,
cellDataLoader: DefaultCellDataLoader(gridCell: gridCell, reloadOnCellChanged: true),
);
case FieldType.RichText:
return GridDefaultCellContext(
gridCell: gridCell,

View File

@ -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/log.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -42,6 +43,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
@override
void initState() {
Log.info("init widget $hashCode");
_cellBloc = getIt<SelectionCellBloc>(param1: widget.cellContext)..add(const SelectionCellEvent.initial());
super.initState();
}
@ -49,7 +51,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
Log.info("build widget $hashCode");
return BlocProvider.value(
value: _cellBloc,
child: BlocBuilder<SelectionCellBloc, SelectionCellState>(
@ -66,7 +68,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
widget.onFocus.value = true;
SelectOptionCellEditor.show(
context,
widget.cellContext,
widget.cellContext.clone(),
() => widget.onFocus.value = false,
);
},
@ -78,8 +80,21 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
);
}
@override
void didUpdateWidget(covariant SingleSelectCell oldWidget) {
if (oldWidget.cellContext != widget.cellContext) {
// setState(() {
// });
// _cellBloc = getIt<SelectionCellBloc>(param1: widget.cellContext)..add(const SelectionCellEvent.initial());
Log.info("did update widget $hashCode");
}
super.didUpdateWidget(oldWidget);
}
@override
Future<void> dispose() async {
Log.info("dispose widget $hashCode");
_cellBloc.close();
super.dispose();
}