mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: cursor jump randomly in check list item (#5565)
* chore: remove debug logs * fix: cursor jump ramdomly in checklist item
This commit is contained in:
parent
ed82ec8eef
commit
75cea400d2
@ -16,7 +16,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:flutter_chat_types/flutter_chat_types.dart';
|
import 'package:flutter_chat_types/flutter_chat_types.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:nanoid/nanoid.dart';
|
import 'package:nanoid/nanoid.dart';
|
||||||
|
|
||||||
import 'chat_message_listener.dart';
|
import 'chat_message_listener.dart';
|
||||||
|
|
||||||
part 'chat_bloc.freezed.dart';
|
part 'chat_bloc.freezed.dart';
|
||||||
|
|
||||||
const sendMessageErrorKey = "sendMessageError";
|
const sendMessageErrorKey = "sendMessageError";
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
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';
|
||||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
|
||||||
import 'package:appflowy/plugins/database/application/cell/bloc/checklist_cell_bloc.dart';
|
import 'package:appflowy/plugins/database/application/cell/bloc/checklist_cell_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_cell_editor.dart';
|
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_cell_editor.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_progress_bar.dart';
|
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_progress_bar.dart';
|
||||||
|
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -62,23 +62,24 @@ class _ChecklistItemsState extends State<ChecklistItems> {
|
|||||||
if (showIncompleteOnly) {
|
if (showIncompleteOnly) {
|
||||||
tasks.removeWhere((task) => task.isSelected);
|
tasks.removeWhere((task) => task.isSelected);
|
||||||
}
|
}
|
||||||
final children = tasks
|
// final children = tasks
|
||||||
.mapIndexed(
|
// .mapIndexed(
|
||||||
(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),
|
// key: ValueKey('${task.data.id}$index'),
|
||||||
task: task,
|
// task: task,
|
||||||
autofocus: widget.state.newTask && index == tasks.length - 1,
|
// autofocus: widget.state.newTask && index == tasks.length - 1,
|
||||||
onSubmitted: () {
|
// onSubmitted: () {
|
||||||
if (index == tasks.length - 1) {
|
// if (index == tasks.length - 1) {
|
||||||
widget.bloc.add(const ChecklistCellEvent.createNewTask(""));
|
// // create a new task under the last task if the users press enter
|
||||||
}
|
// widget.bloc.add(const ChecklistCellEvent.createNewTask(''));
|
||||||
},
|
// }
|
||||||
),
|
// },
|
||||||
),
|
// ),
|
||||||
)
|
// ),
|
||||||
.toList();
|
// )
|
||||||
|
// .toList();
|
||||||
return Align(
|
return Align(
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -116,7 +117,7 @@ class _ChecklistItemsState extends State<ChecklistItems> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const VSpace(2.0),
|
const VSpace(2.0),
|
||||||
...children,
|
_ChecklistCellEditors(tasks: tasks),
|
||||||
ChecklistItemControl(cellNotifer: widget.cellContainerNotifier),
|
ChecklistItemControl(cellNotifer: widget.cellContainerNotifier),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -124,6 +125,41 @@ class _ChecklistItemsState extends State<ChecklistItems> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _ChecklistCellEditors extends StatelessWidget {
|
||||||
|
const _ChecklistCellEditors({
|
||||||
|
required this.tasks,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<ChecklistSelectOption> tasks;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final bloc = context.read<ChecklistCellBloc>();
|
||||||
|
final state = bloc.state;
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
...tasks.mapIndexed(
|
||||||
|
(index, task) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
||||||
|
child: ChecklistItem(
|
||||||
|
key: ValueKey('${task.data.id}$index'),
|
||||||
|
task: task,
|
||||||
|
autofocus: state.newTask && index == tasks.length - 1,
|
||||||
|
onSubmitted: () {
|
||||||
|
if (index == tasks.length - 1) {
|
||||||
|
// create a new task under the last task if the users press enter
|
||||||
|
bloc.add(const ChecklistCellEvent.createNewTask(''));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ChecklistItemControl extends StatelessWidget {
|
class ChecklistItemControl extends StatelessWidget {
|
||||||
const ChecklistItemControl({super.key, required this.cellNotifer});
|
const ChecklistItemControl({super.key, required this.cellNotifer});
|
||||||
|
|
||||||
|
@ -1,23 +1,21 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.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';
|
||||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_option_separator.dart';
|
import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_option_separator.dart';
|
||||||
|
import 'package:appflowy/util/debounce.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/size.dart';
|
import 'package:flowy_infra/size.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import '../../application/cell/bloc/checklist_cell_bloc.dart';
|
import '../../application/cell/bloc/checklist_cell_bloc.dart';
|
||||||
|
import 'checklist_cell_textfield.dart';
|
||||||
import 'checklist_progress_bar.dart';
|
import 'checklist_progress_bar.dart';
|
||||||
|
|
||||||
class ChecklistCellEditor extends StatefulWidget {
|
class ChecklistCellEditor extends StatefulWidget {
|
||||||
@ -89,7 +87,7 @@ class _ChecklistCellEditorState 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 existing tasks and an input field to create
|
||||||
/// a new task if `isAddingNewTask` is true
|
/// a new task if `isAddingNewTask` is true
|
||||||
class ChecklistItemList extends StatelessWidget {
|
class ChecklistItemList extends StatelessWidget {
|
||||||
const ChecklistItemList({
|
const ChecklistItemList({
|
||||||
@ -159,167 +157,136 @@ class ChecklistItem extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ChecklistItemState extends State<ChecklistItem> {
|
class _ChecklistItemState extends State<ChecklistItem> {
|
||||||
late final TextEditingController _textController;
|
TextEditingController textController = TextEditingController();
|
||||||
final FocusNode _focusNode = FocusNode(skipTraversal: true);
|
final textFieldFocusNode = FocusNode();
|
||||||
final FocusNode _textFieldFocusNode = FocusNode();
|
final focusNode = FocusNode(skipTraversal: true);
|
||||||
|
|
||||||
bool _isHovered = false;
|
bool isHovered = false;
|
||||||
bool _isFocused = false;
|
bool isFocused = false;
|
||||||
Timer? _debounceOnChanged;
|
|
||||||
|
final _debounceOnChanged = Debounce(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
);
|
||||||
|
|
||||||
|
final selectTaskShortcut = {
|
||||||
|
SingleActivator(
|
||||||
|
LogicalKeyboardKey.enter,
|
||||||
|
meta: Platform.isMacOS,
|
||||||
|
control: !Platform.isMacOS,
|
||||||
|
): const _SelectTaskIntent(),
|
||||||
|
const SingleActivator(LogicalKeyboardKey.escape):
|
||||||
|
const _EndEditingTaskIntent(),
|
||||||
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_textController = TextEditingController(text: widget.task.data.name);
|
textController.text = widget.task.data.name;
|
||||||
}
|
if (widget.autofocus) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
@override
|
focusNode.requestFocus();
|
||||||
void dispose() {
|
textFieldFocusNode.requestFocus();
|
||||||
_debounceOnChanged?.cancel();
|
});
|
||||||
_textController.dispose();
|
|
||||||
_focusNode.dispose();
|
|
||||||
_textFieldFocusNode.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(ChecklistItem oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
if (widget.task.data.name != oldWidget.task.data.name) {
|
|
||||||
final selection = _textController.selection;
|
|
||||||
// Ensure the selection offset is within the new text bounds
|
|
||||||
int offset = selection.start;
|
|
||||||
if (offset > widget.task.data.name.length) {
|
|
||||||
offset = widget.task.data.name.length;
|
|
||||||
}
|
|
||||||
_textController.selection = TextSelection.collapsed(offset: offset);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_debounceOnChanged.dispose();
|
||||||
|
|
||||||
|
textController.dispose();
|
||||||
|
focusNode.dispose();
|
||||||
|
textFieldFocusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final isFocusedOrHovered =
|
||||||
|
isHovered || isFocused || textFieldFocusNode.hasFocus;
|
||||||
|
final color = isFocusedOrHovered
|
||||||
|
? AFThemeExtension.of(context).lightGreyHover
|
||||||
|
: Colors.transparent;
|
||||||
return FocusableActionDetector(
|
return FocusableActionDetector(
|
||||||
focusNode: _focusNode,
|
focusNode: focusNode,
|
||||||
onShowHoverHighlight: (isHovered) {
|
onShowHoverHighlight: (value) => setState(() {
|
||||||
setState(() => _isHovered = isHovered);
|
isHovered = value;
|
||||||
},
|
}),
|
||||||
onFocusChange: (isFocused) {
|
onFocusChange: (value) => setState(() {
|
||||||
setState(() => _isFocused = isFocused);
|
isFocused = value;
|
||||||
},
|
}),
|
||||||
actions: {
|
actions: _buildActions(),
|
||||||
_SelectTaskIntent: CallbackAction<_SelectTaskIntent>(
|
shortcuts: selectTaskShortcut,
|
||||||
onInvoke: (_SelectTaskIntent intent) {
|
|
||||||
// Log.debug("checklist widget on enter");
|
|
||||||
context
|
|
||||||
.read<ChecklistCellBloc>()
|
|
||||||
.add(ChecklistCellEvent.selectTask(widget.task.data.id));
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_EndEditingTaskIntent: CallbackAction<_EndEditingTaskIntent>(
|
|
||||||
onInvoke: (_EndEditingTaskIntent intent) {
|
|
||||||
// Log.debug("checklist widget on escape");
|
|
||||||
_textFieldFocusNode.unfocus();
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
shortcuts: {
|
|
||||||
SingleActivator(
|
|
||||||
LogicalKeyboardKey.enter,
|
|
||||||
meta: Platform.isMacOS,
|
|
||||||
control: !Platform.isMacOS,
|
|
||||||
): const _SelectTaskIntent(),
|
|
||||||
},
|
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints(minHeight: GridSize.popoverItemHeight),
|
constraints: BoxConstraints(
|
||||||
|
minHeight: GridSize.popoverItemHeight,
|
||||||
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _isHovered || _isFocused || _textFieldFocusNode.hasFocus
|
color: color,
|
||||||
? AFThemeExtension.of(context).lightGreyHover
|
|
||||||
: Colors.transparent,
|
|
||||||
borderRadius: Corners.s6Border,
|
borderRadius: Corners.s6Border,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: _buildChild(
|
||||||
children: [
|
context,
|
||||||
ExcludeFocus(
|
isFocusedOrHovered,
|
||||||
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<ChecklistCellBloc>().add(
|
|
||||||
ChecklistCellEvent.selectTask(widget.task.data.id),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Shortcuts(
|
|
||||||
shortcuts: const {
|
|
||||||
SingleActivator(LogicalKeyboardKey.escape):
|
|
||||||
_EndEditingTaskIntent(),
|
|
||||||
},
|
|
||||||
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) {
|
|
||||||
if (widget.onSubmitted != null) {
|
|
||||||
// Log.debug("checklist widget on submitted");
|
|
||||||
widget.onSubmitted?.call();
|
|
||||||
} else {
|
|
||||||
// Log.debug("checklist widget Focus next task");
|
|
||||||
Actions.invoke(context, const NextFocusIntent());
|
|
||||||
}
|
|
||||||
_submitUpdateTaskDescription(description);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (_isHovered || _isFocused || _textFieldFocusNode.hasFocus)
|
|
||||||
_DeleteTaskButton(
|
|
||||||
onPressed: () => context.read<ChecklistCellBloc>().add(
|
|
||||||
ChecklistCellEvent.deleteTask(widget.task.data.id),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _debounceOnChangedText(String text) {
|
Widget _buildChild(BuildContext context, bool isFocusedOrHovered) {
|
||||||
_debounceOnChanged?.cancel();
|
return Row(
|
||||||
_debounceOnChanged = Timer(const Duration(milliseconds: 300), () {
|
children: [
|
||||||
_submitUpdateTaskDescription(text);
|
ChecklistCellCheckIcon(task: widget.task),
|
||||||
});
|
Expanded(
|
||||||
|
child: ChecklistCellTextfield(
|
||||||
|
textController: textController,
|
||||||
|
focusNode: textFieldFocusNode,
|
||||||
|
autofocus: widget.autofocus,
|
||||||
|
onChanged: () {
|
||||||
|
_debounceOnChanged.call(() {
|
||||||
|
if (textController.selection.isCollapsed) {
|
||||||
|
_submitUpdateTaskDescription(textController.text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSubmitted: () {
|
||||||
|
_submitUpdateTaskDescription(textController.text);
|
||||||
|
|
||||||
|
if (widget.onSubmitted != null) {
|
||||||
|
widget.onSubmitted?.call();
|
||||||
|
} else {
|
||||||
|
Actions.invoke(context, const NextFocusIntent());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isFocusedOrHovered)
|
||||||
|
ChecklistCellDeleteButton(
|
||||||
|
onPressed: () => context.read<ChecklistCellBloc>().add(
|
||||||
|
ChecklistCellEvent.deleteTask(widget.task.data.id),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Type, Action<Intent>> _buildActions() {
|
||||||
|
return {
|
||||||
|
_SelectTaskIntent: CallbackAction<_SelectTaskIntent>(
|
||||||
|
onInvoke: (_SelectTaskIntent intent) {
|
||||||
|
context
|
||||||
|
.read<ChecklistCellBloc>()
|
||||||
|
.add(ChecklistCellEvent.selectTask(widget.task.data.id));
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_EndEditingTaskIntent: CallbackAction<_EndEditingTaskIntent>(
|
||||||
|
onInvoke: (_EndEditingTaskIntent intent) {
|
||||||
|
textFieldFocusNode.unfocus();
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void _submitUpdateTaskDescription(String description) {
|
void _submitUpdateTaskDescription(String description) {
|
||||||
@ -423,55 +390,3 @@ 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 = WidgetStatesController();
|
|
||||||
|
|
||||||
@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 WidgetStatePropertyAll(Size.square(32)),
|
|
||||||
minimumSize: const WidgetStatePropertyAll(Size.square(32)),
|
|
||||||
maximumSize: const WidgetStatePropertyAll(Size.square(32)),
|
|
||||||
overlayColor: WidgetStateProperty.resolveWith((state) {
|
|
||||||
if (state.contains(WidgetState.focused)) {
|
|
||||||
return AFThemeExtension.of(context).greyHover;
|
|
||||||
}
|
|
||||||
return Colors.transparent;
|
|
||||||
}),
|
|
||||||
shape: const WidgetStatePropertyAll(
|
|
||||||
RoundedRectangleBorder(borderRadius: Corners.s6Border),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
statesController: _materialStatesController,
|
|
||||||
child: FlowySvg(
|
|
||||||
FlowySvgs.delete_s,
|
|
||||||
color: _materialStatesController.value.contains(WidgetState.hovered) ||
|
|
||||||
_materialStatesController.value.contains(WidgetState.focused)
|
|
||||||
? Theme.of(context).colorScheme.error
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,129 @@
|
|||||||
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra/size.dart';
|
||||||
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import '../../application/cell/bloc/checklist_cell_bloc.dart';
|
||||||
|
|
||||||
|
class ChecklistCellCheckIcon extends StatelessWidget {
|
||||||
|
const ChecklistCellCheckIcon({
|
||||||
|
super.key,
|
||||||
|
required this.task,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ChecklistSelectOption task;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ExcludeFocus(
|
||||||
|
child: FlowyIconButton(
|
||||||
|
width: 32,
|
||||||
|
icon: FlowySvg(
|
||||||
|
task.isSelected ? FlowySvgs.check_filled_s : FlowySvgs.uncheck_s,
|
||||||
|
blendMode: BlendMode.dst,
|
||||||
|
),
|
||||||
|
hoverColor: Colors.transparent,
|
||||||
|
onPressed: () => context.read<ChecklistCellBloc>().add(
|
||||||
|
ChecklistCellEvent.selectTask(task.data.id),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChecklistCellTextfield extends StatelessWidget {
|
||||||
|
const ChecklistCellTextfield({
|
||||||
|
super.key,
|
||||||
|
required this.textController,
|
||||||
|
required this.focusNode,
|
||||||
|
required this.autofocus,
|
||||||
|
required this.onChanged,
|
||||||
|
this.onSubmitted,
|
||||||
|
});
|
||||||
|
|
||||||
|
final TextEditingController textController;
|
||||||
|
final FocusNode focusNode;
|
||||||
|
final bool autofocus;
|
||||||
|
final VoidCallback? onSubmitted;
|
||||||
|
final VoidCallback onChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
const contentPadding = EdgeInsets.symmetric(
|
||||||
|
vertical: 6.0,
|
||||||
|
horizontal: 2.0,
|
||||||
|
);
|
||||||
|
return TextField(
|
||||||
|
controller: textController,
|
||||||
|
focusNode: focusNode,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
isCollapsed: true,
|
||||||
|
contentPadding: contentPadding,
|
||||||
|
hintText: LocaleKeys.grid_checklist_taskHint.tr(),
|
||||||
|
),
|
||||||
|
textInputAction: onSubmitted == null ? TextInputAction.next : null,
|
||||||
|
onChanged: (_) => onChanged(),
|
||||||
|
onSubmitted: (_) => onSubmitted?.call(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChecklistCellDeleteButton extends StatefulWidget {
|
||||||
|
const ChecklistCellDeleteButton({
|
||||||
|
super.key,
|
||||||
|
required this.onPressed,
|
||||||
|
});
|
||||||
|
|
||||||
|
final VoidCallback onPressed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ChecklistCellDeleteButton> createState() =>
|
||||||
|
_ChecklistCellDeleteButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChecklistCellDeleteButtonState extends State<ChecklistCellDeleteButton> {
|
||||||
|
final _materialStatesController = WidgetStatesController();
|
||||||
|
|
||||||
|
@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 WidgetStatePropertyAll(Size.square(32)),
|
||||||
|
minimumSize: const WidgetStatePropertyAll(Size.square(32)),
|
||||||
|
maximumSize: const WidgetStatePropertyAll(Size.square(32)),
|
||||||
|
overlayColor: WidgetStateProperty.resolveWith((state) {
|
||||||
|
if (state.contains(WidgetState.focused)) {
|
||||||
|
return AFThemeExtension.of(context).greyHover;
|
||||||
|
}
|
||||||
|
return Colors.transparent;
|
||||||
|
}),
|
||||||
|
shape: const WidgetStatePropertyAll(
|
||||||
|
RoundedRectangleBorder(borderRadius: Corners.s6Border),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
statesController: _materialStatesController,
|
||||||
|
child: FlowySvg(
|
||||||
|
FlowySvgs.delete_s,
|
||||||
|
color: _materialStatesController.value.contains(WidgetState.hovered) ||
|
||||||
|
_materialStatesController.value.contains(WidgetState.focused)
|
||||||
|
? Theme.of(context).colorScheme.error
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user