mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: restore to use more conventional keyboard tab navigation (#4851)
This commit is contained in:
parent
14f789f50b
commit
70404816e7
@ -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 {
|
||||||
|
@ -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,
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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));
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user