fix: restore to use more conventional keyboard tab navigation (#4851)

This commit is contained in:
Richard Shiue 2024-03-08 17:30:26 +08:00 committed by GitHub
parent 14f789f50b
commit 70404816e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 125 additions and 103 deletions

View File

@ -20,7 +20,6 @@ const List<FieldType> _supportedFieldTypes = [
FieldType.URL, FieldType.URL,
FieldType.LastEditedTime, FieldType.LastEditedTime,
FieldType.CreatedTime, FieldType.CreatedTime,
FieldType.Relation,
]; ];
class FieldTypeList extends StatelessWidget with FlowyOverlayDelegate { class FieldTypeList extends StatelessWidget with FlowyOverlayDelegate {

View File

@ -67,13 +67,13 @@ class _ChecklistItemsState extends State<ChecklistItems> {
(index, task) => Padding( (index, task) => Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0), padding: const EdgeInsets.symmetric(vertical: 2.0),
child: ChecklistItem( child: ChecklistItem(
key: ValueKey(task.data.id),
task: task, task: task,
autofocus: widget.state.newTask && index == tasks.length - 1, autofocus: widget.state.newTask && index == tasks.length - 1,
onSubmitted: () { onSubmitted: index == tasks.length - 1
if (index == tasks.length - 1) { ? () => widget.bloc
widget.bloc.add(const ChecklistCellEvent.createNewTask("")); .add(const ChecklistCellEvent.createNewTask(""))
} : null,
},
), ),
), ),
) )

View File

@ -136,14 +136,6 @@ class _SelectTaskIntent extends Intent {
const _SelectTaskIntent(); const _SelectTaskIntent();
} }
class _DeleteTaskIntent extends Intent {
const _DeleteTaskIntent();
}
class _StartEditingTaskIntent extends Intent {
const _StartEditingTaskIntent();
}
class _EndEditingTaskIntent extends Intent { class _EndEditingTaskIntent extends Intent {
const _EndEditingTaskIntent(); const _EndEditingTaskIntent();
} }
@ -168,7 +160,9 @@ class ChecklistItem extends StatefulWidget {
class _ChecklistItemState extends State<ChecklistItem> { class _ChecklistItemState extends State<ChecklistItem> {
late final TextEditingController _textController; late final TextEditingController _textController;
final FocusNode _focusNode = FocusNode(); final FocusNode _focusNode = FocusNode(skipTraversal: true);
final FocusNode _textFieldFocusNode = FocusNode();
bool _isHovered = false; bool _isHovered = false;
bool _isFocused = false; bool _isFocused = false;
Timer? _debounceOnChanged; Timer? _debounceOnChanged;
@ -184,6 +178,7 @@ class _ChecklistItemState extends State<ChecklistItem> {
_debounceOnChanged?.cancel(); _debounceOnChanged?.cancel();
_textController.dispose(); _textController.dispose();
_focusNode.dispose(); _focusNode.dispose();
_textFieldFocusNode.dispose();
super.dispose(); super.dispose();
} }
@ -200,6 +195,7 @@ class _ChecklistItemState extends State<ChecklistItem> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FocusableActionDetector( return FocusableActionDetector(
focusNode: _focusNode,
onShowHoverHighlight: (isHovered) { onShowHoverHighlight: (isHovered) {
setState(() => _isHovered = isHovered); setState(() => _isHovered = isHovered);
}, },
@ -212,116 +208,90 @@ class _ChecklistItemState extends State<ChecklistItem> {
.read<ChecklistCellBloc>() .read<ChecklistCellBloc>()
.add(ChecklistCellEvent.selectTask(widget.task.data.id)), .add(ChecklistCellEvent.selectTask(widget.task.data.id)),
), ),
_DeleteTaskIntent: CallbackAction<_DeleteTaskIntent>(
onInvoke: (_DeleteTaskIntent intent) => context
.read<ChecklistCellBloc>()
.add(ChecklistCellEvent.deleteTask(widget.task.data.id)),
),
_StartEditingTaskIntent: CallbackAction<_StartEditingTaskIntent>(
onInvoke: (_StartEditingTaskIntent intent) =>
_focusNode.requestFocus(),
),
_EndEditingTaskIntent: CallbackAction<_EndEditingTaskIntent>( _EndEditingTaskIntent: CallbackAction<_EndEditingTaskIntent>(
onInvoke: (_EndEditingTaskIntent intent) => _focusNode.unfocus(), onInvoke: (_EndEditingTaskIntent intent) =>
_textFieldFocusNode.unfocus(),
), ),
}, },
shortcuts: { shortcuts: {
const SingleActivator(LogicalKeyboardKey.space): SingleActivator(
const _SelectTaskIntent(), LogicalKeyboardKey.enter,
const SingleActivator(LogicalKeyboardKey.delete): meta: Platform.isMacOS,
const _DeleteTaskIntent(), control: !Platform.isMacOS,
const SingleActivator(LogicalKeyboardKey.enter): ): const _SelectTaskIntent(),
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(),
}, },
descendantsAreTraversable: false,
child: Container( child: Container(
constraints: BoxConstraints(minHeight: GridSize.popoverItemHeight), constraints: BoxConstraints(minHeight: GridSize.popoverItemHeight),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _isHovered || _isFocused || _focusNode.hasFocus color: _isHovered || _isFocused || _textFieldFocusNode.hasFocus
? AFThemeExtension.of(context).lightGreyHover ? AFThemeExtension.of(context).lightGreyHover
: Colors.transparent, : Colors.transparent,
borderRadius: Corners.s6Border, borderRadius: Corners.s6Border,
), ),
child: Row( child: Row(
children: [ children: [
FlowyIconButton( ExcludeFocus(
width: 32, child: FlowyIconButton(
icon: FlowySvg( width: 32,
widget.task.isSelected icon: FlowySvg(
? FlowySvgs.check_filled_s widget.task.isSelected
: FlowySvgs.uncheck_s, ? FlowySvgs.check_filled_s
blendMode: BlendMode.dst, : FlowySvgs.uncheck_s,
blendMode: BlendMode.dst,
),
hoverColor: Colors.transparent,
onPressed: () => context.read<ChecklistCellBloc>().add(
ChecklistCellEvent.selectTask(widget.task.data.id),
),
), ),
hoverColor: Colors.transparent,
onPressed: () => context.read<ChecklistCellBloc>().add(
ChecklistCellEvent.selectTask(widget.task.data.id),
),
), ),
Expanded( Expanded(
child: Shortcuts( child: Shortcuts(
shortcuts: { shortcuts: const {
const SingleActivator(LogicalKeyboardKey.space): SingleActivator(LogicalKeyboardKey.escape):
const DoNothingAndStopPropagationIntent(), _EndEditingTaskIntent(),
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(),
}, },
child: TextField( child: Builder(
controller: _textController, builder: (context) {
focusNode: _focusNode, return TextField(
autofocus: widget.autofocus, controller: _textController,
style: Theme.of(context).textTheme.bodyMedium, focusNode: _textFieldFocusNode,
decoration: InputDecoration( autofocus: widget.autofocus,
border: InputBorder.none, style: Theme.of(context).textTheme.bodyMedium,
isCollapsed: true, decoration: InputDecoration(
contentPadding: EdgeInsets.only( border: InputBorder.none,
top: 8.0, isCollapsed: true,
bottom: 8.0, contentPadding: EdgeInsets.only(
left: 2.0, top: 8.0,
right: _isHovered ? 2.0 : 8.0, bottom: 8.0,
), left: 2.0,
hintText: LocaleKeys.grid_checklist_taskHint.tr(), right: _isHovered ? 2.0 : 8.0,
), ),
onChanged: (text) { hintText: LocaleKeys.grid_checklist_taskHint.tr(),
if (_textController.value.composing.isCollapsed) { ),
_debounceOnChangedText(text); textInputAction: widget.onSubmitted == null
} ? TextInputAction.next
}, : null,
onSubmitted: (description) { onChanged: (text) {
_submitUpdateTaskDescription(description); if (_textController.value.composing.isCollapsed) {
widget.onSubmitted?.call(); _debounceOnChangedText(text);
}
},
onSubmitted: (description) {
_submitUpdateTaskDescription(description);
if (widget.onSubmitted != null) {
widget.onSubmitted?.call();
} else {
Actions.invoke(context, const NextFocusIntent());
}
},
);
}, },
), ),
), ),
), ),
if (_isHovered || _isFocused || _focusNode.hasFocus) if (_isHovered || _isFocused || _textFieldFocusNode.hasFocus)
FlowyIconButton( _DeleteTaskButton(
width: 32,
icon: const FlowySvg(FlowySvgs.delete_s),
hoverColor: Colors.transparent,
iconColorOnHover: Theme.of(context).colorScheme.error,
onPressed: () => context.read<ChecklistCellBloc>().add( onPressed: () => context.read<ChecklistCellBloc>().add(
ChecklistCellEvent.deleteTask(widget.task.data.id), ChecklistCellEvent.deleteTask(widget.task.data.id),
), ),
@ -440,3 +410,56 @@ class _NewTaskItemState extends State<NewTaskItem> {
); );
} }
} }
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,
),
);
}
}

View File

@ -38,7 +38,7 @@ class FlowyIconButton extends StatelessWidget {
this.preferBelow = true, this.preferBelow = true,
this.isSelected, this.isSelected,
required this.icon, required this.icon,
}) : assert((richTooltipText != null && tooltipText == null) || }) : assert((richTooltipText != null && tooltipText == null) ||
(richTooltipText == null && tooltipText != null) || (richTooltipText == null && tooltipText != null) ||
(richTooltipText == null && tooltipText == null)); (richTooltipText == null && tooltipText == null));