refactor: enforce lint rules

This commit is contained in:
iamstanlee 2021-09-02 18:24:27 -07:00
parent e4bb807a30
commit 829ced74d6
11 changed files with 120 additions and 91 deletions

View File

@ -27,41 +27,39 @@ Container(
) )
``` ```
Be default, The above snippet would linkify all urls in the string, you can choose what type of link to linkify by passing the `linkTypes` parameter Be default, The above snippet would linkify all urls in the string, you can choose what type of link to linkify by passing the `Links` parameter
```dart ```dart
Container( Container(
child: LinkifyText( child: LinkifyText(
"This text contains a url: https://flutter.dev and #flutter", "This text contains a url: https://flutter.dev and #flutter",
linkStyle: TextStyle(color: Colors.blue, fontSize: 16), linkStyle: TextStyle(color: Colors.blue, fontSize: 16),
linkTypes: [LinkType.url, LinkType.hashtag] Links: [Link.url, Link.hashtag]
onTap: (link) { onTap: (link) {
/// do stuff with `link` like /// do stuff with `link` like
/// if(link.type == Linktype.url) launchUrl(link.value); /// if(link.type == Link.url) launchUrl(link.value);
}, },
); );
) )
``` ```
## API Reference ## API Reference
#### LinkfyText #### LinkfyText
| Parameter | Type | Description | | Parameter | Type | Description |
| :-------- | :------- | :-------------------------------- | | :---------- | :--------------- | :--------------------------------------------------------------------------------------------------------------------------------- |
| `textStyle` | `TextStyle` | style applied to the text | | `textStyle` | `TextStyle` | style applied to the text |
| `linkStyle` | `TextStyle` | style applied to the linkified text. **defaults** to `textStyle` | | `linkStyle` | `TextStyle` | style applied to the linkified text. **defaults** to `textStyle` |
| `linkTypes` | `List<LinkType>` | a list of `LinkType` used to override the links to be linkified in a text either a url, hashtag or email. **defaults** to `[LinkType.url]`| | `Links` | `List<Link>` | a list of `Link` used to override the links to be linkified in a text either a url, hashtag or email. **defaults** to `[Link.url]` |
| `onTap` | `Function(Link)` | function called when a link is pressed | | `onTap` | `Function(Link)` | function called when a link is pressed |
#### Link #### Link
| Parameter | Type | Description | | Parameter | Type | Description |
| :-------- | :------- | :-------------------------------- | | :-------- | :------- | :----------------------------------------- |
| `type` | `LinkType` | the link type either url, email or hashtag | | `type` | `Link` | the link type either url, email or hashtag |
| `value` | `String` | value this link holds | | `value` | `String` | value this link holds |
# Contributions # Contributions
@ -72,5 +70,4 @@ If you fixed a bug or implemented a feature, please send a [pull request](https:
# TODO # TODO
- linkify "@" mention and "$" sign
- LinkifyTextField widget - LinkifyTextField widget

11
analysis_options.yaml Normal file
View File

@ -0,0 +1,11 @@
include: package:lint/analysis_options.yaml
analyzer:
errors:
missing_required_param: error
todo: info
linter:
rules:
always-specify-types: true
prefer-relative-imports: true
sort_pub_dependencies: false
unnecessary_raw_strings: false

View File

@ -12,7 +12,7 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
title: 'linkify_text Demo', title: 'linkfy_text Demo',
scaffoldMessengerKey: _msgKey, scaffoldMessengerKey: _msgKey,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
home: App(), home: App(),

View File

@ -18,7 +18,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: '>=2.12.0 <3.0.0' sdk: ">=2.12.0 <3.0.0"
dependencies: dependencies:
flutter: flutter:
@ -26,7 +26,6 @@ dependencies:
google_fonts: ^2.1.0 google_fonts: ^2.1.0
linkfy_text: linkfy_text:
path: ../ path: ../
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.

View File

@ -1,5 +1,5 @@
/// A lightweight flutter package to linkify texts containing urls, emails and hashtags. /// A lightweight flutter package to linkify texts containing urls, emails and hashtags.
library linkfy_text; library linkfy_text;
export 'src/linkify.dart';
export 'src/enum.dart'; export 'src/enum.dart';
export 'src/linkify.dart';

View File

@ -157,22 +157,22 @@ TextSpan _linkify({
List<LinkType>? linkTypes, List<LinkType>? linkTypes,
Function(Link)? onTap, Function(Link)? onTap,
}) { }) {
RegExp _regExp = constructRegExpFromLinkType(linkTypes ?? [LinkType.url]); final _regExp = constructRegExpFromLinkType(linkTypes ?? [LinkType.url]);
// return the full text if there's no match or if empty // return the full text if there's no match or if empty
if (!_regExp.hasMatch(text) || text.isEmpty) return TextSpan(text: text); if (!_regExp.hasMatch(text) || text.isEmpty) return TextSpan(text: text);
List<String> texts = text.split(_regExp); final texts = text.split(_regExp);
List<TextSpan> spans = []; final List<InlineSpan> spans = [];
List<RegExpMatch> links = _regExp.allMatches(text).toList(); final links = _regExp.allMatches(text).toList();
for (String text in texts) { for (final text in texts) {
spans.add(TextSpan( spans.add(TextSpan(
text: text, text: text,
)); ));
if (links.length > 0) { if (links.isNotEmpty) {
RegExpMatch match = links.removeAt(0); final match = links.removeAt(0);
Link link = Link.fromMatch(match); final link = Link.fromMatch(match);
// add the link // add the link
spans.add( spans.add(
TextSpan( TextSpan(

View File

@ -2,13 +2,13 @@ import 'package:linkfy_text/src/enum.dart';
import 'package:linkfy_text/src/utils/regex.dart'; import 'package:linkfy_text/src/utils/regex.dart';
class Link { class Link {
String? _value; final String? _value;
LinkType? _type; final LinkType? _type;
String? get value => _value; String? get value => _value;
LinkType? get type => _type; LinkType? get type => _type;
/// construct link from matched regExp /// construct link from matched regExp
Link.fromMatch(RegExpMatch match) Link.fromMatch(RegExpMatch match)
: this._type = getMatchedType(match), : _type = getMatchedType(match),
this._value = match.input.substring(match.start, match.end); _value = match.input.substring(match.start, match.end);
} }

View File

@ -6,46 +6,55 @@ String hashtagRegExp = r'(#+[a-zA-Z0-9(_)]{1,})';
String userTagRegExp = r'(?<![\w@])@([\w@]+(?:[.!][\w@]+)*)'; String userTagRegExp = r'(?<![\w@])@([\w@]+(?:[.!][\w@]+)*)';
String emailRegExp = r"([a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+)"; String emailRegExp =
r"([a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+)";
/// construct regexp. pattern from provided link linkTypes /// construct regexp. pattern from provided link types
RegExp constructRegExpFromLinkType(List<LinkType> linkTypes) { RegExp constructRegExpFromLinkType(List<LinkType> types) {
// default case where we always want to match url strings // default case where we always want to match url strings
if (linkTypes.length == 1 && linkTypes.first == LinkType.url) return RegExp(urlRegExp); final len = types.length;
if (len == 1 && types.first == LinkType.url) {
StringBuffer _regexBuffer = StringBuffer(); return RegExp(urlRegExp);
for (var i = 0; i < linkTypes.length; i++) { }
final _type = linkTypes[i]; final buffer = StringBuffer();
final _isLast = i == linkTypes.length - 1; for (var i = 0; i < len; i++) {
switch (_type) { final type = types[i];
final isLast = i == len - 1;
switch (type) {
case LinkType.url: case LinkType.url:
_isLast ? _regexBuffer.write("($urlRegExp)") : _regexBuffer.write("($urlRegExp)|"); isLast ? buffer.write("($urlRegExp)") : buffer.write("($urlRegExp)|");
break; break;
case LinkType.hashTag: case LinkType.hashTag:
_isLast ? _regexBuffer.write("($hashtagRegExp)") : _regexBuffer.write("($hashtagRegExp)|"); isLast
? buffer.write("($hashtagRegExp)")
: buffer.write("($hashtagRegExp)|");
break; break;
case LinkType.userTag: case LinkType.userTag:
_isLast ? _regexBuffer.write("($userTagRegExp)") : _regexBuffer.write("($userTagRegExp)|"); isLast
? buffer.write("($userTagRegExp)")
: buffer.write("($userTagRegExp)|");
break; break;
case LinkType.email: case LinkType.email:
_isLast ? _regexBuffer.write("($emailRegExp)") : _regexBuffer.write("($emailRegExp)|"); isLast
? buffer.write("($emailRegExp)")
: buffer.write("($emailRegExp)|");
break; break;
default: default:
} }
} }
return RegExp(_regexBuffer.toString()); return RegExp(buffer.toString());
} }
LinkType getMatchedType(RegExpMatch match) { LinkType getMatchedType(RegExpMatch match) {
late LinkType _type; late LinkType type;
if (RegExp(urlRegExp).hasMatch(match.input)) { if (RegExp(urlRegExp).hasMatch(match.input)) {
_type = LinkType.url; type = LinkType.url;
} else if (RegExp(hashtagRegExp).hasMatch(match.input)) { } else if (RegExp(hashtagRegExp).hasMatch(match.input)) {
_type = LinkType.hashTag; type = LinkType.hashTag;
} else if (RegExp(emailRegExp).hasMatch(match.input)) { } else if (RegExp(emailRegExp).hasMatch(match.input)) {
_type = LinkType.email; type = LinkType.email;
} else if (RegExp(userTagRegExp).hasMatch(match.input)) { } else if (RegExp(userTagRegExp).hasMatch(match.input)) {
_type = LinkType.userTag; type = LinkType.userTag;
} }
return _type; return type;
} }

View File

@ -151,6 +151,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.6.3" version: "0.6.3"
lint:
dependency: "direct dev"
description:
name: lint
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.3"
logging: logging:
dependency: transitive dependency: transitive
description: description:

View File

@ -15,6 +15,7 @@ dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
test: ^1.16.5 test: ^1.16.5
lint: ^1.5.3
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec

View File

@ -4,17 +4,17 @@ import 'package:test/test.dart';
void main() { void main() {
group('Regular Expression', () { group('Regular Expression', () {
/// test values // test values
final _urlText = const urlText =
"My website url: https://hello.com/GOOGLE search using: www.google.com, social media is facebook.com, http://example.com/method?param=fullstackoverflow.dev"; "My website url: https://hello.com/GOOGLE search using: www.google.com, social media is facebook.com, http://example.com/method?param=fullstackoverflow.dev";
final _hashtagText = "#helloWorld and #dev are trending"; const hashtagText = "#helloWorld and #dev are trending";
final _emailText = const emailText =
"My email address is hey@stanleee.me and dev@gmail.com, yah!"; "My email address is hey@stanleee.me and dev@gmail.com, yah!";
final _text = _urlText + _hashtagText + _emailText; const text = urlText + hashtagText + emailText;
final _emails = ["hello@world.com", "foo.bar@js.com"]; const emails = ["hello@world.com", "foo.bar@js.com"];
final List<String> _urls = [ const urls = [
"http://domain.com", "http://domain.com",
"http://domain.com/", "http://domain.com/",
"https://domain.com", "https://domain.com",
@ -28,7 +28,7 @@ void main() {
"https://sub.domain.com/Google?param=helloworld#hash", "https://sub.domain.com/Google?param=helloworld#hash",
]; ];
final List<String> _hashtags = [ const hashtags = [
"#123", "#123",
"#H", "#H",
"#0", "#0",
@ -38,46 +38,51 @@ void main() {
"#trending_topic", "#trending_topic",
]; ];
const userTags = [
'@helloWorld',
'@helloWorld123',
'@hello_world',
'@hello_world123',
'@hello_world_123'
];
/// ///
test("Should match all emails", () { test("Should match all emails", () {
_emails.forEach((e) { for (final email in emails) {
bool hasMatch = RegExp(emailRegExp).hasMatch(e); expect(RegExp(emailRegExp).hasMatch(email), isTrue);
expect(hasMatch, isTrue); }
});
});
test("Should not match all emails", () {
List<String> _emails = ["hello@world", "@js.com"];
_emails.forEach((e) {
bool hasMatch = RegExp(emailRegExp).hasMatch(e);
expect(hasMatch, isFalse);
});
}); });
test("Should match all urls", () { test("Should match all urls", () {
_urls.forEach((u) { for (final url in urls) {
bool hasMatch = RegExp(urlRegExp).hasMatch(u); expect(RegExp(urlRegExp).hasMatch(url), isTrue);
expect(hasMatch, isTrue); }
});
}); });
test("Should match all hashtags", () { test("Should match all hashtags", () {
_hashtags.forEach((h) { for (final tag in hashtags) {
bool hasMatch = RegExp(hashtagRegExp).hasMatch(h); expect(RegExp(hashtagRegExp).hasMatch(tag), isTrue);
expect(hasMatch, isTrue); }
}); });
test("Should match all usertags", () {
for (final tag in userTags) {
expect(RegExp(userTagRegExp).hasMatch(tag), isTrue);
}
}); });
test("Should construct regex pattern from LinkTypes and match length", () { test(
RegExp _urlRegExp = constructRegExpFromLinkType([LinkType.url]); "Should construct regex pattern from LinkTypes and match required output",
RegExp _hashtagRegExp = constructRegExpFromLinkType([LinkType.hashTag]); () {
RegExp _emailRegExp = constructRegExpFromLinkType([LinkType.email]); final urlRegExp = constructRegExpFromLinkType([LinkType.url]);
RegExp _textRegExp = constructRegExpFromLinkType( final hashtagRegExp = constructRegExpFromLinkType([LinkType.hashTag]);
final emailRegExp = constructRegExpFromLinkType([LinkType.email]);
final textRegExp = constructRegExpFromLinkType(
[LinkType.url, LinkType.hashTag, LinkType.email]); [LinkType.url, LinkType.hashTag, LinkType.email]);
expect(_urlRegExp.allMatches(_urlText).length, 4);
expect(_hashtagRegExp.allMatches(_hashtagText).length, 2); expect(urlRegExp.allMatches(urlText).length, 4);
expect(_emailRegExp.allMatches(_emailText).length, 2); expect(hashtagRegExp.allMatches(hashtagText).length, 2);
expect(_textRegExp.allMatches(_text).length, 8); expect(emailRegExp.allMatches(emailText).length, 2);
expect(textRegExp.allMatches(text).length, 8);
}); });
}); });
} }