Browse Source

Removed dependency on image_picker plugin

Anatoly Pulyaevskiy 5 years ago
parent
commit
94859a3b3b

+ 82
- 22
doc/images.md View File

1
 ## Images
1
 ## Images
2
 
2
 
3
-> Note that described API is considered experimental and is likely to be
3
+> Note that Image API is considered experimental and is likely to be
4
 > changed in backward incompatible ways. If this happens all changes will be
4
 > changed in backward incompatible ways. If this happens all changes will be
5
 > described in detail in the changelog to simplify upgrading.
5
 > described in detail in the changelog to simplify upgrading.
6
 
6
 
7
-Zefyr (and Notus) supports embedded images. In order to handle images in
7
+Zefyr supports embedding images. In order to handle images in
8
 your application you need to implement `ZefyrImageDelegate` interface which
8
 your application you need to implement `ZefyrImageDelegate` interface which
9
 looks like this:
9
 looks like this:
10
 
10
 
11
 ```dart
11
 ```dart
12
 abstract class ZefyrImageDelegate<S> {
12
 abstract class ZefyrImageDelegate<S> {
13
-  /// Builds image widget for specified [imageSource] and [context].
14
-  Widget buildImage(BuildContext context, String imageSource);
13
+  /// Unique key to identify camera source.
14
+  S get cameraSource;
15
+
16
+  /// Unique key to identify gallery source.
17
+  S get gallerySource;
18
+
19
+  /// Builds image widget for specified image [key].
20
+  ///
21
+  /// The [key] argument contains value which was previously returned from
22
+  /// [pickImage].
23
+  Widget buildImage(BuildContext context, String key);
15
 
24
 
16
   /// Picks an image from specified [source].
25
   /// Picks an image from specified [source].
17
   ///
26
   ///
18
   /// Returns unique string key for the selected image. Returned key is stored
27
   /// Returns unique string key for the selected image. Returned key is stored
19
   /// in the document.
28
   /// in the document.
29
+  ///
30
+  /// Depending on your application returned key may represent a path to
31
+  /// an image file on user's device, an HTTP link, or an identifier generated
32
+  /// by a file hosting service like AWS S3 or Google Drive.
20
   Future<String> pickImage(S source);
33
   Future<String> pickImage(S source);
21
 }
34
 }
22
 ```
35
 ```
23
 
36
 
24
-Zefyr comes with default implementation which exists mostly to provide an
25
-example and a starting point for your own version.
37
+There is no default implementation of this interface since resolving image
38
+sources is always application-specific.
26
 
39
 
27
-It is recommended to always have your own implementation specific to your
28
-application.
40
+> Note that prior to 0.7.0 Zefyr did provide simple default implementation of
41
+> `ZefyrImageDelegate` however it was removed as it introduced unnecessary
42
+> dependency on `image_picker` plugin.
29
 
43
 
30
 ### Implementing ZefyrImageDelegate
44
 ### Implementing ZefyrImageDelegate
31
 
45
 
46
+For this example we will use [image_picker](https://pub.dev/packages/image_picker)
47
+plugin which allows us to select images from device's camera or photo gallery.
48
+
32
 Let's start from the `pickImage` method:
49
 Let's start from the `pickImage` method:
33
 
50
 
34
 ```dart
51
 ```dart
35
-// Currently Zefyr depends on image_picker plugin to show camera or image gallery.
36
-// (note that in future versions this may change so that users can choose their
37
-// own plugin and define custom sources)
38
 import 'package:image_picker/image_picker.dart';
52
 import 'package:image_picker/image_picker.dart';
39
 
53
 
40
 class MyAppZefyrImageDelegate implements ZefyrImageDelegate<ImageSource> {
54
 class MyAppZefyrImageDelegate implements ZefyrImageDelegate<ImageSource> {
53
 which essentially serves as an identifier for the image.
67
 which essentially serves as an identifier for the image.
54
 
68
 
55
 Returned value is stored in the document Delta and later on used to build the
69
 Returned value is stored in the document Delta and later on used to build the
56
-appropriate `Widget`.
70
+appropriate image widget.
57
 
71
 
58
 It is up to the developer to define what this value represents.
72
 It is up to the developer to define what this value represents.
59
 
73
 
60
 In the above example we simply return a full path to the file on user's device,
74
 In the above example we simply return a full path to the file on user's device,
61
 e.g. `file:///Users/something/something/image.jpg`. Some other examples
75
 e.g. `file:///Users/something/something/image.jpg`. Some other examples
62
-may include a web link, `https://myapp.com/images/some.jpg` or just some
63
-arbitrary string like an ID.
76
+may include a web link, `https://myapp.com/images/some.jpg` or an
77
+arbitrary string like an identifier of an image in a cloud storage like AWS S3.
64
 
78
 
65
 For instance, if you upload files to your server you can initiate this task
79
 For instance, if you upload files to your server you can initiate this task
66
-in `pickImage`, for instance:
80
+in `pickImage` as follows:
67
 
81
 
68
 ```dart
82
 ```dart
69
 class MyAppZefyrImageDelegate implements ZefyrImageDelegate<ImageSource> {
83
 class MyAppZefyrImageDelegate implements ZefyrImageDelegate<ImageSource> {
84
 
98
 
85
 Next we need to implement `buildImage`. This method takes `imageSource` argument
99
 Next we need to implement `buildImage`. This method takes `imageSource` argument
86
 which contains that same string you returned from `pickImage`. Here you can
100
 which contains that same string you returned from `pickImage`. Here you can
87
-use this value to create a Flutter `Widget` which renders the image. Normally
101
+use this value to create a Flutter widget which renders the image. Normally
88
 you would return the standard `Image` widget from this method, but it is not
102
 you would return the standard `Image` widget from this method, but it is not
89
 a requirement. You are free to create a custom widget which, for instance,
103
 a requirement. You are free to create a custom widget which, for instance,
90
 shows progress of upload operation that you initiated in the `pickImage` call.
104
 shows progress of upload operation that you initiated in the `pickImage` call.
97
   // ...
111
   // ...
98
 
112
 
99
   @override
113
   @override
100
-  Widget buildImage(BuildContext context, String imageSource) {
101
-    final file = new File.fromUri(Uri.parse(imageSource));
102
-    /// Create standard [FileImage] provider. If [imageSource] was an HTTP link
114
+  Widget buildImage(BuildContext context, String key) {
115
+    final file = File.fromUri(Uri.parse(key));
116
+    /// Create standard [FileImage] provider. If [key] was an HTTP link
103
     /// we could use [NetworkImage] instead.
117
     /// we could use [NetworkImage] instead.
104
-    final image = new FileImage(file);
105
-    return new Image(image: image);
118
+    final image = FileImage(file);
119
+    return Image(image: image);
120
+  }
121
+}
122
+```
123
+
124
+There is two more overrides we need to implement which configure source types
125
+used by Zefyr toolbar:
126
+
127
+```dart
128
+class MyAppZefyrImageDelegate implements ZefyrImageDelegate<ImageSource> {
129
+  // ...
130
+  @override
131
+  ImageSource get cameraSource => ImageSource.camera;
132
+
133
+  @override
134
+  ImageSource get gallerySource => ImageSource.gallery;
135
+}
136
+```
137
+
138
+Now our image delegate is ready to be used by Zefyr so the last step is to
139
+pass it to Zefyr editor:
140
+
141
+```dart
142
+import 'package:zefyr/zefyr.dart'
143
+
144
+class MyAppPageState extends State<MyAppPage> {
145
+  FocueNode _focusNode = FocusNode();
146
+  ZefyrController _controller;
147
+
148
+  // ...
149
+
150
+  @override
151
+  Widget build(BuildContext context) {
152
+    final editor = new ZefyrEditor(
153
+      focusNode: _focusNode,
154
+      controller: _controller,
155
+      imageDelegate: MyAppZefyrImageDelegate(),
156
+    );
157
+
158
+    // ... do more with this page's layout
159
+
160
+    return ZefyrScaffold(
161
+      child: Container(
162
+        // ... customize
163
+        child: editor,
164
+      )
165
+    );
106
   }
166
   }
107
 }
167
 }
108
 ```
168
 ```
111
 
171
 
112
 * [Heuristics][heuristics]
172
 * [Heuristics][heuristics]
113
 
173
 
114
-[heuristics]: /doc/heuristics.md
174
+[heuristics]: /doc/heuristics.md

+ 8
- 0
packages/zefyr/CHANGELOG.md View File

10
     - `ZefyrMode.view`: the same as `enabled: false`, read-only.
10
     - `ZefyrMode.view`: the same as `enabled: false`, read-only.
11
 * Added optional `selectionControls` field to `ZefyrEditor` and `ZefyrEditableText`. If not provided
11
 * Added optional `selectionControls` field to `ZefyrEditor` and `ZefyrEditableText`. If not provided
12
   then by default uses platform-specific implementation.
12
   then by default uses platform-specific implementation.
13
+* Added support for "selectAll" action in selection toolbar.
14
+* Breaking change: removed `ZefyrDefaultImageDelegate` as well as dependency on
15
+  `image_picker` plugin. Users are required to provide their own implementation. If image delegate
16
+  is not provided then image toolbar button is disabled.
17
+* Breaking change: added `ZefyrImageDelegate.cameraSource` and `ZefyrImageDelegate.gallerySource`
18
+  fields. For users of `image_picker` plugin these should return `ImageSource.camera` and
19
+  `ImageSource.gallery` respectively. See documentation on implementing image support for more
20
+  details.
13
 
21
 
14
 ## 0.6.0
22
 ## 0.6.0
15
 
23
 

+ 5
- 0
packages/zefyr/example/lib/src/form.dart View File

1
+// Copyright (c) 2018, the Zefyr project authors.  Please see the AUTHORS file
2
+// for details. All rights reserved. Use of this source code is governed by a
3
+// BSD-style license that can be found in the LICENSE file.
4
+
1
 import 'package:flutter/material.dart';
5
 import 'package:flutter/material.dart';
2
 import 'package:zefyr/zefyr.dart';
6
 import 'package:zefyr/zefyr.dart';
3
 
7
 
4
 import 'full_page.dart';
8
 import 'full_page.dart';
9
+import 'images.dart';
5
 
10
 
6
 class FormEmbeddedScreen extends StatefulWidget {
11
 class FormEmbeddedScreen extends StatefulWidget {
7
   @override
12
   @override

+ 6
- 17
packages/zefyr/example/lib/src/full_page.dart View File

1
+// Copyright (c) 2018, the Zefyr project authors.  Please see the AUTHORS file
2
+// for details. All rights reserved. Use of this source code is governed by a
3
+// BSD-style license that can be found in the LICENSE file.
4
+
1
 import 'dart:async';
5
 import 'dart:async';
2
 import 'dart:convert';
6
 import 'dart:convert';
3
 
7
 
5
 import 'package:quill_delta/quill_delta.dart';
9
 import 'package:quill_delta/quill_delta.dart';
6
 import 'package:zefyr/zefyr.dart';
10
 import 'package:zefyr/zefyr.dart';
7
 
11
 
12
+import 'images.dart';
13
+
8
 class ZefyrLogo extends StatelessWidget {
14
 class ZefyrLogo extends StatelessWidget {
9
   @override
15
   @override
10
   Widget build(BuildContext context) {
16
   Widget build(BuildContext context) {
106
     });
112
     });
107
   }
113
   }
108
 }
114
 }
109
-
110
-/// Custom image delegate used by this example to load image from application
111
-/// assets.
112
-///
113
-/// Default image delegate only supports [FileImage]s.
114
-class CustomImageDelegate extends ZefyrDefaultImageDelegate {
115
-  @override
116
-  Widget buildImage(BuildContext context, String imageSource) {
117
-    // We use custom "asset" scheme to distinguish asset images from other files.
118
-    if (imageSource.startsWith('asset://')) {
119
-      final asset = new AssetImage(imageSource.replaceFirst('asset://', ''));
120
-      return new Image(image: asset);
121
-    } else {
122
-      return super.buildImage(context, imageSource);
123
-    }
124
-  }
125
-}

+ 40
- 0
packages/zefyr/example/lib/src/images.dart View File

1
+// Copyright (c) 2018, the Zefyr project authors.  Please see the AUTHORS file
2
+// for details. All rights reserved. Use of this source code is governed by a
3
+// BSD-style license that can be found in the LICENSE file.
4
+
5
+import 'dart:io';
6
+
7
+import 'package:flutter/widgets.dart';
8
+import 'package:image_picker/image_picker.dart';
9
+import 'package:zefyr/zefyr.dart';
10
+
11
+/// Custom image delegate used by this example to load image from application
12
+/// assets.
13
+class CustomImageDelegate implements ZefyrImageDelegate<ImageSource> {
14
+  @override
15
+  ImageSource get cameraSource => ImageSource.camera;
16
+
17
+  @override
18
+  ImageSource get gallerySource => ImageSource.gallery;
19
+
20
+  @override
21
+  Future<String> pickImage(ImageSource source) async {
22
+    final file = await ImagePicker.pickImage(source: source);
23
+    if (file == null) return null;
24
+    return file.uri.toString();
25
+  }
26
+
27
+  @override
28
+  Widget buildImage(BuildContext context, String key) {
29
+    // We use custom "asset" scheme to distinguish asset images from other files.
30
+    if (key.startsWith('asset://')) {
31
+      final asset = AssetImage(key.replaceFirst('asset://', ''));
32
+      return Image(image: asset);
33
+    } else {
34
+      // Otherwise assume this is a file stored locally on user's device.
35
+      final file = File.fromUri(Uri.parse(key));
36
+      final image = FileImage(file);
37
+      return new Image(image: image);
38
+    }
39
+  }
40
+}

+ 5
- 17
packages/zefyr/example/lib/src/view.dart View File

1
+// Copyright (c) 2018, the Zefyr project authors.  Please see the AUTHORS file
2
+// for details. All rights reserved. Use of this source code is governed by a
3
+// BSD-style license that can be found in the LICENSE file.
4
+
1
 import 'dart:convert';
5
 import 'dart:convert';
2
 
6
 
3
 import 'package:flutter/material.dart';
7
 import 'package:flutter/material.dart';
5
 import 'package:zefyr/zefyr.dart';
9
 import 'package:zefyr/zefyr.dart';
6
 
10
 
7
 import 'full_page.dart';
11
 import 'full_page.dart';
12
+import 'images.dart';
8
 
13
 
9
 class ViewScreen extends StatefulWidget {
14
 class ViewScreen extends StatefulWidget {
10
   @override
15
   @override
68
     );
73
     );
69
   }
74
   }
70
 }
75
 }
71
-
72
-/// Custom image delegate used by this example to load image from application
73
-/// assets.
74
-///
75
-/// Default image delegate only supports [FileImage]s.
76
-class CustomImageDelegate extends ZefyrDefaultImageDelegate {
77
-  @override
78
-  Widget buildImage(BuildContext context, String imageSource) {
79
-    // We use custom "asset" scheme to distinguish asset images from other files.
80
-    if (imageSource.startsWith('asset://')) {
81
-      final asset = new AssetImage(imageSource.replaceFirst('asset://', ''));
82
-      return new Image(image: asset);
83
-    } else {
84
-      return super.buildImage(context, imageSource);
85
-    }
86
-  }
87
-}

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

16
   # The following adds the Cupertino Icons font to your application.
16
   # The following adds the Cupertino Icons font to your application.
17
   # Use with the CupertinoIcons class for iOS style icons.
17
   # Use with the CupertinoIcons class for iOS style icons.
18
   cupertino_icons: ^0.1.2
18
   cupertino_icons: ^0.1.2
19
+  image_picker: ^0.6.1
19
   zefyr:
20
   zefyr:
20
     path: ../
21
     path: ../
21
 
22
 

+ 4
- 3
packages/zefyr/lib/src/widgets/buttons.dart View File

3
 // BSD-style license that can be found in the LICENSE file.
3
 // BSD-style license that can be found in the LICENSE file.
4
 import 'package:flutter/material.dart';
4
 import 'package:flutter/material.dart';
5
 import 'package:flutter/services.dart';
5
 import 'package:flutter/services.dart';
6
-import 'package:image_picker/image_picker.dart';
7
 import 'package:notus/notus.dart';
6
 import 'package:notus/notus.dart';
8
 import 'package:url_launcher/url_launcher.dart';
7
 import 'package:url_launcher/url_launcher.dart';
9
 
8
 
279
 
278
 
280
   void _pickFromCamera() async {
279
   void _pickFromCamera() async {
281
     final editor = ZefyrToolbar.of(context).editor;
280
     final editor = ZefyrToolbar.of(context).editor;
282
-    final image = await editor.imageDelegate.pickImage(ImageSource.camera);
281
+    final image =
282
+        await editor.imageDelegate.pickImage(editor.imageDelegate.cameraSource);
283
     if (image != null)
283
     if (image != null)
284
       editor.formatSelection(NotusAttribute.embed.image(image));
284
       editor.formatSelection(NotusAttribute.embed.image(image));
285
   }
285
   }
286
 
286
 
287
   void _pickFromGallery() async {
287
   void _pickFromGallery() async {
288
     final editor = ZefyrToolbar.of(context).editor;
288
     final editor = ZefyrToolbar.of(context).editor;
289
-    final image = await editor.imageDelegate.pickImage(ImageSource.gallery);
289
+    final image = await editor.imageDelegate
290
+        .pickImage(editor.imageDelegate.gallerySource);
290
     if (image != null)
291
     if (image != null)
291
       editor.formatSelection(NotusAttribute.embed.image(image));
292
       editor.formatSelection(NotusAttribute.embed.image(image));
292
   }
293
   }

+ 2
- 2
packages/zefyr/lib/src/widgets/editor.dart View File

121
   @override
121
   @override
122
   void initState() {
122
   void initState() {
123
     super.initState();
123
     super.initState();
124
-    _imageDelegate = widget.imageDelegate ?? new ZefyrDefaultImageDelegate();
124
+    _imageDelegate = widget.imageDelegate;
125
   }
125
   }
126
 
126
 
127
   @override
127
   @override
131
     _scope.controller = widget.controller;
131
     _scope.controller = widget.controller;
132
     _scope.focusNode = widget.focusNode;
132
     _scope.focusNode = widget.focusNode;
133
     if (widget.imageDelegate != oldWidget.imageDelegate) {
133
     if (widget.imageDelegate != oldWidget.imageDelegate) {
134
-      _imageDelegate = widget.imageDelegate ?? new ZefyrDefaultImageDelegate();
134
+      _imageDelegate = widget.imageDelegate;
135
       _scope.imageDelegate = _imageDelegate;
135
       _scope.imageDelegate = _imageDelegate;
136
     }
136
     }
137
   }
137
   }

+ 19
- 20
packages/zefyr/lib/src/widgets/image.dart View File

2
 // for details. All rights reserved. Use of this source code is governed by a
2
 // for details. All rights reserved. Use of this source code is governed by a
3
 // BSD-style license that can be found in the LICENSE file.
3
 // BSD-style license that can be found in the LICENSE file.
4
 import 'dart:async';
4
 import 'dart:async';
5
-import 'dart:io';
6
 import 'dart:math' as math;
5
 import 'dart:math' as math;
7
 import 'dart:ui' as ui;
6
 import 'dart:ui' as ui;
8
 
7
 
9
 import 'package:flutter/rendering.dart';
8
 import 'package:flutter/rendering.dart';
10
 import 'package:flutter/widgets.dart';
9
 import 'package:flutter/widgets.dart';
10
+import 'package:meta/meta.dart';
11
 import 'package:notus/notus.dart';
11
 import 'package:notus/notus.dart';
12
-import 'package:image_picker/image_picker.dart';
13
 
12
 
14
 import 'editable_box.dart';
13
 import 'editable_box.dart';
15
 
14
 
15
+/// Provides interface for embedding images into Zefyr editor.
16
+// TODO: allow configuring image sources and related toolbar buttons.
17
+@experimental
16
 abstract class ZefyrImageDelegate<S> {
18
 abstract class ZefyrImageDelegate<S> {
17
-  /// Builds image widget for specified [imageSource] and [context].
18
-  Widget buildImage(BuildContext context, String imageSource);
19
+  /// Unique key to identify camera source.
20
+  S get cameraSource;
21
+
22
+  /// Unique key to identify gallery source.
23
+  S get gallerySource;
24
+
25
+  /// Builds image widget for specified image [key].
26
+  ///
27
+  /// The [key] argument contains value which was previously returned from
28
+  /// [pickImage] method.
29
+  Widget buildImage(BuildContext context, String key);
19
 
30
 
20
   /// Picks an image from specified [source].
31
   /// Picks an image from specified [source].
21
   ///
32
   ///
22
   /// Returns unique string key for the selected image. Returned key is stored
33
   /// Returns unique string key for the selected image. Returned key is stored
23
   /// in the document.
34
   /// in the document.
35
+  ///
36
+  /// Depending on your application returned key may represent a path to
37
+  /// an image file on user's device, an HTTP link, or an identifier generated
38
+  /// by a file hosting service like AWS S3 or Google Drive.
24
   Future<String> pickImage(S source);
39
   Future<String> pickImage(S source);
25
 }
40
 }
26
 
41
 
27
-class ZefyrDefaultImageDelegate implements ZefyrImageDelegate<ImageSource> {
28
-  @override
29
-  Widget buildImage(BuildContext context, String imageSource) {
30
-    final file = new File.fromUri(Uri.parse(imageSource));
31
-    final image = new FileImage(file);
32
-    return new Image(image: image);
33
-  }
34
-
35
-  @override
36
-  Future<String> pickImage(ImageSource source) async {
37
-    final file = await ImagePicker.pickImage(source: source);
38
-    if (file == null) return null;
39
-    return file.uri.toString();
40
-  }
41
-}
42
-
43
 class ZefyrImage extends StatefulWidget {
42
 class ZefyrImage extends StatefulWidget {
44
   const ZefyrImage({Key key, @required this.node, @required this.delegate})
43
   const ZefyrImage({Key key, @required this.node, @required this.delegate})
45
       : super(key: key);
44
       : super(key: key);

+ 4
- 8
packages/zefyr/lib/src/widgets/scope.dart View File

25
   /// Creates a view-only scope.
25
   /// Creates a view-only scope.
26
   ///
26
   ///
27
   /// Normally used in [ZefyrView].
27
   /// Normally used in [ZefyrView].
28
-  ZefyrScope.view({
29
-    @required ZefyrMode mode,
30
-    @required ZefyrImageDelegate imageDelegate,
31
-  })  : assert(imageDelegate != null),
32
-        isEditable = false,
28
+  ZefyrScope.view({ZefyrImageDelegate imageDelegate})
29
+      : isEditable = false,
30
+        _mode = ZefyrMode.view,
33
         _imageDelegate = imageDelegate;
31
         _imageDelegate = imageDelegate;
34
 
32
 
35
   /// Creates editable scope.
33
   /// Creates editable scope.
38
   ZefyrScope.editable({
36
   ZefyrScope.editable({
39
     @required ZefyrMode mode,
37
     @required ZefyrMode mode,
40
     @required ZefyrController controller,
38
     @required ZefyrController controller,
41
-    @required ZefyrImageDelegate imageDelegate,
42
     @required FocusNode focusNode,
39
     @required FocusNode focusNode,
43
     @required FocusScopeNode focusScope,
40
     @required FocusScopeNode focusScope,
41
+    ZefyrImageDelegate imageDelegate,
44
   })  : assert(mode != null),
42
   })  : assert(mode != null),
45
         assert(controller != null),
43
         assert(controller != null),
46
-        assert(imageDelegate != null),
47
         assert(focusNode != null),
44
         assert(focusNode != null),
48
         assert(focusScope != null),
45
         assert(focusScope != null),
49
         isEditable = true,
46
         isEditable = true,
69
   ZefyrImageDelegate _imageDelegate;
66
   ZefyrImageDelegate _imageDelegate;
70
   ZefyrImageDelegate get imageDelegate => _imageDelegate;
67
   ZefyrImageDelegate get imageDelegate => _imageDelegate;
71
   set imageDelegate(ZefyrImageDelegate value) {
68
   set imageDelegate(ZefyrImageDelegate value) {
72
-    assert(value != null);
73
     if (_imageDelegate != value) {
69
     if (_imageDelegate != value) {
74
       _imageDelegate = value;
70
       _imageDelegate = value;
75
       notifyListeners();
71
       notifyListeners();

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

259
       buildButton(context, ZefyrToolbarAction.quote),
259
       buildButton(context, ZefyrToolbarAction.quote),
260
       buildButton(context, ZefyrToolbarAction.code),
260
       buildButton(context, ZefyrToolbarAction.code),
261
       buildButton(context, ZefyrToolbarAction.horizontalRule),
261
       buildButton(context, ZefyrToolbarAction.horizontalRule),
262
-      ImageButton(),
262
+      if (editor.imageDelegate != null) ImageButton(),
263
     ];
263
     ];
264
     return buttons;
264
     return buttons;
265
   }
265
   }

+ 1
- 5
packages/zefyr/lib/src/widgets/view.dart View File

9
 import 'common.dart';
9
 import 'common.dart';
10
 import 'image.dart';
10
 import 'image.dart';
11
 import 'list.dart';
11
 import 'list.dart';
12
-import 'mode.dart';
13
 import 'paragraph.dart';
12
 import 'paragraph.dart';
14
 import 'quote.dart';
13
 import 'quote.dart';
15
 import 'scope.dart';
14
 import 'scope.dart';
37
   @override
36
   @override
38
   void initState() {
37
   void initState() {
39
     super.initState();
38
     super.initState();
40
-    _scope = ZefyrScope.view(
41
-      mode: ZefyrMode.view,
42
-      imageDelegate: widget.imageDelegate,
43
-    );
39
+    _scope = ZefyrScope.view(imageDelegate: widget.imageDelegate);
44
   }
40
   }
45
 
41
 
46
   @override
42
   @override

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

5
 homepage: https://github.com/memspace/zefyr
5
 homepage: https://github.com/memspace/zefyr
6
 
6
 
7
 environment:
7
 environment:
8
-  sdk: '>=2.1.0 <3.0.0'
8
+  sdk: '>=2.2.2 <3.0.0'
9
 
9
 
10
 dependencies:
10
 dependencies:
11
   flutter:
11
   flutter:
12
     sdk: flutter
12
     sdk: flutter
13
   collection: ^1.14.6
13
   collection: ^1.14.6
14
   url_launcher: ^5.0.0
14
   url_launcher: ^5.0.0
15
-  image_picker: ^0.5.0
16
   quill_delta: ^1.0.0-dev.1.0
15
   quill_delta: ^1.0.0-dev.1.0
17
   notus: ^0.1.0
16
   notus: ^0.1.0
18
   meta: ^1.1.0
17
   meta: ^1.1.0

+ 11
- 3
packages/zefyr/test/testing.dart View File

22
     NotusDocument document,
22
     NotusDocument document,
23
     ZefyrThemeData theme,
23
     ZefyrThemeData theme,
24
     bool autofocus: false,
24
     bool autofocus: false,
25
+    ZefyrImageDelegate imageDelegate,
25
   }) {
26
   }) {
26
     focusNode ??= FocusNode();
27
     focusNode ??= FocusNode();
27
     document ??= NotusDocument.fromDelta(delta);
28
     document ??= NotusDocument.fromDelta(delta);
31
       controller: controller,
32
       controller: controller,
32
       focusNode: focusNode,
33
       focusNode: focusNode,
33
       autofocus: autofocus,
34
       autofocus: autofocus,
35
+      imageDelegate: imageDelegate,
34
     );
36
     );
35
 
37
 
36
     if (theme != null) {
38
     if (theme != null) {
115
 }
117
 }
116
 
118
 
117
 class _ZefyrSandbox extends StatefulWidget {
119
 class _ZefyrSandbox extends StatefulWidget {
118
-  const _ZefyrSandbox(
119
-      {Key key, this.controller, this.focusNode, this.autofocus})
120
-      : super(key: key);
120
+  const _ZefyrSandbox({
121
+    Key key,
122
+    this.controller,
123
+    this.focusNode,
124
+    this.autofocus,
125
+    this.imageDelegate,
126
+  }) : super(key: key);
121
   final ZefyrController controller;
127
   final ZefyrController controller;
122
   final FocusNode focusNode;
128
   final FocusNode focusNode;
123
   final bool autofocus;
129
   final bool autofocus;
130
+  final ZefyrImageDelegate imageDelegate;
124
 
131
 
125
   @override
132
   @override
126
   _ZefyrSandboxState createState() => _ZefyrSandboxState();
133
   _ZefyrSandboxState createState() => _ZefyrSandboxState();
136
       focusNode: widget.focusNode,
143
       focusNode: widget.focusNode,
137
       mode: _enabled ? ZefyrMode.edit : ZefyrMode.view,
144
       mode: _enabled ? ZefyrMode.edit : ZefyrMode.view,
138
       autofocus: widget.autofocus,
145
       autofocus: widget.autofocus,
146
+      imageDelegate: widget.imageDelegate,
139
     );
147
     );
140
   }
148
   }
141
 
149
 

+ 34
- 41
packages/zefyr/test/widgets/buttons_test.dart View File

1
 // Copyright (c) 2018, the Zefyr project authors.  Please see the AUTHORS file
1
 // Copyright (c) 2018, the Zefyr project authors.  Please see the AUTHORS file
2
 // for details. All rights reserved. Use of this source code is governed by a
2
 // for details. All rights reserved. Use of this source code is governed by a
3
 // BSD-style license that can be found in the LICENSE file.
3
 // BSD-style license that can be found in the LICENSE file.
4
+import 'dart:io';
5
+
4
 import 'package:flutter/material.dart';
6
 import 'package:flutter/material.dart';
5
-import 'package:flutter/services.dart';
6
 import 'package:flutter_test/flutter_test.dart';
7
 import 'package:flutter_test/flutter_test.dart';
7
 import 'package:zefyr/src/widgets/buttons.dart';
8
 import 'package:zefyr/src/widgets/buttons.dart';
8
 import 'package:zefyr/zefyr.dart';
9
 import 'package:zefyr/zefyr.dart';
145
   });
146
   });
146
 
147
 
147
   group('$ImageButton', () {
148
   group('$ImageButton', () {
148
-    const MethodChannel channel =
149
-        const MethodChannel('plugins.flutter.io/image_picker');
150
-
151
-    final List<MethodCall> log = <MethodCall>[];
152
-
153
-    setUp(() {
154
-      channel.setMockMethodCallHandler((MethodCall methodCall) async {
155
-        log.add(methodCall);
156
-        return '/tmp/test.jpg';
157
-      });
158
-      log.clear();
159
-    });
160
-
161
     testWidgets('toggle overlay', (tester) async {
149
     testWidgets('toggle overlay', (tester) async {
162
-      final editor = new EditorSandBox(tester: tester);
150
+      final editor = new EditorSandBox(
151
+        tester: tester,
152
+        imageDelegate: _TestImageDelegate(),
153
+      );
163
       await editor.pumpAndTap();
154
       await editor.pumpAndTap();
164
       await editor.tapButtonWithIcon(Icons.photo);
155
       await editor.tapButtonWithIcon(Icons.photo);
165
 
156
 
169
     });
160
     });
170
 
161
 
171
     testWidgets('pick from camera', (tester) async {
162
     testWidgets('pick from camera', (tester) async {
172
-      final editor = new EditorSandBox(tester: tester);
163
+      final editor = new EditorSandBox(
164
+        tester: tester,
165
+        imageDelegate: _TestImageDelegate(),
166
+      );
173
       await editor.pumpAndTap();
167
       await editor.pumpAndTap();
174
       await editor.tapButtonWithIcon(Icons.photo);
168
       await editor.tapButtonWithIcon(Icons.photo);
175
       await editor.tapButtonWithIcon(Icons.photo_camera);
169
       await editor.tapButtonWithIcon(Icons.photo_camera);
176
-      expect(log, hasLength(1));
177
-      expect(
178
-        log.single,
179
-        isMethodCall(
180
-          'pickImage',
181
-          arguments: <String, dynamic>{
182
-            'source': 0,
183
-            'maxWidth': null,
184
-            'maxHeight': null,
185
-          },
186
-        ),
187
-      );
170
+      expect(find.byType(ZefyrImage), findsOneWidget);
188
     });
171
     });
189
 
172
 
190
     testWidgets('pick from gallery', (tester) async {
173
     testWidgets('pick from gallery', (tester) async {
191
-      final editor = new EditorSandBox(tester: tester);
174
+      final editor = new EditorSandBox(
175
+        tester: tester,
176
+        imageDelegate: _TestImageDelegate(),
177
+      );
192
       await editor.pumpAndTap();
178
       await editor.pumpAndTap();
193
       await editor.tapButtonWithIcon(Icons.photo);
179
       await editor.tapButtonWithIcon(Icons.photo);
194
       await editor.tapButtonWithIcon(Icons.photo_library);
180
       await editor.tapButtonWithIcon(Icons.photo_library);
195
-      expect(log, hasLength(1));
196
-      expect(
197
-        log.single,
198
-        isMethodCall(
199
-          'pickImage',
200
-          arguments: <String, dynamic>{
201
-            'source': 1,
202
-            'maxWidth': null,
203
-            'maxHeight': null,
204
-          },
205
-        ),
206
-      );
181
+      expect(find.byType(ZefyrImage), findsOneWidget);
207
     });
182
     });
208
   });
183
   });
209
 }
184
 }
185
+
186
+class _TestImageDelegate implements ZefyrImageDelegate<String> {
187
+  @override
188
+  Widget buildImage(BuildContext context, String key) {
189
+    return Image.file(File(key));
190
+  }
191
+
192
+  @override
193
+  String get cameraSource => "camera";
194
+
195
+  @override
196
+  String get gallerySource => "gallery";
197
+
198
+  @override
199
+  Future<String> pickImage(String source) {
200
+    return Future.value("file:///tmp/test.jpg");
201
+  }
202
+}

+ 36
- 40
packages/zefyr/test/widgets/image_test.dart View File

1
 // Copyright (c) 2018, the Zefyr project authors.  Please see the AUTHORS file
1
 // Copyright (c) 2018, the Zefyr project authors.  Please see the AUTHORS file
2
 // for details. All rights reserved. Use of this source code is governed by a
2
 // for details. All rights reserved. Use of this source code is governed by a
3
 // BSD-style license that can be found in the LICENSE file.
3
 // BSD-style license that can be found in the LICENSE file.
4
+import 'dart:io';
5
+
4
 import 'package:flutter/material.dart';
6
 import 'package:flutter/material.dart';
5
-import 'package:flutter/services.dart';
6
 import 'package:flutter_test/flutter_test.dart';
7
 import 'package:flutter_test/flutter_test.dart';
7
-import 'package:image_picker/image_picker.dart';
8
 import 'package:zefyr/zefyr.dart';
8
 import 'package:zefyr/zefyr.dart';
9
 
9
 
10
 import '../testing.dart';
10
 import '../testing.dart';
11
 
11
 
12
 void main() {
12
 void main() {
13
-  group('$ZefyrDefaultImageDelegate', () {
14
-    const MethodChannel channel =
15
-        const MethodChannel('plugins.flutter.io/image_picker');
16
-
17
-    final List<MethodCall> log = <MethodCall>[];
18
-
19
-    setUp(() {
20
-      channel.setMockMethodCallHandler((MethodCall methodCall) async {
21
-        log.add(methodCall);
22
-        return '/tmp/test.jpg';
23
-      });
24
-      log.clear();
25
-    });
26
-
27
-    test('pick image', () async {
28
-      final delegate = new ZefyrDefaultImageDelegate();
29
-      final result = await delegate.pickImage(ImageSource.gallery);
30
-      expect(result, 'file:///tmp/test.jpg');
31
-    });
32
-  });
33
-
34
   group('$ZefyrImage', () {
13
   group('$ZefyrImage', () {
35
-    const MethodChannel channel =
36
-        const MethodChannel('plugins.flutter.io/image_picker');
37
-
38
-    final List<MethodCall> log = <MethodCall>[];
39
-
40
-    setUp(() {
41
-      channel.setMockMethodCallHandler((MethodCall methodCall) async {
42
-        log.add(methodCall);
43
-        return '/tmp/test.jpg';
44
-      });
45
-      log.clear();
46
-    });
47
-
48
     testWidgets('embed image', (tester) async {
14
     testWidgets('embed image', (tester) async {
49
-      final editor = new EditorSandBox(tester: tester);
15
+      final editor = new EditorSandBox(
16
+        tester: tester,
17
+        imageDelegate: _TestImageDelegate(),
18
+      );
50
       await editor.pumpAndTap();
19
       await editor.pumpAndTap();
51
       await editor.tapButtonWithIcon(Icons.photo);
20
       await editor.tapButtonWithIcon(Icons.photo);
52
       await editor.tapButtonWithIcon(Icons.photo_camera);
21
       await editor.tapButtonWithIcon(Icons.photo_camera);
62
 
31
 
63
     testWidgets('tap on left side of image puts caret before it',
32
     testWidgets('tap on left side of image puts caret before it',
64
         (tester) async {
33
         (tester) async {
65
-      final editor = new EditorSandBox(tester: tester);
34
+      final editor = new EditorSandBox(
35
+        tester: tester,
36
+        imageDelegate: _TestImageDelegate(),
37
+      );
66
       await editor.pumpAndTap();
38
       await editor.pumpAndTap();
67
       await editor.tapButtonWithIcon(Icons.photo);
39
       await editor.tapButtonWithIcon(Icons.photo);
68
       await editor.tapButtonWithIcon(Icons.photo_camera);
40
       await editor.tapButtonWithIcon(Icons.photo_camera);
76
     });
48
     });
77
 
49
 
78
     testWidgets('tap right side of image puts caret after it', (tester) async {
50
     testWidgets('tap right side of image puts caret after it', (tester) async {
79
-      final editor = new EditorSandBox(tester: tester);
51
+      final editor = new EditorSandBox(
52
+        tester: tester,
53
+        imageDelegate: _TestImageDelegate(),
54
+      );
80
       await editor.pumpAndTap();
55
       await editor.pumpAndTap();
81
       await editor.tapButtonWithIcon(Icons.photo);
56
       await editor.tapButtonWithIcon(Icons.photo);
82
       await editor.tapButtonWithIcon(Icons.photo_camera);
57
       await editor.tapButtonWithIcon(Icons.photo_camera);
92
     });
67
     });
93
 
68
 
94
     testWidgets('selects on long press', (tester) async {
69
     testWidgets('selects on long press', (tester) async {
95
-      final editor = new EditorSandBox(tester: tester);
70
+      final editor = new EditorSandBox(
71
+        tester: tester,
72
+        imageDelegate: _TestImageDelegate(),
73
+      );
96
       await editor.pumpAndTap();
74
       await editor.pumpAndTap();
97
       await editor.tapButtonWithIcon(Icons.photo);
75
       await editor.tapButtonWithIcon(Icons.photo);
98
       await editor.tapButtonWithIcon(Icons.photo_camera);
76
       await editor.tapButtonWithIcon(Icons.photo_camera);
108
     });
86
     });
109
   });
87
   });
110
 }
88
 }
89
+
90
+class _TestImageDelegate implements ZefyrImageDelegate<String> {
91
+  @override
92
+  Widget buildImage(BuildContext context, String key) {
93
+    return Image.file(File(key));
94
+  }
95
+
96
+  @override
97
+  String get cameraSource => "camera";
98
+
99
+  @override
100
+  String get gallerySource => "gallery";
101
+
102
+  @override
103
+  Future<String> pickImage(String source) {
104
+    return Future.value("file:///tmp/test.jpg");
105
+  }
106
+}

+ 19
- 2
packages/zefyr/test/widgets/scope_test.dart View File

16
       scope = ZefyrScope.editable(
16
       scope = ZefyrScope.editable(
17
         mode: ZefyrMode.edit,
17
         mode: ZefyrMode.edit,
18
         controller: ZefyrController(doc),
18
         controller: ZefyrController(doc),
19
-        imageDelegate: ZefyrDefaultImageDelegate(),
20
         focusNode: FocusNode(),
19
         focusNode: FocusNode(),
21
         focusScope: FocusScopeNode(),
20
         focusScope: FocusScopeNode(),
22
       );
21
       );
27
       scope.addListener(() {
26
       scope.addListener(() {
28
         notified = true;
27
         notified = true;
29
       });
28
       });
30
-      final delegate = ZefyrDefaultImageDelegate();
29
+      final delegate = _TestImageDelegate();
31
       scope.imageDelegate = delegate;
30
       scope.imageDelegate = delegate;
32
       expect(notified, isTrue);
31
       expect(notified, isTrue);
33
       notified = false;
32
       notified = false;
74
     });
73
     });
75
   });
74
   });
76
 }
75
 }
76
+
77
+class _TestImageDelegate implements ZefyrImageDelegate<String> {
78
+  @override
79
+  Widget buildImage(BuildContext context, String key) {
80
+    return null;
81
+  }
82
+
83
+  @override
84
+  String get cameraSource => null;
85
+
86
+  @override
87
+  String get gallerySource => null;
88
+
89
+  @override
90
+  Future<String> pickImage(String source) {
91
+    return null;
92
+  }
93
+}