|
24 | 24 | - [1. Installing all the needed dependencies](#1-installing-all-the-needed-dependencies)
|
25 | 25 | - [2. Setting up the responsive framework](#2-setting-up-the-responsive-framework)
|
26 | 26 | - [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) |
27 | 30 |
|
28 | 31 |
|
29 | 32 | # Why? 🤷
|
@@ -295,6 +298,9 @@ class HomePageState extends State<HomePage> {
|
295 | 298 | /// `flutter-quill` editor controller
|
296 | 299 | QuillController? _controller;
|
297 | 300 |
|
| 301 | + /// Focus node used to obtain keyboard focus and events |
| 302 | + final FocusNode _focusNode = FocusNode(); |
| 303 | +
|
298 | 304 | @override
|
299 | 305 | void initState() {
|
300 | 306 | super.initState();
|
@@ -346,4 +352,240 @@ and the **text** that is written in the editor.
|
346 | 352 |
|
347 | 353 | For the Quill Editor to properly function,
|
348 | 354 | 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 | +``` |
349 | 576 |
|
| 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