diff --git a/README.md b/README.md
index e353363..3a59aac 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,12 @@
# WidgetKit
-A kit of widgets that are (almost) always needed in the different apps.
+A kit of widgets that are (almost) always needed in the different Flutter apps.
-## List of widgets
+## List of Widgets
-- Shared: Widgets that can be used globally, regardless of which app you are based off(Material or Cupertino).
-- Material: Widgets for MaterialApp based apps or for the Material look and feel.
-- (WIP)Cupertino: Widgets for CupertinoApp based apps or for the Cupertino(iOS) look and feel.
+- [Shared](#shared): Widgets that can be used globally, regardless of which app you are based off(Material or Cupertino).
+- [Material](#material): Widgets for MaterialApp based apps or for the Material look and feel.
+- [Cupertino](#cupertino) **(Coming Soon!)**: Widgets for CupertinoApp based apps or for the Cupertino(iOS) look and feel.
### Shared
@@ -14,8 +14,16 @@ A kit of widgets that are (almost) always needed in the different apps.
A widget that you can use to wrap other widgets (like a Scaffold or a Form) that usually contain inputs, this will help hide the keyboard when touching outside.
+#### AutolinkText
+
+A text widget, that turns URLs, email and phone numbers into clickable inline links in text for flutter. A null safe version of FogNature's [AutolinkText](https://github.com/FogNature/flutter_autolink_text).
+
### Material
#### Password TextField
-A widget that allows you to show or hide the password already embedded.
\ No newline at end of file
+A widget that allows you to show or hide the password already embedded.
+
+### Cupertino
+
+#### Coming Soon
diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
index 1d526a1..919434a 100644
--- a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -2,6 +2,6 @@
+ location = "self:">
diff --git a/example/lib/main.dart b/example/lib/main.dart
index b750de6..731ef67 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -7,16 +7,15 @@ void main() {
}
class MyApp extends StatelessWidget {
- // This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
- title: 'Flutter Demo',
+ title: 'Widgetkit Demo',
theme: ThemeData(
- primarySwatch: Colors.blue,
+ primarySwatch: Colors.blueGrey,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
- home: MyHomePage(title: 'Flutter Demo Home Page'),
+ home: MyHomePage(title: 'Widgetkit Demo'),
);
}
}
@@ -42,6 +41,11 @@ class _MyHomePageState extends State {
padding: const EdgeInsets.all(8.0),
child: ListView(
children: [
+ Text(
+ "PasswordTextField",
+ style: Theme.of(context).textTheme.headline6,
+ ),
+ SizedBox(height: 16),
PasswordTextField(),
SizedBox(height: 16),
PasswordTextField(
@@ -49,10 +53,64 @@ class _MyHomePageState extends State {
border: OutlineInputBorder(),
),
),
+ SizedBox(height: 32),
+ Text(
+ "CupertinoPasswordTextField",
+ style: Theme.of(context).textTheme.headline6,
+ ),
SizedBox(height: 16),
CupertinoTextField(
obscureText: true,
- suffix: Icon(CupertinoIcons.eye),
+ suffix: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 8),
+ child: Icon(CupertinoIcons.eye),
+ ),
+ ),
+ SizedBox(height: 32),
+ Text(
+ "AutolinkText",
+ style: Theme.of(context).textTheme.headline6,
+ ),
+ SizedBox(height: 16),
+ AutolinkText(
+ "Hello world from https://www.flutter.dev/",
+ textStyle: TextStyle(color: Colors.black),
+ linkStyle: TextStyle(
+ color: Colors.blue,
+ decoration: TextDecoration.underline,
+ ),
+ onWebLinkTap: (String url) => print(url),
+ ),
+ SizedBox(height: 16),
+ AutolinkText(
+ "Humanized (removes scheme) https://www.flutter.dev/",
+ humanize: true,
+ textStyle: TextStyle(color: Colors.black),
+ linkStyle: TextStyle(
+ color: Colors.blue,
+ decoration: TextDecoration.underline,
+ ),
+ onWebLinkTap: (String url) => print(url),
+ ),
+ SizedBox(height: 16),
+ AutolinkText(
+ "Autolink email me@codingale.dev",
+ textStyle: TextStyle(color: Colors.black),
+ linkStyle: TextStyle(
+ color: Colors.green,
+ decoration: TextDecoration.underline,
+ ),
+ onEmailTap: (String url) => print(url),
+ ),
+ SizedBox(height: 16),
+ AutolinkText(
+ "Autolink phone +50688884444",
+ textStyle: TextStyle(color: Colors.black),
+ linkStyle: TextStyle(
+ color: Colors.orange,
+ decoration: TextDecoration.underline,
+ ),
+ onPhoneTap: (String url) => print(url),
),
],
),
diff --git a/example/pubspec.lock b/example/pubspec.lock
index 18f401a..cf58199 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -7,42 +7,42 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
- version: "2.5.0-nullsafety.1"
+ version: "2.5.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.0-nullsafety.1"
+ version: "2.1.0"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.0-nullsafety.3"
+ version: "1.1.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
- version: "1.2.0-nullsafety.1"
+ version: "1.2.0"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.0-nullsafety.1"
+ version: "1.1.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
- version: "1.15.0-nullsafety.3"
+ version: "1.15.0"
cupertino_icons:
dependency: "direct main"
description:
@@ -56,7 +56,7 @@ packages:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
- version: "1.2.0-nullsafety.1"
+ version: "1.2.0"
flutter:
dependency: "direct main"
description: flutter
@@ -73,21 +73,21 @@ packages:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
- version: "0.12.10-nullsafety.1"
+ version: "0.12.10"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
- version: "1.3.0-nullsafety.3"
+ version: "1.3.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
- version: "1.8.0-nullsafety.1"
+ version: "1.8.0"
sky_engine:
dependency: transitive
description: flutter
@@ -99,63 +99,63 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
- version: "1.8.0-nullsafety.2"
+ version: "1.8.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
- version: "1.10.0-nullsafety.1"
+ version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.0-nullsafety.1"
+ version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.0-nullsafety.1"
+ version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
- version: "1.2.0-nullsafety.1"
+ version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
- version: "0.2.19-nullsafety.2"
+ version: "0.2.19"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
- version: "1.3.0-nullsafety.3"
+ version: "1.3.0"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.0-nullsafety.3"
+ version: "2.1.0"
widgetkit:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
- version: "0.0.1"
+ version: "0.2.0"
sdks:
- dart: ">=2.10.0-110 <2.11.0"
- flutter: ">=1.17.0 <2.0.0"
+ dart: ">=2.12.0 <3.0.0"
+ flutter: ">=1.17.0"
diff --git a/lib/material/password_textfield.dart b/lib/material/password_textfield.dart
index 6c7f8c0..1ea85c7 100644
--- a/lib/material/password_textfield.dart
+++ b/lib/material/password_textfield.dart
@@ -1,14 +1,14 @@
import 'package:flutter/material.dart';
class PasswordTextField extends StatefulWidget {
- final Key key;
- final TextEditingController controller;
- final InputDecoration decoration;
- final Function(String) validator;
- final bool enabled;
- final AutovalidateMode autovalidateMode;
+ final Key? key;
+ final TextEditingController? controller;
+ final InputDecoration? decoration;
+ final String? Function(String?)? validator;
+ final bool? enabled;
+ final AutovalidateMode? autovalidateMode;
final bool autocorrect;
- final void Function(String) onChanged;
+ final void Function(String)? onChanged;
PasswordTextField({
this.key,
@@ -26,7 +26,7 @@ class PasswordTextField extends StatefulWidget {
}
class _PasswordTextFieldState extends State {
- bool _passwordHidden;
+ late bool _passwordHidden;
@override
void initState() {
@@ -67,7 +67,7 @@ class _PasswordTextFieldState extends State {
),
);
} else {
- decoration = widget.decoration.copyWith(
+ decoration = widget.decoration!.copyWith(
suffixIcon: IconButton(
icon: Icon(
_passwordHidden ? Icons.visibility : Icons.visibility_off,
diff --git a/lib/shared/autolink_text.dart b/lib/shared/autolink_text.dart
new file mode 100644
index 0000000..31cc589
--- /dev/null
+++ b/lib/shared/autolink_text.dart
@@ -0,0 +1,183 @@
+import 'package:flutter/widgets.dart';
+import 'package:flutter/gestures.dart';
+
+typedef VoidArgumentedCallback = void Function(String);
+
+RegExp _phoneRegExp = RegExp(r"[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s\./0-9]*");
+RegExp _emailRegExp = RegExp(
+ r"[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*");
+RegExp _linksRegExp = RegExp(
+ r"(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})");
+
+class AutolinkText extends StatelessWidget {
+ final String text;
+ final VoidArgumentedCallback? onWebLinkTap, onPhoneTap, onEmailTap;
+ final TextStyle? textStyle, linkStyle;
+ final bool humanize;
+
+ AutolinkText(
+ this.text, {
+ Key? key,
+ this.textStyle,
+ this.linkStyle,
+ this.onWebLinkTap,
+ this.onEmailTap,
+ this.onPhoneTap,
+ this.humanize = false,
+ }) : super(key: key);
+
+ void _onLinkTap(String link, _MatchType type) {
+ switch (type) {
+ case _MatchType.phone:
+ onPhoneTap!(link);
+ break;
+ case _MatchType.email:
+ onEmailTap!(link);
+ break;
+ case _MatchType.link:
+ onWebLinkTap!(link);
+ break;
+ case _MatchType.none:
+ break;
+ }
+ }
+
+ String _getTypes() {
+ String types = '';
+ if (onWebLinkTap != null) types += 'web';
+ if (onEmailTap != null) types += 'email';
+ if (onPhoneTap != null) types += 'phone';
+ return types;
+ }
+
+ List _buildTextSpans() {
+ return _findMatches(text, _getTypes(), humanize).map((match) {
+ if (match.type == _MatchType.none)
+ return TextSpan(
+ text: match.text,
+ style: textStyle,
+ );
+ final recognizer = TapGestureRecognizer();
+ recognizer.onTap = () => _onLinkTap(match.text, match.type);
+ return TextSpan(
+ text: match.text,
+ style: linkStyle,
+ recognizer: recognizer,
+ );
+ }).toList();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return RichText(
+ text: TextSpan(children: _buildTextSpans()),
+ );
+ }
+}
+
+enum _MatchType { phone, email, link, none }
+
+class _MatchedString {
+ final _MatchType type;
+ final String text;
+
+ _MatchedString({
+ required this.text,
+ required this.type,
+ });
+
+ @override
+ String toString() {
+ return text;
+ }
+}
+
+List<_MatchedString> _findMatches(String text, String types, bool humanize) {
+ List<_MatchedString> matched = [
+ _MatchedString(type: _MatchType.none, text: text)
+ ];
+
+ if (types.contains('phone')) {
+ List<_MatchedString> newMatched = [];
+ for (_MatchedString matchedBefore in matched) {
+ if (matchedBefore.type == _MatchType.none) {
+ newMatched
+ .addAll(_findLinksByType(matchedBefore.text, _MatchType.phone));
+ } else
+ newMatched.add(matchedBefore);
+ }
+ matched = newMatched;
+ }
+
+ if (types.contains('email')) {
+ List<_MatchedString> newMatched = [];
+ for (_MatchedString matchedBefore in matched) {
+ if (matchedBefore.type == _MatchType.none) {
+ newMatched
+ .addAll(_findLinksByType(matchedBefore.text, _MatchType.email));
+ } else
+ newMatched.add(matchedBefore);
+ }
+ matched = newMatched;
+ }
+
+ if (types.contains('web')) {
+ List<_MatchedString> newMatched = [];
+ for (_MatchedString matchedBefore in matched) {
+ if (matchedBefore.type == _MatchType.none) {
+ final webMatches =
+ _findLinksByType(matchedBefore.text, _MatchType.link);
+ for (_MatchedString webMatch in webMatches) {
+ if (webMatch.type == _MatchType.link &&
+ (webMatch.text.startsWith('http://') ||
+ webMatch.text.startsWith('https://')) &&
+ humanize) {
+ newMatched.add(_MatchedString(
+ text: webMatch.text
+ .substring(webMatch.text.startsWith('http://') ? 7 : 8),
+ type: _MatchType.link));
+ } else {
+ newMatched.add(webMatch);
+ }
+ }
+ } else
+ newMatched.add(matchedBefore);
+ }
+ matched = newMatched;
+ }
+
+ return matched;
+}
+
+RegExp? _getRegExpByType(_MatchType type) {
+ switch (type) {
+ case _MatchType.phone:
+ return _phoneRegExp;
+ case _MatchType.email:
+ return _emailRegExp;
+ case _MatchType.link:
+ return _linksRegExp;
+ default:
+ return null;
+ }
+}
+
+List<_MatchedString> _findLinksByType(String text, _MatchType type) {
+ List<_MatchedString> output = [];
+ final matches = _getRegExpByType(type)!.allMatches(text);
+ int endOfMatch = 0;
+ for (Match match in matches) {
+ final before = text.substring(endOfMatch, match.start);
+ if (before.isNotEmpty)
+ output.add(_MatchedString(text: before, type: _MatchType.none));
+ final lastCharacterIndex =
+ text[match.end - 1] == ' ' ? match.end - 1 : match.end;
+ output.add(_MatchedString(
+ type: type, text: text.substring(match.start, lastCharacterIndex)));
+ endOfMatch = lastCharacterIndex;
+ }
+ final endOfText = text.substring(endOfMatch);
+ if (endOfText.isNotEmpty)
+ output.add(_MatchedString(text: endOfText, type: _MatchType.none));
+ return output;
+}
diff --git a/lib/shared/hide_keyboard_on_touch_outside.dart b/lib/shared/hide_keyboard_on_touch_outside.dart
index ba226c9..53f7f00 100644
--- a/lib/shared/hide_keyboard_on_touch_outside.dart
+++ b/lib/shared/hide_keyboard_on_touch_outside.dart
@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
class HideKeyboardOnTouchOutside extends StatelessWidget {
final Widget child;
- HideKeyboardOnTouchOutside({@required this.child});
+ HideKeyboardOnTouchOutside({required this.child});
@override
Widget build(BuildContext context) {
diff --git a/lib/shared/widgetkit.dart b/lib/shared/widgetkit.dart
index 715116a..fe6cd35 100644
--- a/lib/shared/widgetkit.dart
+++ b/lib/shared/widgetkit.dart
@@ -1 +1,2 @@
export 'hide_keyboard_on_touch_outside.dart';
+export 'autolink_text.dart';
diff --git a/pubspec.lock b/pubspec.lock
index 58937f9..b55f540 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -7,49 +7,49 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
- version: "2.5.0-nullsafety.1"
+ version: "2.5.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.0-nullsafety.1"
+ version: "2.1.0"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.0-nullsafety.3"
+ version: "1.1.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
- version: "1.2.0-nullsafety.1"
+ version: "1.2.0"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.0-nullsafety.1"
+ version: "1.1.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
- version: "1.15.0-nullsafety.3"
+ version: "1.15.0"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
- version: "1.2.0-nullsafety.1"
+ version: "1.2.0"
flutter:
dependency: "direct main"
description: flutter
@@ -66,21 +66,21 @@ packages:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
- version: "0.12.10-nullsafety.1"
+ version: "0.12.10"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
- version: "1.3.0-nullsafety.3"
+ version: "1.3.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
- version: "1.8.0-nullsafety.1"
+ version: "1.8.0"
sky_engine:
dependency: transitive
description: flutter
@@ -92,56 +92,56 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
- version: "1.8.0-nullsafety.2"
+ version: "1.8.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
- version: "1.10.0-nullsafety.1"
+ version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.0-nullsafety.1"
+ version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.0-nullsafety.1"
+ version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
- version: "1.2.0-nullsafety.1"
+ version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
- version: "0.2.19-nullsafety.2"
+ version: "0.2.19"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
- version: "1.3.0-nullsafety.3"
+ version: "1.3.0"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.0-nullsafety.3"
+ version: "2.1.0"
sdks:
- dart: ">=2.10.0-110 <2.11.0"
- flutter: ">=1.17.0 <2.0.0"
+ dart: ">=2.12.0 <3.0.0"
+ flutter: ">=1.17.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index 4aa49fa..1537c4a 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,11 +1,11 @@
name: widgetkit
description: A kit of widgets that are (almost) always needed in the different apps.
-version: 0.0.1
+version: 0.2.0
author:
homepage:
environment:
- sdk: ">=2.7.0 <3.0.0"
+ sdk: '>=2.12.0 <3.0.0'
flutter: ">=1.17.0 <2.0.0"
dependencies:
@@ -16,4 +16,4 @@ dev_dependencies:
flutter_test:
sdk: flutter
-flutter:
\ No newline at end of file
+flutter: