diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/date_picker/mobile_date_picker_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/date_picker/mobile_date_picker_screen.dart index 301c375c4b..587db41474 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/date_picker/mobile_date_picker_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/date_picker/mobile_date_picker_screen.dart @@ -77,7 +77,7 @@ class _MobileDateCellEditScreenState extends State { create: (_) => DateCellEditorBloc( reminderBloc: getIt(), cellController: widget.controller, - )..add(const DateCellEditorEvent.initial()), + ), ), ], child: BlocBuilder( diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_edit_field_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_edit_field_screen.dart index 7aa473a2ea..d29116d50d 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_edit_field_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_edit_field_screen.dart @@ -63,7 +63,7 @@ class _MobileEditPropertyScreenState extends State { isPrimary: widget.field.isPrimary, defaultValues: FieldOptionValues.fromField(field: widget.field.field), actions: [ - widget.field.fieldSettings?.visibility.isVisibleState() ?? true + widget.field.visibility?.isVisibleState() ?? true ? FieldOptionAction.hide : FieldOptionAction.show, FieldOptionAction.duplicate, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart index 10a6916373..f3b76447f4 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart @@ -44,8 +44,8 @@ class _QuickEditFieldState extends State { @override void initState() { super.initState(); - fieldVisibility = widget.fieldInfo.fieldSettings?.visibility ?? - FieldVisibility.AlwaysShown; + fieldVisibility = + widget.fieldInfo.visibility ?? FieldVisibility.AlwaysShown; controller.text = widget.fieldInfo.field.name; } diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/checkbox_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/checkbox_cell_bloc.dart index e3f53c260a..4b32a30b5d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/checkbox_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/checkbox_cell_bloc.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; +import 'package:appflowy/plugins/database/application/field/field_info.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -20,8 +21,10 @@ class CheckboxCellBloc extends Bloc { @override Future close() async { if (_onCellChangedFn != null) { - cellController.removeListener(_onCellChangedFn!); - _onCellChangedFn = null; + cellController.removeListener( + onCellChanged: _onCellChangedFn!, + onFieldChanged: _onFieldChangedListener, + ); } await cellController.dispose(); return super.close(); @@ -53,13 +56,15 @@ class CheckboxCellBloc extends Bloc { add(CheckboxCellEvent.didUpdateCell(_isSelected(cellData))); } }, - onCellFieldChanged: (field) { - if (!isClosed) { - add(CheckboxCellEvent.didUpdateField(field.name)); - } - }, + onFieldChanged: _onFieldChangedListener, ); } + + void _onFieldChangedListener(FieldInfo fieldInfo) { + if (!isClosed) { + add(CheckboxCellEvent.didUpdateField(fieldInfo.name)); + } + } } @freezed diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/checklist_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/checklist_cell_bloc.dart index 7fe92cd137..7aad1108b1 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/checklist_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/checklist_cell_bloc.dart @@ -36,8 +36,7 @@ class ChecklistCellBloc extends Bloc { @override Future close() async { if (_onCellChangedFn != null) { - cellController.removeListener(_onCellChangedFn!); - _onCellChangedFn = null; + cellController.removeListener(onCellChanged: _onCellChangedFn!); } await cellController.dispose(); return super.close(); @@ -47,7 +46,7 @@ class ChecklistCellBloc extends Bloc { on( (event, emit) async { await event.when( - didReceiveOptions: (data) { + didUpdateCell: (data) { if (data == null) { emit( const ChecklistCellState( @@ -58,7 +57,6 @@ class ChecklistCellBloc extends Bloc { ); return; } - emit( state.copyWith( tasks: _makeChecklistSelectOptions(data), @@ -91,7 +89,7 @@ class ChecklistCellBloc extends Bloc { _onCellChangedFn = cellController.addListener( onCellChanged: (data) { if (!isClosed) { - add(ChecklistCellEvent.didReceiveOptions(data)); + add(ChecklistCellEvent.didUpdateCell(data)); } }, ); @@ -111,9 +109,9 @@ class ChecklistCellBloc extends Bloc { @freezed class ChecklistCellEvent with _$ChecklistCellEvent { - const factory ChecklistCellEvent.didReceiveOptions( + const factory ChecklistCellEvent.didUpdateCell( ChecklistCellDataPB? data, - ) = _DidReceiveCellUpdate; + ) = _DidUpdateCell; const factory ChecklistCellEvent.updateTaskName( SelectOptionPB option, String name, diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/date_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/date_cell_bloc.dart index 9497f28ff5..c5573a9061 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/date_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/date_cell_bloc.dart @@ -12,6 +12,7 @@ class DateCellBloc extends Bloc { DateCellBloc({required this.cellController}) : super(DateCellState.initial(cellController)) { _dispatch(); + _startListening(); } final DateCellController cellController; @@ -20,8 +21,10 @@ class DateCellBloc extends Bloc { @override Future close() async { if (_onCellChangedFn != null) { - cellController.removeListener(_onCellChangedFn!); - _onCellChangedFn = null; + cellController.removeListener( + onCellChanged: _onCellChangedFn!, + onFieldChanged: _onFieldChangedListener, + ); } await cellController.dispose(); return super.close(); @@ -31,7 +34,6 @@ class DateCellBloc extends Bloc { on( (event, emit) async { event.when( - initial: () => _startListening(), didReceiveCellUpdate: (DateCellDataPB? cellData) { emit( state.copyWith( @@ -40,6 +42,9 @@ class DateCellBloc extends Bloc { ), ); }, + didUpdateField: (fieldInfo) { + emit(state.copyWith(fieldInfo: fieldInfo)); + }, ); }, ); @@ -52,15 +57,23 @@ class DateCellBloc extends Bloc { add(DateCellEvent.didReceiveCellUpdate(data)); } }, + onFieldChanged: _onFieldChangedListener, ); } + + void _onFieldChangedListener(FieldInfo fieldInfo) { + if (!isClosed) { + add(DateCellEvent.didUpdateField(fieldInfo)); + } + } } @freezed class DateCellEvent with _$DateCellEvent { - const factory DateCellEvent.initial() = _InitialCell; const factory DateCellEvent.didReceiveCellUpdate(DateCellDataPB? data) = _DidReceiveCellUpdate; + const factory DateCellEvent.didUpdateField(FieldInfo fieldInfo) = + _DidUpdateField; } @freezed diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/date_cell_editor_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/date_cell_editor_bloc.dart index bb6c50c2e0..879e759f35 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/date_cell_editor_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/date_cell_editor_bloc.dart @@ -39,6 +39,7 @@ class DateCellEditorBloc ), super(DateCellEditorState.initial(cellController, reminderBloc)) { _dispatch(); + _startListening(); } final DateCellBackendService _dateCellBackendService; @@ -50,7 +51,6 @@ class DateCellEditorBloc on( (event, emit) async { await event.when( - initial: () async => _startListening(), didReceiveCellUpdate: (DateCellDataPB? cellData) { final dateCellData = _dateDataFromCellData(cellData); final endDay = @@ -365,8 +365,9 @@ class DateCellEditorBloc @override Future close() async { if (_onCellChangedFn != null) { - cellController.removeListener(_onCellChangedFn!); - _onCellChangedFn = null; + cellController.removeListener( + onCellChanged: _onCellChangedFn!, + ); } return super.close(); } @@ -417,9 +418,6 @@ class DateCellEditorBloc @freezed class DateCellEditorEvent with _$DateCellEditorEvent { - // initial event - const factory DateCellEditorEvent.initial() = _Initial; - // notification that cell is updated in the backend const factory DateCellEditorEvent.didReceiveCellUpdate( DateCellDataPB? data, diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/number_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/number_cell_bloc.dart index 8593c42f1e..df159b817b 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/number_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/number_cell_bloc.dart @@ -1,15 +1,18 @@ import 'dart:async'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; +import 'package:appflowy/plugins/database/application/field/field_info.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'number_cell_bloc.freezed.dart'; class NumberCellBloc extends Bloc { - NumberCellBloc({required this.cellController}) - : super(NumberCellState.initial(cellController)) { + NumberCellBloc({ + required this.cellController, + }) : super(NumberCellState.initial(cellController)) { _dispatch(); + _startListening(); } final NumberCellController cellController; @@ -18,8 +21,10 @@ class NumberCellBloc extends Bloc { @override Future close() async { if (_onCellChangedFn != null) { - cellController.removeListener(_onCellChangedFn!); - _onCellChangedFn = null; + cellController.removeListener( + onCellChanged: _onCellChangedFn!, + onFieldChanged: _onFieldChangedListener, + ); } await cellController.dispose(); return super.close(); @@ -29,12 +34,15 @@ class NumberCellBloc extends Bloc { on( (event, emit) async { await event.when( - initial: () { - _startListening(); - }, didReceiveCellUpdate: (cellData) { emit(state.copyWith(content: cellData ?? "")); }, + didUpdateField: (fieldInfo) { + final wrap = fieldInfo.wrapCellContent; + if (wrap != null) { + emit(state.copyWith(wrap: wrap)); + } + }, updateCell: (text) async { if (state.content != text) { emit(state.copyWith(content: text)); @@ -62,27 +70,38 @@ class NumberCellBloc extends Bloc { add(NumberCellEvent.didReceiveCellUpdate(cellContent)); } }, + onFieldChanged: _onFieldChangedListener, ); } + + void _onFieldChangedListener(FieldInfo fieldInfo) { + if (!isClosed) { + add(NumberCellEvent.didUpdateField(fieldInfo)); + } + } } @freezed class NumberCellEvent with _$NumberCellEvent { - const factory NumberCellEvent.initial() = _Initial; - const factory NumberCellEvent.updateCell(String text) = _UpdateCell; const factory NumberCellEvent.didReceiveCellUpdate(String? cellContent) = _DidReceiveCellUpdate; + const factory NumberCellEvent.didUpdateField(FieldInfo fieldInfo) = + _DidUpdateField; + const factory NumberCellEvent.updateCell(String text) = _UpdateCell; } @freezed class NumberCellState with _$NumberCellState { const factory NumberCellState({ required String content, + required bool wrap, }) = _NumberCellState; factory NumberCellState.initial(TextCellController cellController) { + final wrap = cellController.fieldInfo.wrapCellContent; return NumberCellState( content: cellController.getCellData() ?? "", + wrap: wrap ?? true, ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/relation_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/relation_cell_bloc.dart index ac3e59f848..28ff1b2f78 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/relation_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/relation_cell_bloc.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; +import 'package:appflowy/plugins/database/application/field/field_info.dart'; import 'package:appflowy/plugins/database/application/field/type_option/relation_type_option_cubit.dart'; import 'package:appflowy/plugins/database/application/field/type_option/type_option_data_parser.dart'; import 'package:appflowy/plugins/database/domain/field_service.dart'; @@ -16,7 +17,7 @@ part 'relation_cell_bloc.freezed.dart'; class RelationCellBloc extends Bloc { RelationCellBloc({required this.cellController}) - : super(RelationCellState.initial()) { + : super(RelationCellState.initial(cellController)) { _dispatch(); _startListening(); _init(); @@ -28,8 +29,10 @@ class RelationCellBloc extends Bloc { @override Future close() async { if (_onCellChangedFn != null) { - cellController.removeListener(_onCellChangedFn!); - _onCellChangedFn = null; + cellController.removeListener( + onCellChanged: _onCellChangedFn!, + onFieldChanged: _onFieldChangedListener, + ); } return super.close(); } @@ -38,7 +41,7 @@ class RelationCellBloc extends Bloc { on( (event, emit) async { await event.when( - didUpdateCell: (RelationCellDataPB? cellData) async { + didUpdateCell: (cellData) async { if (cellData == null || cellData.rowIds.isEmpty || state.relatedDatabaseMeta == null) { @@ -60,7 +63,13 @@ class RelationCellBloc extends Bloc { ); emit(state.copyWith(rows: rows)); }, - didUpdateRelationTypeOption: (typeOption) async { + didUpdateField: (FieldInfo fieldInfo) async { + final wrap = fieldInfo.wrapCellContent; + if (wrap != null) { + emit(state.copyWith(wrap: wrap)); + } + final RelationTypeOptionPB typeOption = + cellController.getTypeOption(RelationTypeOptionDataParser()); if (typeOption.databaseId.isEmpty) { return; } @@ -86,25 +95,23 @@ class RelationCellBloc extends Bloc { add(RelationCellEvent.didUpdateCell(data)); } }, - onCellFieldChanged: (field) { - if (!isClosed) { - final RelationTypeOptionPB typeOption = - cellController.getTypeOption(RelationTypeOptionDataParser()); - add(RelationCellEvent.didUpdateRelationTypeOption(typeOption)); - } - }, + onFieldChanged: _onFieldChangedListener, ); } + void _onFieldChangedListener(FieldInfo fieldInfo) { + if (!isClosed) { + add(RelationCellEvent.didUpdateField(fieldInfo)); + } + } + void _init() { - final typeOption = - cellController.getTypeOption(RelationTypeOptionDataParser()); - add(RelationCellEvent.didUpdateRelationTypeOption(typeOption)); + add(RelationCellEvent.didUpdateField(cellController.fieldInfo)); } void _loadCellData() { final cellData = cellController.getCellData(); - if (!isClosed) { + if (!isClosed && cellData != null) { add(RelationCellEvent.didUpdateCell(cellData)); } } @@ -166,11 +173,10 @@ class RelationCellBloc extends Bloc { @freezed class RelationCellEvent with _$RelationCellEvent { - const factory RelationCellEvent.didUpdateRelationTypeOption( - RelationTypeOptionPB typeOption, - ) = _DidUpdateRelationTypeOption; const factory RelationCellEvent.didUpdateCell(RelationCellDataPB? data) = _DidUpdateCell; + const factory RelationCellEvent.didUpdateField(FieldInfo fieldInfo) = + _DidUpdateField; const factory RelationCellEvent.selectDatabaseId( String databaseId, ) = _SelectDatabaseId; @@ -182,10 +188,15 @@ class RelationCellState with _$RelationCellState { const factory RelationCellState({ required DatabaseMeta? relatedDatabaseMeta, required List rows, + required bool wrap, }) = _RelationCellState; - factory RelationCellState.initial() => const RelationCellState( - relatedDatabaseMeta: null, - rows: [], - ); + factory RelationCellState.initial(RelationCellController cellController) { + final wrap = cellController.fieldInfo.wrapCellContent; + return RelationCellState( + relatedDatabaseMeta: null, + rows: [], + wrap: wrap ?? true, + ); + } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/select_option_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/select_option_cell_bloc.dart index eb1ff69790..ca5af1e1a2 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/select_option_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/select_option_cell_bloc.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; +import 'package:appflowy/plugins/database/application/field/field_info.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/select_option_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -9,9 +10,11 @@ part 'select_option_cell_bloc.freezed.dart'; class SelectOptionCellBloc extends Bloc { - SelectOptionCellBloc({required this.cellController}) - : super(SelectOptionCellState.initial(cellController)) { + SelectOptionCellBloc({ + required this.cellController, + }) : super(SelectOptionCellState.initial(cellController)) { _dispatch(); + _startListening(); } final SelectOptionCellController cellController; @@ -20,8 +23,10 @@ class SelectOptionCellBloc @override Future close() async { if (_onCellChangedFn != null) { - cellController.removeListener(_onCellChangedFn!); - _onCellChangedFn = null; + cellController.removeListener( + onCellChanged: _onCellChangedFn!, + onFieldChanged: _onFieldChangedListener, + ); } await cellController.dispose(); return super.close(); @@ -29,11 +34,8 @@ class SelectOptionCellBloc void _dispatch() { on( - (event, emit) async { - await event.when( - initial: () async { - _startListening(); - }, + (event, emit) { + event.when( didReceiveOptions: (List selectedOptions) { emit( state.copyWith( @@ -41,6 +43,12 @@ class SelectOptionCellBloc ), ); }, + didUpdateField: (fieldInfo) { + final wrap = fieldInfo.wrapCellContent; + if (wrap != null) { + emit(state.copyWith(wrap: wrap)); + } + }, ); }, ); @@ -57,31 +65,41 @@ class SelectOptionCellBloc ); } }, + onFieldChanged: _onFieldChangedListener, ); } + + void _onFieldChangedListener(FieldInfo fieldInfo) { + if (!isClosed) { + add(SelectOptionCellEvent.didUpdateField(fieldInfo)); + } + } } @freezed class SelectOptionCellEvent with _$SelectOptionCellEvent { - const factory SelectOptionCellEvent.initial() = _InitialCell; const factory SelectOptionCellEvent.didReceiveOptions( List selectedOptions, ) = _DidReceiveOptions; + const factory SelectOptionCellEvent.didUpdateField(FieldInfo fieldInfo) = + _DidUpdateField; } @freezed class SelectOptionCellState with _$SelectOptionCellState { const factory SelectOptionCellState({ required List selectedOptions, + required bool wrap, }) = _SelectOptionCellState; factory SelectOptionCellState.initial( SelectOptionCellController cellController, ) { final data = cellController.getCellData(); - + final wrap = cellController.fieldInfo.wrapCellContent; return SelectOptionCellState( selectedOptions: data?.selectOptions ?? [], + wrap: wrap ?? true, ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/select_option_cell_editor_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/select_option_cell_editor_bloc.dart index aa360ff36e..c0bfa48a85 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/select_option_cell_editor_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/select_option_cell_editor_bloc.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; +import 'package:appflowy/plugins/database/application/field/field_info.dart'; import 'package:appflowy/plugins/database/application/field/type_option/select_type_option_actions.dart'; import 'package:appflowy/plugins/database/application/field/type_option/type_option_data_parser.dart'; import 'package:appflowy/plugins/database/domain/field_service.dart'; @@ -163,8 +164,10 @@ class SelectOptionCellEditorBloc @override Future close() async { if (_onCellChangedFn != null) { - cellController.removeListener(_onCellChangedFn!); - _onCellChangedFn = null; + cellController.removeListener( + onCellChanged: _onCellChangedFn!, + onFieldChanged: _onFieldChangedListener, + ); } return super.close(); } @@ -172,27 +175,25 @@ class SelectOptionCellEditorBloc void _startListening() { _onCellChangedFn = cellController.addListener( onCellChanged: (cellData) { - if (isClosed) { - Log.warn("Unexpecteded closing the bloc"); - return; + if (!isClosed) { + add( + SelectOptionCellEditorEvent.didUpdateCell( + cellData == null ? [] : cellData.selectOptions, + ), + ); } - add( - SelectOptionCellEditorEvent.didUpdateCell( - cellData == null ? [] : cellData.selectOptions, - ), - ); - }, - onCellFieldChanged: (field) { - if (isClosed) { - Log.warn("Unexpecteded closing the bloc"); - return; - } - final loadedOptions = _loadAllOptions(cellController); - add(SelectOptionCellEditorEvent.didUpdateOptions(loadedOptions)); }, + onFieldChanged: _onFieldChangedListener, ); } + void _onFieldChangedListener(FieldInfo fieldInfo) { + if (!isClosed) { + final loadedOptions = _loadAllOptions(cellController); + add(SelectOptionCellEditorEvent.didUpdateOptions(loadedOptions)); + } + } + Future _createOption({ required String name, required SelectOptionColorPB color, diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/text_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/text_cell_bloc.dart index 3a2371cf4b..f9f4d47d41 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/text_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/text_cell_bloc.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; +import 'package:appflowy/plugins/database/application/field/field_info.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -10,6 +11,7 @@ class TextCellBloc extends Bloc { TextCellBloc({required this.cellController}) : super(TextCellState.initial(cellController)) { _dispatch(); + _startListening(); } final TextCellController cellController; @@ -18,8 +20,10 @@ class TextCellBloc extends Bloc { @override Future close() async { if (_onCellChangedFn != null) { - cellController.removeListener(_onCellChangedFn!); - _onCellChangedFn = null; + cellController.removeListener( + onCellChanged: _onCellChangedFn!, + onFieldChanged: _onFieldChangedListener, + ); } await cellController.dispose(); return super.close(); @@ -29,12 +33,15 @@ class TextCellBloc extends Bloc { on( (event, emit) { event.when( - initial: () { - _startListening(); - }, didReceiveCellUpdate: (String content) { emit(state.copyWith(content: content)); }, + didUpdateField: (fieldInfo) { + final wrap = fieldInfo.wrapCellContent; + if (wrap != null) { + emit(state.copyWith(wrap: wrap)); + } + }, didUpdateEmoji: (String emoji) { emit(state.copyWith(emoji: emoji)); }, @@ -58,20 +65,30 @@ class TextCellBloc extends Bloc { add(TextCellEvent.didReceiveCellUpdate(cellContent ?? "")); } }, - onRowMetaChanged: () { - if (!isClosed && cellController.fieldInfo.isPrimary) { - add(TextCellEvent.didUpdateEmoji(cellController.icon ?? "")); - } - }, + onFieldChanged: _onFieldChangedListener, + onRowMetaChanged: cellController.fieldInfo.isPrimary + ? () { + if (!isClosed) { + add(TextCellEvent.didUpdateEmoji(cellController.icon ?? "")); + } + } + : null, ); } + + void _onFieldChangedListener(FieldInfo fieldInfo) { + if (!isClosed) { + add(TextCellEvent.didUpdateField(fieldInfo)); + } + } } @freezed class TextCellEvent with _$TextCellEvent { - const factory TextCellEvent.initial() = _InitialCell; const factory TextCellEvent.didReceiveCellUpdate(String cellContent) = _DidReceiveCellUpdate; + const factory TextCellEvent.didUpdateField(FieldInfo fieldInfo) = + _DidUpdateField; const factory TextCellEvent.updateText(String text) = _UpdateText; const factory TextCellEvent.enableEdit(bool enabled) = _EnableEdit; const factory TextCellEvent.didUpdateEmoji(String emoji) = _UpdateEmoji; @@ -83,13 +100,20 @@ class TextCellState with _$TextCellState { required String content, required String emoji, required bool enableEdit, + required bool wrap, }) = _TextCellState; - factory TextCellState.initial(TextCellController cellController) => - TextCellState( - content: cellController.getCellData() ?? "", - emoji: - cellController.fieldInfo.isPrimary ? cellController.icon ?? "" : "", - enableEdit: false, - ); + factory TextCellState.initial(TextCellController cellController) { + final cellData = cellController.getCellData() ?? ""; + final wrap = cellController.fieldInfo.wrapCellContent ?? false; + final emoji = + cellController.fieldInfo.isPrimary ? cellController.icon ?? "" : ""; + + return TextCellState( + content: cellData, + emoji: emoji, + enableEdit: false, + wrap: wrap, + ); + } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/timestamp_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/timestamp_cell_bloc.dart index 240ce7182c..6d80109c71 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/timestamp_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/timestamp_cell_bloc.dart @@ -9,9 +9,11 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'timestamp_cell_bloc.freezed.dart'; class TimestampCellBloc extends Bloc { - TimestampCellBloc({required this.cellController}) - : super(TimestampCellState.initial(cellController)) { + TimestampCellBloc({ + required this.cellController, + }) : super(TimestampCellState.initial(cellController)) { _dispatch(); + _startListening(); } final TimestampCellController cellController; @@ -20,8 +22,10 @@ class TimestampCellBloc extends Bloc { @override Future close() async { if (_onCellChangedFn != null) { - cellController.removeListener(_onCellChangedFn!); - _onCellChangedFn = null; + cellController.removeListener( + onCellChanged: _onCellChangedFn!, + onFieldChanged: _onFieldChangedListener, + ); } await cellController.dispose(); return super.close(); @@ -31,7 +35,6 @@ class TimestampCellBloc extends Bloc { on( (event, emit) async { event.when( - initial: () => _startListening(), didReceiveCellUpdate: (TimestampCellDataPB? cellData) { emit( state.copyWith( @@ -40,6 +43,12 @@ class TimestampCellBloc extends Bloc { ), ); }, + didUpdateField: (fieldInfo) { + final wrap = fieldInfo.wrapCellContent; + if (wrap != null) { + emit(state.copyWith(wrap: wrap)); + } + }, ); }, ); @@ -52,16 +61,24 @@ class TimestampCellBloc extends Bloc { add(TimestampCellEvent.didReceiveCellUpdate(data)); } }, + onFieldChanged: _onFieldChangedListener, ); } + + void _onFieldChangedListener(FieldInfo fieldInfo) { + if (!isClosed) { + add(TimestampCellEvent.didUpdateField(fieldInfo)); + } + } } @freezed class TimestampCellEvent with _$TimestampCellEvent { - const factory TimestampCellEvent.initial() = _InitialCell; const factory TimestampCellEvent.didReceiveCellUpdate( TimestampCellDataPB? data, ) = _DidReceiveCellUpdate; + const factory TimestampCellEvent.didUpdateField(FieldInfo fieldInfo) = + _DidUpdateField; } @freezed @@ -70,15 +87,18 @@ class TimestampCellState with _$TimestampCellState { required TimestampCellDataPB? data, required String dateStr, required FieldInfo fieldInfo, + required bool wrap, }) = _TimestampCellState; - factory TimestampCellState.initial(TimestampCellController context) { - final cellData = context.getCellData(); + factory TimestampCellState.initial(TimestampCellController cellController) { + final cellData = cellController.getCellData(); + final wrap = cellController.fieldInfo.wrapCellContent; return TimestampCellState( - fieldInfo: context.fieldInfo, + fieldInfo: cellController.fieldInfo, data: cellData, dateStr: cellData?.dateTime ?? "", + wrap: wrap ?? true, ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/url_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/url_cell_bloc.dart index fe52a73aac..81ea6d60d1 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/url_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/url_cell_bloc.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; +import 'package:appflowy/plugins/database/application/field/field_info.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/url_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -9,9 +10,11 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'url_cell_bloc.freezed.dart'; class URLCellBloc extends Bloc { - URLCellBloc({required this.cellController}) - : super(URLCellState.initial(cellController)) { + URLCellBloc({ + required this.cellController, + }) : super(URLCellState.initial(cellController)) { _dispatch(); + _startListening(); } final URLCellController cellController; @@ -20,8 +23,10 @@ class URLCellBloc extends Bloc { @override Future close() async { if (_onCellChangedFn != null) { - cellController.removeListener(_onCellChangedFn!); - _onCellChangedFn = null; + cellController.removeListener( + onCellChanged: _onCellChangedFn!, + onFieldChanged: _onFieldChangedListener, + ); } await cellController.dispose(); return super.close(); @@ -31,12 +36,9 @@ class URLCellBloc extends Bloc { on( (event, emit) async { await event.when( - initial: () { - _startListening(); - }, - didReceiveCellUpdate: (cellData) async { + didUpdateCell: (cellData) async { final content = cellData?.content ?? ""; - final isValid = await isUrlValid(content); + final isValid = await _isUrlValid(content); emit( state.copyWith( content: content, @@ -44,6 +46,12 @@ class URLCellBloc extends Bloc { ), ); }, + didUpdateField: (fieldInfo) { + final wrap = fieldInfo.wrapCellContent; + if (wrap != null) { + emit(state.copyWith(wrap: wrap)); + } + }, updateURL: (String url) { cellController.saveCellData(url, debounce: true); }, @@ -56,13 +64,20 @@ class URLCellBloc extends Bloc { _onCellChangedFn = cellController.addListener( onCellChanged: (cellData) { if (!isClosed) { - add(URLCellEvent.didReceiveCellUpdate(cellData)); + add(URLCellEvent.didUpdateCell(cellData)); } }, + onFieldChanged: _onFieldChangedListener, ); } - Future isUrlValid(String content) async { + void _onFieldChangedListener(FieldInfo fieldInfo) { + if (!isClosed) { + add(URLCellEvent.didUpdateField(fieldInfo)); + } + } + + Future _isUrlValid(String content) async { if (content.isEmpty) { return true; } @@ -90,10 +105,11 @@ class URLCellBloc extends Bloc { @freezed class URLCellEvent with _$URLCellEvent { - const factory URLCellEvent.initial() = _InitialCell; const factory URLCellEvent.updateURL(String url) = _UpdateURL; - const factory URLCellEvent.didReceiveCellUpdate(URLCellDataPB? cell) = - _DidReceiveCellUpdate; + const factory URLCellEvent.didUpdateCell(URLCellDataPB? cell) = + _DidUpdateCell; + const factory URLCellEvent.didUpdateField(FieldInfo fieldInfo) = + _DidUpdateField; } @freezed @@ -101,13 +117,16 @@ class URLCellState with _$URLCellState { const factory URLCellState({ required String content, required bool isValid, + required bool wrap, }) = _URLCellState; - factory URLCellState.initial(URLCellController context) { - final cellData = context.getCellData(); + factory URLCellState.initial(URLCellController cellController) { + final cellData = cellController.getCellData(); + final wrap = cellController.fieldInfo.wrapCellContent; return URLCellState( content: cellData?.content ?? "", isValid: true, + wrap: wrap ?? true, ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/url_cell_editor_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/url_cell_editor_bloc.dart deleted file mode 100644 index 1ee86a97b7..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/url_cell_editor_bloc.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'dart:async'; - -import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/url_entities.pb.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'url_cell_editor_bloc.freezed.dart'; - -class URLCellEditorBloc extends Bloc { - URLCellEditorBloc({required this.cellController}) - : super(URLCellEditorState.initial(cellController)) { - _dispatch(); - } - - final URLCellController cellController; - void Function()? _onCellChangedFn; - - void _dispatch() { - on( - (event, emit) async { - await event.when( - initial: () { - _startListening(); - }, - updateText: (text) async { - await cellController.saveCellData(text); - emit( - state.copyWith( - content: text, - isFinishEditing: true, - ), - ); - }, - didReceiveCellUpdate: (cellData) { - emit(state.copyWith(content: cellData?.content ?? "")); - }, - ); - }, - ); - } - - @override - Future close() async { - if (_onCellChangedFn != null) { - cellController.removeListener(_onCellChangedFn!); - _onCellChangedFn = null; - } - await cellController.dispose(); - return super.close(); - } - - void _startListening() { - _onCellChangedFn = cellController.addListener( - onCellChanged: (cellData) { - if (!isClosed) { - add(URLCellEditorEvent.didReceiveCellUpdate(cellData)); - } - }, - ); - } -} - -@freezed -class URLCellEditorEvent with _$URLCellEditorEvent { - const factory URLCellEditorEvent.initial() = _InitialCell; - const factory URLCellEditorEvent.didReceiveCellUpdate(URLCellDataPB? cell) = - _DidReceiveCellUpdate; - const factory URLCellEditorEvent.updateText(String text) = _UpdateText; -} - -@freezed -class URLCellEditorState with _$URLCellEditorState { - const factory URLCellEditorState({ - required String content, - required bool isFinishEditing, - }) = _URLCellEditorState; - - factory URLCellEditorState.initial(URLCellController context) { - final cellData = context.getCellData(); - return URLCellEditorState( - content: cellData?.content ?? "", - isFinishEditing: true, - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_controller.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_controller.dart index c810fc1a82..426a54da76 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_controller.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_controller.dart @@ -64,7 +64,6 @@ class CellController { RowMetaListener? _rowMetaListener; CellDataNotifier? _cellDataNotifier; - void Function(FieldInfo field)? _onCellFieldChanged; VoidCallback? _onRowMetaChanged; Timer? _loadDataOperation; Timer? _saveDataOperation; @@ -104,16 +103,7 @@ class CellController { // 2. Listen on the field event and load the cell data if needed. _fieldController.addSingleFieldListener( fieldId, - onFieldChanged: (fieldInfo) { - // reloadOnFieldChanged should be true if you want to reload the cell - // data when the corresponding field is changed. - // For example: - // ¥12 -> $12 - if (_cellDataLoader.reloadOnFieldChange) { - _loadData(); - } - _onCellFieldChanged?.call(fieldInfo); - }, + onFieldChanged: _onFieldChangedListener, ); // 3. If the field is primary listen to row meta changes. @@ -130,22 +120,49 @@ class CellController { /// Add a new listener VoidCallback? addListener({ required void Function(T?) onCellChanged, - void Function(FieldInfo field)? onCellFieldChanged, + void Function(FieldInfo fieldInfo)? onFieldChanged, VoidCallback? onRowMetaChanged, }) { - _onCellFieldChanged = onCellFieldChanged; - _onRowMetaChanged = onRowMetaChanged; - - /// Notify the listener, the cell data was changed. + /// an adaptor for the onCellChanged listener void onCellChangedFn() => onCellChanged(_cellDataNotifier?.value); _cellDataNotifier?.addListener(onCellChangedFn); + if (onFieldChanged != null) { + _fieldController.addSingleFieldListener( + fieldId, + onFieldChanged: onFieldChanged, + ); + } + + _onRowMetaChanged = onRowMetaChanged; + // Return the function pointer that can be used when calling removeListener. return onCellChangedFn; } - void removeListener(VoidCallback fn) { - _cellDataNotifier?.removeListener(fn); + void removeListener({ + required VoidCallback onCellChanged, + void Function(FieldInfo fieldInfo)? onFieldChanged, + VoidCallback? onRowMetaChanged, + }) { + _cellDataNotifier?.removeListener(onCellChanged); + + if (onFieldChanged != null) { + _fieldController.removeSingleFieldListener( + fieldId: fieldId, + onFieldChanged: onFieldChanged, + ); + } + } + + void _onFieldChangedListener(FieldInfo fieldInfo) { + // reloadOnFieldChanged should be true if you want to reload the cell + // data when the corresponding field is changed. + // For example: + // ¥12 -> $12 + if (_cellDataLoader.reloadOnFieldChange) { + _loadData(); + } } /// Get the cell data. The cell data will be read from the cache first, @@ -218,6 +235,11 @@ class CellController { await _cellListener?.stop(); _cellListener = null; + _fieldController.removeSingleFieldListener( + fieldId: fieldId, + onFieldChanged: _onFieldChangedListener, + ); + _loadDataOperation?.cancel(); _saveDataOperation?.cancel(); _cellDataNotifier?.dispose(); diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_data_loader.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_data_loader.dart index c5502bd8b1..791423901f 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_data_loader.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_data_loader.dart @@ -37,12 +37,7 @@ class CellDataLoader { (result) => result.fold( (CellPB cell) { try { - // Return null if the data of the cell is empty. - if (cell.data.isEmpty) { - return null; - } else { - return parser.parserData(cell.data); - } + return parser.parserData(cell.data); } catch (e, s) { Log.error('$parser parser cellData failed, $e'); Log.error('Stack trace \n $s'); diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_cell_bloc.dart index a8983a98c5..1a5a149c9a 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_cell_bloc.dart @@ -30,8 +30,7 @@ class FieldCellBloc extends Bloc { emit(state.copyWith(width: width)); }, endUpdateWidth: () { - if (state.width != - state.fieldInfo.fieldSettings?.width.toDouble()) { + if (state.width != state.fieldInfo.width) { _fieldSettingsService.updateFieldSettings( fieldId: state.fieldInfo.id, width: state.width, diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_editor_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_editor_bloc.dart index 15f00e79b4..b33f9f44ee 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_editor_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_editor_bloc.dart @@ -99,6 +99,14 @@ class FieldEditorBloc extends Bloc { ); _logIfError(result); }, + toggleWrapCellContent: () async { + final currentWrap = state.field.wrapCellContent ?? false; + final result = await fieldSettingsService.updateFieldSettings( + fieldId: state.field.id, + wrapCellContent: !currentWrap, + ); + _logIfError(result); + }, ); }, ); @@ -148,6 +156,8 @@ class FieldEditorEvent with _$FieldEditorEvent { const factory FieldEditorEvent.insertRight() = _InsertRight; const factory FieldEditorEvent.toggleFieldVisibility() = _ToggleFieldVisiblity; + const factory FieldEditorEvent.toggleWrapCellContent() = + _ToggleWrapCellContent; } @freezed diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_info.dart b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_info.dart index 1612ab6a23..1022b1f839 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_info.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_info.dart @@ -31,8 +31,12 @@ class FieldInfo with _$FieldInfo { bool get isPrimary => field.isPrimary; + double? get width => fieldSettings?.width.toDouble(); + FieldVisibility? get visibility => fieldSettings?.visibility; + bool? get wrapCellContent => fieldSettings?.wrapCellContent; + bool get canBeGroup { switch (field.fieldType) { case FieldType.URL: diff --git a/frontend/appflowy_flutter/lib/plugins/database/domain/field_settings_service.dart b/frontend/appflowy_flutter/lib/plugins/database/domain/field_settings_service.dart index 916371f752..0c36d1864e 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/domain/field_settings_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/domain/field_settings_service.dart @@ -58,6 +58,7 @@ class FieldSettingsBackendService { required String fieldId, FieldVisibility? fieldVisibility, double? width, + bool? wrapCellContent, }) { final FieldSettingsChangesetPB payload = FieldSettingsChangesetPB.create() ..viewId = viewId @@ -71,6 +72,10 @@ class FieldSettingsBackendService { payload.width = width.round(); } + if (wrapCellContent != null) { + payload.wrapCellContent = wrapCellContent; + } + return DatabaseEventUpdateFieldSettings(payload).send(); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/mobile_grid_page.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/mobile_grid_page.dart index 234192cc1d..80952f30f2 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/mobile_grid_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/mobile_grid_page.dart @@ -441,7 +441,7 @@ class _AddRowButton extends StatelessWidget { double getMobileGridContentWidth(List fields) { final visibleFields = fields.where( - (field) => field.fieldSettings?.visibility != FieldVisibility.AlwaysHidden, + (field) => field.visibility != FieldVisibility.AlwaysHidden, ); return (visibleFields.length + 1) * 200 + GridSize.horizontalHeaderPadding * 2; diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/card/card_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/card/card_bloc.dart index 1db75c0a73..3cb47d9f43 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/card/card_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/card/card_bloc.dart @@ -113,7 +113,7 @@ List _makeCells( cellContexts.removeWhere((cellContext) { final fieldInfo = fieldController.getField(cellContext.fieldId); return fieldInfo == null || - !(fieldInfo.fieldSettings?.visibility.isVisibleState() ?? false) || + !(fieldInfo.visibility?.isVisibleState() ?? false) || (groupFieldId != null && cellContext.fieldId == groupFieldId); }); return cellContexts.toList(); diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/date_card_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/date_card_cell.dart index 8e3485c9f0..3b47971fdb 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/date_card_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/date_card_cell.dart @@ -41,7 +41,7 @@ class _DateCellState extends State { widget.databaseController, widget.cellContext, ).as(), - )..add(const DateCellEvent.initial()); + ); }, child: BlocBuilder( buildWhen: (previous, current) => previous.dateStr != current.dateStr, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/number_card_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/number_card_cell.dart index 7906a445bc..ee160f09be 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/number_card_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/number_card_cell.dart @@ -41,7 +41,7 @@ class _NumberCellState extends State { widget.databaseController, widget.cellContext, ).as(), - )..add(const NumberCellEvent.initial()); + ); }, child: BlocBuilder( buildWhen: (previous, current) => previous.content != current.content, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/select_option_card_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/select_option_card_cell.dart index fdca339deb..b705e93abb 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/select_option_card_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/select_option_card_cell.dart @@ -46,7 +46,7 @@ class _SelectOptionCellState extends State { widget.databaseController, widget.cellContext, ).as(), - )..add(const SelectOptionCellEvent.initial()); + ); }, child: BlocBuilder( buildWhen: (previous, current) { diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/text_card_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/text_card_cell.dart index 8e53f0dcf5..a36d539bb1 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/text_card_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/text_card_cell.dart @@ -52,7 +52,7 @@ class _TextCellState extends State { widget.databaseController, widget.cellContext, ).as(), - )..add(const TextCellEvent.initial()); + ); late final TextEditingController _textEditingController = TextEditingController(text: cellBloc.state.content); final focusNode = SingleListenerFocusNode(); diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/timestamp_card_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/timestamp_card_cell.dart index cabb23a444..5e560bbee4 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/timestamp_card_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/timestamp_card_cell.dart @@ -41,7 +41,7 @@ class _TimestampCellState extends State { widget.databaseController, widget.cellContext, ).as(), - )..add(const TimestampCellEvent.initial()); + ); }, child: BlocBuilder( buildWhen: (previous, current) => previous.dateStr != current.dateStr, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/url_card_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/url_card_cell.dart index 4adcbd188c..92b210d184 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/url_card_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/card_cell_skeleton/url_card_cell.dart @@ -41,7 +41,7 @@ class _URLCellState extends State { widget.databaseController, widget.cellContext, ).as(), - )..add(const URLCellEvent.initial()); + ); }, child: BlocBuilder( buildWhen: (previous, current) => previous.content != current.content, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_date_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_date_cell.dart index 3cbd2ba9b3..537207c12c 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_date_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_date_cell.dart @@ -27,27 +27,15 @@ class DesktopGridDateCellSkin extends IEditableDateCellSkin { direction: PopoverDirection.bottomWithLeftAligned, constraints: BoxConstraints.loose(const Size(260, 620)), margin: EdgeInsets.zero, - child: Container( + child: Align( alignment: AlignmentDirectional.centerStart, - padding: GridSize.cellContentInsets, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: FlowyText.medium( - state.dateStr, - overflow: TextOverflow.ellipsis, + child: state.fieldInfo.wrapCellContent ?? false + ? _buildCellContent(state) + : SingleChildScrollView( + physics: const NeverScrollableScrollPhysics(), + scrollDirection: Axis.horizontal, + child: _buildCellContent(state), ), - ), - if (state.data?.reminderId.isNotEmpty ?? false) ...[ - const HSpace(4), - FlowyTooltip( - message: LocaleKeys.grid_field_reminderOnDateTooltip.tr(), - child: const FlowySvg(FlowySvgs.clock_alarm_s), - ), - ], - ], - ), ), popupBuilder: (BuildContext popoverContent) { return DateCellEditor( @@ -60,4 +48,30 @@ class DesktopGridDateCellSkin extends IEditableDateCellSkin { }, ); } + + Widget _buildCellContent(DateCellState state) { + final wrap = state.fieldInfo.wrapCellContent ?? false; + return Padding( + padding: GridSize.cellContentInsets, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: FlowyText.medium( + state.dateStr, + overflow: wrap ? null : TextOverflow.ellipsis, + maxLines: wrap ? null : 1, + ), + ), + if (state.data?.reminderId.isNotEmpty ?? false) ...[ + const HSpace(4), + FlowyTooltip( + message: LocaleKeys.grid_field_reminderOnDateTooltip.tr(), + child: const FlowySvg(FlowySvgs.clock_alarm_s), + ), + ], + ], + ), + ); + } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_number_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_number_cell.dart index 33d290bb66..04368bc725 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_number_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_number_cell.dart @@ -2,6 +2,7 @@ import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/number_cell_bloc.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import '../editable_cell_skeleton/number.dart'; @@ -19,7 +20,7 @@ class DesktopGridNumberCellSkin extends IEditableNumberCellSkin { focusNode: focusNode, onEditingComplete: () => focusNode.unfocus(), onSubmitted: (_) => focusNode.unfocus(), - maxLines: null, + maxLines: context.watch().state.wrap ? null : 1, style: Theme.of(context).textTheme.bodyMedium, textInputAction: TextInputAction.done, decoration: InputDecoration( diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_relation_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_relation_cell.dart index 471edd364a..80649f6a02 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_relation_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_relation_cell.dart @@ -3,6 +3,7 @@ import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/database/widgets/cell_editor/relation_cell_editor.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -32,13 +33,52 @@ class DesktopGridRelationCellSkin extends IEditableRelationCellSkin { child: const RelationCellEditor(), ); }, - child: Container( + child: Align( alignment: AlignmentDirectional.centerStart, + child: state.wrap + ? _buildWrapRows(context, state.rows) + : _buildNoWrapRows(context, state.rows), + ), + ); + } + + Widget _buildWrapRows( + BuildContext context, + List rows, + ) { + return Padding( + padding: GridSize.cellContentInsets, + child: Wrap( + runSpacing: 4, + spacing: 4.0, + children: rows.map( + (row) { + final isEmpty = row.name.isEmpty; + return FlowyText.medium( + isEmpty ? LocaleKeys.grid_row_titlePlaceholder.tr() : row.name, + color: isEmpty ? Theme.of(context).hintColor : null, + decoration: TextDecoration.underline, + overflow: TextOverflow.ellipsis, + ); + }, + ).toList(), + ), + ); + } + + Widget _buildNoWrapRows( + BuildContext context, + List rows, + ) { + return SingleChildScrollView( + physics: const NeverScrollableScrollPhysics(), + scrollDirection: Axis.horizontal, + child: Padding( padding: GridSize.cellContentInsets, - child: Wrap( - runSpacing: 4.0, - spacing: 4.0, - children: state.rows.map( + child: SeparatedRow( + separatorBuilder: () => const HSpace(4.0), + mainAxisSize: MainAxisSize.min, + children: rows.map( (row) { final isEmpty = row.name.isEmpty; return FlowyText.medium( diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_select_option_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_select_option_cell.dart index 5a76dcdc4d..45b43efcec 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_select_option_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_select_option_cell.dart @@ -3,7 +3,7 @@ import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart' import 'package:appflowy/plugins/database/widgets/cell_editor/extension.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/select_option_cell_bloc.dart'; import 'package:appflowy/plugins/database/widgets/cell_editor/select_option_cell_editor.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/select_option_entities.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/widgets.dart'; @@ -23,6 +23,7 @@ class DesktopGridSelectOptionCellSkin extends IEditableSelectOptionCellSkin { controller: popoverController, constraints: const BoxConstraints.tightFor(width: 300), margin: EdgeInsets.zero, + triggerActions: PopoverTriggerFlags.none, direction: PopoverDirection.bottomWithLeftAligned, popupBuilder: (BuildContext popoverContext) { return SelectOptionCellEditor( @@ -32,35 +33,67 @@ class DesktopGridSelectOptionCellSkin extends IEditableSelectOptionCellSkin { onClose: () => cellContainerNotifier.isFocus = false, child: BlocBuilder( builder: (context, state) { - return Container( + return Align( alignment: AlignmentDirectional.centerStart, - padding: GridSize.cellContentInsets, - child: state.selectedOptions.isEmpty - ? const SizedBox.shrink() - : _buildOptions(context, state.selectedOptions), + child: state.wrap + ? _buildWrapOptions(context, state.selectedOptions) + : _buildNoWrapOptions(context, state.selectedOptions), ); }, ), ); } - Widget _buildOptions(context, List options) { - return Wrap( - runSpacing: 4, - children: options.map( - (option) { - return Padding( - padding: const EdgeInsets.only(right: 4), - child: SelectOptionTag( - option: option, - padding: const EdgeInsets.symmetric( - vertical: 1, - horizontal: 8, + Widget _buildWrapOptions(BuildContext context, List options) { + return Padding( + padding: GridSize.cellContentInsets, + child: Wrap( + runSpacing: 4, + children: options.map( + (option) { + return Padding( + padding: const EdgeInsets.only(right: 4), + child: SelectOptionTag( + option: option, + padding: const EdgeInsets.symmetric( + vertical: 1, + horizontal: 8, + ), ), - ), - ); - }, - ).toList(), + ); + }, + ).toList(), + ), + ); + } + + Widget _buildNoWrapOptions( + BuildContext context, + List options, + ) { + return SingleChildScrollView( + physics: const NeverScrollableScrollPhysics(), + scrollDirection: Axis.horizontal, + child: Padding( + padding: GridSize.cellContentInsets, + child: Row( + mainAxisSize: MainAxisSize.min, + children: options.map( + (option) { + return Padding( + padding: const EdgeInsets.only(right: 4), + child: SelectOptionTag( + option: option, + padding: const EdgeInsets.symmetric( + vertical: 1, + horizontal: 8, + ), + ), + ); + }, + ).toList(), + ), + ), ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_text_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_text_cell.dart index 2ead6eb5ad..8001590840 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_text_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_text_cell.dart @@ -44,7 +44,7 @@ class DesktopGridTextCellSkin extends IEditableTextCellSkin { child: TextField( controller: textEditingController, focusNode: focusNode, - maxLines: null, + maxLines: context.watch().state.wrap ? null : 1, style: Theme.of(context).textTheme.bodyMedium, decoration: const InputDecoration( border: InputBorder.none, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_timestamp_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_timestamp_cell.dart index 13c3381ff9..d1b6131680 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_timestamp_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_timestamp_cell.dart @@ -16,10 +16,23 @@ class DesktopGridTimestampCellSkin extends IEditableTimestampCellSkin { ) { return Container( alignment: AlignmentDirectional.centerStart, + child: state.wrap + ? _buildCellContent(state) + : SingleChildScrollView( + physics: const NeverScrollableScrollPhysics(), + scrollDirection: Axis.horizontal, + child: _buildCellContent(state), + ), + ); + } + + Widget _buildCellContent(TimestampCellState state) { + return Padding( padding: GridSize.cellContentInsets, child: FlowyText.medium( state.dateStr, - maxLines: null, + overflow: state.wrap ? null : TextOverflow.ellipsis, + maxLines: state.wrap ? null : 1, ), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_url_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_url_cell.dart index 04861c71f8..0fce67a29c 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_url_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_url_cell.dart @@ -11,6 +11,7 @@ import 'package:appflowy/workspace/presentation/home/toast.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import '../editable_cell_skeleton/url.dart'; @@ -24,28 +25,31 @@ class DesktopGridURLSkin extends IEditableURLCellSkin { TextEditingController textEditingController, URLCellDataNotifier cellDataNotifier, ) { - return TextField( - controller: textEditingController, - focusNode: focusNode, - maxLines: null, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.primary, - decoration: TextDecoration.underline, - ), - decoration: InputDecoration( - contentPadding: GridSize.cellContentInsets, - border: InputBorder.none, - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none, - errorBorder: InputBorder.none, - disabledBorder: InputBorder.none, - hintStyle: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith(color: Theme.of(context).hintColor), - isDense: true, + return BlocSelector( + selector: (state) => state.wrap, + builder: (context, wrap) => TextField( + controller: textEditingController, + focusNode: focusNode, + maxLines: wrap ? null : 1, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.primary, + decoration: TextDecoration.underline, + ), + decoration: InputDecoration( + contentPadding: GridSize.cellContentInsets, + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, + hintStyle: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(color: Theme.of(context).hintColor), + isDense: true, + ), + onTapOutside: (_) => focusNode.unfocus(), ), - onTapOutside: (_) => focusNode.unfocus(), ); } @@ -179,8 +183,7 @@ class _VisitURLAccessoryState extends State<_VisitURLAccessory> bool enable() => widget.cellDataNotifier.value.isNotEmpty; @override - void onTap() => - openUrlCellLink(widget.cellDataNotifier.value); + void onTap() => openUrlCellLink(widget.cellDataNotifier.value); } class _URLAccessoryIconContainer extends StatelessWidget { @@ -190,9 +193,8 @@ class _URLAccessoryIconContainer extends StatelessWidget { @override Widget build(BuildContext context) { - return SizedBox( - width: 26, - height: 26, + return SizedBox.square( + dimension: 26, child: Padding( padding: const EdgeInsets.all(3.0), child: child, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/date.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/date.dart index d6c2ae8434..4b12c780d1 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/date.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/date.dart @@ -57,7 +57,7 @@ class _DateCellState extends GridCellState { widget.databaseController, widget.cellContext, ).as(), - )..add(const DateCellEvent.initial()); + ); @override void dispose() { diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/number.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/number.dart index 689b411550..bae9523207 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/number.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/number.dart @@ -58,7 +58,7 @@ class _NumberCellState extends GridEditableTextCell { widget.databaseController, widget.cellContext, ).as(), - )..add(const NumberCellEvent.initial()); + ); @override void initState() { @@ -81,12 +81,16 @@ class _NumberCellState extends GridEditableTextCell { child: BlocListener( listener: (context, state) => _textEditingController.text = state.content, - child: widget.skin.build( - context, - widget.cellContainerNotifier, - cellBloc, - focusNode, - _textEditingController, + child: Builder( + builder: (context) { + return widget.skin.build( + context, + widget.cellContainerNotifier, + cellBloc, + focusNode, + _textEditingController, + ); + }, ), ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/select_option.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/select_option.dart index 32df29a60a..b45018f4f5 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/select_option.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/select_option.dart @@ -64,7 +64,7 @@ class _SelectOptionCellState extends GridCellState { widget.databaseController, widget.cellContext, ).as(), - )..add(const SelectOptionCellEvent.initial()); + ); @override void dispose() { diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/text.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/text.dart index 622b6a471d..18423ccbd0 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/text.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/text.dart @@ -58,7 +58,7 @@ class _TextCellState extends GridEditableTextCell { widget.databaseController, widget.cellContext, ).as(), - )..add(const TextCellEvent.initial()); + ); @override void initState() { @@ -82,12 +82,16 @@ class _TextCellState extends GridEditableTextCell { listener: (context, state) { _textEditingController.text = state.content; }, - child: widget.skin.build( - context, - widget.cellContainerNotifier, - cellBloc, - focusNode, - _textEditingController, + child: Builder( + builder: (context) { + return widget.skin.build( + context, + widget.cellContainerNotifier, + cellBloc, + focusNode, + _textEditingController, + ); + }, ), ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/timestamp.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/timestamp.dart index 5296ec7943..6c00e8b4b4 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/timestamp.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/timestamp.dart @@ -57,7 +57,7 @@ class _TimestampCellState extends GridCellState { widget.databaseController, widget.cellContext, ).as(), - )..add(const TimestampCellEvent.initial()); + ); @override void dispose() { diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/url.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/url.dart index 3628f6c511..2646aadf1f 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/url.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/url.dart @@ -85,7 +85,7 @@ class _GridURLCellState extends GridEditableTextCell { widget.databaseController, widget.cellContext, ).as(), - )..add(const URLCellEvent.initial()); + ); @override SingleListenerFocusNode focusNode = SingleListenerFocusNode(); diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/date_editor.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/date_editor.dart index f2258d2f78..e0d7c3944f 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/date_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/date_editor.dart @@ -42,7 +42,7 @@ class _DateCellEditor extends State { create: (context) => DateCellEditorBloc( reminderBloc: getIt(), cellController: widget.cellController, - )..add(const DateCellEditorEvent.initial()), + ), ), ], child: BlocBuilder( diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart index 96b04e7b41..e417ca2d8f 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart @@ -1,7 +1,5 @@ import 'dart:typed_data'; -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/field/field_controller.dart'; @@ -12,12 +10,14 @@ import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_option_separator.dart'; import 'package:appflowy/util/field_type_extension.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart'; +import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:styled_widget/styled_widget.dart'; import 'field_type_list.dart'; import 'type_option_editor/builder.dart'; @@ -87,10 +87,12 @@ class _FieldEditorState extends State { mainAxisSize: MainAxisSize.min, children: [ FieldNameTextField( - padding: const EdgeInsets.fromLTRB(4, 4, 4, 8), + padding: const EdgeInsets.fromLTRB(12, 12, 12, 8), textEditingController: textController, ), + VSpace(GridSize.typeOptionSeparatorHeight), _EditFieldButton( + padding: const EdgeInsets.symmetric(horizontal: 8.0), onTap: () { setState(() => _currentPage = FieldEditorPage.details); }, @@ -107,8 +109,11 @@ class _FieldEditorState extends State { _actionCell(FieldAction.clearData), VSpace(GridSize.typeOptionSeparatorHeight), _actionCell(FieldAction.delete), + const TypeOptionSeparator(spacing: 8.0), + _actionCell(FieldAction.wrap), + const VSpace(8.0), ], - ).padding(all: 8.0), + ), ); } @@ -124,24 +129,32 @@ class _FieldEditorState extends State { Widget _actionCell(FieldAction action) { return BlocBuilder( - builder: (context, state) => FieldActionCell( - viewId: widget.viewId, - fieldInfo: state.field, - action: action, + builder: (context, state) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: FieldActionCell( + viewId: widget.viewId, + fieldInfo: state.field, + action: action, + ), ), ); } } class _EditFieldButton extends StatelessWidget { - const _EditFieldButton({this.onTap}); + const _EditFieldButton({ + required this.padding, + this.onTap, + }); + final EdgeInsetsGeometry padding; final void Function()? onTap; @override Widget build(BuildContext context) { - return SizedBox( + return Container( height: GridSize.popoverItemHeight, + padding: padding, child: FlowyButton( leftIcon: const FlowySvg(FlowySvgs.edit_s), text: FlowyText.medium( @@ -184,10 +197,11 @@ class FieldActionCell extends StatelessWidget { ), onHover: (_) => popoverMutex?.close(), onTap: () => action.run(context, viewId, fieldInfo), - leftIcon: action.icon( + leftIcon: action.leading( fieldInfo, enable ? null : Theme.of(context).disabledColor, ), + rightIcon: action.trailing(context, fieldInfo), ); } } @@ -198,10 +212,11 @@ enum FieldAction { toggleVisibility, duplicate, clearData, - delete; + delete, + wrap; - Widget icon(FieldInfo fieldInfo, Color? color) { - late final FlowySvgData svgData; + Widget? leading(FieldInfo fieldInfo, Color? color) { + FlowySvgData? svgData; switch (this) { case FieldAction.insertLeft: svgData = FlowySvgs.arrow_s; @@ -220,6 +235,11 @@ enum FieldAction { svgData = FlowySvgs.reload_s; case FieldAction.delete: svgData = FlowySvgs.delete_s; + default: + } + + if (svgData == null) { + return null; } final icon = FlowySvg( svgData, @@ -231,6 +251,21 @@ enum FieldAction { : icon; } + Widget? trailing(BuildContext context, FieldInfo fieldInfo) { + if (this == FieldAction.wrap) { + return Toggle( + value: fieldInfo.wrapCellContent ?? false, + onChanged: (_) => context + .read() + .add(const FieldEditorEvent.toggleWrapCellContent()), + style: ToggleStyle.big, + padding: EdgeInsets.zero, + ); + } + + return null; + } + String title(FieldInfo fieldInfo) { switch (this) { case FieldAction.insertLeft: @@ -250,6 +285,8 @@ enum FieldAction { return LocaleKeys.grid_field_clear.tr(); case FieldAction.delete: return LocaleKeys.grid_field_delete.tr(); + case FieldAction.wrap: + return LocaleKeys.grid_field_wrap.tr(); } } @@ -308,6 +345,11 @@ enum FieldAction { ).show(context); PopoverContainer.of(context).close(); break; + case FieldAction.wrap: + context + .read() + .add(const FieldEditorEvent.toggleWrapCellContent()); + break; } } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/cells/cell_container.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/cells/cell_container.dart index a878192106..22a9ce1381 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/cells/cell_container.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/cells/cell_container.dart @@ -117,7 +117,7 @@ class _GridCellEnterRegion extends StatelessWidget { onExit: (p) => CellContainerNotifier.of(context, listen: false).isHover = false, child: Stack( - alignment: AlignmentDirectional.center, + alignment: Alignment.center, fit: StackFit.expand, children: children, ), diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 8f8c8dd53c..53850f1d92 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -653,6 +653,7 @@ "insertRight": "Insert Right", "duplicate": "Duplicate", "delete": "Delete", + "wrap": "Wrap", "clear": "Clear cells", "textFieldName": "Text", "checkboxFieldName": "Checkbox", @@ -1474,4 +1475,4 @@ "betaTooltip": "We currently only support searching for pages", "fromTrashHint": "From trash" } -} +} \ No newline at end of file diff --git a/frontend/rust-lib/flowy-database2/src/entities/field_settings_entities.rs b/frontend/rust-lib/flowy-database2/src/entities/field_settings_entities.rs index a995607952..1d59b0b87e 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/field_settings_entities.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/field_settings_entities.rs @@ -1,11 +1,13 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; +use lib_infra::validator_fn::required_not_empty_str; use std::ops::Deref; +use validator::Validate; use crate::entities::parser::NotEmptyStr; use crate::entities::RepeatedFieldIdPB; use crate::impl_into_field_visibility; -use crate::services::field_settings::{FieldSettings, FieldSettingsChangesetParams}; +use crate::services::field_settings::FieldSettings; /// Defines the field settings for a field in a view. #[derive(Debug, Default, Clone, ProtoBuf, Eq, PartialEq)] @@ -18,6 +20,9 @@ pub struct FieldSettingsPB { #[pb(index = 3)] pub width: i32, + + #[pb(index = 4)] + pub wrap_cell_content: bool, } impl From for FieldSettingsPB { @@ -26,6 +31,7 @@ impl From for FieldSettingsPB { field_id: value.field_id, visibility: value.visibility, width: value.width, + wrap_cell_content: value.wrap_cell_content, } } } @@ -93,11 +99,13 @@ impl std::convert::From> for RepeatedFieldSettingsPB { } } -#[derive(Debug, Default, Clone, ProtoBuf)] +#[derive(Debug, Default, Clone, ProtoBuf, Validate)] pub struct FieldSettingsChangesetPB { + #[validate(custom = "required_not_empty_str")] #[pb(index = 1)] pub view_id: String, + #[validate(custom = "required_not_empty_str")] #[pb(index = 2)] pub field_id: String, @@ -106,28 +114,7 @@ pub struct FieldSettingsChangesetPB { #[pb(index = 4, one_of)] pub width: Option, -} -impl From for FieldSettingsChangesetPB { - fn from(value: FieldSettingsChangesetParams) -> Self { - Self { - view_id: value.view_id, - field_id: value.field_id, - visibility: value.visibility, - width: value.width, - } - } -} - -impl TryFrom for FieldSettingsChangesetParams { - type Error = ErrorCode; - - fn try_from(value: FieldSettingsChangesetPB) -> Result { - Ok(FieldSettingsChangesetParams { - view_id: value.view_id, - field_id: value.field_id, - visibility: value.visibility, - width: value.width, - }) - } + #[pb(index = 5, one_of)] + pub wrap_cell_content: Option, } diff --git a/frontend/rust-lib/flowy-database2/src/event_handler.rs b/frontend/rust-lib/flowy-database2/src/event_handler.rs index 25645417ba..c8ae34433e 100644 --- a/frontend/rust-lib/flowy-database2/src/event_handler.rs +++ b/frontend/rust-lib/flowy-database2/src/event_handler.rs @@ -14,7 +14,6 @@ use crate::services::field::{ type_option_data_from_pb, ChecklistCellChangeset, DateCellChangeset, RelationCellChangeset, SelectOptionCellChangeset, }; -use crate::services::field_settings::FieldSettingsChangesetParams; use crate::services::group::GroupChangeset; use crate::services::share::csv::CSVFormat; @@ -944,7 +943,7 @@ pub(crate) async fn update_field_settings_handler( manager: AFPluginState>, ) -> FlowyResult<()> { let manager = upgrade_manager(manager)?; - let params: FieldSettingsChangesetParams = data.into_inner().try_into()?; + let params = data.try_into_inner()?; let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?; database_editor .update_field_settings_with_changeset(params) diff --git a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs index e4ddc2c619..7eb3a3ff6a 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs @@ -12,9 +12,7 @@ use crate::services::field::{ type_option_data_from_pb, ChecklistCellChangeset, RelationTypeOption, SelectOptionCellChangeset, StrCellData, TimestampCellData, TypeOptionCellDataHandler, TypeOptionCellExt, }; -use crate::services::field_settings::{ - default_field_settings_by_layout_map, FieldSettings, FieldSettingsChangesetParams, -}; +use crate::services::field_settings::{default_field_settings_by_layout_map, FieldSettings}; use crate::services::filter::{Filter, FilterChangeset}; use crate::services::group::{default_group_setting, GroupChangeset, GroupSetting, RowChangeset}; use crate::services::share::csv::{CSVExport, CSVFormat}; @@ -1296,17 +1294,10 @@ impl DatabaseEditor { pub async fn update_field_settings_with_changeset( &self, - params: FieldSettingsChangesetParams, + params: FieldSettingsChangesetPB, ) -> FlowyResult<()> { let view = self.database_views.get_view_editor(¶ms.view_id).await?; - view - .v_update_field_settings( - ¶ms.view_id, - ¶ms.field_id, - params.visibility, - params.width, - ) - .await?; + view.v_update_field_settings(params).await?; Ok(()) } @@ -1735,45 +1726,43 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl { field_settings } - fn update_field_settings( - &self, - view_id: &str, - field_id: &str, - visibility: Option, - width: Option, - ) { - let field_settings_map = self.get_field_settings(view_id, &[field_id.to_string()]); + fn update_field_settings(&self, params: FieldSettingsChangesetPB) { + let field_settings_map = self.get_field_settings(¶ms.view_id, &[params.field_id.clone()]); - let new_field_settings = if let Some(field_settings) = field_settings_map.get(field_id) { - FieldSettings { - field_id: field_settings.field_id.clone(), - visibility: visibility.unwrap_or(field_settings.visibility.clone()), - width: width.unwrap_or(field_settings.width), - } - } else { - let layout_type = self.get_layout_for_view(view_id); - let default_field_settings = default_field_settings_by_layout_map() - .get(&layout_type) - .unwrap() - .to_owned(); - let field_settings = - FieldSettings::from_any_map(field_id, layout_type, &default_field_settings); - FieldSettings { - field_id: field_settings.field_id.clone(), - visibility: visibility.unwrap_or(field_settings.visibility), - width: width.unwrap_or(field_settings.width), - } + let field_settings = field_settings_map + .get(¶ms.field_id) + .cloned() + .unwrap_or_else(|| { + let layout_type = self.get_layout_for_view(¶ms.view_id); + let default_field_settings = default_field_settings_by_layout_map(); + let default_field_settings = default_field_settings.get(&layout_type).unwrap(); + + FieldSettings::from_any_map(¶ms.field_id, layout_type, default_field_settings) + }); + + let new_field_settings = FieldSettings { + visibility: params + .visibility + .unwrap_or_else(|| field_settings.visibility.clone()), + width: params.width.unwrap_or(field_settings.width), + wrap_cell_content: params + .wrap_cell_content + .unwrap_or(field_settings.wrap_cell_content), + ..field_settings }; self.database.lock().update_field_settings( - view_id, - Some(vec![field_id.to_string()]), + ¶ms.view_id, + Some(vec![params.field_id]), new_field_settings.clone(), ); - send_notification(view_id, DatabaseNotification::DidUpdateFieldSettings) - .payload(FieldSettingsPB::from(new_field_settings)) - .send() + send_notification( + ¶ms.view_id, + DatabaseNotification::DidUpdateFieldSettings, + ) + .payload(FieldSettingsPB::from(new_field_settings)) + .send() } fn update_calculation(&self, view_id: &str, calculation: Calculation) { diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs index a3522d8895..e952cf4701 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs @@ -15,10 +15,10 @@ use lib_dispatch::prelude::af_spawn; use crate::entities::{ CalendarEventPB, CreateRowParams, CreateRowPayloadPB, DatabaseLayoutMetaPB, - DatabaseLayoutSettingPB, DeleteSortPayloadPB, FieldType, FieldVisibility, GroupChangesPB, - GroupPB, LayoutSettingChangeset, LayoutSettingParams, RemoveCalculationChangesetPB, - ReorderSortPayloadPB, RowMetaPB, RowsChangePB, SortChangesetNotificationPB, SortPB, - UpdateCalculationChangesetPB, UpdateSortPayloadPB, + DatabaseLayoutSettingPB, DeleteSortPayloadPB, FieldSettingsChangesetPB, FieldType, + GroupChangesPB, GroupPB, LayoutSettingChangeset, LayoutSettingParams, + RemoveCalculationChangesetPB, ReorderSortPayloadPB, RowMetaPB, RowsChangePB, + SortChangesetNotificationPB, SortPB, UpdateCalculationChangesetPB, UpdateSortPayloadPB, }; use crate::notification::{send_notification, DatabaseNotification}; use crate::services::calculations::{Calculation, CalculationChangeset, CalculationsController}; @@ -1034,20 +1034,8 @@ impl DatabaseViewEditor { self.delegate.get_field_settings(&self.view_id, field_ids) } - // pub async fn v_get_all_field_settings(&self) -> HashMap { - // self.delegate.get_all_field_settings(&self.view_id) - // } - - pub async fn v_update_field_settings( - &self, - view_id: &str, - field_id: &str, - visibility: Option, - width: Option, - ) -> FlowyResult<()> { - self - .delegate - .update_field_settings(view_id, field_id, visibility, width); + pub async fn v_update_field_settings(&self, params: FieldSettingsChangesetPB) -> FlowyResult<()> { + self.delegate.update_field_settings(params); Ok(()) } diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs index 867eca4dc5..3a912646cd 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs @@ -11,7 +11,7 @@ use flowy_error::FlowyError; use lib_infra::future::{Fut, FutureResult}; use lib_infra::priority_task::TaskDispatcher; -use crate::entities::{FieldType, FieldVisibility}; +use crate::entities::{FieldSettingsChangesetPB, FieldType}; use crate::services::calculations::Calculation; use crate::services::field::TypeOptionCellDataHandler; use crate::services::field_settings::FieldSettings; @@ -126,11 +126,5 @@ pub trait DatabaseViewOperation: Send + Sync + 'static { field_ids: &[String], ) -> HashMap; - fn update_field_settings( - &self, - view_id: &str, - field_id: &str, - visibility: Option, - width: Option, - ); + fn update_field_settings(&self, params: FieldSettingsChangesetPB); } diff --git a/frontend/rust-lib/flowy-database2/src/services/field_settings/entities.rs b/frontend/rust-lib/flowy-database2/src/services/field_settings/entities.rs index 600643ca23..586539df45 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field_settings/entities.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field_settings/entities.rs @@ -10,12 +10,13 @@ pub struct FieldSettings { pub field_id: String, pub visibility: FieldVisibility, pub width: i32, + pub wrap_cell_content: bool, } pub const VISIBILITY: &str = "visibility"; pub const WIDTH: &str = "width"; - pub const DEFAULT_WIDTH: i32 = 150; +pub const WRAP_CELL_CONTENT: &str = "wrap"; impl FieldSettings { pub fn from_any_map( @@ -31,11 +32,15 @@ impl FieldSettings { .get_i64_value(WIDTH) .map(|value| value as i32) .unwrap_or(DEFAULT_WIDTH); + let wrap_cell_content = field_settings + .get_bool_value(WRAP_CELL_CONTENT) + .unwrap_or(false); Self { field_id: field_id.to_string(), visibility, width, + wrap_cell_content, } } } @@ -45,15 +50,7 @@ impl From for FieldSettingsMap { FieldSettingsMapBuilder::new() .insert_i64_value(VISIBILITY, field_settings.visibility.into()) .insert_i64_value(WIDTH, field_settings.width as i64) + .insert_bool_value(WRAP_CELL_CONTENT, field_settings.wrap_cell_content) .build() } } - -/// Contains the changeset to a field's settings. -/// A `Some` value constitutes a change in that particular setting -pub struct FieldSettingsChangesetParams { - pub view_id: String, - pub field_id: String, - pub visibility: Option, - pub width: Option, -} diff --git a/frontend/rust-lib/flowy-database2/src/services/field_settings/field_settings_builder.rs b/frontend/rust-lib/flowy-database2/src/services/field_settings/field_settings_builder.rs index 4b4b49412b..3ddbf16a8f 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field_settings/field_settings_builder.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field_settings/field_settings_builder.rs @@ -7,7 +7,7 @@ use collab_database::views::{ use strum::IntoEnumIterator; use crate::entities::FieldVisibility; -use crate::services::field_settings::{FieldSettings, VISIBILITY}; +use crate::services::field_settings::{FieldSettings, DEFAULT_WIDTH, VISIBILITY}; /// Helper struct to create a new field setting pub struct FieldSettingsBuilder { @@ -19,8 +19,10 @@ impl FieldSettingsBuilder { let field_settings = FieldSettings { field_id: field_id.to_string(), visibility: FieldVisibility::AlwaysShown, - width: 150, + width: DEFAULT_WIDTH, + wrap_cell_content: false, }; + Self { inner: field_settings, } diff --git a/frontend/rust-lib/flowy-database2/tests/database/field_settings_test/script.rs b/frontend/rust-lib/flowy-database2/tests/database/field_settings_test/script.rs index cbea2fe754..b87684163b 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/field_settings_test/script.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/field_settings_test/script.rs @@ -1,5 +1,4 @@ -use flowy_database2::entities::FieldVisibility; -use flowy_database2::services::field_settings::FieldSettingsChangesetParams; +use flowy_database2::entities::{FieldSettingsChangesetPB, FieldVisibility}; use crate::database::database_editor::DatabaseEditorTest; @@ -60,11 +59,12 @@ impl FieldSettingsTest { visibility: Option, width: Option, ) { - let params = FieldSettingsChangesetParams { + let params = FieldSettingsChangesetPB { view_id: self.view_id.clone(), field_id, visibility, width, + wrap_cell_content: None, }; let _ = self .editor