|
29 | 29 | - [4.2 Defining triple click selection behaviour](#42-defining-triple-click-selection-behaviour)
|
30 | 30 | - [4.3 Reassigning `quillEditor` on web platforms](#43-reassigning-quilleditor-on-web-platforms)
|
31 | 31 | - [4.3.1 Creating web embeds](#431-creating-web-embeds)
|
| 32 | + - [4.4 Creating the toolbar](#44-creating-the-toolbar) |
32 | 33 |
|
33 | 34 |
|
34 | 35 | # Why? 🤷
|
@@ -654,7 +655,253 @@ As noted in `flutter-quill`'s documentation
|
654 | 655 | (https://github.com/singerdmx/flutter-quill/tree/master#web),
|
655 | 656 | we need to define web embeds so Quill Editor works properly.
|
656 | 657 |
|
| 658 | +If we want to embed an image on a web-based platform |
| 659 | +and show it to the person, |
| 660 | +we need to define our own embed. |
| 661 | +For this, create a folder called `web_embeds` inside `lib`. |
| 662 | +Create a file called `web_embeds.dart` |
| 663 | +and paste the following code. |
| 664 | + |
| 665 | +```dart |
| 666 | +
|
| 667 | +import 'package:app/main.dart'; |
| 668 | +import 'package:flutter/cupertino.dart'; |
| 669 | +import 'package:flutter_quill/flutter_quill.dart'; |
| 670 | +import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; |
| 671 | +import 'package:responsive_framework/responsive_framework.dart'; |
| 672 | +import 'package:universal_html/html.dart' as html; |
| 673 | +
|
| 674 | +// Conditionally importing the PlatformViewRegistry class according to the platform |
| 675 | +import 'mobile_platform_registry.dart' if (dart.library.html) 'web_platform_registry.dart' as ui_instance; |
| 676 | +
|
| 677 | +/// Custom embed for images to work on the web. |
| 678 | +class ImageEmbedBuilderWeb extends EmbedBuilder { |
| 679 | + @override |
| 680 | + String get key => BlockEmbed.imageType; |
| 681 | +
|
| 682 | + @override |
| 683 | + Widget build( |
| 684 | + BuildContext context, |
| 685 | + QuillController controller, |
| 686 | + Embed node, |
| 687 | + bool readOnly, |
| 688 | + bool inline, |
| 689 | + TextStyle textStyle, |
| 690 | + ) { |
| 691 | + final imageUrl = node.value.data; |
| 692 | + if (isImageBase64(imageUrl)) { |
| 693 | + // TODO: handle imageUrl of base64 |
| 694 | + return const SizedBox(); |
| 695 | + } |
| 696 | + final size = MediaQuery.of(context).size; |
| 697 | +
|
| 698 | + // This is needed for images to be correctly embedded on the web. |
| 699 | + ImageUniversalUI().platformViewRegistry.registerViewFactory(PlatformService(), imageUrl, (viewId) { |
| 700 | + return html.ImageElement() |
| 701 | + ..src = imageUrl |
| 702 | + ..style.height = 'auto' |
| 703 | + ..style.width = 'auto'; |
| 704 | + }); |
| 705 | +
|
| 706 | + // Rendering responsive image |
| 707 | + return Padding( |
| 708 | + padding: EdgeInsets.only( |
| 709 | + right: ResponsiveBreakpoints.of(context).smallerThan(TABLET) |
| 710 | + ? size.width * 0.5 |
| 711 | + : (ResponsiveBreakpoints.of(context).equals('4K')) |
| 712 | + ? size.width * 0.75 |
| 713 | + : size.width * 0.2, |
| 714 | + ), |
| 715 | + child: SizedBox( |
| 716 | + height: MediaQuery.of(context).size.height * 0.45, |
| 717 | + child: HtmlElementView( |
| 718 | + viewType: imageUrl, |
| 719 | + ), |
| 720 | + ), |
| 721 | + ); |
| 722 | + } |
| 723 | +} |
| 724 | +
|
| 725 | +/// List of default web embed builders. |
| 726 | +List<EmbedBuilder> get defaultEmbedBuildersWeb => [ |
| 727 | + ImageEmbedBuilderWeb(), |
| 728 | + ]; |
| 729 | +
|
| 730 | +``` |
| 731 | + |
| 732 | +This is where we define `defaultEmbedBuildersWeb` |
| 733 | +we're using in `_buildEditor()`. |
| 734 | +This array variable uses the `ImageEmbedBuilderWeb`, |
| 735 | +our custom web embed so images are shown in web platforms. |
| 736 | +We technically can add more embeds |
| 737 | +(for example, to show videos). |
| 738 | +But for now, let's keep it simple and only allow the person to add images. |
| 739 | + |
| 740 | +The `ImageEmbedBuilderWeb` class pertains |
| 741 | +to the web image embed, |
| 742 | +extending the [`EmbedBuilder`](https://github.com/singerdmx/flutter-quill/blob/36d72c1987f0cb8d6c689c12542600364c07e20f/lib/src/widgets/embeds.dart#L9) |
| 743 | +class from `flutter-quill`. |
| 744 | + |
| 745 | +Let's break it down. |
| 746 | +We define the `key` of the class |
| 747 | +of *what type of object the embed pertains to*. |
| 748 | +In our case, it's an image. |
| 749 | + |
| 750 | +```dart |
| 751 | + @override |
| 752 | + String get key => BlockEmbed.imageType; |
| 753 | +``` |
| 754 | + |
| 755 | +In the `build` function, |
| 756 | +we render the widget we want in the screen. |
| 757 | +In this case, we render an `ImageElement` |
| 758 | +with the `imageURL` that is passed to the class. |
| 759 | +We use `ResponsiveBreakpoints` from `responsive_framework` |
| 760 | +to show properly show the image across different device sizes. |
| 761 | + |
| 762 | +Inside the `build()` function, you may notice the following lines: |
| 763 | + |
| 764 | +```dart |
| 765 | + ImageUniversalUI().platformViewRegistry.registerViewFactory(PlatformService(), imageUrl, (viewId) { |
| 766 | + return html.ImageElement() |
| 767 | + ..src = imageUrl |
| 768 | + ..style.height = 'auto' |
| 769 | + ..style.width = 'auto'; |
| 770 | + }); |
| 771 | +``` |
| 772 | + |
| 773 | +We need to call `registerViewFactory` from `dart:ui_web` |
| 774 | +so the image is properly shown in web devices. |
| 775 | +If we do not do this, |
| 776 | +**build compilation fails**. |
| 777 | +This is because the package it's called from doesn't compile |
| 778 | +when we create a build for mobile devices. |
| 779 | +This is why we create a `ImageUniversalUI` class |
| 780 | +that conditionally imports the package |
| 781 | +so it compiles on both type of devices. |
| 782 | +For more information, |
| 783 | +check https://github.com/flutter/flutter/issues/41563#issuecomment-547923478. |
| 784 | + |
| 785 | +For this to work, |
| 786 | +in the same file, |
| 787 | +add this piece of code: |
| 788 | + |
| 789 | +```dart |
| 790 | +/// Class used to conditionally register the view factory. |
| 791 | +/// For more information, check https://github.com/flutter/flutter/issues/41563#issuecomment-547923478. |
| 792 | +class PlatformViewRegistryFix { |
| 793 | + void registerViewFactory(PlatformService platformService, imageURL, dynamic cbFnc) { |
| 794 | + if (platformService.isWebPlatform()) { |
| 795 | + ui_instance.PlatformViewRegistry.registerViewFactory( |
| 796 | + imageURL, |
| 797 | + cbFnc, |
| 798 | + ); |
| 799 | + } |
| 800 | + } |
| 801 | +} |
| 802 | +
|
| 803 | +/// Class that conditionally registers the `platformViewRegistry`. |
| 804 | +class ImageUniversalUI { |
| 805 | + PlatformViewRegistryFix platformViewRegistry = PlatformViewRegistryFix(); |
| 806 | +} |
| 807 | +``` |
| 808 | + |
| 809 | +`PlatformViewRegistryFix` calls the `registerViewFactory` |
| 810 | +only on web platforms. |
| 811 | +It uses the `ui_instance` object, |
| 812 | +which is conditionally imported on top of the file. |
| 813 | +This `ui_instance` variable uses the appropriate package |
| 814 | +to call `registerViewFactory`. |
| 815 | + |
| 816 | +> [!NOTE] |
| 817 | +> Check https://github.com/flutter/flutter/issues/41563#issuecomment-547923478 |
| 818 | +> for more information about `dart:ui` and `dart:web_ui` |
| 819 | +> to better understand why we need to conditionally import them separately |
| 820 | +> so the application compiles to both target devices. |
| 821 | +
|
| 822 | +This is why we import the correct `ui_instance` |
| 823 | +so we can compile to both targets `web` and `mobile` devices. |
| 824 | + |
| 825 | +```dart |
| 826 | +import 'mobile_platform_registry.dart' if (dart.library.html) 'web_platform_registry.dart' as ui_instance; |
| 827 | +``` |
| 828 | + |
| 829 | +Neither of these files are created. |
| 830 | +So let's do that! |
| 831 | +In the same folder `lib/web_embeds`, |
| 832 | +create `mobile_platform_registry.dart` |
| 833 | +and add: |
| 834 | + |
| 835 | +```dart |
| 836 | +/// Class used to `registerViewFactory` for mobile platforms. |
| 837 | +/// |
| 838 | +/// Please check https://github.com/flutter/flutter/issues/41563#issuecomment-547923478 for more information. |
| 839 | +class PlatformViewRegistry { |
| 840 | + static void registerViewFactory(String viewId, dynamic cb) {} |
| 841 | +} |
| 842 | +``` |
| 843 | + |
| 844 | +This is just a simple class with `registerViewFactory` function |
| 845 | +that effectively does nothing. |
| 846 | +We don't *need for it to do anything* |
| 847 | +because we are implementing the embed **only for the web**. |
| 848 | +So we just only need this to compile. |
| 849 | + |
| 850 | +Now, in the same folder, create the file `web_platform_registry.dart` |
| 851 | +and add: |
| 852 | + |
| 853 | +```dart |
| 854 | +import 'dart:ui_web' as web_ui; |
| 855 | +
|
| 856 | +/// Class used to `registerViewFactory` for web platforms. |
| 857 | +/// |
| 858 | +/// Please check https://github.com/flutter/flutter/issues/41563#issuecomment-547923478 for more information. |
| 859 | +class PlatformViewRegistry { |
| 860 | + static void registerViewFactory(String viewId, dynamic cb) { |
| 861 | + web_ui.platformViewRegistry.registerViewFactory(viewId, cb); |
| 862 | + } |
| 863 | +} |
| 864 | +``` |
| 865 | + |
| 866 | +In here, we are importing `dart:ui_web` |
| 867 | +(which only compiles on web devices) |
| 868 | +and performing the `registerViewFactory`. |
| 869 | + |
| 870 | +And that's it! |
| 871 | +We've successfully created a web embed |
| 872 | +that embeds image on the web! |
| 873 | + |
| 874 | +To recap: |
| 875 | +- we export the `defaultEmbedBuildersWeb` |
| 876 | +array variable with our custom web embed |
| 877 | +`ImageEmbedBuilderWeb()`. |
| 878 | +- `ImageEmbedBuilderWeb` class returns a widget |
| 879 | +that shows an HTML image in the editor. |
| 880 | +For this to work, we need to call `registerViewFactory` |
| 881 | +from the `dart:web_ui` package. |
| 882 | +- because `dart:web_ui` only compiles for the web, |
| 883 | +we need to import `dart:ui` and `dart:web_ui` |
| 884 | +separately from different files |
| 885 | +that perform this register. |
| 886 | +This will ensure the packages |
| 887 | +are conditionally imported |
| 888 | +and compilation always work, |
| 889 | +regardless of the target platform! |
| 890 | + |
| 891 | + |
| 892 | +### 4.4 Creating the toolbar |
| 893 | + |
| 894 | +Now that we've defined the editor |
| 895 | +and the appropriate web embeds so images work on web devices, |
| 896 | +it's time to create **our toolbar**. |
| 897 | + |
| 898 | +This toolbar will have some options for the person |
| 899 | +to stylize the text (e.g make it bold or italic) |
| 900 | +and add images |
| 901 | +(which make use of the embed we've just created for web devices). |
| 902 | + |
657 | 903 |
|
658 | 904 |
|
659 | 905 | - toolbar next
|
| 906 | +- show how it passes the imageURL to the web embed |
660 | 907 |
|
0 commit comments