mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: clone cell context
This commit is contained in:
parent
0ac17fa6df
commit
8b4c46f75b
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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));
|
||||
|
@ -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 {
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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));
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user