Skip to content

Commit ddd6dcc

Browse files
committed
chore: Adding quill editor instantiation section on README. #1
1 parent cda282f commit ddd6dcc

File tree

1 file changed

+242
-0
lines changed

1 file changed

+242
-0
lines changed

README.md

+242
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
- [1. Installing all the needed dependencies](#1-installing-all-the-needed-dependencies)
2525
- [2. Setting up the responsive framework](#2-setting-up-the-responsive-framework)
2626
- [3. Create `HomePage` with basic editor](#3-create-homepage-with-basic-editor)
27+
- [4. Adding the `Quill` editor](#4-adding-the-quill-editor)
28+
- [4.1 Instantiating `QuillEditor`](#41-instantiating-quilleditor)
29+
- [4.2 Defining triple click selection behaviour](#42-defining-triple-click-selection-behaviour)
2730

2831

2932
# Why? 🤷‍
@@ -295,6 +298,9 @@ class HomePageState extends State<HomePage> {
295298
/// `flutter-quill` editor controller
296299
QuillController? _controller;
297300
301+
/// Focus node used to obtain keyboard focus and events
302+
final FocusNode _focusNode = FocusNode();
303+
298304
@override
299305
void initState() {
300306
super.initState();
@@ -346,4 +352,240 @@ and the **text** that is written in the editor.
346352
347353
For the Quill Editor to properly function,
348354
we are going to use this `_controller` parameter later on.
355+
The `_focusNode` parameter is also needed in the next section.
356+
357+
358+
## 4. Adding the `Quill` editor
359+
360+
Now here comes to fun part!
361+
Let's instantiate the editor so it can be shown on screen!
362+
363+
Inside `HomePageState`,
364+
we are going to instantiate and define
365+
our editor and render it on its body.
366+
So, let's do that!
367+
368+
Inside the `build()` function,
369+
change it to this:
370+
371+
```dart
372+
return Scaffold(
373+
appBar: AppBar(
374+
backgroundColor: Colors.white,
375+
elevation: 0,
376+
centerTitle: false,
377+
title: const Text(
378+
'Flutter Quill',
379+
),
380+
),
381+
body: _buildEditor(),
382+
);
383+
```
384+
385+
Now, inside `HomePageState`,
386+
let's create this `_buildEditor()` function,
387+
which will return the **Quill Editor** widget! 🥳
388+
389+
```dart
390+
Widget _buildEditor(BuildContext context) {
391+
392+
}
393+
```
394+
395+
Now, let's start implementing our editor!
396+
397+
398+
### 4.1 Instantiating `QuillEditor`
399+
400+
[`QuillEditor`](https://github.com/singerdmx/flutter-quill/blob/36d72c1987f0cb8d6c689c12542600364c07e20f/lib/src/widgets/editor.dart#L149)
401+
is the main class in which we define the behaviour
402+
we want from our editor.
403+
404+
In `_buildEditor`, add this:
405+
406+
```dart
407+
Widget quillEditor = QuillEditor(
408+
controller: _controller!,
409+
scrollController: ScrollController(),
410+
scrollable: true,
411+
focusNode: _focusNode,
412+
autoFocus: false,
413+
readOnly: false,
414+
placeholder: 'Write what\'s on your mind.',
415+
enableSelectionToolbar: isMobile(),
416+
expands: false,
417+
padding: EdgeInsets.zero,
418+
onTapUp: (details, p1) {
419+
return _onTripleClickSelection();
420+
},
421+
customStyles: DefaultStyles(
422+
h1: DefaultTextBlockStyle(
423+
const TextStyle(
424+
fontSize: 32,
425+
color: Colors.black,
426+
height: 1.15,
427+
fontWeight: FontWeight.w300,
428+
),
429+
const VerticalSpacing(16, 0),
430+
const VerticalSpacing(0, 0),
431+
null,
432+
),
433+
sizeSmall: const TextStyle(fontSize: 9),
434+
subscript: const TextStyle(
435+
fontFamily: 'SF-UI-Display',
436+
fontFeatures: [FontFeature.subscripts()],
437+
),
438+
superscript: const TextStyle(
439+
fontFamily: 'SF-UI-Display',
440+
fontFeatures: [FontFeature.superscripts()],
441+
),
442+
),
443+
embedBuilders: [...FlutterQuillEmbeds.builders()],
444+
);
445+
```
446+
447+
As you may see, the `QuillEditor` class
448+
can take a *lot* of parameters.
449+
450+
So let's go over them one by one!
451+
452+
- **`controller`** is where
453+
we need to pass a `QuillController` object.
454+
This field is mandatory.
455+
- **`scrollController`** receives a
456+
[`ScrollController`](https://api.flutter.dev/flutter/widgets/ScrollController-class.html)
457+
object.
458+
This is used to properly scroll the editor vertically and horizontally.
459+
- **`scrollable`** is a boolean
460+
that defines if the editor is scrollable or not.
461+
- **`focusNode`** parameter
462+
receives a [`FocusNode`](https://api.flutter.dev/flutter/widgets/FocusNode-class.html)
463+
object.
464+
With this, we can control the events that come from the keyboard.
465+
We've defined `_focusNode` in the previous section for this very purpose.
466+
- **`autoFocus`** defines whether the editor
467+
should focus itself if nothing else is already focused.
468+
If `true`, the keyboard will open as soon as the editor obtain focus.
469+
Otherwise, the keyboard is only shown *after* the person taps the editor.
470+
- **`readOnly`** defines whether the text in the editor is read-only.
471+
- **`placeholder`** pertains
472+
to an optional placeholder text the person can see when the editor is empty.
473+
- **`enableSelectionToolbar`** defines whether to show
474+
the cut/copy/paste menu when selecting text.
475+
We are only doing this on mobile devices
476+
by using `flutter_quill`'s `isMobile()` function.
477+
- **`expands`** pertains to
478+
whether this editor's height will be sized to fill its parent.
479+
- **`padding`** will configure the padding of the editor.
480+
We've set it to `EdgeInsets.zero`,
481+
meaning there's no padding.
482+
- **`customStyles`**,
483+
which allows us to override the default styles applied to text.
484+
The editor allows us to set different styles
485+
of the text with the [`DefaultStyles`](https://github.com/singerdmx/flutter-quill/blob/36d72c1987f0cb8d6c689c12542600364c07e20f/lib/src/widgets/default_styles.dart#L139)
486+
class.
487+
For example, we can set how big a `h1` text is.
488+
- **`embedBuilders`** receives [embed blocks](https://github.com/singerdmx/flutter-quill#embed-blocks).
489+
These provide implementation details for rendering images/videos and other files
490+
in the editor.
491+
These are *not* provided by default as part of this package,
492+
hence why we use the `flutter_quill_extensions` package
493+
to get the default embeds.
494+
- **`onTapUp`** defines a callback
495+
when the person presses *up* from the editor.
496+
This is useful to define behaviour of
497+
**text selection**.
498+
We are going to implement the `_onTripleClickSelection()`
499+
in the next section.
500+
501+
502+
### 4.2 Defining triple click selection behaviour
503+
504+
We can see the behaviour of triple clicking on text
505+
and selecting different pieces of text everywhere on the internet.
506+
If you use browsers like Chrome,
507+
if you click on a word *two times*,
508+
the word is selected.
509+
If you click again (*three clicks*),
510+
a whole paragraph is selected.
511+
512+
We can customize *what we select*
513+
in the `onTapUp` parameter of `QuillEditor`.
514+
This is what we're going to be doing in
515+
`_onTripleClickSelection()`.
516+
517+
Add the following line on top of the `home_page.dart` file,
518+
under the imports.
519+
520+
```dart
521+
enum _SelectionType {
522+
none,
523+
word,
524+
}
525+
```
526+
527+
This `_SelectionType` enum is going to be used inside
528+
`_onTripleClickSelection()` to switch over the selections
529+
as the person clicks on the text.
530+
531+
Now, inside `HomePageState` (outside `_buildEditor()` function),
532+
let's create the `_onTripleClickSelection()` function.
533+
534+
```dart
535+
/// Callback called whenever the person taps on the text.
536+
/// It will select nothing, then the word if another tap is detected
537+
/// and then the whole text if another tap is detected (triple).
538+
bool _onTripleClickSelection() {
539+
final controller = _controller!;
540+
541+
// If nothing is selected, selection type is `none`
542+
if (controller.selection.isCollapsed) {
543+
_selectionType = _SelectionType.none;
544+
}
545+
546+
// If nothing is selected, selection type becomes `word
547+
if (_selectionType == _SelectionType.none) {
548+
_selectionType = _SelectionType.word;
549+
return false;
550+
}
551+
552+
// If the word is selected, select all text
553+
if (_selectionType == _SelectionType.word) {
554+
final child = controller.document.queryChild(
555+
controller.selection.baseOffset,
556+
);
557+
final offset = child.node?.documentOffset ?? 0;
558+
final length = child.node?.length ?? 0;
559+
560+
final selection = TextSelection(
561+
baseOffset: offset,
562+
extentOffset: offset + length,
563+
);
564+
565+
// Select all text and make next selection to `none`
566+
controller.updateSelection(selection, ChangeSource.REMOTE);
567+
568+
_selectionType = _SelectionType.none;
569+
570+
return true;
571+
}
572+
573+
return false;
574+
}
575+
```
349576

577+
In this function,
578+
we check the current state of the selection.
579+
If there is no selection in the controller,
580+
we set the `_SelectionType` to `none`.
581+
582+
On the second tap (meaning the current `_SelectionType` is `none`),
583+
we set the `_SelectionType` to `word`.
584+
585+
On the third tap (meaning the current `_SelectionType` is `word`),
586+
we select the *whole text in the editor*.
587+
For this, we get the text offset from the `controller`
588+
and use it to set the text selection to the whole text.
589+
We then set the `_selectionType` back to `none`,
590+
so on the next click,
591+
the selection loops back to nothing.

0 commit comments

Comments
 (0)