Skip to content

Commit 93d2179

Browse files
angelosilvestrematthew-carroll
authored andcommitted
[SuperReader] - Add support for rendering tasks (Resolves #2354) (#2538)
1 parent 077a1fa commit 93d2179

File tree

9 files changed

+151
-7
lines changed

9 files changed

+151
-7
lines changed

super_editor/example/lib/demos/demo_animated_task_height.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ class _AnimatedTaskComponentState extends State<_AnimatedTaskComponent>
130130
child: Checkbox(
131131
value: widget.viewModel.isComplete,
132132
onChanged: (newValue) {
133-
widget.viewModel.setComplete(newValue!);
133+
widget.viewModel.setComplete?.call(newValue!);
134134
},
135135
),
136136
),

super_editor/example/lib/demos/super_reader/demo_super_reader.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ class _SuperReaderDemoState extends State<SuperReaderDemo> {
134134
selection: _selection,
135135
overlayController: _overlayController,
136136
selectionLayerLinks: _selectionLayerLinks,
137+
stylesheet: defaultStylesheet.copyWith(
138+
addRulesAfter: [
139+
taskStyles,
140+
],
141+
),
137142
androidToolbarBuilder: (_) => AndroidTextEditingFloatingToolbar(
138143
onCopyPressed: _copy,
139144
onSelectAllPressed: _selectAll,

super_editor/example/lib/demos/super_reader/example_document.dart

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,38 @@ Document createInitialDocument() {
8686
id: Editor.createNodeId(),
8787
text: AttributedText('Use AttributedText to quickly and easily apply metadata spans to a string.'),
8888
),
89+
ParagraphNode(
90+
id: Editor.createNodeId(),
91+
text: AttributedText('Quickstart 🚀'),
92+
metadata: {
93+
'blockType': header2Attribution,
94+
},
95+
),
96+
ParagraphNode(
97+
id: Editor.createNodeId(),
98+
text: AttributedText('To get started with your own editing experience, take the following steps:'),
99+
),
100+
TaskNode(
101+
id: Editor.createNodeId(),
102+
isComplete: true,
103+
text: AttributedText(
104+
'Create and configure your document, for example, by creating a new MutableDocument.',
105+
),
106+
),
107+
TaskNode(
108+
id: Editor.createNodeId(),
109+
isComplete: false,
110+
text: AttributedText(
111+
"If you want programmatic control over the user's selection and styles, create a DocumentComposer.",
112+
),
113+
),
114+
TaskNode(
115+
id: Editor.createNodeId(),
116+
isComplete: false,
117+
text: AttributedText(
118+
"Build a SuperEditor widget in your widget tree, configured with your Document and (optionally) your DocumentComposer.",
119+
),
120+
),
89121
ParagraphNode(
90122
id: Editor.createNodeId(),
91123
text: AttributedText(

super_editor/example_perf/lib/demos/rebuild_demo.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ class _AnimatedTaskComponentState extends State<_AnimatedTaskComponent>
136136
child: Checkbox(
137137
value: widget.viewModel.isComplete,
138138
onChanged: (newValue) {
139-
widget.viewModel.setComplete(newValue!);
139+
widget.viewModel.setComplete?.call(newValue!);
140140
},
141141
),
142142
),

super_editor/lib/src/default_editor/tasks.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ class TaskComponentViewModel extends SingleColumnLayoutComponentViewModel with T
245245
TextBlockIndentCalculator indentCalculator;
246246

247247
bool isComplete;
248-
void Function(bool) setComplete;
248+
void Function(bool)? setComplete;
249249

250250
@override
251251
AttributedText text;
@@ -398,9 +398,11 @@ class _TaskComponentState extends State<TaskComponent> with ProxyDocumentCompone
398398
child: Checkbox(
399399
visualDensity: Theme.of(context).visualDensity,
400400
value: widget.viewModel.isComplete,
401-
onChanged: (newValue) {
402-
widget.viewModel.setComplete(newValue!);
403-
},
401+
onChanged: widget.viewModel.setComplete != null
402+
? (newValue) {
403+
widget.viewModel.setComplete!(newValue!);
404+
}
405+
: null,
404406
),
405407
),
406408
Expanded(

super_editor/lib/src/super_reader/super_reader.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import 'package:super_editor/src/default_editor/layout_single_column/_styler_shy
2020
import 'package:super_editor/src/default_editor/layout_single_column/_styler_user_selection.dart';
2121
import 'package:super_editor/src/default_editor/list_items.dart';
2222
import 'package:super_editor/src/default_editor/paragraph.dart';
23+
import 'package:super_editor/src/default_editor/tasks.dart';
2324
import 'package:super_editor/src/default_editor/text.dart';
2425
import 'package:super_editor/src/default_editor/unknown_component.dart';
2526
import 'package:super_editor/src/infrastructure/_logging.dart';
@@ -34,6 +35,7 @@ import 'package:super_editor/src/infrastructure/links.dart';
3435
import 'package:super_editor/src/infrastructure/platforms/ios/ios_document_controls.dart';
3536
import 'package:super_editor/src/infrastructure/platforms/ios/toolbar.dart';
3637
import 'package:super_editor/src/infrastructure/platforms/platform.dart';
38+
import 'package:super_editor/src/super_reader/tasks.dart';
3739

3840
import '../infrastructure/platforms/mobile_documents.dart';
3941
import '../infrastructure/text_input.dart';
@@ -742,6 +744,7 @@ final readOnlyDefaultComponentBuilders = <ComponentBuilder>[
742744
const ListItemComponentBuilder(),
743745
const ImageComponentBuilder(),
744746
const HorizontalRuleComponentBuilder(),
747+
const ReadOnlyTaskComponentBuilder(),
745748
];
746749

747750
/// Stylesheet applied to all [SuperReader]s by default.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:super_editor/src/core/document.dart';
3+
import 'package:super_editor/src/default_editor/layout_single_column/layout_single_column.dart';
4+
import 'package:super_editor/src/default_editor/tasks.dart';
5+
import 'package:super_editor/src/default_editor/text_tools.dart';
6+
7+
/// Builds [TaskComponentViewModel]s and [TaskComponent]s for every
8+
/// [TaskNode] in a document.
9+
///
10+
/// A [TaskComponent] built by this builder is read-only, meaning that
11+
/// the user cannot toggle it.
12+
class ReadOnlyTaskComponentBuilder implements ComponentBuilder {
13+
const ReadOnlyTaskComponentBuilder();
14+
15+
@override
16+
TaskComponentViewModel? createViewModel(Document document, DocumentNode node) {
17+
if (node is! TaskNode) {
18+
return null;
19+
}
20+
21+
final textDirection = getParagraphDirection(node.text.toPlainText());
22+
23+
return TaskComponentViewModel(
24+
nodeId: node.id,
25+
padding: EdgeInsets.zero,
26+
indent: node.indent,
27+
isComplete: node.isComplete,
28+
setComplete: null,
29+
text: node.text,
30+
textDirection: textDirection,
31+
textAlignment: textDirection == TextDirection.ltr ? TextAlign.left : TextAlign.right,
32+
textStyleBuilder: noStyleBuilder,
33+
selectionColor: const Color(0x00000000),
34+
);
35+
}
36+
37+
@override
38+
Widget? createComponent(
39+
SingleColumnDocumentComponentContext componentContext, SingleColumnLayoutComponentViewModel componentViewModel) {
40+
if (componentViewModel is! TaskComponentViewModel) {
41+
return null;
42+
}
43+
44+
return TaskComponent(
45+
key: componentContext.componentKey,
46+
viewModel: componentViewModel,
47+
);
48+
}
49+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import 'package:flutter_test/flutter_test.dart';
2+
import 'package:flutter_test_runners/flutter_test_runners.dart';
3+
import 'package:super_editor/src/test/super_editor_test/tasks_test_tools.dart';
4+
import 'package:super_editor/super_editor.dart';
5+
6+
import '../reader_test_tools.dart';
7+
8+
void main() {
9+
group('SuperReader tasks >', () {
10+
testWidgetsOnAllPlatforms("are displayed in a read-only document", (tester) async {
11+
await tester //
12+
.createDocument()
13+
.withCustomContent(
14+
MutableDocument(
15+
nodes: [
16+
TaskNode(id: "1", text: AttributedText(), isComplete: false),
17+
TaskNode(id: "2", text: AttributedText(), isComplete: true),
18+
],
19+
),
20+
)
21+
.pump();
22+
23+
// Ensure the default task component is rendered.
24+
expect(find.byType(TaskComponent), findsNWidgets(2));
25+
});
26+
27+
testWidgetsOnAllPlatforms("cannot be toggled by the user", (tester) async {
28+
await tester //
29+
.createDocument()
30+
.withCustomContent(
31+
MutableDocument(
32+
nodes: [
33+
TaskNode(id: '1', text: AttributedText(), isComplete: false),
34+
TaskNode(id: '2', text: AttributedText(), isComplete: true),
35+
],
36+
),
37+
)
38+
.pump();
39+
40+
// Tap on the first task's checkbox.
41+
await tester.tapOnCheckbox('1');
42+
43+
// Ensure that the task's checkbox didn't change from unchecked to checked.
44+
expect(TaskInspector.isChecked('1'), isFalse);
45+
46+
// Tap on the second task's checkbox.
47+
await tester.tapOnCheckbox('2');
48+
49+
// Ensure that the task's checkbox didn't change from checked to unchecked.
50+
expect(TaskInspector.isChecked('1'), isFalse);
51+
});
52+
});
53+
}

super_editor/test/super_reader/reader_test_tools.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ class TestDocumentConfigurator {
287287
stylesheet: _stylesheet,
288288
componentBuilders: [
289289
..._addedComponents,
290-
...(_componentBuilders ?? defaultComponentBuilders),
290+
...(_componentBuilders ?? readOnlyDefaultComponentBuilders),
291291
],
292292
scrollController: _scrollController,
293293
androidToolbarBuilder: _androidToolbarBuilder,

0 commit comments

Comments
 (0)