Browse Source

Image support WIP

Anatoly Pulyaevskiy 6 years ago
parent
commit
9f63c94024

+ 45
- 0
packages/zefyr/lib/src/widgets/buttons.dart View File

@@ -235,6 +235,51 @@ class _HeadingButtonState extends State<HeadingButton> {
235 235
   }
236 236
 }
237 237
 
238
+/// Controls heading styles.
239
+///
240
+/// When pressed, this button displays overlay toolbar with three
241
+/// buttons for each heading level.
242
+class ImageButton extends StatefulWidget {
243
+  const ImageButton({Key key}) : super(key: key);
244
+
245
+  @override
246
+  _ImageButtonState createState() => _ImageButtonState();
247
+}
248
+
249
+class _ImageButtonState extends State<HeadingButton> {
250
+  @override
251
+  Widget build(BuildContext context) {
252
+    final toolbar = ZefyrToolbar.of(context);
253
+    return toolbar.buildButton(
254
+      context,
255
+      ZefyrToolbarAction.image,
256
+      onPressed: showOverlay,
257
+    );
258
+  }
259
+
260
+  void showOverlay() {
261
+    final toolbar = ZefyrToolbar.of(context);
262
+    toolbar.showOverlay(buildOverlay);
263
+  }
264
+
265
+  Widget buildOverlay(BuildContext context) {
266
+    final toolbar = ZefyrToolbar.of(context);
267
+    final buttons = Row(
268
+      children: <Widget>[
269
+        SizedBox(width: 8.0),
270
+        toolbar.buildButton(context, ZefyrToolbarAction.cameraImage,
271
+            onPressed: _pickFromCamera),
272
+        toolbar.buildButton(context, ZefyrToolbarAction.galleryImage,
273
+            onPressed: _pickFromGallery),
274
+      ],
275
+    );
276
+    return ZefyrToolbarScaffold(body: buttons);
277
+  }
278
+
279
+  void _pickFromCamera() {}
280
+  void _pickFromGallery() {}
281
+}
282
+
238 283
 class LinkButton extends StatefulWidget {
239 284
   const LinkButton({Key key}) : super(key: key);
240 285
 

packages/zefyr/lib/src/widgets/editable_image.dart → packages/zefyr/lib/src/widgets/image.dart View File

@@ -1,23 +1,126 @@
1 1
 // Copyright (c) 2018, the Zefyr project authors.  Please see the AUTHORS file
2 2
 // for details. All rights reserved. Use of this source code is governed by a
3 3
 // BSD-style license that can be found in the LICENSE file.
4
+import 'dart:async';
5
+import 'dart:io';
4 6
 import 'dart:math' as math;
5 7
 import 'dart:ui' as ui;
6 8
 
7 9
 import 'package:flutter/rendering.dart';
8 10
 import 'package:flutter/widgets.dart';
9 11
 import 'package:notus/notus.dart';
12
+import 'package:image_picker/image_picker.dart';
10 13
 
11 14
 import 'editable_box.dart';
12 15
 
13
-class EditableImage extends LeafRenderObjectWidget {
14
-  EditableImage({@required this.node}) : assert(node != null);
16
+abstract class ZefyrImageDelegate<S> {
17
+  /// Creates [ImageProvider] for specified [imageSource].
18
+  ImageProvider createImageProvider(String imageSource);
19
+
20
+  /// Picks an image from specified [source].
21
+  ///
22
+  /// Returns unique string key for the selected image. Returned key is stored
23
+  /// in the document.
24
+  Future<String> pickImage(S source);
25
+}
26
+
27
+class ZefyrDefaultImageDelegate implements ZefyrImageDelegate<ImageSource> {
28
+  @override
29
+  ImageProvider createImageProvider(String imageSource) {
30
+    final file = new File.fromUri(Uri.parse(imageSource));
31
+    return new FileImage(file);
32
+  }
33
+
34
+  @override
35
+  Future<String> pickImage(ImageSource source) async {
36
+    final file = await ImagePicker.pickImage(source: source);
37
+    if (file == null) return null;
38
+    return file.uri.toString();
39
+  }
40
+}
41
+
42
+class ImageEmbed extends StatefulWidget {
43
+  const ImageEmbed({Key key, @required this.node, @required this.delegate})
44
+      : super(key: key);
45
+
46
+  final EmbedNode node;
47
+  final ZefyrImageDelegate delegate;
48
+
49
+  @override
50
+  _ImageEmbedState createState() => _ImageEmbedState();
51
+}
52
+
53
+class _ImageEmbedState extends State<ImageEmbed> {
54
+  ImageProvider _provider;
55
+  ImageStream _imageStream;
56
+  ImageInfo _imageInfo;
57
+
58
+  @override
59
+  void initState() {
60
+    super.initState();
61
+    EmbedAttribute attribute = widget.node.style.get(NotusAttribute.embed);
62
+    final source = attribute.value['source'];
63
+    _provider = widget.delegate.createImageProvider(source);
64
+  }
65
+
66
+  @override
67
+  void didChangeDependencies() {
68
+    super.didChangeDependencies();
69
+    _getImage();
70
+  }
71
+
72
+  @override
73
+  void didUpdateWidget(ImageEmbed oldWidget) {
74
+    super.didUpdateWidget(oldWidget);
75
+    EmbedAttribute oldStyle = oldWidget.node.style.get(NotusAttribute.embed);
76
+    final oldSource = oldStyle.value['source'];
77
+    EmbedAttribute style = widget.node.style.get(NotusAttribute.embed);
78
+    final source = style.value['source'];
79
+    if (oldSource != source || oldWidget.delegate != widget.delegate) {
80
+      _provider = widget.delegate.createImageProvider(source);
81
+      _getImage();
82
+    }
83
+  }
84
+
85
+  @override
86
+  void dispose() {
87
+    _imageStream.removeListener(_updateImage);
88
+    super.dispose();
89
+  }
90
+
91
+  @override
92
+  Widget build(BuildContext context) {
93
+    return _EditableImage(
94
+      node: widget.node,
95
+      image: _imageInfo?.image,
96
+    );
97
+  }
98
+
99
+  void _getImage() {
100
+    final oldImageStream = _imageStream;
101
+    _imageStream = _provider.resolve(createLocalImageConfiguration(context));
102
+    if (_imageStream.key != oldImageStream?.key) {
103
+      oldImageStream?.removeListener(_updateImage);
104
+      _imageStream.addListener(_updateImage);
105
+    }
106
+  }
107
+
108
+  void _updateImage(ImageInfo imageInfo, bool synchronousCall) {
109
+    setState(() {
110
+      _imageInfo = imageInfo;
111
+    });
112
+  }
113
+}
114
+
115
+class _EditableImage extends LeafRenderObjectWidget {
116
+  _EditableImage({@required this.node, this.image}) : assert(node != null);
15 117
 
16 118
   final EmbedNode node;
119
+  final ui.Image image;
17 120
 
18 121
   @override
19 122
   RenderEditableImage createRenderObject(BuildContext context) {
20
-    return new RenderEditableImage(node: node);
123
+    return new RenderEditableImage(node: node, image: image);
21 124
   }
22 125
 
23 126
   @override
@@ -35,9 +138,6 @@ class RenderEditableImage extends RenderImage implements RenderEditableBox {
35 138
         super(
36 139
           image: image,
37 140
         );
38
-  //
39
-  // Public members
40
-  //
41 141
 
42 142
   @override
43 143
   EmbedNode get node => _node;

+ 6
- 0
packages/zefyr/lib/src/widgets/toolbar.dart View File

@@ -29,6 +29,9 @@ enum ZefyrToolbarAction {
29 29
   code,
30 30
   quote,
31 31
   horizontalRule,
32
+  image,
33
+  cameraImage,
34
+  galleryImage,
32 35
   hideKeyboard,
33 36
   close,
34 37
   confirm,
@@ -348,6 +351,9 @@ class _DefaultZefyrToolbarDelegate implements ZefyrToolbarDelegate {
348 351
     ZefyrToolbarAction.code: Icons.code,
349 352
     ZefyrToolbarAction.quote: Icons.format_quote,
350 353
     ZefyrToolbarAction.horizontalRule: Icons.remove,
354
+    ZefyrToolbarAction.image: Icons.photo,
355
+    ZefyrToolbarAction.cameraImage: Icons.photo_camera,
356
+    ZefyrToolbarAction.galleryImage: Icons.photo_library,
351 357
     ZefyrToolbarAction.hideKeyboard: Icons.keyboard_hide,
352 358
     ZefyrToolbarAction.close: Icons.close,
353 359
     ZefyrToolbarAction.confirm: Icons.check,

+ 1
- 0
packages/zefyr/pubspec.yaml View File

@@ -9,6 +9,7 @@ dependencies:
9 9
     sdk: flutter
10 10
   collection: ^1.14.6
11 11
   url_launcher: ^3.0.0
12
+  image_picker: ^0.4.5
12 13
   quill_delta: ^1.0.0-dev.1.0
13 14
   notus:
14 15
     git: