Browse Source

make links and image clickable

lucky1213 4 years ago
parent
commit
4da49ea26e

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

5
 import 'dart:async';
5
 import 'dart:async';
6
 import 'dart:convert';
6
 import 'dart:convert';
7
 
7
 
8
+import 'package:flutter/gestures.dart';
8
 import 'package:flutter/material.dart';
9
 import 'package:flutter/material.dart';
9
 import 'package:quill_delta/quill_delta.dart';
10
 import 'package:quill_delta/quill_delta.dart';
10
 import 'package:zefyr/zefyr.dart';
11
 import 'package:zefyr/zefyr.dart';
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
     if (_darkTheme) {
128
     if (_darkTheme) {
108
       return Theme(data: ThemeData.dark(), child: result);
129
       return Theme(data: ThemeData.dark(), child: result);

+ 31
- 13
packages/zefyr/example/lib/src/images.dart View File

34
     // We use custom "asset" scheme to distinguish asset images from other files.
34
     // We use custom "asset" scheme to distinguish asset images from other files.
35
     if (key.startsWith('asset://')) {
35
     if (key.startsWith('asset://')) {
36
       final asset = AssetImage(key.replaceFirst('asset://', ''));
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
     } else {
43
     } else {
39
       // Otherwise assume this is a file stored locally on user's device.
44
       // Otherwise assume this is a file stored locally on user's device.
40
       final file = File.fromUri(Uri.parse(key));
45
       final file = File.fromUri(Uri.parse(key));
41
       final image = FileImage(file);
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
 }
68
 }
59
 
69
 
60
 class CustomLinkDelegate implements ZefyrLinkDelegate {
70
 class CustomLinkDelegate implements ZefyrLinkDelegate {
61
-
62
   @override
71
   @override
63
-  Future<ZefyrLinkEntity> fillLink(BuildContext context, ZefyrLinkEntity value) async {
72
+  Future<ZefyrLinkEntity> fillLink(
73
+      BuildContext context, ZefyrLinkEntity value) async {
64
     TextEditingController _controller = TextEditingController();
74
     TextEditingController _controller = TextEditingController();
65
     return showGeneralDialog<ZefyrLinkEntity>(
75
     return showGeneralDialog<ZefyrLinkEntity>(
66
       context: context,
76
       context: context,
79
                 color: Colors.white,
89
                 color: Colors.white,
80
                 child: Column(
90
                 child: Column(
81
                   children: <Widget>[
91
                   children: <Widget>[
82
-                    TextField(controller: _controller, inputFormatters: [WhitelistingTextInputFormatter.digitsOnly],),
92
+                    TextField(
93
+                      controller: _controller,
94
+                      inputFormatters: [
95
+                        WhitelistingTextInputFormatter.digitsOnly
96
+                      ],
97
+                    ),
83
                     FlatButton(
98
                     FlatButton(
84
                       onPressed: () {
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
                       child: Text('确定'),
108
                       child: Text('确定'),
91
                     )
109
                     )
104
       useRootNavigator: true,
122
       useRootNavigator: true,
105
     );
123
     );
106
   }
124
   }
107
-  
125
+
108
   @override
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 View File

136
     final _linkDelegate = ZefyrScope.of(context).linkDelegate;
136
     final _linkDelegate = ZefyrScope.of(context).linkDelegate;
137
     final TextNode segment = node;
137
     final TextNode segment = node;
138
     final attrs = segment.style;
138
     final attrs = segment.style;
139
-    GestureRecognizer recognizer = TapGestureRecognizer();
139
+    TapGestureRecognizer recognizer;
140
     if (attrs.contains(NotusAttribute.link)) {
140
     if (attrs.contains(NotusAttribute.link)) {
141
       if (_linkDelegate != null) {
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
     return TextSpan(
152
     return TextSpan(
149
       text: segment.value,
153
       text: segment.value,
150
-      recognizer: TapGestureRecognizer()..onTap = () {
151
-        print('object');
152
-      },
154
+      recognizer: recognizer,
153
       style: _getTextStyle(attrs, theme),
155
       style: _getTextStyle(attrs, theme),
154
     );
156
     );
155
   }
157
   }

+ 10
- 0
packages/zefyr/lib/src/widgets/controller.dart View File

221
     return lineStyle;
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
   TextEditingValue get plainTextEditingValue {
234
   TextEditingValue get plainTextEditingValue {
225
     return TextEditingValue(
235
     return TextEditingValue(
226
       text: document.toPlainText(),
236
       text: document.toPlainText(),

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

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

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

26
 
26
 
27
   Future<ZefyrLinkEntity> fillLink(BuildContext context, ZefyrLinkEntity value);
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 View File

12
 import 'editable_box.dart';
12
 import 'editable_box.dart';
13
 
13
 
14
 /// Represents single paragraph of Zefyr rich-text.
14
 /// Represents single paragraph of Zefyr rich-text.
15
-class ZefyrRichText extends LeafRenderObjectWidget {
15
+class ZefyrRichText extends MultiChildRenderObjectWidget {
16
   ZefyrRichText({
16
   ZefyrRichText({
17
+    Key key,
17
     @required this.node,
18
     @required this.node,
18
     @required this.text,
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
   final LineNode node;
22
   final LineNode node;
22
   final TextSpan text;
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
   @override
36
   @override
25
   RenderObject createRenderObject(BuildContext context) {
37
   RenderObject createRenderObject(BuildContext context) {
26
     return RenderZefyrParagraph(
38
     return RenderZefyrParagraph(
69
 
81
 
70
   LineNode node;
82
   LineNode node;
71
 
83
 
84
+  @override
85
+  bool hitTestSelf(Offset position) => true;
86
+
72
   @override
87
   @override
73
   double get preferredLineHeight => _prototypePainter.height;
88
   double get preferredLineHeight => _prototypePainter.height;
74
 
89
 
96
     );
111
     );
97
   }
112
   }
98
 
113
 
114
+  TextPosition getRenderBoxPositionForOffset(Offset offset) {
115
+    return super.getPositionForOffset(offset);
116
+  }
117
+
99
   @override
118
   @override
100
   TextRange getWordBoundary(TextPosition position) {
119
   TextRange getWordBoundary(TextPosition position) {
101
     final localPosition = TextPosition(
120
     final localPosition = TextPosition(

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

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

+ 57
- 13
packages/zefyr/lib/src/widgets/selection.dart View File

12
 
12
 
13
 import 'controller.dart';
13
 import 'controller.dart';
14
 import 'editable_box.dart';
14
 import 'editable_box.dart';
15
+import 'image.dart' show RenderEditableImage;
16
+import 'rich_text.dart' show RenderZefyrParagraph;
15
 import 'scope.dart';
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
 /// Selection overlay controls selection handles and other gestures.
18
 /// Selection overlay controls selection handles and other gestures.
27
 class ZefyrSelectionOverlay extends StatefulWidget {
19
 class ZefyrSelectionOverlay extends StatefulWidget {
28
   const ZefyrSelectionOverlay({Key key, @required this.controls})
20
   const ZefyrSelectionOverlay({Key key, @required this.controls})
67
     return isSelectionCollapsed;
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
   void showToolbar() {
71
   void showToolbar() {
71
     final toolbarOpacity = _toolbarController.view;
72
     final toolbarOpacity = _toolbarController.view;
72
     _toolbar = OverlayEntry(
73
     _toolbar = OverlayEntry(
86
   TextEditingValue get textEditingValue =>
87
   TextEditingValue get textEditingValue =>
87
       _scope.controller.plainTextEditingValue;
88
       _scope.controller.plainTextEditingValue;
88
 
89
 
90
+  @override
89
   set textEditingValue(TextEditingValue value) {
91
   set textEditingValue(TextEditingValue value) {
90
     final cursorPosition = value.selection.extentOffset;
92
     final cursorPosition = value.selection.extentOffset;
91
     final oldText = _scope.controller.document.toPlainText();
93
     final oldText = _scope.controller.document.toPlainText();
180
             position: _SelectionHandlePosition.extent,
182
             position: _SelectionHandlePosition.extent,
181
             selectionOverlay: this,
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
     WidgetsBinding.instance.hitTest(result, globalPoint);
251
     WidgetsBinding.instance.hitTest(result, globalPoint);
245
 
252
 
246
     RenderEditableProxyBox box = _getEditableBox(result);
253
     RenderEditableProxyBox box = _getEditableBox(result);
247
-    if (box == null) {
248
-      box = _scope.renderContext.closestBoxForGlobalPoint(globalPoint);
249
-    }
254
+    
255
+    box ??= _scope.renderContext.closestBoxForGlobalPoint(globalPoint);
250
     if (box == null) return null;
256
     if (box == null) return null;
251
 
257
 
252
     final localPoint = box.globalToLocal(globalPoint);
258
     final localPoint = box.globalToLocal(globalPoint);
255
       offset: position.offset,
261
       offset: position.offset,
256
       affinity: position.affinity,
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
     if (_didCaretTap && _selection == selection) {
295
     if (_didCaretTap && _selection == selection) {
259
       _didCaretTap = false;
296
       _didCaretTap = false;
260
       if (isToolbarVisible) {
297
       if (isToolbarVisible) {
340
 
377
 
341
     final Offset paintOffset = Offset.zero;
378
     final Offset paintOffset = Offset.zero;
342
     final List<ui.TextBox> boxes = block.getEndpointsForSelection(selection);
379
     final List<ui.TextBox> boxes = block.getEndpointsForSelection(selection);
380
+    if (boxes.isEmpty) return null;
343
     final Offset start =
381
     final Offset start =
344
         Offset(boxes.first.start, boxes.first.bottom) + paintOffset;
382
         Offset(boxes.first.start, boxes.first.bottom) + paintOffset;
345
     final Offset end = Offset(boxes.last.end, boxes.last.bottom) + paintOffset;
383
     final Offset end = Offset(boxes.last.end, boxes.last.bottom) + paintOffset;
388
     }
426
     }
389
 
427
 
390
     final List<TextSelectionPoint> endpoints = getEndpointsForSelection(block);
428
     final List<TextSelectionPoint> endpoints = getEndpointsForSelection(block);
429
+    if (endpoints == null || endpoints.isEmpty) return Container();
430
+
391
     Offset point;
431
     Offset point;
392
     TextSelectionHandleType type;
432
     TextSelectionHandleType type;
393
 
433
 
568
       return Container();
608
       return Container();
569
     }
609
     }
570
     final boxes = block.getEndpointsForSelection(selection);
610
     final boxes = block.getEndpointsForSelection(selection);
611
+    print('boxes: ${boxes}');
612
+    if (boxes.isEmpty) {
613
+      return Container();
614
+    }
571
     // Find the horizontal midpoint, just above the selected text.
615
     // Find the horizontal midpoint, just above the selected text.
572
     Offset midpoint = Offset(
616
     Offset midpoint = Offset(
573
       (boxes.length == 1)
617
       (boxes.length == 1)

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

8
 import 'code.dart';
8
 import 'code.dart';
9
 import 'common.dart';
9
 import 'common.dart';
10
 import 'image.dart';
10
 import 'image.dart';
11
+import 'link.dart';
11
 import 'list.dart';
12
 import 'list.dart';
12
 import 'paragraph.dart';
13
 import 'paragraph.dart';
13
 import 'quote.dart';
14
 import 'quote.dart';
19
 class ZefyrView extends StatefulWidget {
20
 class ZefyrView extends StatefulWidget {
20
   final NotusDocument document;
21
   final NotusDocument document;
21
   final ZefyrImageDelegate imageDelegate;
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
       : super(key: key);
26
       : super(key: key);
25
 
27
 
26
   @override
28
   @override
36
   @override
38
   @override
37
   void initState() {
39
   void initState() {
38
     super.initState();
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
   @override
44
   @override