Selaa lähdekoodia

make links and image clickable

lucky1213 4 vuotta sitten
vanhempi
commit
4da49ea26e

+ 38
- 17
packages/zefyr/example/lib/src/full_page.dart Näytä tiedosto

@@ -5,6 +5,7 @@
5 5
 import 'dart:async';
6 6
 import 'dart:convert';
7 7
 
8
+import 'package:flutter/gestures.dart';
8 9
 import 'package:flutter/material.dart';
9 10
 import 'package:quill_delta/quill_delta.dart';
10 11
 import 'package:zefyr/zefyr.dart';
@@ -83,26 +84,46 @@ class _FullPageEditorScreenState extends State<FullPageEditorScreen> {
83 84
           )
84 85
         ],
85 86
       ),
86
-      body: ZefyrScaffold(
87
-              child: ZefyrTheme(
88
-                data: ZefyrThemeData(
89
-                  // attributeTheme: AttributeTheme(
90
-                  //   link: TextStyle(
91
-                  //     color: Colors.red,
92
-                  //   ),
93
-                  // ),
94
-                ),
95
-                child: ZefyrEditor(
96
-                  autofocus: false,
97
-                  controller: _controller,
98
-                  focusNode: _focusNode,
99
-                  mode: ZefyrMode.select,
100
-                  imageDelegate: CustomImageDelegate(),
101
-                  linkDelegate: CustomLinkDelegate(),
102
-                  keyboardAppearance: _darkTheme ? Brightness.dark : Brightness.light,
87
+      body: Column(
88
+        children: <Widget>[
89
+          Container(
90
+            height: 50,
91
+            child: RichText(
92
+              text: TextSpan(
93
+                text: '123',
94
+                style: TextStyle(
95
+                  color: Colors.black
103 96
                 ),
97
+                recognizer: TapGestureRecognizer()..onTap = () {
98
+                  print('object');
99
+                }
104 100
               ),
105 101
             ),
102
+          ),
103
+          Expanded(
104
+                      child: ZefyrScaffold(
105
+                    child: ZefyrTheme(
106
+                      data: ZefyrThemeData(
107
+                        // attributeTheme: AttributeTheme(
108
+                        //   link: TextStyle(
109
+                        //     color: Colors.red,
110
+                        //   ),
111
+                        // ),
112
+                      ),
113
+                      child: ZefyrEditor(
114
+                        autofocus: false,
115
+                        controller: _controller,
116
+                        focusNode: _focusNode,
117
+                        mode: ZefyrMode.edit,
118
+                        imageDelegate: CustomImageDelegate(),
119
+                        linkDelegate: CustomLinkDelegate(),
120
+                        keyboardAppearance: _darkTheme ? Brightness.dark : Brightness.light,
121
+                      ),
122
+                    ),
123
+                  ),
124
+          ),
125
+        ],
126
+      ),
106 127
     );
107 128
     if (_darkTheme) {
108 129
       return Theme(data: ThemeData.dark(), child: result);

+ 31
- 13
packages/zefyr/example/lib/src/images.dart Näytä tiedosto

@@ -34,12 +34,22 @@ class CustomImageDelegate implements ZefyrImageDelegate<ImageSource> {
34 34
     // We use custom "asset" scheme to distinguish asset images from other files.
35 35
     if (key.startsWith('asset://')) {
36 36
       final asset = AssetImage(key.replaceFirst('asset://', ''));
37
-      return Image(image: asset);
37
+      return GestureDetector(
38
+        child: Image(image: asset),
39
+        onTap: () {
40
+          print('object2');
41
+        },
42
+      );
38 43
     } else {
39 44
       // Otherwise assume this is a file stored locally on user's device.
40 45
       final file = File.fromUri(Uri.parse(key));
41 46
       final image = FileImage(file);
42
-      return Image(image: image);
47
+      return GestureDetector(
48
+        child: Image(image: image),
49
+        onTap: () {
50
+          print('object2');
51
+        },
52
+      );
43 53
     }
44 54
   }
45 55
 
@@ -58,9 +68,9 @@ class CustomImageDelegate implements ZefyrImageDelegate<ImageSource> {
58 68
 }
59 69
 
60 70
 class CustomLinkDelegate implements ZefyrLinkDelegate {
61
-
62 71
   @override
63
-  Future<ZefyrLinkEntity> fillLink(BuildContext context, ZefyrLinkEntity value) async {
72
+  Future<ZefyrLinkEntity> fillLink(
73
+      BuildContext context, ZefyrLinkEntity value) async {
64 74
     TextEditingController _controller = TextEditingController();
65 75
     return showGeneralDialog<ZefyrLinkEntity>(
66 76
       context: context,
@@ -79,13 +89,21 @@ class CustomLinkDelegate implements ZefyrLinkDelegate {
79 89
                 color: Colors.white,
80 90
                 child: Column(
81 91
                   children: <Widget>[
82
-                    TextField(controller: _controller, inputFormatters: [WhitelistingTextInputFormatter.digitsOnly],),
92
+                    TextField(
93
+                      controller: _controller,
94
+                      inputFormatters: [
95
+                        WhitelistingTextInputFormatter.digitsOnly
96
+                      ],
97
+                    ),
83 98
                     FlatButton(
84 99
                       onPressed: () {
85
-                        Navigator.pop(buildContext, ZefyrLinkEntity(
86
-                          text: '测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用',
87
-                          url: _controller.text,
88
-                        ));
100
+                        Navigator.pop(
101
+                            buildContext,
102
+                            ZefyrLinkEntity(
103
+                              text:
104
+                                  '测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用测试用',
105
+                              url: _controller.text,
106
+                            ));
89 107
                       },
90 108
                       child: Text('确定'),
91 109
                     )
@@ -104,11 +122,11 @@ class CustomLinkDelegate implements ZefyrLinkDelegate {
104 122
       useRootNavigator: true,
105 123
     );
106 124
   }
107
-  
125
+
108 126
   @override
109
-  GestureRecognizer provideLinkGesture(ZefyrLinkEntity value) {
110
-    // return 
111
-    return TapGestureRecognizer()..onTap = () {print('The word touched is 123123');};
127
+  void onLinkTap(ZefyrLinkEntity value) {
128
+    // return
129
+    print('The word touched is 123123');
112 130
   }
113 131
 }
114 132
 

+ 10
- 8
packages/zefyr/lib/src/widgets/common.dart Näytä tiedosto

@@ -136,20 +136,22 @@ class _ZefyrLineState extends State<ZefyrLine> {
136 136
     final _linkDelegate = ZefyrScope.of(context).linkDelegate;
137 137
     final TextNode segment = node;
138 138
     final attrs = segment.style;
139
-    GestureRecognizer recognizer = TapGestureRecognizer();
139
+    TapGestureRecognizer recognizer;
140 140
     if (attrs.contains(NotusAttribute.link)) {
141 141
       if (_linkDelegate != null) {
142
-        recognizer = ZefyrScope.of(context).linkDelegate.provideLinkGesture(ZefyrLinkEntity(
143
-          text: segment.value,
144
-          url: attrs.value(NotusAttribute.link),
145
-        ));
142
+        final tapGestureRecognizer = TapGestureRecognizer();
143
+        tapGestureRecognizer.onTap = () {
144
+          _linkDelegate.onLinkTap(ZefyrLinkEntity(
145
+            text: segment.value,
146
+            url: attrs.value(NotusAttribute.link),
147
+          ));
148
+        };
149
+        recognizer = tapGestureRecognizer;
146 150
       }
147 151
     }
148 152
     return TextSpan(
149 153
       text: segment.value,
150
-      recognizer: TapGestureRecognizer()..onTap = () {
151
-        print('object');
152
-      },
154
+      recognizer: recognizer,
153 155
       style: _getTextStyle(attrs, theme),
154 156
     );
155 157
   }

+ 10
- 0
packages/zefyr/lib/src/widgets/controller.dart Näytä tiedosto

@@ -221,6 +221,16 @@ class ZefyrController extends ChangeNotifier {
221 221
     return lineStyle;
222 222
   }
223 223
 
224
+  NotusStyle getStyleForSelection(TextSelection selection) {
225
+    int start = selection.start;
226
+    int length = selection.end - start;
227
+    var lineStyle = _document.collectStyle(start, length);
228
+
229
+    lineStyle = lineStyle.mergeAll(toggledStyles);
230
+
231
+    return lineStyle;
232
+  }
233
+
224 234
   TextEditingValue get plainTextEditingValue {
225 235
     return TextEditingValue(
226 236
       text: document.toPlainText(),

+ 2
- 1
packages/zefyr/lib/src/widgets/editable_box.dart Näytä tiedosto

@@ -230,6 +230,7 @@ class RenderEditableProxyBox extends RenderBox
230 230
   }
231 231
 
232 232
   void _paintCursor(PaintingContext context, Offset offset) {
233
+    final _ = getOffsetForCaret(_selection.extent, Rect.zero);
233 234
     Offset caretOffset =
234 235
         getOffsetForCaret(_selection.extent, _cursorPainter.prototype);
235 236
     _cursorPainter.paint(context.canvas, caretOffset + offset);
@@ -246,7 +247,7 @@ class RenderEditableProxyBox extends RenderBox
246 247
     }
247 248
     return false;
248 249
   }
249
-
250
+  
250 251
   //
251 252
   // Proxy methods
252 253
   //

+ 1
- 1
packages/zefyr/lib/src/widgets/link.dart Näytä tiedosto

@@ -26,5 +26,5 @@ abstract class ZefyrLinkDelegate {
26 26
 
27 27
   Future<ZefyrLinkEntity> fillLink(BuildContext context, ZefyrLinkEntity value);
28 28
 
29
-  GestureRecognizer provideLinkGesture(ZefyrLinkEntity value);
29
+  void onLinkTap(ZefyrLinkEntity value);
30 30
 }

+ 21
- 2
packages/zefyr/lib/src/widgets/rich_text.dart Näytä tiedosto

@@ -12,15 +12,27 @@ import 'caret.dart';
12 12
 import 'editable_box.dart';
13 13
 
14 14
 /// Represents single paragraph of Zefyr rich-text.
15
-class ZefyrRichText extends LeafRenderObjectWidget {
15
+class ZefyrRichText extends MultiChildRenderObjectWidget {
16 16
   ZefyrRichText({
17
+    Key key,
17 18
     @required this.node,
18 19
     @required this.text,
19
-  }) : assert(node != null && text != null);
20
+  }) : assert(node != null && text != null), super(key: key, children: _extractChildren(text));
20 21
 
21 22
   final LineNode node;
22 23
   final TextSpan text;
23 24
 
25
+  static List<Widget> _extractChildren(InlineSpan span) {
26
+    final result = <Widget>[];
27
+    span.visitChildren((InlineSpan span) {
28
+      if (span is WidgetSpan) {
29
+        result.add(span.child);
30
+      }
31
+      return true;
32
+    });
33
+    return result;
34
+  }
35
+
24 36
   @override
25 37
   RenderObject createRenderObject(BuildContext context) {
26 38
     return RenderZefyrParagraph(
@@ -69,6 +81,9 @@ class RenderZefyrParagraph extends RenderParagraph
69 81
 
70 82
   LineNode node;
71 83
 
84
+  @override
85
+  bool hitTestSelf(Offset position) => true;
86
+
72 87
   @override
73 88
   double get preferredLineHeight => _prototypePainter.height;
74 89
 
@@ -96,6 +111,10 @@ class RenderZefyrParagraph extends RenderParagraph
96 111
     );
97 112
   }
98 113
 
114
+  TextPosition getRenderBoxPositionForOffset(Offset offset) {
115
+    return super.getPositionForOffset(offset);
116
+  }
117
+
99 118
   @override
100 119
   TextRange getWordBoundary(TextPosition position) {
101 120
     final localPosition = TextPosition(

+ 3
- 2
packages/zefyr/lib/src/widgets/scope.dart Näytä tiedosto

@@ -29,10 +29,11 @@ class ZefyrScope extends ChangeNotifier {
29 29
   /// Creates a view-only scope.
30 30
   ///
31 31
   /// Normally used in [ZefyrView].
32
-  ZefyrScope.view({ZefyrImageDelegate imageDelegate})
32
+  ZefyrScope.view({ZefyrImageDelegate imageDelegate, ZefyrLinkDelegate linkDelegate})
33 33
       : isEditable = false,
34 34
         _mode = ZefyrMode.view,
35
-        _imageDelegate = imageDelegate;
35
+        _imageDelegate = imageDelegate,
36
+        _linkDelegate = linkDelegate;
36 37
 
37 38
   /// Creates editable scope.
38 39
   ///

+ 57
- 13
packages/zefyr/lib/src/widgets/selection.dart Näytä tiedosto

@@ -12,17 +12,9 @@ import 'package:zefyr/util.dart';
12 12
 
13 13
 import 'controller.dart';
14 14
 import 'editable_box.dart';
15
+import 'image.dart' show RenderEditableImage;
16
+import 'rich_text.dart' show RenderZefyrParagraph;
15 17
 import 'scope.dart';
16
-
17
-RenderEditableBox _getEditableBox(HitTestResult result) {
18
-  for (var entry in result.path) {
19
-    if (entry.target is RenderEditableBox) {
20
-      return entry.target as RenderEditableBox;
21
-    }
22
-  }
23
-  return null;
24
-}
25
-
26 18
 /// Selection overlay controls selection handles and other gestures.
27 19
 class ZefyrSelectionOverlay extends StatefulWidget {
28 20
   const ZefyrSelectionOverlay({Key key, @required this.controls})
@@ -67,6 +59,15 @@ class _ZefyrSelectionOverlayState extends State<ZefyrSelectionOverlay>
67 59
     return isSelectionCollapsed;
68 60
   }
69 61
 
62
+  RenderEditableBox _getEditableBox(HitTestResult result) {
63
+    for (var entry in result.path) {
64
+      if (entry.target is RenderEditableBox) {
65
+        return entry.target as RenderEditableBox;
66
+      }
67
+    }
68
+    return null;
69
+  }
70
+
70 71
   void showToolbar() {
71 72
     final toolbarOpacity = _toolbarController.view;
72 73
     _toolbar = OverlayEntry(
@@ -86,6 +87,7 @@ class _ZefyrSelectionOverlayState extends State<ZefyrSelectionOverlay>
86 87
   TextEditingValue get textEditingValue =>
87 88
       _scope.controller.plainTextEditingValue;
88 89
 
90
+  @override
89 91
   set textEditingValue(TextEditingValue value) {
90 92
     final cursorPosition = value.selection.extentOffset;
91 93
     final oldText = _scope.controller.document.toPlainText();
@@ -180,6 +182,11 @@ class _ZefyrSelectionOverlayState extends State<ZefyrSelectionOverlay>
180 182
             position: _SelectionHandlePosition.extent,
181 183
             selectionOverlay: this,
182 184
           ),
185
+          // SizedBox(
186
+          //   height: 20,
187
+          //   width: 30,
188
+          //   child: Container(color: Colors.black,),
189
+          // ),
183 190
         ],
184 191
       ),
185 192
     );
@@ -244,9 +251,8 @@ class _ZefyrSelectionOverlayState extends State<ZefyrSelectionOverlay>
244 251
     WidgetsBinding.instance.hitTest(result, globalPoint);
245 252
 
246 253
     RenderEditableProxyBox box = _getEditableBox(result);
247
-    if (box == null) {
248
-      box = _scope.renderContext.closestBoxForGlobalPoint(globalPoint);
249
-    }
254
+    
255
+    box ??= _scope.renderContext.closestBoxForGlobalPoint(globalPoint);
250 256
     if (box == null) return null;
251 257
 
252 258
     final localPoint = box.globalToLocal(globalPoint);
@@ -255,6 +261,37 @@ class _ZefyrSelectionOverlayState extends State<ZefyrSelectionOverlay>
255 261
       offset: position.offset,
256 262
       affinity: position.affinity,
257 263
     );
264
+    // 如果是link或者image
265
+    if (box?.child is RenderZefyrParagraph) {
266
+      RenderZefyrParagraph paragraph = box.child;
267
+      if (_scope.controller.getStyleForSelection(selection).contains(NotusAttribute.link)) {
268
+        // 当前点击的是link
269
+        var position = paragraph.getRenderBoxPositionForOffset(localPoint);
270
+        // 此处加1是点击text尾,不算点中
271
+        final TextSpan span = paragraph.text.getSpanForPosition(TextPosition(
272
+          offset: position.offset + 1,
273
+          affinity: position.affinity,
274
+        ));
275
+        final recognizer = (span?.recognizer as TapGestureRecognizer);
276
+        if (recognizer != null) {
277
+          print('当前点击的是link');
278
+          // 关闭键盘 触发tap,改变光标,但不弹出键盘
279
+          _scope.closeKeyboard();
280
+          recognizer.onTap();
281
+          _scope.controller.updateSelection(selection, source: ChangeSource.remote);
282
+          return;
283
+        }
284
+      }
285
+    } else if (box?.child is RenderEditableImage) {
286
+      RenderEditableImage image = box.child;
287
+      if ((image.child as RenderPadding).child is RenderSemanticsGestureHandler) {
288
+        RenderSemanticsGestureHandler gestureBox = (image.child as RenderPadding).child;
289
+        _scope.closeKeyboard();
290
+        gestureBox.onTap();
291
+        _scope.controller.updateSelection(selection, source: ChangeSource.remote);
292
+        return;
293
+      }
294
+    }
258 295
     if (_didCaretTap && _selection == selection) {
259 296
       _didCaretTap = false;
260 297
       if (isToolbarVisible) {
@@ -340,6 +377,7 @@ class _SelectionHandleDriverState extends State<SelectionHandleDriver>
340 377
 
341 378
     final Offset paintOffset = Offset.zero;
342 379
     final List<ui.TextBox> boxes = block.getEndpointsForSelection(selection);
380
+    if (boxes.isEmpty) return null;
343 381
     final Offset start =
344 382
         Offset(boxes.first.start, boxes.first.bottom) + paintOffset;
345 383
     final Offset end = Offset(boxes.last.end, boxes.last.bottom) + paintOffset;
@@ -388,6 +426,8 @@ class _SelectionHandleDriverState extends State<SelectionHandleDriver>
388 426
     }
389 427
 
390 428
     final List<TextSelectionPoint> endpoints = getEndpointsForSelection(block);
429
+    if (endpoints == null || endpoints.isEmpty) return Container();
430
+
391 431
     Offset point;
392 432
     TextSelectionHandleType type;
393 433
 
@@ -568,6 +608,10 @@ class _SelectionToolbarState extends State<_SelectionToolbar> {
568 608
       return Container();
569 609
     }
570 610
     final boxes = block.getEndpointsForSelection(selection);
611
+    print('boxes: ${boxes}');
612
+    if (boxes.isEmpty) {
613
+      return Container();
614
+    }
571 615
     // Find the horizontal midpoint, just above the selected text.
572 616
     Offset midpoint = Offset(
573 617
       (boxes.length == 1)

+ 4
- 2
packages/zefyr/lib/src/widgets/view.dart Näytä tiedosto

@@ -8,6 +8,7 @@ import 'package:notus/notus.dart';
8 8
 import 'code.dart';
9 9
 import 'common.dart';
10 10
 import 'image.dart';
11
+import 'link.dart';
11 12
 import 'list.dart';
12 13
 import 'paragraph.dart';
13 14
 import 'quote.dart';
@@ -19,8 +20,9 @@ import 'theme.dart';
19 20
 class ZefyrView extends StatefulWidget {
20 21
   final NotusDocument document;
21 22
   final ZefyrImageDelegate imageDelegate;
23
+  final ZefyrLinkDelegate linkDelegate;
22 24
 
23
-  const ZefyrView({Key key, @required this.document, this.imageDelegate})
25
+  const ZefyrView({Key key, @required this.document, this.imageDelegate, this.linkDelegate})
24 26
       : super(key: key);
25 27
 
26 28
   @override
@@ -36,7 +38,7 @@ class ZefyrViewState extends State<ZefyrView> {
36 38
   @override
37 39
   void initState() {
38 40
     super.initState();
39
-    _scope = ZefyrScope.view(imageDelegate: widget.imageDelegate);
41
+    _scope = ZefyrScope.view(imageDelegate: widget.imageDelegate, linkDelegate: widget.linkDelegate);
40 42
   }
41 43
 
42 44
   @override