diff --git a/frontend/app_flowy/lib/plugins/board/application/card/board_text_cell_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/card/board_text_cell_bloc.dart index 3df8b029f8..9d1b14c605 100644 --- a/frontend/app_flowy/lib/plugins/board/application/card/board_text_cell_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/card/board_text_cell_bloc.dart @@ -27,6 +27,9 @@ class BoardTextCellBloc extends Bloc { emit(state.copyWith(content: text)); } }, + enableEdit: (bool enabled) { + emit(state.copyWith(enableEdit: enabled)); + }, ); }, ); @@ -57,6 +60,7 @@ class BoardTextCellBloc extends Bloc { class BoardTextCellEvent with _$BoardTextCellEvent { const factory BoardTextCellEvent.initial() = _InitialCell; const factory BoardTextCellEvent.updateText(String text) = _UpdateContent; + const factory BoardTextCellEvent.enableEdit(bool enabled) = _EnableEdit; const factory BoardTextCellEvent.didReceiveCellUpdate(String cellContent) = _DidReceiveCellUpdate; } @@ -65,10 +69,12 @@ class BoardTextCellEvent with _$BoardTextCellEvent { class BoardTextCellState with _$BoardTextCellState { const factory BoardTextCellState({ required String content, + required bool enableEdit, }) = _BoardTextCellState; factory BoardTextCellState.initial(GridCellController context) => BoardTextCellState( content: context.getCellData() ?? "", + enableEdit: false, ); } diff --git a/frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart index 7f1ae766e6..ad30d2b250 100644 --- a/frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart @@ -107,7 +107,10 @@ class BoardCardState with _$BoardCardState { factory BoardCardState.initial( RowPB rowPB, UnmodifiableListView cells) => - BoardCardState(rowPB: rowPB, cells: cells); + BoardCardState( + rowPB: rowPB, + cells: cells, + ); } class BoardCellEquatable extends Equatable { diff --git a/frontend/app_flowy/lib/plugins/board/presentation/card/board_cell.dart b/frontend/app_flowy/lib/plugins/board/presentation/card/board_cell.dart index 3c0c0298e9..4a7455f365 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/board_cell.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/board_cell.dart @@ -1,3 +1,54 @@ +import 'package:app_flowy/plugins/grid/application/prelude.dart'; +import 'package:flowy_infra/notifier.dart'; + abstract class FocusableBoardCell { set becomeFocus(bool isFocus); } + +class EditableCellNotifier { + final Notifier becomeFirstResponder = Notifier(); + + final Notifier resignFirstResponder = Notifier(); + + EditableCellNotifier(); +} + +class EditableRowNotifier { + Map cells = {}; + + void insertCell( + GridCellIdentifier cellIdentifier, + EditableCellNotifier notifier, + ) { + cells[EditableCellId.from(cellIdentifier)] = notifier; + } + + void becomeFirstResponder() { + for (final notifier in cells.values) { + notifier.becomeFirstResponder.notify(); + } + } + + void resignFirstResponder() { + for (final notifier in cells.values) { + notifier.resignFirstResponder.notify(); + } + } +} + +abstract class EditableCell { + EditableCellNotifier? get editableNotifier; +} + +class EditableCellId { + String fieldId; + String rowId; + + EditableCellId(this.rowId, this.fieldId); + + factory EditableCellId.from(GridCellIdentifier cellIdentifier) => + EditableCellId( + cellIdentifier.rowId, + cellIdentifier.fieldId, + ); +} diff --git a/frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart b/frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart index e0607db306..173f569f07 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart @@ -5,13 +5,18 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_c import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -class BoardSelectOptionCell extends StatefulWidget { +import 'board_cell.dart'; + +class BoardSelectOptionCell extends StatefulWidget with EditableCell { final String groupId; final GridCellControllerBuilder cellControllerBuilder; + @override + final EditableCellNotifier? editableNotifier; const BoardSelectOptionCell({ required this.groupId, required this.cellControllerBuilder, + this.editableNotifier, Key? key, }) : super(key: key); diff --git a/frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart b/frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart index 20b1a07085..49048d2472 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart @@ -4,15 +4,19 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/cell/cell_builder.da import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -class BoardTextCell extends StatefulWidget { +import 'board_cell.dart'; + +class BoardTextCell extends StatefulWidget with EditableCell { final String groupId; final bool isFocus; - + @override + final EditableCellNotifier? editableNotifier; final GridCellControllerBuilder cellControllerBuilder; const BoardTextCell({ required this.groupId, required this.cellControllerBuilder, + this.editableNotifier, this.isFocus = false, Key? key, }) : super(key: key); @@ -37,6 +41,18 @@ class _BoardTextCellState extends State { if (widget.isFocus) { focusNode.requestFocus(); } + + widget.editableNotifier?.becomeFirstResponder.addListener(() { + if (!mounted) return; + focusNode.requestFocus(); + _cellBloc.add(const BoardTextCellEvent.enableEdit(true)); + }); + + widget.editableNotifier?.resignFirstResponder.addListener(() { + if (!mounted) return; + _cellBloc.add(const BoardTextCellEvent.enableEdit(false)); + }); + super.initState(); } @@ -50,18 +66,26 @@ class _BoardTextCellState extends State { _controller.text = state.content; } }, - child: TextField( - controller: _controller, - focusNode: focusNode, - onChanged: (value) => focusChanged(), - onEditingComplete: () => focusNode.unfocus(), - maxLines: 1, - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), - decoration: const InputDecoration( - contentPadding: EdgeInsets.symmetric(vertical: 6), - border: InputBorder.none, - isDense: true, - ), + child: BlocBuilder( + buildWhen: (previous, current) => + previous.enableEdit != current.enableEdit, + builder: (context, state) { + return TextField( + // autofocus: true, + // enabled: state.enableEdit, + controller: _controller, + focusNode: focusNode, + onChanged: (value) => focusChanged(), + onEditingComplete: () => focusNode.unfocus(), + maxLines: 1, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + decoration: const InputDecoration( + contentPadding: EdgeInsets.symmetric(vertical: 6), + border: InputBorder.none, + isDense: true, + ), + ); + }, ), ), ); diff --git a/frontend/app_flowy/lib/plugins/board/presentation/card/card.dart b/frontend/app_flowy/lib/plugins/board/presentation/card/card.dart index 08331f6021..6c0aaf3c48 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/card.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/card.dart @@ -7,6 +7,7 @@ import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui_web.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'board_cell.dart'; import 'card_cell_builder.dart'; import 'card_container.dart'; @@ -36,9 +37,11 @@ class BoardCard extends StatefulWidget { class _BoardCardState extends State { late BoardCardBloc _cardBloc; + late EditableRowNotifier rowNotifier; @override void initState() { + rowNotifier = EditableRowNotifier(); _cardBloc = BoardCardBloc( gridId: widget.gridId, fieldId: widget.fieldId, @@ -58,7 +61,12 @@ class _BoardCardState extends State { builder: (context, state) { return BoardCardContainer( accessoryBuilder: (context) { - return [const _CardMoreOption()]; + return [ + _CardEditOption( + startEditing: () => rowNotifier.becomeFirstResponder(), + ), + const _CardMoreOption(), + ]; }, onTap: (context) { widget.openCard(context); @@ -83,11 +91,14 @@ class _BoardCardState extends State { final List children = []; cells.asMap().forEach( (int index, GridCellIdentifier cellId) { + final cellNotifier = EditableCellNotifier(); Widget child = widget.cellBuilder.buildCell( widget.groupId, cellId, widget.isEditing, + cellNotifier, ); + rowNotifier.insertCell(cellId, cellNotifier); if (index != 0) { child = Padding( @@ -121,7 +132,11 @@ class _CardMoreOption extends StatelessWidget with CardAccessory { @override Widget build(BuildContext context) { - return svgWidget('grid/details', color: context.read().iconColor); + return Padding( + padding: const EdgeInsets.all(3.0), + child: + svgWidget('grid/details', color: context.read().iconColor), + ); } @override @@ -131,3 +146,27 @@ class _CardMoreOption extends StatelessWidget with CardAccessory { ).show(context, direction: AnchorDirection.bottomWithCenterAligned); } } + +class _CardEditOption extends StatelessWidget with CardAccessory { + final VoidCallback startEditing; + const _CardEditOption({ + required this.startEditing, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(3.0), + child: svgWidget( + 'editor/edit', + color: context.read().iconColor, + ), + ); + } + + @override + void onTap(BuildContext context) { + startEditing(); + } +} diff --git a/frontend/app_flowy/lib/plugins/board/presentation/card/card_cell_builder.dart b/frontend/app_flowy/lib/plugins/board/presentation/card/card_cell_builder.dart index b292457193..99bd3f3b3a 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/card_cell_builder.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/card_cell_builder.dart @@ -2,6 +2,7 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_servic import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flutter/material.dart'; +import 'board_cell.dart'; import 'board_checkbox_cell.dart'; import 'board_date_cell.dart'; import 'board_number_cell.dart'; @@ -23,6 +24,7 @@ class BoardCellBuilder { String groupId, GridCellIdentifier cellId, bool isEditing, + EditableCellNotifier cellNotifier, ) { final cellControllerBuilder = GridCellControllerBuilder( delegate: delegate, @@ -54,6 +56,7 @@ class BoardCellBuilder { return BoardSelectOptionCell( groupId: groupId, cellControllerBuilder: cellControllerBuilder, + editableNotifier: cellNotifier, key: key, ); case FieldType.Number: @@ -67,6 +70,7 @@ class BoardCellBuilder { groupId: groupId, cellControllerBuilder: cellControllerBuilder, isFocus: isEditing, + editableNotifier: cellNotifier, key: key, ); case FieldType.URL: diff --git a/frontend/app_flowy/lib/plugins/board/presentation/card/card_container.dart b/frontend/app_flowy/lib/plugins/board/presentation/card/card_container.dart index 3ca934e508..e3660d68f3 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/card_container.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/card_container.dart @@ -69,12 +69,11 @@ class CardAccessoryContainer extends StatelessWidget { style: HoverStyle( hoverColor: theme.hover, backgroundColor: theme.surface, + borderRadius: BorderRadius.zero, ), - builder: (_, onHover) => Container( - width: 26, - height: 26, - padding: const EdgeInsets.all(3), - decoration: _makeBoxDecoration(context), + builder: (_, onHover) => SizedBox( + width: 24, + height: 24, child: accessory, ), ); @@ -85,7 +84,11 @@ class CardAccessoryContainer extends StatelessWidget { ); }).toList(); - return Wrap(children: children, spacing: 6); + return Container( + clipBehavior: Clip.hardEdge, + decoration: _makeBoxDecoration(context), + child: Row(children: children), + ); } } @@ -95,15 +98,16 @@ BoxDecoration _makeBoxDecoration(BuildContext context) { return BoxDecoration( color: Colors.transparent, border: Border.fromBorderSide(borderSide), - boxShadow: const [ - BoxShadow( - color: Colors.transparent, - spreadRadius: 0, - blurRadius: 2, - offset: Offset.zero, - ) - ], - borderRadius: const BorderRadius.all(Radius.circular(6)), + // boxShadow: const [ + // BoxShadow( + // color: Colors.transparent, + // spreadRadius: 0, + // blurRadius: 5, + // offset: Offset.zero, + // ) + // ], + + borderRadius: const BorderRadius.all(Radius.circular(4)), ); } diff --git a/frontend/app_flowy/packages/flowy_infra/lib/notifier.dart b/frontend/app_flowy/packages/flowy_infra/lib/notifier.dart index 7eaf139f0c..ac225775d7 100644 --- a/frontend/app_flowy/packages/flowy_infra/lib/notifier.dart +++ b/frontend/app_flowy/packages/flowy_infra/lib/notifier.dart @@ -31,7 +31,8 @@ class PublishNotifier extends ChangeNotifier { T? get currentValue => _value; - void addPublishListener(void Function(T) callback, {bool Function()? listenWhen}) { + void addPublishListener(void Function(T) callback, + {bool Function()? listenWhen}) { super.addListener( () { if (_value == null) { @@ -47,3 +48,9 @@ class PublishNotifier extends ChangeNotifier { ); } } + +class Notifier extends ChangeNotifier { + void notify() { + notifyListeners(); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs index 9270f73684..12f94c943c 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs @@ -154,6 +154,7 @@ pub fn select_option_color_from_index(index: usize) -> SelectOptionColorPB { _ => SelectOptionColorPB::Purple, } } + pub struct SelectOptionIds(Vec); impl SelectOptionIds {