mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: checklist cell editor a11y improvement (#4784)
This commit is contained in:
parent
f4170755fa
commit
b38fc43300
@ -11,7 +11,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
|||||||
part 'checklist_cell_bloc.freezed.dart';
|
part 'checklist_cell_bloc.freezed.dart';
|
||||||
|
|
||||||
class ChecklistSelectOption {
|
class ChecklistSelectOption {
|
||||||
ChecklistSelectOption(this.isSelected, this.data);
|
ChecklistSelectOption({required this.isSelected, required this.data});
|
||||||
|
|
||||||
final bool isSelected;
|
final bool isSelected;
|
||||||
final SelectOptionPB data;
|
final SelectOptionPB data;
|
||||||
@ -26,6 +26,7 @@ class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
|||||||
),
|
),
|
||||||
super(ChecklistCellState.initial(cellController)) {
|
super(ChecklistCellState.initial(cellController)) {
|
||||||
_dispatch();
|
_dispatch();
|
||||||
|
_startListening();
|
||||||
}
|
}
|
||||||
|
|
||||||
final ChecklistCellController cellController;
|
final ChecklistCellController cellController;
|
||||||
@ -46,9 +47,6 @@ class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
|||||||
on<ChecklistCellEvent>(
|
on<ChecklistCellEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.when(
|
await event.when(
|
||||||
initial: () {
|
|
||||||
_startListening();
|
|
||||||
},
|
|
||||||
didReceiveOptions: (data) {
|
didReceiveOptions: (data) {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
emit(
|
emit(
|
||||||
@ -71,8 +69,8 @@ class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
|||||||
updateTaskName: (option, name) {
|
updateTaskName: (option, name) {
|
||||||
_updateOption(option, name);
|
_updateOption(option, name);
|
||||||
},
|
},
|
||||||
selectTask: (option) async {
|
selectTask: (id) async {
|
||||||
await _checklistCellService.select(optionId: option.id);
|
await _checklistCellService.select(optionId: id);
|
||||||
},
|
},
|
||||||
createNewTask: (name) async {
|
createNewTask: (name) async {
|
||||||
final result = await _checklistCellService.create(name: name);
|
final result = await _checklistCellService.create(name: name);
|
||||||
@ -81,8 +79,8 @@ class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
|||||||
(err) => Log.error(err),
|
(err) => Log.error(err),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
deleteTask: (option) async {
|
deleteTask: (id) async {
|
||||||
await _deleteOption([option]);
|
await _deleteOption([id]);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -102,21 +100,17 @@ class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
|
|||||||
void _updateOption(SelectOptionPB option, String name) async {
|
void _updateOption(SelectOptionPB option, String name) async {
|
||||||
final result =
|
final result =
|
||||||
await _checklistCellService.updateName(option: option, name: name);
|
await _checklistCellService.updateName(option: option, name: name);
|
||||||
|
|
||||||
result.fold((l) => null, (err) => Log.error(err));
|
result.fold((l) => null, (err) => Log.error(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _deleteOption(List<SelectOptionPB> options) async {
|
Future<void> _deleteOption(List<String> options) async {
|
||||||
final result = await _checklistCellService.delete(
|
final result = await _checklistCellService.delete(optionIds: options);
|
||||||
optionIds: options.map((e) => e.id).toList(),
|
|
||||||
);
|
|
||||||
result.fold((l) => null, (err) => Log.error(err));
|
result.fold((l) => null, (err) => Log.error(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class ChecklistCellEvent with _$ChecklistCellEvent {
|
class ChecklistCellEvent with _$ChecklistCellEvent {
|
||||||
const factory ChecklistCellEvent.initial() = _InitialCell;
|
|
||||||
const factory ChecklistCellEvent.didReceiveOptions(
|
const factory ChecklistCellEvent.didReceiveOptions(
|
||||||
ChecklistCellDataPB? data,
|
ChecklistCellDataPB? data,
|
||||||
) = _DidReceiveCellUpdate;
|
) = _DidReceiveCellUpdate;
|
||||||
@ -124,12 +118,10 @@ class ChecklistCellEvent with _$ChecklistCellEvent {
|
|||||||
SelectOptionPB option,
|
SelectOptionPB option,
|
||||||
String name,
|
String name,
|
||||||
) = _UpdateTaskName;
|
) = _UpdateTaskName;
|
||||||
const factory ChecklistCellEvent.selectTask(SelectOptionPB task) =
|
const factory ChecklistCellEvent.selectTask(String taskId) = _SelectTask;
|
||||||
_SelectTask;
|
|
||||||
const factory ChecklistCellEvent.createNewTask(String description) =
|
const factory ChecklistCellEvent.createNewTask(String description) =
|
||||||
_CreateNewTask;
|
_CreateNewTask;
|
||||||
const factory ChecklistCellEvent.deleteTask(SelectOptionPB option) =
|
const factory ChecklistCellEvent.deleteTask(String taskId) = _DeleteTask;
|
||||||
_DeleteTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@ -157,16 +149,14 @@ List<ChecklistSelectOption> _makeChecklistSelectOptions(
|
|||||||
if (data == null) {
|
if (data == null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
return data.options
|
||||||
final List<ChecklistSelectOption> options = [];
|
.map(
|
||||||
final List<SelectOptionPB> allOptions = List.from(data.options);
|
(option) => ChecklistSelectOption(
|
||||||
final selectedOptionIds = data.selectedOptions.map((e) => e.id).toList();
|
isSelected: data.selectedOptions.any(
|
||||||
|
(selected) => selected.id == option.id,
|
||||||
for (final option in allOptions) {
|
),
|
||||||
options.add(
|
data: option,
|
||||||
ChecklistSelectOption(selectedOptionIds.contains(option.id), option),
|
),
|
||||||
);
|
)
|
||||||
}
|
.toList();
|
||||||
|
|
||||||
return options;
|
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ class _ChecklistCellState extends State<ChecklistCardCell> {
|
|||||||
widget.databaseController,
|
widget.databaseController,
|
||||||
widget.cellContext,
|
widget.cellContext,
|
||||||
).as(),
|
).as(),
|
||||||
)..add(const ChecklistCellEvent.initial());
|
);
|
||||||
},
|
},
|
||||||
child: BlocBuilder<ChecklistCellBloc, ChecklistCellState>(
|
child: BlocBuilder<ChecklistCellBloc, ChecklistCellState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
@ -64,14 +64,17 @@ class _ChecklistItemsState extends State<ChecklistItems> {
|
|||||||
}
|
}
|
||||||
final children = tasks
|
final children = tasks
|
||||||
.mapIndexed(
|
.mapIndexed(
|
||||||
(index, task) => ChecklistItem(
|
(index, task) => Padding(
|
||||||
task: task,
|
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
||||||
autofocus: widget.state.newTask && index == tasks.length - 1,
|
child: ChecklistItem(
|
||||||
onSubmitted: () {
|
task: task,
|
||||||
if (index == tasks.length - 1) {
|
autofocus: widget.state.newTask && index == tasks.length - 1,
|
||||||
widget.bloc.add(const ChecklistCellEvent.createNewTask(""));
|
onSubmitted: () {
|
||||||
}
|
if (index == tasks.length - 1) {
|
||||||
},
|
widget.bloc.add(const ChecklistCellEvent.createNewTask(""));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
@ -111,7 +114,7 @@ class _ChecklistItemsState extends State<ChecklistItems> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const VSpace(4),
|
const VSpace(2.0),
|
||||||
...children,
|
...children,
|
||||||
ChecklistItemControl(cellNotifer: widget.cellContainerNotifier),
|
ChecklistItemControl(cellNotifer: widget.cellContainerNotifier),
|
||||||
],
|
],
|
||||||
@ -136,7 +139,7 @@ class ChecklistItemControl extends StatelessWidget {
|
|||||||
.read<ChecklistCellBloc>()
|
.read<ChecklistCellBloc>()
|
||||||
.add(const ChecklistCellEvent.createNewTask("")),
|
.add(const ChecklistCellEvent.createNewTask("")),
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 0),
|
margin: const EdgeInsets.fromLTRB(8.0, 2.0, 8.0, 0),
|
||||||
height: 12,
|
height: 12,
|
||||||
child: AnimatedSwitcher(
|
child: AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 150),
|
duration: const Duration(milliseconds: 150),
|
||||||
|
@ -58,7 +58,7 @@ class GridChecklistCellState extends GridCellState<EditableChecklistCell> {
|
|||||||
widget.databaseController,
|
widget.databaseController,
|
||||||
widget.cellContext,
|
widget.cellContext,
|
||||||
).as(),
|
).as(),
|
||||||
)..add(const ChecklistCellEvent.initial());
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
@ -23,10 +24,10 @@ class ChecklistCellEditor extends StatefulWidget {
|
|||||||
final ChecklistCellController cellController;
|
final ChecklistCellController cellController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChecklistCellEditor> createState() => _GridChecklistCellState();
|
State<ChecklistCellEditor> createState() => _ChecklistCellEditorState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GridChecklistCellState extends State<ChecklistCellEditor> {
|
class _ChecklistCellEditorState extends State<ChecklistCellEditor> {
|
||||||
/// Focus node for the new task text field
|
/// Focus node for the new task text field
|
||||||
late final FocusNode newTaskFocusNode;
|
late final FocusNode newTaskFocusNode;
|
||||||
|
|
||||||
@ -56,18 +57,14 @@ class _GridChecklistCellState extends State<ChecklistCellEditor> {
|
|||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
AnimatedSwitcher(
|
if (state.tasks.isNotEmpty)
|
||||||
duration: const Duration(milliseconds: 300),
|
Padding(
|
||||||
child: state.tasks.isEmpty
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 4),
|
||||||
? const SizedBox.shrink()
|
child: ChecklistProgressBar(
|
||||||
: Padding(
|
tasks: state.tasks,
|
||||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 4),
|
percent: state.percent,
|
||||||
child: ChecklistProgressBar(
|
),
|
||||||
tasks: state.tasks,
|
),
|
||||||
percent: state.percent,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ChecklistItemList(
|
ChecklistItemList(
|
||||||
options: state.tasks,
|
options: state.tasks,
|
||||||
onUpdateTask: () => newTaskFocusNode.requestFocus(),
|
onUpdateTask: () => newTaskFocusNode.requestFocus(),
|
||||||
@ -92,7 +89,7 @@ class _GridChecklistCellState extends State<ChecklistCellEditor> {
|
|||||||
|
|
||||||
/// Displays the a list of all the exisiting tasks and an input field to create
|
/// Displays the a list of all the exisiting tasks and an input field to create
|
||||||
/// a new task if `isAddingNewTask` is true
|
/// a new task if `isAddingNewTask` is true
|
||||||
class ChecklistItemList extends StatefulWidget {
|
class ChecklistItemList extends StatelessWidget {
|
||||||
const ChecklistItemList({
|
const ChecklistItemList({
|
||||||
super.key,
|
super.key,
|
||||||
required this.options,
|
required this.options,
|
||||||
@ -102,26 +99,19 @@ class ChecklistItemList extends StatefulWidget {
|
|||||||
final List<ChecklistSelectOption> options;
|
final List<ChecklistSelectOption> options;
|
||||||
final VoidCallback onUpdateTask;
|
final VoidCallback onUpdateTask;
|
||||||
|
|
||||||
@override
|
|
||||||
State<ChecklistItemList> createState() => _ChecklistItemListState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ChecklistItemListState extends State<ChecklistItemList> {
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (widget.options.isEmpty) {
|
if (options.isEmpty) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
final itemList = widget.options
|
final itemList = options
|
||||||
.mapIndexed(
|
.mapIndexed(
|
||||||
(index, option) => Padding(
|
(index, option) => Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: ChecklistItem(
|
child: ChecklistItem(
|
||||||
task: option,
|
task: option,
|
||||||
onSubmitted: index == widget.options.length - 1
|
onSubmitted: index == options.length - 1 ? onUpdateTask : null,
|
||||||
? widget.onUpdateTask
|
|
||||||
: null,
|
|
||||||
key: ValueKey(option.data.id),
|
key: ValueKey(option.data.id),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -140,6 +130,22 @@ class _ChecklistItemListState extends State<ChecklistItemList> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _SelectTaskIntent extends Intent {
|
||||||
|
const _SelectTaskIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DeleteTaskIntent extends Intent {
|
||||||
|
const _DeleteTaskIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StartEditingTaskIntent extends Intent {
|
||||||
|
const _StartEditingTaskIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EndEditingTaskIntent extends Intent {
|
||||||
|
const _EndEditingTaskIntent();
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents an existing task
|
/// Represents an existing task
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
class ChecklistItem extends StatefulWidget {
|
class ChecklistItem extends StatefulWidget {
|
||||||
@ -160,58 +166,80 @@ class ChecklistItem extends StatefulWidget {
|
|||||||
|
|
||||||
class _ChecklistItemState extends State<ChecklistItem> {
|
class _ChecklistItemState extends State<ChecklistItem> {
|
||||||
late final TextEditingController _textController;
|
late final TextEditingController _textController;
|
||||||
late final FocusNode _focusNode;
|
final FocusNode _focusNode = FocusNode();
|
||||||
bool _isHovered = false;
|
bool _isHovered = false;
|
||||||
|
bool _isFocused = false;
|
||||||
Timer? _debounceOnChanged;
|
Timer? _debounceOnChanged;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_textController = TextEditingController(text: widget.task.data.name);
|
_textController = TextEditingController(text: widget.task.data.name);
|
||||||
_focusNode = FocusNode(
|
|
||||||
onKeyEvent: (node, event) {
|
|
||||||
if (event.logicalKey == LogicalKeyboardKey.escape) {
|
|
||||||
node.unfocus();
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
}
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (widget.autofocus) {
|
|
||||||
_focusNode.requestFocus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_debounceOnChanged?.cancel();
|
||||||
_textController.dispose();
|
_textController.dispose();
|
||||||
_focusNode.dispose();
|
_focusNode.dispose();
|
||||||
_debounceOnChanged?.cancel();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(ChecklistItem oldWidget) {
|
void didUpdateWidget(ChecklistItem oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (widget.task.data.name != oldWidget.task.data.name &&
|
if (widget.task.data.name != oldWidget.task.data.name) {
|
||||||
!_focusNode.hasFocus) {
|
|
||||||
_textController.text = widget.task.data.name;
|
_textController.text = widget.task.data.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final icon = FlowySvg(
|
return FocusableActionDetector(
|
||||||
widget.task.isSelected ? FlowySvgs.check_filled_s : FlowySvgs.uncheck_s,
|
onShowHoverHighlight: (isHovered) {
|
||||||
blendMode: BlendMode.dst,
|
setState(() => _isHovered = isHovered);
|
||||||
);
|
},
|
||||||
return MouseRegion(
|
onFocusChange: (isFocused) {
|
||||||
onEnter: (event) => setState(() => _isHovered = true),
|
setState(() => _isFocused = isFocused);
|
||||||
onExit: (event) => setState(() => _isHovered = false),
|
},
|
||||||
|
actions: {
|
||||||
|
_SelectTaskIntent: CallbackAction<_SelectTaskIntent>(
|
||||||
|
onInvoke: (_SelectTaskIntent intent) => context
|
||||||
|
.read<ChecklistCellBloc>()
|
||||||
|
.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>(
|
||||||
|
onInvoke: (_EndEditingTaskIntent intent) => _focusNode.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(),
|
||||||
|
},
|
||||||
|
descendantsAreTraversable: false,
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints(minHeight: GridSize.popoverItemHeight),
|
constraints: BoxConstraints(minHeight: GridSize.popoverItemHeight),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _isHovered
|
color: _isHovered || _isFocused || _focusNode.hasFocus
|
||||||
? AFThemeExtension.of(context).lightGreyHover
|
? AFThemeExtension.of(context).lightGreyHover
|
||||||
: Colors.transparent,
|
: Colors.transparent,
|
||||||
borderRadius: Corners.s6Border,
|
borderRadius: Corners.s6Border,
|
||||||
@ -220,43 +248,65 @@ class _ChecklistItemState extends State<ChecklistItem> {
|
|||||||
children: [
|
children: [
|
||||||
FlowyIconButton(
|
FlowyIconButton(
|
||||||
width: 32,
|
width: 32,
|
||||||
icon: icon,
|
icon: FlowySvg(
|
||||||
|
widget.task.isSelected
|
||||||
|
? FlowySvgs.check_filled_s
|
||||||
|
: FlowySvgs.uncheck_s,
|
||||||
|
blendMode: BlendMode.dst,
|
||||||
|
),
|
||||||
hoverColor: Colors.transparent,
|
hoverColor: Colors.transparent,
|
||||||
onPressed: () => context.read<ChecklistCellBloc>().add(
|
onPressed: () => context.read<ChecklistCellBloc>().add(
|
||||||
ChecklistCellEvent.selectTask(widget.task.data),
|
ChecklistCellEvent.selectTask(widget.task.data.id),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: Shortcuts(
|
||||||
controller: _textController,
|
shortcuts: const {
|
||||||
focusNode: _focusNode,
|
SingleActivator(LogicalKeyboardKey.space):
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
DoNothingAndStopPropagationIntent(),
|
||||||
decoration: InputDecoration(
|
SingleActivator(LogicalKeyboardKey.delete):
|
||||||
border: InputBorder.none,
|
DoNothingAndStopPropagationIntent(),
|
||||||
isCollapsed: true,
|
SingleActivator(LogicalKeyboardKey.enter):
|
||||||
contentPadding: EdgeInsets.only(
|
DoNothingAndStopPropagationIntent(),
|
||||||
top: 8.0,
|
SingleActivator(LogicalKeyboardKey.escape):
|
||||||
bottom: 8.0,
|
_EndEditingTaskIntent(),
|
||||||
left: 2.0,
|
|
||||||
right: _isHovered ? 2.0 : 8.0,
|
|
||||||
),
|
|
||||||
hintText: LocaleKeys.grid_checklist_taskHint.tr(),
|
|
||||||
),
|
|
||||||
onChanged: _debounceOnChangedText,
|
|
||||||
onSubmitted: (description) {
|
|
||||||
_submitUpdateTaskDescription(description);
|
|
||||||
widget.onSubmitted?.call();
|
|
||||||
},
|
},
|
||||||
|
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();
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_isHovered)
|
if (_isHovered || _isFocused || _focusNode.hasFocus)
|
||||||
FlowyIconButton(
|
FlowyIconButton(
|
||||||
width: 32,
|
width: 32,
|
||||||
icon: const FlowySvg(FlowySvgs.delete_s),
|
icon: const FlowySvg(FlowySvgs.delete_s),
|
||||||
hoverColor: Colors.transparent,
|
hoverColor: Colors.transparent,
|
||||||
iconColorOnHover: Theme.of(context).colorScheme.error,
|
iconColorOnHover: Theme.of(context).colorScheme.error,
|
||||||
onPressed: () => context.read<ChecklistCellBloc>().add(
|
onPressed: () => context.read<ChecklistCellBloc>().add(
|
||||||
ChecklistCellEvent.deleteTask(widget.task.data),
|
ChecklistCellEvent.deleteTask(widget.task.data.id),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -276,7 +326,7 @@ class _ChecklistItemState extends State<ChecklistItem> {
|
|||||||
context.read<ChecklistCellBloc>().add(
|
context.read<ChecklistCellBloc>().add(
|
||||||
ChecklistCellEvent.updateTaskName(
|
ChecklistCellEvent.updateTaskName(
|
||||||
widget.task.data,
|
widget.task.data,
|
||||||
description.trim(),
|
description,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -159,7 +159,7 @@ class _ChecklistItemState extends State<_ChecklistItem> {
|
|||||||
borderRadius: BorderRadius.circular(22),
|
borderRadius: BorderRadius.circular(22),
|
||||||
onTap: () => context
|
onTap: () => context
|
||||||
.read<ChecklistCellBloc>()
|
.read<ChecklistCellBloc>()
|
||||||
.add(ChecklistCellEvent.selectTask(widget.task.data)),
|
.add(ChecklistCellEvent.selectTask(widget.task.data.id)),
|
||||||
child: SizedBox.square(
|
child: SizedBox.square(
|
||||||
dimension: 44,
|
dimension: 44,
|
||||||
child: Center(
|
child: Center(
|
||||||
@ -239,7 +239,7 @@ class _ChecklistItemState extends State<_ChecklistItem> {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.read<ChecklistCellBloc>().add(
|
context.read<ChecklistCellBloc>().add(
|
||||||
ChecklistCellEvent.deleteTask(widget.task.data),
|
ChecklistCellEvent.deleteTask(widget.task.data.id),
|
||||||
);
|
);
|
||||||
context.pop();
|
context.pop();
|
||||||
},
|
},
|
||||||
|
@ -161,15 +161,16 @@ class PopoverState extends State<Popover> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return FocusScope(
|
return CallbackShortcuts(
|
||||||
onKey: (node, event) {
|
bindings: {
|
||||||
if (event.logicalKey == LogicalKeyboardKey.escape) {
|
const SingleActivator(LogicalKeyboardKey.escape): () =>
|
||||||
_removeRootOverlay();
|
_removeRootOverlay(),
|
||||||
return KeyEventResult.handled;
|
|
||||||
}
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
},
|
},
|
||||||
child: Stack(children: children),
|
child: FocusScope(
|
||||||
|
child: Stack(
|
||||||
|
children: children,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
_rootEntry.addEntry(context, this, newEntry, widget.asBarrier);
|
_rootEntry.addEntry(context, this, newEntry, widget.asBarrier);
|
||||||
|
Loading…
Reference in New Issue
Block a user