|
@@ -1,6 +1,8 @@
|
|
import 'dart:async';
|
|
import 'dart:async';
|
|
import 'dart:ui';
|
|
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/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.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/editor_state.dart';
|
|
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
|
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
|
import 'package:appflowy_editor/src/render/selection/selectable.dart';
|
|
import 'package:appflowy_editor/src/render/selection/selectable.dart';
|
|
-import 'package:url_launcher/url_launcher_string.dart';
|
|
|
|
|
|
|
|
typedef FlowyTextSpanDecorator = TextSpan Function(TextSpan textSpan);
|
|
typedef FlowyTextSpanDecorator = TextSpan Function(TextSpan textSpan);
|
|
|
|
|
|
@@ -204,53 +205,23 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
|
var offset = 0;
|
|
var offset = 0;
|
|
return TextSpan(
|
|
return TextSpan(
|
|
children: widget.textNode.delta.whereType<TextInsert>().map((insert) {
|
|
children: widget.textNode.delta.whereType<TextInsert>().map((insert) {
|
|
- GestureRecognizer? gestureDetector;
|
|
|
|
|
|
+ GestureRecognizer? gestureRecognizer;
|
|
if (insert.attributes?[StyleKey.href] != null) {
|
|
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;
|
|
offset += insert.length;
|
|
final textSpan = RichTextStyle(
|
|
final textSpan = RichTextStyle(
|
|
attributes: insert.attributes ?? {},
|
|
attributes: insert.attributes ?? {},
|
|
text: insert.content,
|
|
text: insert.content,
|
|
height: _lineHeight,
|
|
height: _lineHeight,
|
|
- gestureRecognizer: gestureDetector,
|
|
|
|
|
|
+ gestureRecognizer: gestureRecognizer,
|
|
).toTextSpan();
|
|
).toTextSpan();
|
|
return textSpan;
|
|
return textSpan;
|
|
}).toList(growable: false),
|
|
}).toList(growable: false),
|
|
@@ -266,4 +237,31 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
|
height: _lineHeight,
|
|
height: _lineHeight,
|
|
).toTextSpan()
|
|
).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;
|
|
|
|
+ }
|
|
}
|
|
}
|