diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/field_type_list.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/field_type_list.dart index 4451a52b6f..3cc9bd1c72 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/field_type_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/field_type_list.dart @@ -20,7 +20,6 @@ const List _supportedFieldTypes = [ FieldType.URL, FieldType.LastEditedTime, FieldType.CreatedTime, - FieldType.Relation, ]; class FieldTypeList extends StatelessWidget with FlowyOverlayDelegate { diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart index 69d7c07c09..4a1f1f05bc 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart @@ -67,13 +67,13 @@ class _ChecklistItemsState extends State { (index, task) => Padding( padding: const EdgeInsets.symmetric(vertical: 2.0), child: ChecklistItem( + key: ValueKey(task.data.id), task: task, autofocus: widget.state.newTask && index == tasks.length - 1, - onSubmitted: () { - if (index == tasks.length - 1) { - widget.bloc.add(const ChecklistCellEvent.createNewTask("")); - } - }, + onSubmitted: index == tasks.length - 1 + ? () => widget.bloc + .add(const ChecklistCellEvent.createNewTask("")) + : null, ), ), ) diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_editor.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_editor.dart index d32725c166..3ab883329a 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_editor.dart @@ -136,14 +136,6 @@ class _SelectTaskIntent extends Intent { const _SelectTaskIntent(); } -class _DeleteTaskIntent extends Intent { - const _DeleteTaskIntent(); -} - -class _StartEditingTaskIntent extends Intent { - const _StartEditingTaskIntent(); -} - class _EndEditingTaskIntent extends Intent { const _EndEditingTaskIntent(); } @@ -168,7 +160,9 @@ class ChecklistItem extends StatefulWidget { class _ChecklistItemState extends State { late final TextEditingController _textController; - final FocusNode _focusNode = FocusNode(); + final FocusNode _focusNode = FocusNode(skipTraversal: true); + final FocusNode _textFieldFocusNode = FocusNode(); + bool _isHovered = false; bool _isFocused = false; Timer? _debounceOnChanged; @@ -184,6 +178,7 @@ class _ChecklistItemState extends State { _debounceOnChanged?.cancel(); _textController.dispose(); _focusNode.dispose(); + _textFieldFocusNode.dispose(); super.dispose(); } @@ -200,6 +195,7 @@ class _ChecklistItemState extends State { @override Widget build(BuildContext context) { return FocusableActionDetector( + focusNode: _focusNode, onShowHoverHighlight: (isHovered) { setState(() => _isHovered = isHovered); }, @@ -212,116 +208,90 @@ class _ChecklistItemState extends State { .read() .add(ChecklistCellEvent.selectTask(widget.task.data.id)), ), - _DeleteTaskIntent: CallbackAction<_DeleteTaskIntent>( - onInvoke: (_DeleteTaskIntent intent) => context - .read() - .add(ChecklistCellEvent.deleteTask(widget.task.data.id)), - ), - _StartEditingTaskIntent: CallbackAction<_StartEditingTaskIntent>( - onInvoke: (_StartEditingTaskIntent intent) => - _focusNode.requestFocus(), - ), _EndEditingTaskIntent: CallbackAction<_EndEditingTaskIntent>( - onInvoke: (_EndEditingTaskIntent intent) => _focusNode.unfocus(), + onInvoke: (_EndEditingTaskIntent intent) => + _textFieldFocusNode.unfocus(), ), }, shortcuts: { - const SingleActivator(LogicalKeyboardKey.space): - const _SelectTaskIntent(), - const SingleActivator(LogicalKeyboardKey.delete): - const _DeleteTaskIntent(), - const SingleActivator(LogicalKeyboardKey.enter): - const _StartEditingTaskIntent(), - if (Platform.isMacOS) - const SingleActivator(LogicalKeyboardKey.enter, meta: true): - const _SelectTaskIntent() - else - const SingleActivator(LogicalKeyboardKey.enter, control: true): - const _SelectTaskIntent(), - const SingleActivator(LogicalKeyboardKey.arrowUp): - const PreviousFocusIntent(), - const SingleActivator(LogicalKeyboardKey.arrowDown): - const NextFocusIntent(), + SingleActivator( + LogicalKeyboardKey.enter, + meta: Platform.isMacOS, + control: !Platform.isMacOS, + ): const _SelectTaskIntent(), }, - descendantsAreTraversable: false, child: Container( constraints: BoxConstraints(minHeight: GridSize.popoverItemHeight), decoration: BoxDecoration( - color: _isHovered || _isFocused || _focusNode.hasFocus + color: _isHovered || _isFocused || _textFieldFocusNode.hasFocus ? AFThemeExtension.of(context).lightGreyHover : Colors.transparent, borderRadius: Corners.s6Border, ), child: Row( children: [ - FlowyIconButton( - width: 32, - icon: FlowySvg( - widget.task.isSelected - ? FlowySvgs.check_filled_s - : FlowySvgs.uncheck_s, - blendMode: BlendMode.dst, + ExcludeFocus( + child: FlowyIconButton( + width: 32, + icon: FlowySvg( + widget.task.isSelected + ? FlowySvgs.check_filled_s + : FlowySvgs.uncheck_s, + blendMode: BlendMode.dst, + ), + hoverColor: Colors.transparent, + onPressed: () => context.read().add( + ChecklistCellEvent.selectTask(widget.task.data.id), + ), ), - hoverColor: Colors.transparent, - onPressed: () => context.read().add( - ChecklistCellEvent.selectTask(widget.task.data.id), - ), ), Expanded( child: Shortcuts( - shortcuts: { - const SingleActivator(LogicalKeyboardKey.space): - const DoNothingAndStopPropagationIntent(), - const SingleActivator(LogicalKeyboardKey.delete): - const DoNothingAndStopPropagationIntent(), - if (Platform.isMacOS) - LogicalKeySet( - LogicalKeyboardKey.fn, - LogicalKeyboardKey.backspace, - ): const DoNothingAndStopPropagationIntent(), - const SingleActivator(LogicalKeyboardKey.enter): - const DoNothingAndStopPropagationIntent(), - const SingleActivator(LogicalKeyboardKey.escape): - const _EndEditingTaskIntent(), - const SingleActivator(LogicalKeyboardKey.arrowUp): - const DoNothingAndStopPropagationIntent(), - const SingleActivator(LogicalKeyboardKey.arrowDown): - const DoNothingAndStopPropagationIntent(), + shortcuts: const { + SingleActivator(LogicalKeyboardKey.escape): + _EndEditingTaskIntent(), }, - child: TextField( - controller: _textController, - focusNode: _focusNode, - autofocus: widget.autofocus, - style: Theme.of(context).textTheme.bodyMedium, - decoration: InputDecoration( - border: InputBorder.none, - isCollapsed: true, - contentPadding: EdgeInsets.only( - top: 8.0, - bottom: 8.0, - left: 2.0, - right: _isHovered ? 2.0 : 8.0, - ), - hintText: LocaleKeys.grid_checklist_taskHint.tr(), - ), - onChanged: (text) { - if (_textController.value.composing.isCollapsed) { - _debounceOnChangedText(text); - } - }, - onSubmitted: (description) { - _submitUpdateTaskDescription(description); - widget.onSubmitted?.call(); + child: Builder( + builder: (context) { + return TextField( + controller: _textController, + focusNode: _textFieldFocusNode, + autofocus: widget.autofocus, + style: Theme.of(context).textTheme.bodyMedium, + decoration: InputDecoration( + border: InputBorder.none, + isCollapsed: true, + contentPadding: EdgeInsets.only( + top: 8.0, + bottom: 8.0, + left: 2.0, + right: _isHovered ? 2.0 : 8.0, + ), + hintText: LocaleKeys.grid_checklist_taskHint.tr(), + ), + textInputAction: widget.onSubmitted == null + ? TextInputAction.next + : null, + onChanged: (text) { + if (_textController.value.composing.isCollapsed) { + _debounceOnChangedText(text); + } + }, + onSubmitted: (description) { + _submitUpdateTaskDescription(description); + if (widget.onSubmitted != null) { + widget.onSubmitted?.call(); + } else { + Actions.invoke(context, const NextFocusIntent()); + } + }, + ); }, ), ), ), - if (_isHovered || _isFocused || _focusNode.hasFocus) - FlowyIconButton( - width: 32, - icon: const FlowySvg(FlowySvgs.delete_s), - hoverColor: Colors.transparent, - iconColorOnHover: Theme.of(context).colorScheme.error, + if (_isHovered || _isFocused || _textFieldFocusNode.hasFocus) + _DeleteTaskButton( onPressed: () => context.read().add( ChecklistCellEvent.deleteTask(widget.task.data.id), ), @@ -440,3 +410,56 @@ class _NewTaskItemState extends State { ); } } + +class _DeleteTaskButton extends StatefulWidget { + const _DeleteTaskButton({ + required this.onPressed, + }); + + final VoidCallback onPressed; + + @override + State<_DeleteTaskButton> createState() => _DeleteTaskButtonState(); +} + +class _DeleteTaskButtonState extends State<_DeleteTaskButton> { + final _materialStatesController = MaterialStatesController(); + + @override + void dispose() { + _materialStatesController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return TextButton( + onPressed: widget.onPressed, + onHover: (_) => setState(() {}), + onFocusChange: (_) => setState(() {}), + style: ButtonStyle( + fixedSize: const MaterialStatePropertyAll(Size.square(32)), + minimumSize: const MaterialStatePropertyAll(Size.square(32)), + maximumSize: const MaterialStatePropertyAll(Size.square(32)), + overlayColor: MaterialStateProperty.resolveWith((state) { + if (state.contains(MaterialState.focused)) { + return AFThemeExtension.of(context).greyHover; + } + return Colors.transparent; + }), + shape: const MaterialStatePropertyAll( + RoundedRectangleBorder(borderRadius: Corners.s6Border), + ), + ), + statesController: _materialStatesController, + child: FlowySvg( + FlowySvgs.delete_s, + color: _materialStatesController.value + .contains(MaterialState.hovered) || + _materialStatesController.value.contains(MaterialState.focused) + ? Theme.of(context).colorScheme.error + : null, + ), + ); + } +} diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/icon_button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/icon_button.dart index 010c7cd6f9..116158b10b 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/icon_button.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/icon_button.dart @@ -38,7 +38,7 @@ class FlowyIconButton extends StatelessWidget { this.preferBelow = true, this.isSelected, required this.icon, - }) : assert((richTooltipText != null && tooltipText == null) || + }) : assert((richTooltipText != null && tooltipText == null) || (richTooltipText == null && tooltipText != null) || (richTooltipText == null && tooltipText == null));