mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: checklist ux flow redesign (#3418)
* chore: ux flow redesign * chore: remove unused imports * fix: allow creation of tasks of the same name * chore: apply code suggestions from Mathias
This commit is contained in:
parent
6ba7fc0317
commit
3c65a96b04
@ -456,9 +456,6 @@ void main() {
|
|||||||
// assert that the checklist editor is shown
|
// assert that the checklist editor is shown
|
||||||
tester.assertChecklistEditorVisible(visible: true);
|
tester.assertChecklistEditorVisible(visible: true);
|
||||||
|
|
||||||
// assert that new task editor is shown
|
|
||||||
tester.assertNewCheckListTaskEditorVisible(visible: true);
|
|
||||||
|
|
||||||
// create a new task with enter
|
// create a new task with enter
|
||||||
await tester.createNewChecklistTask(name: "task 0", enter: true);
|
await tester.createNewChecklistTask(name: "task 0", enter: true);
|
||||||
|
|
||||||
@ -481,7 +478,6 @@ void main() {
|
|||||||
|
|
||||||
// dismiss new task editor
|
// dismiss new task editor
|
||||||
await tester.dismissCellEditor();
|
await tester.dismissCellEditor();
|
||||||
tester.assertNewCheckListTaskEditorVisible(visible: false);
|
|
||||||
|
|
||||||
// dismiss checklist cell editor
|
// dismiss checklist cell editor
|
||||||
await tester.dismissCellEditor();
|
await tester.dismissCellEditor();
|
||||||
@ -489,11 +485,8 @@ void main() {
|
|||||||
// assert that progress bar is shown in grid at 0%
|
// assert that progress bar is shown in grid at 0%
|
||||||
tester.assertChecklistCellInGrid(rowIndex: 0, percent: 0);
|
tester.assertChecklistCellInGrid(rowIndex: 0, percent: 0);
|
||||||
|
|
||||||
// start editing the first checklist cell again, click on new task button
|
// start editing the first checklist cell again
|
||||||
await tester.tapChecklistCellInGrid(rowIndex: 0);
|
await tester.tapChecklistCellInGrid(rowIndex: 0);
|
||||||
tester.assertNewCheckListTaskEditorVisible(visible: false);
|
|
||||||
await tester.tapChecklistNewTaskButton();
|
|
||||||
tester.assertNewCheckListTaskEditorVisible(visible: true);
|
|
||||||
|
|
||||||
// create another task with the create button
|
// create another task with the create button
|
||||||
await tester.createNewChecklistTask(name: "task 2", button: true);
|
await tester.createNewChecklistTask(name: "task 2", button: true);
|
||||||
@ -540,9 +533,6 @@ void main() {
|
|||||||
// delete the remaining task
|
// delete the remaining task
|
||||||
await tester.deleteChecklistTask(index: 0);
|
await tester.deleteChecklistTask(index: 0);
|
||||||
|
|
||||||
// assert that the new task editor is shown
|
|
||||||
tester.assertNewCheckListTaskEditorVisible(visible: true);
|
|
||||||
|
|
||||||
// dismiss the cell editor
|
// dismiss the cell editor
|
||||||
await tester.dismissCellEditor();
|
await tester.dismissCellEditor();
|
||||||
|
|
||||||
|
@ -46,7 +46,6 @@ import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/date_
|
|||||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/text_field.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/text_field.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/timestamp_cell/timestamp_cell.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/row_action.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/row_action.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/row_banner.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/row_banner.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
|
||||||
@ -494,15 +493,6 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void assertNewCheckListTaskEditorVisible({required bool visible}) {
|
|
||||||
final editor = find.byType(NewTaskItem);
|
|
||||||
if (visible) {
|
|
||||||
expect(editor, findsOneWidget);
|
|
||||||
} else {
|
|
||||||
expect(editor, findsNothing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> createNewChecklistTask({
|
Future<void> createNewChecklistTask({
|
||||||
required String name,
|
required String name,
|
||||||
enter = false,
|
enter = false,
|
||||||
@ -515,10 +505,10 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await enterText(textField, name);
|
await enterText(textField, name);
|
||||||
await pumpAndSettle(const Duration(milliseconds: 300));
|
await pumpAndSettle();
|
||||||
if (enter) {
|
if (enter) {
|
||||||
await testTextInput.receiveAction(TextInputAction.done);
|
await testTextInput.receiveAction(TextInputAction.done);
|
||||||
await pumpAndSettle(const Duration(milliseconds: 300));
|
await pumpAndSettle();
|
||||||
} else {
|
} else {
|
||||||
await tapButton(
|
await tapButton(
|
||||||
find.descendant(
|
find.descendant(
|
||||||
@ -555,11 +545,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
|
|
||||||
await enterText(textField, name);
|
await enterText(textField, name);
|
||||||
await testTextInput.receiveAction(TextInputAction.done);
|
await testTextInput.receiveAction(TextInputAction.done);
|
||||||
await pumpAndSettle(const Duration(milliseconds: 300));
|
await pumpAndSettle();
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> tapChecklistNewTaskButton() async {
|
|
||||||
await tapButton(find.byType(ChecklistNewTaskButton));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> checkChecklistTask({required int index}) async {
|
Future<void> checkChecklistTask({required int index}) async {
|
||||||
|
@ -67,6 +67,7 @@ class GridCellBuilder {
|
|||||||
case FieldType.Checklist:
|
case FieldType.Checklist:
|
||||||
return GridChecklistCell(
|
return GridChecklistCell(
|
||||||
cellControllerBuilder: cellControllerBuilder,
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
|
style: style,
|
||||||
key: key,
|
key: key,
|
||||||
);
|
);
|
||||||
case FieldType.Number:
|
case FieldType.Number:
|
||||||
|
@ -4,4 +4,5 @@ export 'date_cell/date_cell.dart';
|
|||||||
export 'number_cell/number_cell.dart';
|
export 'number_cell/number_cell.dart';
|
||||||
export 'select_option_cell/select_option_cell.dart';
|
export 'select_option_cell/select_option_cell.dart';
|
||||||
export 'text_cell/text_cell.dart';
|
export 'text_cell/text_cell.dart';
|
||||||
|
export 'timestamp_cell/timestamp_cell.dart';
|
||||||
export 'url_cell/url_cell.dart';
|
export 'url_cell/url_cell.dart';
|
||||||
|
@ -10,10 +10,26 @@ import 'checklist_cell_bloc.dart';
|
|||||||
import 'checklist_cell_editor.dart';
|
import 'checklist_cell_editor.dart';
|
||||||
import 'checklist_progress_bar.dart';
|
import 'checklist_progress_bar.dart';
|
||||||
|
|
||||||
|
class ChecklistCellStyle extends GridCellStyle {
|
||||||
|
String placeholder;
|
||||||
|
EdgeInsets? cellPadding;
|
||||||
|
|
||||||
|
ChecklistCellStyle({
|
||||||
|
required this.placeholder,
|
||||||
|
this.cellPadding,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
class GridChecklistCell extends GridCellWidget {
|
class GridChecklistCell extends GridCellWidget {
|
||||||
final CellControllerBuilder cellControllerBuilder;
|
final CellControllerBuilder cellControllerBuilder;
|
||||||
GridChecklistCell({required this.cellControllerBuilder, Key? key})
|
late final ChecklistCellStyle? cellStyle;
|
||||||
: super(key: key);
|
GridChecklistCell({
|
||||||
|
required this.cellControllerBuilder,
|
||||||
|
GridCellStyle? style,
|
||||||
|
super.key,
|
||||||
|
}) {
|
||||||
|
cellStyle = style as ChecklistCellStyle?;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GridCellState<GridChecklistCell> createState() => GridChecklistCellState();
|
GridCellState<GridChecklistCell> createState() => GridChecklistCellState();
|
||||||
@ -53,15 +69,22 @@ class GridChecklistCellState extends GridCellState<GridChecklistCell> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
onClose: () => widget.onCellFocus.value = false,
|
onClose: () => widget.onCellFocus.value = false,
|
||||||
child: Padding(
|
child: Align(
|
||||||
padding: GridSize.cellContentInsets,
|
alignment: Alignment.centerLeft,
|
||||||
child: BlocBuilder<ChecklistCardCellBloc, ChecklistCellState>(
|
child: Padding(
|
||||||
builder: (context, state) {
|
padding:
|
||||||
if (state.allOptions.isEmpty) {
|
widget.cellStyle?.cellPadding ?? GridSize.cellContentInsets,
|
||||||
return const SizedBox.shrink();
|
child: BlocBuilder<ChecklistCardCellBloc, ChecklistCellState>(
|
||||||
}
|
builder: (context, state) {
|
||||||
return ChecklistProgressBar(percent: state.percent);
|
if (state.allOptions.isEmpty) {
|
||||||
},
|
return FlowyText.medium(
|
||||||
|
widget.cellStyle?.placeholder ?? "",
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ChecklistProgressBar(percent: state.percent);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
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_view/application/cell/cell_controller_builder.dart';
|
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
||||||
@ -30,13 +32,19 @@ class _GridChecklistCellEditorState extends State<GridChecklistCellEditor> {
|
|||||||
/// Focus node for the new task text field
|
/// Focus node for the new task text field
|
||||||
late final FocusNode newTaskFocusNode;
|
late final FocusNode newTaskFocusNode;
|
||||||
|
|
||||||
/// A flag that determines whether the new task text field is visible
|
|
||||||
bool _isAddingNewTask = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
newTaskFocusNode = FocusNode();
|
newTaskFocusNode = FocusNode(
|
||||||
|
onKey: (node, event) {
|
||||||
|
if (event is RawKeyDownEvent &&
|
||||||
|
event.logicalKey == LogicalKeyboardKey.escape) {
|
||||||
|
node.unfocus();
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
},
|
||||||
|
);
|
||||||
_bloc = ChecklistCellEditorBloc(cellController: widget.cellController)
|
_bloc = ChecklistCellEditorBloc(cellController: widget.cellController)
|
||||||
..add(const ChecklistCellEditorEvent.initial());
|
..add(const ChecklistCellEditorEvent.initial());
|
||||||
}
|
}
|
||||||
@ -48,59 +56,35 @@ class _GridChecklistCellEditorState extends State<GridChecklistCellEditor> {
|
|||||||
child: BlocConsumer<ChecklistCellEditorBloc, ChecklistCellEditorState>(
|
child: BlocConsumer<ChecklistCellEditorBloc, ChecklistCellEditorState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state.allOptions.isEmpty) {
|
if (state.allOptions.isEmpty) {
|
||||||
setState(() => _isAddingNewTask = true);
|
newTaskFocusNode.requestFocus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Focus(
|
return Column(
|
||||||
onKey: (node, event) {
|
mainAxisSize: MainAxisSize.min,
|
||||||
// don't hide new task text field if there are no tasks at all
|
children: [
|
||||||
if (state.allOptions.isNotEmpty &&
|
AnimatedSwitcher(
|
||||||
event is RawKeyDownEvent &&
|
duration: const Duration(milliseconds: 300),
|
||||||
event.logicalKey == LogicalKeyboardKey.escape) {
|
child: state.allOptions.isEmpty
|
||||||
setState(() {
|
? const SizedBox.shrink()
|
||||||
_isAddingNewTask = false;
|
: Padding(
|
||||||
});
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 4),
|
||||||
return KeyEventResult.handled;
|
child: ChecklistProgressBar(
|
||||||
}
|
percent: state.percent,
|
||||||
return KeyEventResult.ignored;
|
),
|
||||||
},
|
),
|
||||||
child: CustomScrollView(
|
),
|
||||||
shrinkWrap: true,
|
ChecklistItemList(
|
||||||
physics: StyledScrollPhysics(),
|
options: state.allOptions,
|
||||||
slivers: [
|
onUpdateTask: () => newTaskFocusNode.requestFocus(),
|
||||||
SliverToBoxAdapter(
|
),
|
||||||
child: AnimatedSwitcher(
|
if (state.allOptions.isNotEmpty)
|
||||||
duration: const Duration(milliseconds: 300),
|
const TypeOptionSeparator(spacing: 0.0),
|
||||||
child: state.allOptions.isEmpty
|
Padding(
|
||||||
? const SizedBox.shrink()
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
: Padding(
|
child: NewTaskItem(focusNode: newTaskFocusNode),
|
||||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 4),
|
),
|
||||||
child: ChecklistProgressBar(
|
],
|
||||||
percent: state.percent,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ChecklistItemList(
|
|
||||||
options: state.allOptions,
|
|
||||||
newTaskFocusNode: newTaskFocusNode,
|
|
||||||
isAddingNewTask: _isAddingNewTask,
|
|
||||||
onUpdateTask: () => setState(() {
|
|
||||||
_isAddingNewTask = true;
|
|
||||||
newTaskFocusNode.requestFocus();
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
const SliverToBoxAdapter(
|
|
||||||
child: TypeOptionSeparator(spacing: 0.0),
|
|
||||||
),
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: ChecklistNewTaskButton(
|
|
||||||
onTap: () => setState(() => _isAddingNewTask = true),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -118,16 +102,12 @@ class _GridChecklistCellEditorState extends State<GridChecklistCellEditor> {
|
|||||||
/// a new task if `isAddingNewTask` is true
|
/// a new task if `isAddingNewTask` is true
|
||||||
class ChecklistItemList extends StatefulWidget {
|
class ChecklistItemList extends StatefulWidget {
|
||||||
final List<ChecklistSelectOption> options;
|
final List<ChecklistSelectOption> options;
|
||||||
final FocusNode newTaskFocusNode;
|
|
||||||
final bool isAddingNewTask;
|
|
||||||
final VoidCallback onUpdateTask;
|
final VoidCallback onUpdateTask;
|
||||||
|
|
||||||
const ChecklistItemList({
|
const ChecklistItemList({
|
||||||
super.key,
|
super.key,
|
||||||
required this.options,
|
required this.options,
|
||||||
required this.onUpdateTask,
|
required this.onUpdateTask,
|
||||||
required this.isAddingNewTask,
|
|
||||||
required this.newTaskFocusNode,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -137,32 +117,28 @@ class ChecklistItemList extends StatefulWidget {
|
|||||||
class _ChecklistItemListState extends State<ChecklistItemList> {
|
class _ChecklistItemListState extends State<ChecklistItemList> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final itemList = [
|
if (widget.options.isEmpty) {
|
||||||
const VSpace(6.0),
|
return const SizedBox.shrink();
|
||||||
...widget.options.mapIndexed(
|
}
|
||||||
(index, option) => Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 2),
|
final itemList = widget.options
|
||||||
child: ChecklistItem(
|
.mapIndexed(
|
||||||
|
(index, option) => ChecklistItem(
|
||||||
option: option,
|
option: option,
|
||||||
onSubmitted:
|
onSubmitted:
|
||||||
index == widget.options.length - 1 ? widget.onUpdateTask : null,
|
index == widget.options.length - 1 ? widget.onUpdateTask : null,
|
||||||
key: ValueKey(option.data.id),
|
key: ValueKey(option.data.id),
|
||||||
// only allow calling the callback for the last task in the list
|
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
),
|
.toList();
|
||||||
AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
return Flexible(
|
||||||
child: widget.isAddingNewTask
|
child: ListView.separated(
|
||||||
? NewTaskItem(focusNode: widget.newTaskFocusNode)
|
itemBuilder: (context, index) => itemList[index],
|
||||||
: const SizedBox.shrink(),
|
separatorBuilder: (context, index) => const VSpace(4),
|
||||||
),
|
itemCount: itemList.length,
|
||||||
const VSpace(6.0),
|
shrinkWrap: true,
|
||||||
];
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
return SliverList(
|
|
||||||
delegate: SliverChildBuilderDelegate(
|
|
||||||
(BuildContext context, int index) => itemList[index],
|
|
||||||
childCount: itemList.length,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -187,6 +163,7 @@ class _ChecklistItemState extends State<ChecklistItem> {
|
|||||||
late final TextEditingController _textController;
|
late final TextEditingController _textController;
|
||||||
late final FocusNode _focusNode;
|
late final FocusNode _focusNode;
|
||||||
bool _isHovered = false;
|
bool _isHovered = false;
|
||||||
|
Timer? _debounceOnChanged;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -249,13 +226,9 @@ class _ChecklistItemState extends State<ChecklistItem> {
|
|||||||
),
|
),
|
||||||
hintText: LocaleKeys.grid_checklist_taskHint.tr(),
|
hintText: LocaleKeys.grid_checklist_taskHint.tr(),
|
||||||
),
|
),
|
||||||
onSubmitted: (taskDescription) {
|
onChanged: _debounceOnChangedText,
|
||||||
context.read<ChecklistCellEditorBloc>().add(
|
onSubmitted: (description) {
|
||||||
ChecklistCellEditorEvent.updateTaskName(
|
_submitUpdateTaskDescription(description);
|
||||||
widget.option.data,
|
|
||||||
taskDescription,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
widget.onSubmitted?.call();
|
widget.onSubmitted?.call();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -276,6 +249,22 @@ class _ChecklistItemState extends State<ChecklistItem> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _debounceOnChangedText(String text) {
|
||||||
|
_debounceOnChanged?.cancel();
|
||||||
|
_debounceOnChanged = Timer(const Duration(milliseconds: 300), () {
|
||||||
|
_submitUpdateTaskDescription(text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _submitUpdateTaskDescription(String description) {
|
||||||
|
context.read<ChecklistCellEditorBloc>().add(
|
||||||
|
ChecklistCellEditorEvent.updateTaskName(
|
||||||
|
widget.option.data,
|
||||||
|
description,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new task after entering the description and pressing enter.
|
/// Creates a new task after entering the description and pressing enter.
|
||||||
@ -304,19 +293,12 @@ class _NewTaskItemState extends State<NewTaskItem> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
constraints: BoxConstraints(minHeight: GridSize.popoverItemHeight),
|
constraints: BoxConstraints(minHeight: GridSize.popoverItemHeight),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const FlowyIconButton(
|
const HSpace(8),
|
||||||
width: 32,
|
|
||||||
icon: FlowySvg(
|
|
||||||
FlowySvgs.uncheck_s,
|
|
||||||
blendMode: BlendMode.dst,
|
|
||||||
),
|
|
||||||
hoverColor: Colors.transparent,
|
|
||||||
),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
focusNode: widget.focusNode,
|
focusNode: widget.focusNode,
|
||||||
@ -330,7 +312,7 @@ class _NewTaskItemState extends State<NewTaskItem> {
|
|||||||
vertical: 6.0,
|
vertical: 6.0,
|
||||||
horizontal: 2.0,
|
horizontal: 2.0,
|
||||||
),
|
),
|
||||||
hintText: LocaleKeys.grid_checklist_taskHint.tr(),
|
hintText: LocaleKeys.grid_checklist_addNew.tr(),
|
||||||
),
|
),
|
||||||
onSubmitted: (taskDescription) {
|
onSubmitted: (taskDescription) {
|
||||||
if (taskDescription.trim().isNotEmpty) {
|
if (taskDescription.trim().isNotEmpty) {
|
||||||
@ -340,15 +322,21 @@ class _NewTaskItemState extends State<NewTaskItem> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
widget.focusNode.requestFocus();
|
||||||
_textEditingController.clear();
|
_textEditingController.clear();
|
||||||
},
|
},
|
||||||
|
onChanged: (value) => setState(() {}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
FlowyTextButton(
|
FlowyTextButton(
|
||||||
LocaleKeys.grid_checklist_submitNewTask.tr(),
|
LocaleKeys.grid_checklist_submitNewTask.tr(),
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fillColor: Theme.of(context).colorScheme.primary,
|
fillColor: _textEditingController.text.isEmpty
|
||||||
hoverColor: Theme.of(context).colorScheme.primaryContainer,
|
? Theme.of(context).disabledColor
|
||||||
|
: Theme.of(context).colorScheme.primary,
|
||||||
|
hoverColor: _textEditingController.text.isEmpty
|
||||||
|
? Theme.of(context).disabledColor
|
||||||
|
: Theme.of(context).colorScheme.primaryContainer,
|
||||||
fontColor: Theme.of(context).colorScheme.onPrimary,
|
fontColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -359,6 +347,7 @@ class _NewTaskItemState extends State<NewTaskItem> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
widget.focusNode.requestFocus();
|
||||||
_textEditingController.clear();
|
_textEditingController.clear();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -367,25 +356,3 @@ class _NewTaskItemState extends State<NewTaskItem> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
|
||||||
class ChecklistNewTaskButton extends StatelessWidget {
|
|
||||||
final VoidCallback onTap;
|
|
||||||
const ChecklistNewTaskButton({super.key, required this.onTap});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0),
|
|
||||||
child: SizedBox(
|
|
||||||
height: 30,
|
|
||||||
child: FlowyButton(
|
|
||||||
text: FlowyText.medium(LocaleKeys.grid_checklist_addNew.tr()),
|
|
||||||
margin: const EdgeInsets.all(6),
|
|
||||||
leftIcon: const FlowySvg(FlowySvgs.add_s),
|
|
||||||
onTap: onTap,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -6,6 +6,7 @@ import 'package:appflowy/plugins/database_view/application/field/type_option/typ
|
|||||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||||
@ -19,13 +20,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
|
|
||||||
import 'accessory/cell_accessory.dart';
|
import 'accessory/cell_accessory.dart';
|
||||||
import 'cell_builder.dart';
|
import 'cell_builder.dart';
|
||||||
import 'cells/checkbox_cell/checkbox_cell.dart';
|
|
||||||
import 'cells/date_cell/date_cell.dart';
|
|
||||||
import 'cells/number_cell/number_cell.dart';
|
|
||||||
import 'cells/select_option_cell/select_option_cell.dart';
|
|
||||||
import 'cells/text_cell/text_cell.dart';
|
|
||||||
import 'cells/timestamp_cell/timestamp_cell.dart';
|
|
||||||
import 'cells/url_cell/url_cell.dart';
|
|
||||||
|
|
||||||
/// Display the row properties in a list. Only use this widget in the
|
/// Display the row properties in a list. Only use this widget in the
|
||||||
/// [RowDetailPage].
|
/// [RowDetailPage].
|
||||||
@ -251,8 +245,9 @@ GridCellStyle? _customCellStyle(FieldType fieldType) {
|
|||||||
cellPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
|
cellPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
|
||||||
);
|
);
|
||||||
case FieldType.Checklist:
|
case FieldType.Checklist:
|
||||||
return SelectOptionCellStyle(
|
return ChecklistCellStyle(
|
||||||
placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
|
placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
|
||||||
|
cellPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||||
);
|
);
|
||||||
case FieldType.Number:
|
case FieldType.Number:
|
||||||
return GridNumberCellStyle(
|
return GridNumberCellStyle(
|
||||||
|
@ -87,7 +87,7 @@ impl CellDataChangeset for ChecklistTypeOption {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn update_cell_data_with_changeset(
|
fn update_cell_data_with_changeset(
|
||||||
cell_data: &mut ChecklistCellData,
|
cell_data: &mut ChecklistCellData,
|
||||||
mut changeset: ChecklistCellChangeset,
|
changeset: ChecklistCellChangeset,
|
||||||
) {
|
) {
|
||||||
// Delete the options
|
// Delete the options
|
||||||
cell_data
|
cell_data
|
||||||
@ -98,12 +98,6 @@ fn update_cell_data_with_changeset(
|
|||||||
.retain(|option_id| !changeset.delete_option_ids.contains(option_id));
|
.retain(|option_id| !changeset.delete_option_ids.contains(option_id));
|
||||||
|
|
||||||
// Insert new options
|
// Insert new options
|
||||||
changeset.insert_options.retain(|option_name| {
|
|
||||||
!cell_data
|
|
||||||
.options
|
|
||||||
.iter()
|
|
||||||
.any(|option| option.name == *option_name)
|
|
||||||
});
|
|
||||||
changeset
|
changeset
|
||||||
.insert_options
|
.insert_options
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
Loading…
Reference in New Issue
Block a user