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 251ca0ef29..5b19a48d9c 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 @@ -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}"; + } } 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 cfd99b3e98..3eb0ca16f4 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 @@ -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 { _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 { Future _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 { 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 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 get props => [ + _field.id, + _field.fieldType, + _field.visibility, + ]; +} 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 4228ddd863..2edd818b9b 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 @@ -42,7 +42,7 @@ class RowDetailBloc extends Bloc { Future _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, ); } 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 82879b647e..232b8fbf0e 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 @@ -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 fields, + required UnmodifiableListView fields, required double height, Row? data, }) = _GridRow; 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 8d7ea06c54..0b13aab846 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 @@ -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; diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart index c012db3304..c0b3427e65 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart +++ b/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 { late NumberCellBloc _cellBloc; late TextEditingController _controller; - late FocusNode _focusNode; + late CellSingleFocusNode _focusNode; Timer? _delayOperation; @override @@ -30,11 +30,8 @@ class _NumberCellState extends State { final cellContext = widget.cellContextBuilder.build(); _cellBloc = getIt(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 { 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 focusChanged() async { if (mounted) { _delayOperation?.cancel(); @@ -92,6 +98,14 @@ class _NumberCellState extends State { } } + 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) { diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart index affcacafd2..14ace02d28 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/text_cell.dart +++ b/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 { 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 { _cellBloc = getIt(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( + child: BlocListener( 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 { 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); 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 68908f1243..200a079d55 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 @@ -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( - 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), )); }, );