From 341b9b015d69bc452c62205432e2790c63959358 Mon Sep 17 00:00:00 2001
From: singerdmx <singerdmx@gmail.com>
Date: Wed, 23 Dec 2020 18:39:56 -0800
Subject: [PATCH] Initial Image Picker implementation

---
 app/assets/sample_data.json           |  5 +-
 app/pubspec.lock                      |  7 +++
 lib/models/documents/document.dart    | 13 +++--
 lib/models/documents/nodes/embed.dart | 78 ++++-----------------------
 lib/widgets/editor.dart               | 20 ++++++-
 lib/widgets/image.dart                | 31 +++++++++++
 lib/widgets/toolbar.dart              |  2 +-
 pubspec.lock                          |  7 +++
 pubspec.yaml                          |  1 +
 9 files changed, 87 insertions(+), 77 deletions(-)
 create mode 100644 lib/widgets/image.dart

diff --git a/app/assets/sample_data.json b/app/assets/sample_data.json
index e83f8e6b..6fc2cd30 100644
--- a/app/assets/sample_data.json
+++ b/app/assets/sample_data.json
@@ -9,9 +9,8 @@
       "insert":"\n"
    },
    {
-      "insert":{
-         "_type":"hr",
-         "_inline":false
+      "insert": {
+         "image":"https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png"
       }
    },
    {
diff --git a/app/pubspec.lock b/app/pubspec.lock
index 2afc2e95..f380fd48 100644
--- a/app/pubspec.lock
+++ b/app/pubspec.lock
@@ -149,6 +149,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.9.2"
+  photo_view:
+    dependency: transitive
+    description:
+      name: photo_view
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.10.3"
   plugin_platform_interface:
     dependency: transitive
     description:
diff --git a/lib/models/documents/document.dart b/lib/models/documents/document.dart
index 7e02313d..d249926a 100644
--- a/lib/models/documents/document.dart
+++ b/lib/models/documents/document.dart
@@ -160,11 +160,14 @@ class Document {
   }
 
   Object _normalize(Object data) {
-    return data is String
-        ? data
-        : data is Embeddable
-        ? data
-        : Embeddable.fromJson(data);
+    if (data is String) {
+      return data;
+    }
+
+    if (data is Embeddable) {
+      return data;
+    }
+    return Embeddable.fromJson(data);
   }
 
   close() {
diff --git a/lib/models/documents/nodes/embed.dart b/lib/models/documents/nodes/embed.dart
index 7c004251..a8dff833 100644
--- a/lib/models/documents/nodes/embed.dart
+++ b/lib/models/documents/nodes/embed.dart
@@ -1,84 +1,28 @@
-import 'dart:collection';
-
-import 'package:collection/collection.dart';
-import 'package:quiver_hashcode/hashcode.dart';
-
 class Embeddable {
-  static const TYPE_KEY = '_type';
-  static const INLINE_KEY = '_inline';
   final String type;
-  final bool inline;
-  final Map<String, dynamic> _data;
+  final dynamic data;
 
-  Embeddable(this.type, this.inline, Map<String, dynamic> data)
+  Embeddable(this.type, this.data)
       : assert(type != null),
-        assert(inline != null),
-        assert(!data.containsKey(TYPE_KEY)),
-        assert(!data.containsKey(INLINE_KEY)),
-        _data = Map.from(data);
-
-  Map<String, dynamic> get data => UnmodifiableMapView(_data);
+        assert(data != null);
 
   Map<String, dynamic> toJson() {
-    Map<String, dynamic> m = Map<String, dynamic>.from(_data);
-    m[TYPE_KEY] = type;
-    m[INLINE_KEY] = inline;
+    Map<String, String> m = {type: data};
     return m;
   }
 
   static Embeddable fromJson(Map<String, dynamic> json) {
-    String type = json[TYPE_KEY] as String;
-    bool inline = json[INLINE_KEY] as bool;
-    Map<String, dynamic> data = Map<String, dynamic>.from(json);
-    data.remove(TYPE_KEY);
-    data.remove(INLINE_KEY);
-    if (inline) {
-      return Span(type, data: data);
-    }
-    return BlockEmbed(type, data: data);
-  }
-
-  @override
-  bool operator ==(dynamic other) {
-    if (identical(this, other)) {
-      return true;
-    }
-    if (other is! Embeddable) {
-      return false;
-    }
-    final typedOther = other;
-    return typedOther.type == type &&
-        typedOther.inline == inline &&
-        DeepCollectionEquality().equals(typedOther._data, _data);
-  }
-
-  @override
-  int get hashCode {
-    if (_data.isEmpty) {
-      return hash2(type, inline);
-    }
+    Map<String, dynamic> m = Map<String, dynamic>.from(json);
+    assert(m.length == 1, 'Embeddable map has one key');
 
-    final dataHash = hashObjects(
-      _data.entries.map((e) => hash2(e.key, e.value)),
-    );
-    return hash3(type, inline, dataHash);
+    return BlockEmbed(m.keys.first, m.values.first);
   }
 }
 
-class Span extends Embeddable {
-  Span(
-    String type, {
-    Map<String, dynamic> data = const {},
-  }) : super(type, true, data);
-}
-
 class BlockEmbed extends Embeddable {
-  BlockEmbed(
-    String type, {
-    Map<String, dynamic> data = const {},
-  }) : super(type, false, data);
+  BlockEmbed(String type, String data) : super(type, data);
+
+  static final BlockEmbed horizontalRule = BlockEmbed('divider', 'hr');
 
-  static final BlockEmbed horizontalRule = BlockEmbed('hr');
-  static BlockEmbed image(String source) =>
-      BlockEmbed('image', data: {'source': source});
+  static BlockEmbed image(String imageUrl) => BlockEmbed('image', imageUrl);
 }
diff --git a/lib/widgets/editor.dart b/lib/widgets/editor.dart
index 40d96e63..793ed54f 100644
--- a/lib/widgets/editor.dart
+++ b/lib/widgets/editor.dart
@@ -13,6 +13,7 @@ import 'package:flutter_quill/models/documents/nodes/container.dart'
 import 'package:flutter_quill/models/documents/nodes/leaf.dart';
 import 'package:flutter_quill/models/documents/nodes/line.dart';
 import 'package:flutter_quill/models/documents/nodes/node.dart';
+import 'package:flutter_quill/widgets/image.dart';
 import 'package:flutter_quill/widgets/raw_editor.dart';
 import 'package:flutter_quill/widgets/text_selection.dart';
 import 'package:url_launcher/url_launcher.dart';
@@ -73,13 +74,15 @@ abstract class RenderAbstractEditor {
 
 Widget _defaultEmbedBuilder(BuildContext context, Embed node) {
   switch (node.value.type) {
-    case 'hr':
+    case 'divider':
       final style = QuillStyles.getStyles(context, true);
       return Divider(
         height: style.paragraph.style.fontSize * style.paragraph.style.height,
         thickness: 2,
         color: Colors.grey.shade200,
       );
+    case 'image':
+      return _buildImage(context, node.value.data);
     default:
       throw UnimplementedError(
           'Embeddable type "${node.value.type}" is not supported by default embed '
@@ -88,6 +91,21 @@ Widget _defaultEmbedBuilder(BuildContext context, Embed node) {
   }
 }
 
+Widget _buildImage(BuildContext context, String imageUrl) {
+  return GestureDetector(
+    child: Image.network(imageUrl),
+    onTap: () {
+      Navigator.push(
+        context,
+        MaterialPageRoute(
+          builder: (context) =>
+              ImageTapWrapper(imageProvider: NetworkImage(imageUrl)),
+        ),
+      );
+    },
+  );
+}
+
 class QuillEditor extends StatefulWidget {
   final QuillController controller;
   final FocusNode focusNode;
diff --git a/lib/widgets/image.dart b/lib/widgets/image.dart
new file mode 100644
index 00000000..f308a420
--- /dev/null
+++ b/lib/widgets/image.dart
@@ -0,0 +1,31 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:photo_view/photo_view.dart';
+
+class ImageTapWrapper extends StatelessWidget {
+  const ImageTapWrapper({
+    this.imageProvider,
+  });
+
+  final ImageProvider imageProvider;
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      body: Container(
+        constraints: BoxConstraints.expand(
+          height: MediaQuery.of(context).size.height,
+        ),
+        child: GestureDetector(
+          onTapDown: (_) {
+            Navigator.pop(context);
+          },
+          child: PhotoView(
+            imageProvider: imageProvider,
+          ),
+        ),
+      ),
+    );
+  }
+}
\ No newline at end of file
diff --git a/lib/widgets/toolbar.dart b/lib/widgets/toolbar.dart
index e0154263..3bda6e6e 100644
--- a/lib/widgets/toolbar.dart
+++ b/lib/widgets/toolbar.dart
@@ -435,7 +435,7 @@ class _ImageButtonState extends State<ImageButton> {
               if (imageUploadUrl != null)
                 {
                   widget.controller.replaceText(
-                      index, length, BlockEmbed(imageUploadUrl), null)
+                      index, length, BlockEmbed.image(imageUploadUrl), null)
                 }
             });
       },
diff --git a/pubspec.lock b/pubspec.lock
index c13b3f11..09ae8cdc 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -135,6 +135,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.9.2"
+  photo_view:
+    dependency: "direct main"
+    description:
+      name: photo_view
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.10.3"
   plugin_platform_interface:
     dependency: transitive
     description:
diff --git a/pubspec.yaml b/pubspec.yaml
index e6c95cf1..c6d4374e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -19,6 +19,7 @@ dependencies:
   url_launcher: ^5.7.10
   flutter_colorpicker: ^0.3.4
   image_picker: ^0.6.7+17
+  photo_view: ^0.10.3
 
 dev_dependencies:
   flutter_test: