Quellcode durchsuchen

chore: reload row when fields were changed

appflowy vor 3 Jahren
Ursprung
Commit
518d11162b

+ 7 - 0
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service.dart

@@ -365,4 +365,11 @@ class GridCell with _$GridCell {
     required Field field,
     Cell? cell,
   }) = _GridCell;
+
+  // ignore: unused_element
+  const GridCell._();
+
+  String cellId() {
+    return rowId + field.id + "${field.fieldType}";
+  }
 }

+ 30 - 5
frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart

@@ -1,5 +1,7 @@
 import 'dart:collection';
 import 'package:app_flowy/workspace/application/grid/cell/cell_service.dart';
+import 'package:equatable/equatable.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
@@ -28,7 +30,13 @@ class RowBloc extends Bloc<RowEvent, RowState> {
             _rowService.createRow();
           },
           didReceiveCellDatas: (_DidReceiveCellDatas value) async {
-            emit(state.copyWith(cellDataMap: value.cellData));
+            final fields = value.gridCellMap.values.map((e) => CellSnapshot(e.field)).toList();
+            final snapshots = UnmodifiableListView(fields);
+            emit(state.copyWith(
+              gridCellMap: value.gridCellMap,
+              snapshots: snapshots,
+              changeReason: value.reason,
+            ));
           },
         );
       },
@@ -47,7 +55,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
   Future<void> _startListening() async {
     _rowListenFn = _rowCache.addRowListener(
       rowId: state.rowData.rowId,
-      onUpdated: (cellDatas) => add(RowEvent.didReceiveCellDatas(cellDatas)),
+      onUpdated: (cellDatas, reason) => add(RowEvent.didReceiveCellDatas(cellDatas, reason)),
       listenWhen: () => !isClosed,
     );
   }
@@ -57,18 +65,35 @@ class RowBloc extends Bloc<RowEvent, RowState> {
 class RowEvent with _$RowEvent {
   const factory RowEvent.initial() = _InitialRow;
   const factory RowEvent.createRow() = _CreateRow;
-  const factory RowEvent.didReceiveCellDatas(GridCellMap cellData) = _DidReceiveCellDatas;
+  const factory RowEvent.didReceiveCellDatas(GridCellMap gridCellMap, GridRowChangeReason reason) =
+      _DidReceiveCellDatas;
 }
 
 @freezed
 class RowState with _$RowState {
   const factory RowState({
     required GridRow rowData,
-    required GridCellMap cellDataMap,
+    required GridCellMap gridCellMap,
+    required UnmodifiableListView<CellSnapshot> snapshots,
+    GridRowChangeReason? changeReason,
   }) = _RowState;
 
   factory RowState.initial(GridRow rowData, GridCellMap cellDataMap) => RowState(
         rowData: rowData,
-        cellDataMap: cellDataMap,
+        gridCellMap: cellDataMap,
+        snapshots: UnmodifiableListView(cellDataMap.values.map((e) => CellSnapshot(e.field)).toList()),
       );
 }
+
+class CellSnapshot extends Equatable {
+  final Field _field;
+
+  const CellSnapshot(Field field) : _field = field;
+
+  @override
+  List<Object?> get props => [
+        _field.id,
+        _field.fieldType,
+        _field.visibility,
+      ];
+}

+ 1 - 1
frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart

@@ -42,7 +42,7 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
   Future<void> _startListening() async {
     _rowListenFn = _rowCache.addRowListener(
       rowId: rowData.rowId,
-      onUpdated: (cellDatas) => add(RowDetailEvent.didReceiveCellDatas(cellDatas.values.toList())),
+      onUpdated: (cellDatas, reason) => add(RowDetailEvent.didReceiveCellDatas(cellDatas.values.toList())),
       listenWhen: () => !isClosed,
     );
   }

+ 3 - 3
frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart

@@ -83,7 +83,7 @@ class GridRowCache {
 
   RowUpdateCallback addRowListener({
     required String rowId,
-    void Function(GridCellMap)? onUpdated,
+    void Function(GridCellMap, GridRowChangeReason)? onUpdated,
     bool Function()? listenWhen,
   }) {
     listenrHandler() async {
@@ -99,7 +99,7 @@ class GridRowCache {
         final row = _rowsNotifier.rowDataWithId(rowId);
         if (row != null) {
           final GridCellMap cellDataMap = _makeGridCells(rowId, row);
-          onUpdated(cellDataMap);
+          onUpdated(cellDataMap, _rowsNotifier._changeReason);
         }
       }
 
@@ -339,7 +339,7 @@ class GridRow with _$GridRow {
   const factory GridRow({
     required String gridId,
     required String rowId,
-    required List<Field> fields,
+    required UnmodifiableListView<Field> fields,
     required double height,
     Row? data,
   }) = _GridRow;

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

@@ -15,7 +15,7 @@ import 'selection_cell/selection_cell.dart';
 import 'text_cell.dart';
 
 GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {GridCellStyle? style}) {
-  final key = ValueKey(gridCell.rowId + gridCell.field.id);
+  final key = ValueKey(gridCell.cellId());
 
   final cellContextBuilder = GridCellContextBuilder(gridCell: gridCell, cellCache: cellCache);
 
@@ -82,6 +82,25 @@ class GridCellRequestFocusNotifier extends ChangeNotifier {
 
 abstract class GridCellStyle {}
 
+class CellSingleFocusNode extends FocusNode {
+  VoidCallback? _listener;
+
+  void setSingleListener(VoidCallback listener) {
+    if (_listener != null) {
+      removeListener(_listener!);
+    }
+
+    _listener = listener;
+    super.addListener(listener);
+  }
+
+  void removeSingleListener() {
+    if (_listener != null) {
+      removeListener(_listener!);
+    }
+  }
+}
+
 class CellStateNotifier extends ChangeNotifier {
   bool _isFocus = false;
   bool _onEnter = false;

+ 20 - 6
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart

@@ -22,7 +22,7 @@ class NumberCell extends GridCellWidget {
 class _NumberCellState extends State<NumberCell> {
   late NumberCellBloc _cellBloc;
   late TextEditingController _controller;
-  late FocusNode _focusNode;
+  late CellSingleFocusNode _focusNode;
   Timer? _delayOperation;
 
   @override
@@ -30,11 +30,8 @@ class _NumberCellState extends State<NumberCell> {
     final cellContext = widget.cellContextBuilder.build();
     _cellBloc = getIt<NumberCellBloc>(param1: cellContext)..add(const NumberCellEvent.initial());
     _controller = TextEditingController(text: _cellBloc.state.content);
-    _focusNode = FocusNode();
-    _focusNode.addListener(() {
-      widget.onFocus.value = _focusNode.hasFocus;
-      focusChanged();
-    });
+    _focusNode = CellSingleFocusNode();
+    _listenFocusNode();
     super.initState();
   }
 
@@ -72,10 +69,19 @@ class _NumberCellState extends State<NumberCell> {
     widget.requestFocus.removeAllListener();
     _delayOperation?.cancel();
     _cellBloc.close();
+    _focusNode.removeSingleListener();
     _focusNode.dispose();
     super.dispose();
   }
 
+  @override
+  void didUpdateWidget(covariant NumberCell oldWidget) {
+    if (oldWidget != widget) {
+      _listenFocusNode();
+    }
+    super.didUpdateWidget(oldWidget);
+  }
+
   Future<void> focusChanged() async {
     if (mounted) {
       _delayOperation?.cancel();
@@ -92,6 +98,14 @@ class _NumberCellState extends State<NumberCell> {
     }
   }
 
+  void _listenFocusNode() {
+    widget.onFocus.value = _focusNode.hasFocus;
+    _focusNode.setSingleListener(() {
+      widget.onFocus.value = _focusNode.hasFocus;
+      focusChanged();
+    });
+  }
+
   void _listenCellRequestFocus(BuildContext context) {
     widget.requestFocus.addListener(() {
       if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) {

+ 33 - 30
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart

@@ -35,9 +35,7 @@ class GridTextCell extends GridCellWidget {
 class _GridTextCellState extends State<GridTextCell> {
   late TextCellBloc _cellBloc;
   late TextEditingController _controller;
-  late FocusNode _focusNode;
-
-  VoidCallback? _focusNodeListener;
+  late CellSingleFocusNode _focusNode;
   Timer? _delayOperation;
 
   @override
@@ -46,44 +44,37 @@ class _GridTextCellState extends State<GridTextCell> {
     _cellBloc = getIt<TextCellBloc>(param1: cellContext);
     _cellBloc.add(const TextCellEvent.initial());
     _controller = TextEditingController(text: _cellBloc.state.content);
-    _focusNode = FocusNode();
-    _focusNode.addListener(() {
-      widget.onFocus.value = _focusNode.hasFocus;
-      focusChanged();
-    });
+    _focusNode = CellSingleFocusNode();
 
+    _listenFocusNode();
+    _listenRequestFocus(context);
     super.initState();
   }
 
   @override
   Widget build(BuildContext context) {
-    _listenCellRequestFocus(context);
-
     return BlocProvider.value(
       value: _cellBloc,
-      child: BlocConsumer<TextCellBloc, TextCellState>(
+      child: BlocListener<TextCellBloc, TextCellState>(
         listener: (context, state) {
           if (_controller.text != state.content) {
             _controller.text = state.content;
           }
         },
-        buildWhen: (previous, current) => previous.content != current.content,
-        builder: (context, state) {
-          return TextField(
-            controller: _controller,
-            focusNode: _focusNode,
-            onChanged: (value) => focusChanged(),
-            onEditingComplete: () => _focusNode.unfocus(),
-            maxLines: null,
-            style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
-            decoration: InputDecoration(
-              contentPadding: EdgeInsets.zero,
-              border: InputBorder.none,
-              hintText: widget.cellStyle?.placeholder,
-              isDense: true,
-            ),
-          );
-        },
+        child: TextField(
+          controller: _controller,
+          focusNode: _focusNode,
+          onChanged: (value) => focusChanged(),
+          onEditingComplete: () => _focusNode.unfocus(),
+          maxLines: null,
+          style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
+          decoration: InputDecoration(
+            contentPadding: EdgeInsets.zero,
+            border: InputBorder.none,
+            hintText: widget.cellStyle?.placeholder,
+            isDense: true,
+          ),
+        ),
       ),
     );
   }
@@ -93,17 +84,29 @@ class _GridTextCellState extends State<GridTextCell> {
     widget.requestFocus.removeAllListener();
     _delayOperation?.cancel();
     _cellBloc.close();
+    _focusNode.removeSingleListener();
     _focusNode.dispose();
+
     super.dispose();
   }
 
   @override
   void didUpdateWidget(covariant GridTextCell oldWidget) {
-    // TODO: implement didUpdateWidget
+    if (oldWidget != widget) {
+      _listenFocusNode();
+    }
     super.didUpdateWidget(oldWidget);
   }
 
-  void _listenCellRequestFocus(BuildContext context) {
+  void _listenFocusNode() {
+    widget.onFocus.value = _focusNode.hasFocus;
+    _focusNode.setSingleListener(() {
+      widget.onFocus.value = _focusNode.hasFocus;
+      focusChanged();
+    });
+  }
+
+  void _listenRequestFocus(BuildContext context) {
     widget.requestFocus.addListener(() {
       if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) {
         FocusScope.of(context).requestFocus(_focusNode);

+ 3 - 2
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart

@@ -4,6 +4,7 @@ import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/p
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/icon_button.dart';
+import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:provider/provider.dart';
@@ -153,14 +154,14 @@ class _RowCells extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return BlocBuilder<RowBloc, RowState>(
-      buildWhen: (previous, current) => previous.cellDataMap != current.cellDataMap,
+      buildWhen: (previous, current) => !listEquals(previous.snapshots, current.snapshots),
       builder: (context, state) {
         return IntrinsicHeight(
             child: Row(
           mainAxisSize: MainAxisSize.max,
           mainAxisAlignment: MainAxisAlignment.start,
           crossAxisAlignment: CrossAxisAlignment.stretch,
-          children: _makeCells(context, state.cellDataMap),
+          children: _makeCells(context, state.gridCellMap),
         ));
       },
     );