Merge pull request #11 from apptreesoftware/main

Add support for phone links
This commit is contained in:
Stanley Akpama 2022-10-19 15:21:19 +01:00 committed by GitHub
commit 4a7b20605f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 81 additions and 48 deletions

View File

@ -21,6 +21,6 @@
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>
<string>8.0</string> <string>11.0</string>
</dict> </dict>
</plist> </plist>

View File

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project # Uncomment this line to define a global platform for your project
# platform :ios, '9.0' # platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@ -14,9 +14,9 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/path_provider/ios" :path: ".symlinks/plugins/path_provider/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
COCOAPODS: 1.10.1 COCOAPODS: 1.11.3

View File

@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 46; objectVersion = 50;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@ -156,7 +156,7 @@
97C146E61CF9000F007C117D /* Project object */ = { 97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 1020; LastUpgradeCheck = 1300;
ORGANIZATIONNAME = ""; ORGANIZATIONNAME = "";
TargetAttributes = { TargetAttributes = {
97C146ED1CF9000F007C117D = { 97C146ED1CF9000F007C117D = {
@ -340,7 +340,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@ -414,7 +414,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@ -463,7 +463,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0; IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1020" LastUpgradeVersion = "1300"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View File

@ -41,5 +41,7 @@
</array> </array>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<false/> <false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@ -46,14 +46,19 @@ class _AppState extends State<App> {
"text": "O4. This text contains a @user tag", "text": "O4. This text contains a @user tag",
"types": [LinkType.userTag] "types": [LinkType.userTag]
}, },
{
"text": "O5. This text contains a phone number: (555) 444 2223",
"types": [LinkType.phone]
},
{ {
"text": "text":
"O5. My website url: https://hello.com/GOOGLE search using: www.google.com, social media is facebook.com, additional link http://example.com/method?param=fullstackoverflow.dev, hashtag #trending & mention @dev.user", "O6. My website url: https://hello.com/GOOGLE search using: www.google.com, social media is facebook.com, additional link http://example.com/method?param=fullstackoverflow.dev, hashtag #trending & mention @dev.user +18009999999",
"types": [ "types": [
LinkType.phone,
LinkType.email, LinkType.email,
LinkType.url, LinkType.url,
LinkType.hashTag, LinkType.hashTag,
LinkType.userTag LinkType.userTag,
] ]
}, },
]; ];
@ -96,13 +101,14 @@ class _AppState extends State<App> {
LinkType.hashTag: TextStyle(color: Colors.green), LinkType.hashTag: TextStyle(color: Colors.green),
LinkType.userTag: TextStyle(color: Colors.deepPurple), LinkType.userTag: TextStyle(color: Colors.deepPurple),
LinkType.url: TextStyle(color: Colors.pink), LinkType.url: TextStyle(color: Colors.pink),
LinkType.phone: TextStyle(color: Colors.deepOrange),
}, },
linkStyle: textStyle.copyWith( linkStyle: textStyle.copyWith(
color: Colors.blue, color: Colors.blue,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
onTap: (link) => onTap: (link) => showSnackbar(
showSnackbar("link pressed: ${link.value!}"), "link pressed: ${link.value!}. Type: ${link.type}"),
)), )),
], ],
), ),

View File

@ -7,7 +7,7 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.8.2" version: "2.9.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -21,7 +21,7 @@ packages:
name: characters name: characters
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
@ -35,7 +35,7 @@ packages:
name: clock name: clock
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@ -63,7 +63,7 @@ packages:
name: fake_async name: fake_async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" version: "1.3.1"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
@ -122,28 +122,28 @@ packages:
name: matcher name: matcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.11" version: "0.12.12"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.4" version: "0.1.5"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.7.0" version: "1.8.0"
path: path:
dependency: transitive dependency: transitive
description: description:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.1" version: "1.8.2"
path_provider: path_provider:
dependency: transitive dependency: transitive
description: description:
@ -218,7 +218,7 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.2" version: "1.9.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -239,21 +239,21 @@ packages:
name: string_scanner name: string_scanner
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
name: term_glyph name: term_glyph
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.9" version: "0.4.12"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:

View File

@ -1 +1 @@
enum LinkType { url, email, hashTag, userTag } enum LinkType { url, email, hashTag, userTag, phone }

View File

@ -5,7 +5,8 @@ String urlRegExp = r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+';
String hashtagRegExp = r'(#+[a-zA-Z0-9(_)]{1,})'; String hashtagRegExp = r'(#+[a-zA-Z0-9(_)]{1,})';
String userTagRegExp = r'(?<![\w@])@([\w@]+(?:[.!][\w@]+)*)'; String userTagRegExp = r'(?<![\w@])@([\w@]+(?:[.!][\w@]+)*)';
String phoneRegExp =
r'\s*(?:\+?(\d{1,3}))?[-. (]*(\d{3})[-. )]*(\d{3})[-. ]*(\d{4})(?: *x(\d+))?\s*';
String emailRegExp = String emailRegExp =
r"([a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+)"; r"([a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+)";
@ -39,6 +40,11 @@ RegExp constructRegExpFromLinkType(List<LinkType> types) {
? buffer.write("($emailRegExp)") ? buffer.write("($emailRegExp)")
: buffer.write("($emailRegExp)|"); : buffer.write("($emailRegExp)|");
break; break;
case LinkType.phone:
isLast
? buffer.write("($phoneRegExp)")
: buffer.write("($phoneRegExp)|");
break;
default: default:
} }
} }
@ -49,6 +55,8 @@ LinkType getMatchedType(String match) {
late LinkType type; late LinkType type;
if (RegExp(emailRegExp).hasMatch(match)) { if (RegExp(emailRegExp).hasMatch(match)) {
type = LinkType.email; type = LinkType.email;
} else if (RegExp(phoneRegExp).hasMatch(match)) {
type = LinkType.phone;
} else if (RegExp(userTagRegExp).hasMatch(match)) { } else if (RegExp(userTagRegExp).hasMatch(match)) {
type = LinkType.userTag; type = LinkType.userTag;
} else if (RegExp(urlRegExp).hasMatch(match)) { } else if (RegExp(urlRegExp).hasMatch(match)) {

View File

@ -28,7 +28,7 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.8.2" version: "2.9.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -42,7 +42,7 @@ packages:
name: characters name: characters
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
@ -56,7 +56,7 @@ packages:
name: clock name: clock
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@ -91,7 +91,7 @@ packages:
name: fake_async name: fake_async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" version: "1.3.1"
file: file:
dependency: transitive dependency: transitive
description: description:
@ -171,21 +171,21 @@ packages:
name: matcher name: matcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.11" version: "0.12.12"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.4" version: "0.1.5"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.7.0" version: "1.8.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@ -213,7 +213,7 @@ packages:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.1" version: "1.8.2"
pedantic: pedantic:
dependency: transitive dependency: transitive
description: description:
@ -295,7 +295,7 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.2" version: "1.9.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -316,35 +316,35 @@ packages:
name: string_scanner name: string_scanner
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
name: term_glyph name: term_glyph
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
test: test:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: test name: test
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.21.1" version: "1.21.4"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.9" version: "0.4.12"
test_core: test_core:
dependency: transitive dependency: transitive
description: description:
name: test_core name: test_core
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.13" version: "0.4.16"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:

View File

@ -10,7 +10,9 @@ void main() {
const hashtagText = "#helloWorld and #dev are trending"; const hashtagText = "#helloWorld and #dev are trending";
const 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!";
const text = urlText + hashtagText + emailText;
const phoneText = "My phone number is (222)322-3222 or +15552223333";
const text = urlText + hashtagText + emailText + phoneText;
const emails = ["hello@world.com", "foo.bar@js.com"]; const emails = ["hello@world.com", "foo.bar@js.com"];
@ -46,6 +48,14 @@ void main() {
'@hello_world_123' '@hello_world_123'
]; ];
const phoneNums = [
"1112223333",
"(111)222 3333",
"+5444333222",
"+91 (123) 456-7890",
"123-456-7890"
];
/// ///
test("Should match all emails", () { test("Should match all emails", () {
for (final email in emails) { for (final email in emails) {
@ -69,6 +79,11 @@ void main() {
expect(RegExp(userTagRegExp).hasMatch(tag), isTrue); expect(RegExp(userTagRegExp).hasMatch(tag), isTrue);
} }
}); });
test("Should match all phones", () {
for (final tag in phoneNums) {
expect(RegExp(phoneRegExp).hasMatch(tag), isTrue);
}
});
test( test(
"Should construct regex pattern from LinkTypes and match required output", "Should construct regex pattern from LinkTypes and match required output",
@ -76,13 +91,15 @@ void main() {
final urlRegExp = constructRegExpFromLinkType([LinkType.url]); final urlRegExp = constructRegExpFromLinkType([LinkType.url]);
final hashtagRegExp = constructRegExpFromLinkType([LinkType.hashTag]); final hashtagRegExp = constructRegExpFromLinkType([LinkType.hashTag]);
final emailRegExp = constructRegExpFromLinkType([LinkType.email]); final emailRegExp = constructRegExpFromLinkType([LinkType.email]);
final phoneRegExp = constructRegExpFromLinkType([LinkType.phone]);
final textRegExp = constructRegExpFromLinkType( final textRegExp = constructRegExpFromLinkType(
[LinkType.url, LinkType.hashTag, LinkType.email]); [LinkType.url, LinkType.hashTag, LinkType.email, LinkType.phone]);
expect(urlRegExp.allMatches(urlText).length, 4); expect(urlRegExp.allMatches(urlText).length, 4);
expect(hashtagRegExp.allMatches(hashtagText).length, 2); expect(hashtagRegExp.allMatches(hashtagText).length, 2);
expect(emailRegExp.allMatches(emailText).length, 2); expect(emailRegExp.allMatches(emailText).length, 2);
expect(textRegExp.allMatches(text).length, 8); expect(phoneRegExp.allMatches(phoneText).length, 2);
expect(textRegExp.allMatches(text).length, 10);
}); });
}); });
} }