| 
					
				 | 
			
			
				@@ -1,6 +1,8 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import 'dart:async'; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import 'dart:ui'; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import 'package:appflowy_editor/src/extensions/url_launcher_extension.dart'; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import 'package:appflowy_editor/src/render/toolbar/toolbar_item.dart'; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import 'package:flutter/gestures.dart'; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import 'package:flutter/material.dart'; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import 'package:flutter/rendering.dart'; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -13,7 +15,6 @@ import 'package:appflowy_editor/src/document/text_delta.dart'; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import 'package:appflowy_editor/src/editor_state.dart'; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart'; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import 'package:appflowy_editor/src/render/selection/selectable.dart'; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import 'package:url_launcher/url_launcher_string.dart'; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 typedef FlowyTextSpanDecorator = TextSpan Function(TextSpan textSpan); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -204,53 +205,23 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     var offset = 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     return TextSpan( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       children: widget.textNode.delta.whereType<TextInsert>().map((insert) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        GestureRecognizer? gestureDetector; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        GestureRecognizer? gestureRecognizer; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         if (insert.attributes?[StyleKey.href] != null) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          final startOffset = offset; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          Timer? timer; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          var tapCount = 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          gestureDetector = TapGestureRecognizer() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            ..onTap = () async { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              // implement a simple double tap logic 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              tapCount += 1; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              timer?.cancel(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              if (tapCount == 2) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                tapCount = 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                final href = insert.attributes![StyleKey.href]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                final uri = Uri.parse(href); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                // url_launcher cannot open a link without scheme. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                final newHref = 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    (uri.scheme.isNotEmpty ? href : 'http://$href').trim(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                if (await canLaunchUrlString(newHref)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  await launchUrlString(newHref); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              timer = Timer(const Duration(milliseconds: 200), () { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                tapCount = 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                // update selection 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                final selection = Selection.single( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  path: widget.textNode.path, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  startOffset: startOffset, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  endOffset: startOffset + insert.length, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                widget.editorState.service.selectionService 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    .updateSelection(selection); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                WidgetsBinding.instance.addPostFrameCallback((timeStamp) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  widget.editorState.service.toolbarService 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                      ?.triggerHandler('appflowy.toolbar.link'); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          gestureRecognizer = _buildTapHrefGestureRecognizer( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            insert.attributes![StyleKey.href], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            Selection.single( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              path: widget.textNode.path, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              startOffset: offset, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              endOffset: offset + insert.length, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         offset += insert.length; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         final textSpan = RichTextStyle( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           attributes: insert.attributes ?? {}, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           text: insert.content, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           height: _lineHeight, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          gestureRecognizer: gestureDetector, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          gestureRecognizer: gestureRecognizer, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         ).toTextSpan(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         return textSpan; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       }).toList(growable: false), 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -266,4 +237,31 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           height: _lineHeight, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         ).toTextSpan() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       ]); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  GestureRecognizer _buildTapHrefGestureRecognizer( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      String href, Selection selection) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    Timer? timer; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    var tapCount = 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    final tapGestureRecognizer = TapGestureRecognizer() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      ..onTap = () async { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        // implement a simple double tap logic 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        tapCount += 1; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        timer?.cancel(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if (tapCount == 2) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          tapCount = 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          safeLaunchUrl(href); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        timer = Timer(const Duration(milliseconds: 200), () { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          tapCount = 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          WidgetsBinding.instance.addPostFrameCallback((timeStamp) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            showLinkMenu(context, widget.editorState, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                customSelection: selection); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return tapGestureRecognizer; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } 
			 |