From 2cc7ff9767d409856eeaf47c0685e656d3a753dc Mon Sep 17 00:00:00 2001 From: Pieter Otten Date: Mon, 11 Mar 2019 01:03:53 +0100 Subject: [PATCH 1/3] Added support for mailto: links. EG: "mailto:piotapps@gmail.com" will be a linked "piotapps@gmail.com". --- example/lib/main.dart | 2 +- example/pubspec.yaml | 2 +- lib/linkify.dart | 49 +++++++++++++++++++++++++++++++------------ pubspec.yaml | 4 ++-- 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index d97d6de..5af89cb 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -22,7 +22,7 @@ class LinkifyExample extends StatelessWidget { throw 'Could not launch $url'; } }, - text: "Made by https://cretezy.com", + text: "Made by https://cretezy.com\n\nMail: mailto:charles@cretezy.com", ), ), ), diff --git a/example/pubspec.yaml b/example/pubspec.yaml index d45dc69..ac3cf73 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - url_launcher: ^3.0.3 + url_launcher: ^5.0.1 flutter_linkify: path: .. diff --git a/lib/linkify.dart b/lib/linkify.dart index c62a433..c0dffe6 100644 --- a/lib/linkify.dart +++ b/lib/linkify.dart @@ -25,11 +25,16 @@ class TextElement extends LinkifyElement { } } -final _linkifyRegex = RegExp( +final _linkifyUrlRegex = RegExp( r"(\n*?.*?\s*?)((?:https?):\/\/[^\s/$.?#].[^\s]*)", caseSensitive: false, ); +final _linkifyEmailRegex = RegExp( + r"(\n*?.*?\s*?)((mailto:)[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4})", + caseSensitive: false +); + /// Turns [text] into a list of [LinkifyElement] /// /// Use [humanize] to remove http/https from the start of the URL shown. @@ -40,29 +45,47 @@ List linkify(String text, {bool humanize = false}) { return list; } - final match = _linkifyRegex.firstMatch(text); - if (match == null) { + final urlMatch = _linkifyUrlRegex.firstMatch(text); + final emailMatch = _linkifyEmailRegex.firstMatch(text); + if (urlMatch == null && emailMatch == null) { list.add(TextElement(text)); - } else { - text = text.replaceFirst(_linkifyRegex, ""); + } else if (urlMatch != null){ + text = text.replaceFirst(_linkifyUrlRegex, ""); - if (match.group(1).isNotEmpty) { - list.add(TextElement(match.group(1))); + if (urlMatch.group(1).isNotEmpty) { + list.add(TextElement(urlMatch.group(1))); } - if (match.group(2).isNotEmpty) { + if (urlMatch.group(2).isNotEmpty) { if (humanize ?? false) { - print("humanizing ${match.group(2)}"); + print("humanizing url ${urlMatch.group(2)}"); list.add(LinkElement( - match.group(2), - match.group(2).replaceFirst(RegExp(r"https?://"), ""), + urlMatch.group(2), + urlMatch.group(2).replaceFirst(RegExp(r"https?://"), ""), )); } else { - print("not humanizing ${match.group(2)}"); - list.add(LinkElement(match.group(2))); + print("not humanizing url ${urlMatch.group(2)}"); + list.add(LinkElement(urlMatch.group(2))); } } + list.addAll(linkify(text, humanize: humanize)); + } else if (emailMatch != null) { + text = text.replaceFirst(_linkifyEmailRegex, ""); + + if (emailMatch.group(1).isNotEmpty) { + list.add(TextElement(emailMatch.group(1))); + } + + if (emailMatch.group(2).isNotEmpty) { + // Always humanize emails + print("humanizing email ${emailMatch.group(2)}"); + list.add(LinkElement( + emailMatch.group(2), + emailMatch.group(2).replaceFirst(RegExp(r"mailto:"), ""), + )); + } + list.addAll(linkify(text, humanize: humanize)); } diff --git a/pubspec.yaml b/pubspec.yaml index 716a823..1acd97c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_linkify -description: Turns text URLs into clickable inline links in text for Flutter. -version: 1.1.1 +description: Turns text URLs and Mailto links into clickable inline links in text for Flutter. +version: 1.2.0 author: Charles Crete homepage: https://github.com/Cretezy/flutter_linkify From 99a80ccfca73303d9f5b73f93d251fac71ced11a Mon Sep 17 00:00:00 2001 From: Pieter Otten Date: Mon, 11 Mar 2019 14:30:51 +0100 Subject: [PATCH 2/3] Created an EmailElement, made the "mailto:" part of the RegEx optional and added a separate EmailCallback --- example/lib/main.dart | 24 +++++++++++++++++------- lib/flutter_linkify.dart | 39 +++++++++++++++++++++++++++++++-------- lib/linkify.dart | 23 ++++++++++++++++------- 3 files changed, 64 insertions(+), 22 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 5af89cb..6570990 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; +import 'dart:async'; + import 'package:url_launcher/url_launcher.dart'; void main() => runApp(new LinkifyExample()); @@ -15,17 +17,25 @@ class LinkifyExample extends StatelessWidget { ), body: Center( child: Linkify( - onOpen: (url) async { - if (await canLaunch(url)) { - await launch(url); - } else { - throw 'Could not launch $url'; - } + onLinkOpen: (url) { + _launchLink(url); + }, + onEmailOpen: (emailAddress) { + final emailAddressLink = "mailto:$emailAddress"; + _launchLink(emailAddressLink); }, - text: "Made by https://cretezy.com\n\nMail: mailto:charles@cretezy.com", + text: "Made by https://cretezy.com\n\nMail: example@gmail.com", ), ), ), ); } + + Future _launchLink(String link) async { + if (await canLaunch(link)) { + await launch(link); + } else { + throw 'Could not launch $link'; + } + } } diff --git a/lib/flutter_linkify.dart b/lib/flutter_linkify.dart index 71e215d..576bf00 100644 --- a/lib/flutter_linkify.dart +++ b/lib/flutter_linkify.dart @@ -5,6 +5,9 @@ import 'package:flutter_linkify/linkify.dart'; /// Callback with URL to open typedef LinkCallback(String url); +/// Callback with Email address +typedef EmailCallback(String emailAddress); + /// Turns URLs into links class Linkify extends StatelessWidget { /// Text to be linkified @@ -17,8 +20,12 @@ class Linkify extends StatelessWidget { final TextStyle linkStyle; /// Callback for tapping a link - final LinkCallback onOpen; + final LinkCallback onLinkOpen; + + /// Callback for tapping an email + final EmailCallback onEmailOpen; + /// Text direction of the text final TextDirection textDirection; /// Removes http/https from shown URLS @@ -29,7 +36,8 @@ class Linkify extends StatelessWidget { this.text, this.style, this.linkStyle, - this.onOpen, + this.onLinkOpen, + this.onEmailOpen, this.textDirection, this.humanize = false, }) : super(key: key); @@ -51,7 +59,8 @@ class Linkify extends StatelessWidget { decoration: TextDecoration.underline, ) .merge(linkStyle), - onOpen: onOpen, + onLinkOpen: onLinkOpen, + onEmailOpen: onEmailOpen, humanize: humanize, ), ); @@ -63,12 +72,19 @@ TextSpan buildTextSpan({ String text, TextStyle style, TextStyle linkStyle, - LinkCallback onOpen, + LinkCallback onLinkOpen, + EmailCallback onEmailOpen, bool humanize = false, }) { - void _onOpen(String url) { - if (onOpen != null) { - onOpen(url); + void _onLinkOpen(String url) { + if (onLinkOpen != null) { + onLinkOpen(url); + } + } + + void _onEmailOpen(String emailAddress) { + if (onEmailOpen != null) { + onEmailOpen(emailAddress); // TODO: discussable; add "mailto:" here for immediate use with url_launcher or except developers to do it themselves } } @@ -90,7 +106,14 @@ TextSpan buildTextSpan({ text: element.text, style: linkStyle, recognizer: TapGestureRecognizer() - ..onTap = () => _onOpen(element.url), + ..onTap = () => _onLinkOpen(element.url), + ); + } else if (element is EmailElement) { + return TextSpan( + text: element.text, + style: linkStyle, + recognizer: TapGestureRecognizer() + ..onTap = () => _onEmailOpen(element.emailAddress), ); } }, diff --git a/lib/linkify.dart b/lib/linkify.dart index c0dffe6..b3ec086 100644 --- a/lib/linkify.dart +++ b/lib/linkify.dart @@ -13,6 +13,19 @@ class LinkElement extends LinkifyElement { } } +/// Represents an element containing an email address +class EmailElement extends LinkifyElement { + final String emailAddress; + final String text; + + EmailElement(this.emailAddress, [String text]) : this.text = text ?? emailAddress; + + @override + String toString() { + return "EmailElement: $emailAddress ($text)"; + } +} + /// Represents an element containing text class TextElement extends LinkifyElement { final String text; @@ -31,7 +44,7 @@ final _linkifyUrlRegex = RegExp( ); final _linkifyEmailRegex = RegExp( - r"(\n*?.*?\s*?)((mailto:)[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4})", + r"(\n*?.*?\s*?)((mailto:)?[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4})", caseSensitive: false ); @@ -58,13 +71,11 @@ List linkify(String text, {bool humanize = false}) { if (urlMatch.group(2).isNotEmpty) { if (humanize ?? false) { - print("humanizing url ${urlMatch.group(2)}"); list.add(LinkElement( urlMatch.group(2), urlMatch.group(2).replaceFirst(RegExp(r"https?://"), ""), )); } else { - print("not humanizing url ${urlMatch.group(2)}"); list.add(LinkElement(urlMatch.group(2))); } } @@ -79,10 +90,8 @@ List linkify(String text, {bool humanize = false}) { if (emailMatch.group(2).isNotEmpty) { // Always humanize emails - print("humanizing email ${emailMatch.group(2)}"); - list.add(LinkElement( - emailMatch.group(2), - emailMatch.group(2).replaceFirst(RegExp(r"mailto:"), ""), + list.add(EmailElement( + emailMatch.group(2).replaceFirst(RegExp(r"mailto:"), "") )); } From 437bc30e2bdd2bafa7631705f07b87eba921bb35 Mon Sep 17 00:00:00 2001 From: Pieter Otten Date: Mon, 11 Mar 2019 16:25:09 +0100 Subject: [PATCH 3/3] Fixed formatting and typo --- lib/flutter_linkify.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/flutter_linkify.dart b/lib/flutter_linkify.dart index 576bf00..98251d0 100644 --- a/lib/flutter_linkify.dart +++ b/lib/flutter_linkify.dart @@ -84,7 +84,7 @@ TextSpan buildTextSpan({ void _onEmailOpen(String emailAddress) { if (onEmailOpen != null) { - onEmailOpen(emailAddress); // TODO: discussable; add "mailto:" here for immediate use with url_launcher or except developers to do it themselves + onEmailOpen(emailAddress); // TODO: discussable; add "mailto:" here for immediate use with url_launcher or expect developers to do it themselves } } @@ -110,10 +110,10 @@ TextSpan buildTextSpan({ ); } else if (element is EmailElement) { return TextSpan( - text: element.text, - style: linkStyle, - recognizer: TapGestureRecognizer() - ..onTap = () => _onEmailOpen(element.emailAddress), + text: element.text, + style: linkStyle, + recognizer: TapGestureRecognizer() + ..onTap = () => _onEmailOpen(element.emailAddress), ); } },