Browse Source

chore: clone cell context

appflowy 3 năm trước cách đây
mục cha
commit
8b4c46f75b

+ 97 - 49
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service.dart

@@ -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),
-        );
-      });
+  VoidCallback? startListening({required void Function(T) onCellChanged}) {
+    if (isListening) {
+      Log.error("Already started. It seems like you should call clone first");
+      return null;
+    }
 
-      if (cellDataLoader.reloadOnFieldChanged) {
-        cellCache.addListener(cacheKey, () => reloadCellData());
-      }
+    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!);
     }
 
-    _cellDataNotifier.addListener(() {
+    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;
       }
     }

+ 7 - 1
frontend/app_flowy/lib/workspace/application/grid/cell/checkbox_cell_bloc.dart

@@ -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));
       }

+ 6 - 1
frontend/app_flowy/lib/workspace/application/grid/cell/date_cell_bloc.dart

@@ -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));

+ 6 - 2
frontend/app_flowy/lib/workspace/application/grid/cell/number_cell_bloc.dart

@@ -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));

+ 1 - 4
frontend/app_flowy/lib/workspace/application/grid/cell/select_option_service.dart

@@ -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 {

+ 6 - 1
frontend/app_flowy/lib/workspace/application/grid/cell/selection_cell_bloc.dart

@@ -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(

+ 6 - 1
frontend/app_flowy/lib/workspace/application/grid/cell/selection_editor_bloc.dart

@@ -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(

+ 6 - 1
frontend/app_flowy/lib/workspace/application/grid/cell/text_cell_bloc.dart

@@ -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));

+ 5 - 0
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart

@@ -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,

+ 17 - 2
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/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();
   }