Merge pull request 'Allow Tor Caching + Our Own Linkify' (#316) from torcache into trunk
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
Reviewed-on: #316 Reviewed-by: erinn <erinn@openprivacy.ca>
This commit is contained in:
commit
e99fc45a28
|
@ -1 +1 @@
|
||||||
2022-01-17-17-19-v1.5.4-5-g4cf95d6
|
2022-01-18-16-33-v1.5.4-8-g2aea700
|
|
@ -1 +1 @@
|
||||||
2022-01-17-22-19-v1.5.4-5-g4cf95d6
|
2022-01-18-21-29-v1.5.4-8-g2aea700
|
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"@@locale": "de",
|
"@@locale": "de",
|
||||||
"@@last_modified": "2022-01-17T21:20:54+01:00",
|
"@@last_modified": "2022-01-18T00:38:14+01:00",
|
||||||
|
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
|
||||||
|
"torSettingsEnableCache": "Cache Tor Consensus",
|
||||||
"labelTorNetwork": "Tor Network",
|
"labelTorNetwork": "Tor Network",
|
||||||
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
||||||
"labelACNCircuitInfo": "ACN Circuit Info",
|
"labelACNCircuitInfo": "ACN Circuit Info",
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"@@locale": "en",
|
"@@locale": "en",
|
||||||
"@@last_modified": "2022-01-17T21:20:54+01:00",
|
"@@last_modified": "2022-01-18T00:38:14+01:00",
|
||||||
|
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
|
||||||
|
"torSettingsEnableCache": "Cache Tor Consensus",
|
||||||
"labelTorNetwork": "Tor Network",
|
"labelTorNetwork": "Tor Network",
|
||||||
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
||||||
"labelACNCircuitInfo": "ACN Circuit Info",
|
"labelACNCircuitInfo": "ACN Circuit Info",
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"@@locale": "es",
|
"@@locale": "es",
|
||||||
"@@last_modified": "2022-01-17T21:20:54+01:00",
|
"@@last_modified": "2022-01-18T00:38:14+01:00",
|
||||||
|
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
|
||||||
|
"torSettingsEnableCache": "Cache Tor Consensus",
|
||||||
"labelTorNetwork": "Tor Network",
|
"labelTorNetwork": "Tor Network",
|
||||||
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
||||||
"labelACNCircuitInfo": "ACN Circuit Info",
|
"labelACNCircuitInfo": "ACN Circuit Info",
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"@@locale": "fr",
|
"@@locale": "fr",
|
||||||
"@@last_modified": "2022-01-17T21:20:54+01:00",
|
"@@last_modified": "2022-01-18T00:38:14+01:00",
|
||||||
|
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
|
||||||
|
"torSettingsEnableCache": "Cache Tor Consensus",
|
||||||
"labelTorNetwork": "Tor Network",
|
"labelTorNetwork": "Tor Network",
|
||||||
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
||||||
"labelACNCircuitInfo": "ACN Circuit Info",
|
"labelACNCircuitInfo": "ACN Circuit Info",
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"@@locale": "it",
|
"@@locale": "it",
|
||||||
"@@last_modified": "2022-01-17T21:20:54+01:00",
|
"@@last_modified": "2022-01-18T00:38:14+01:00",
|
||||||
|
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
|
||||||
|
"torSettingsEnableCache": "Cache Tor Consensus",
|
||||||
"labelTorNetwork": "Tor Network",
|
"labelTorNetwork": "Tor Network",
|
||||||
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
||||||
"labelACNCircuitInfo": "ACN Circuit Info",
|
"labelACNCircuitInfo": "ACN Circuit Info",
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"@@locale": "pl",
|
"@@locale": "pl",
|
||||||
"@@last_modified": "2022-01-17T21:20:54+01:00",
|
"@@last_modified": "2022-01-18T00:38:14+01:00",
|
||||||
|
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
|
||||||
|
"torSettingsEnableCache": "Cache Tor Consensus",
|
||||||
"labelTorNetwork": "Tor Network",
|
"labelTorNetwork": "Tor Network",
|
||||||
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
||||||
"labelACNCircuitInfo": "ACN Circuit Info",
|
"labelACNCircuitInfo": "ACN Circuit Info",
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"@@locale": "pt",
|
"@@locale": "pt",
|
||||||
"@@last_modified": "2022-01-17T21:20:54+01:00",
|
"@@last_modified": "2022-01-18T00:38:14+01:00",
|
||||||
|
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
|
||||||
|
"torSettingsEnableCache": "Cache Tor Consensus",
|
||||||
"labelTorNetwork": "Tor Network",
|
"labelTorNetwork": "Tor Network",
|
||||||
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
||||||
"labelACNCircuitInfo": "ACN Circuit Info",
|
"labelACNCircuitInfo": "ACN Circuit Info",
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"@@locale": "ru",
|
"@@locale": "ru",
|
||||||
"@@last_modified": "2022-01-17T21:20:54+01:00",
|
"@@last_modified": "2022-01-18T00:38:14+01:00",
|
||||||
|
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
|
||||||
|
"torSettingsEnableCache": "Cache Tor Consensus",
|
||||||
"labelTorNetwork": "Tor Network",
|
"labelTorNetwork": "Tor Network",
|
||||||
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
||||||
"labelACNCircuitInfo": "ACN Circuit Info",
|
"labelACNCircuitInfo": "ACN Circuit Info",
|
||||||
|
|
|
@ -116,4 +116,26 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''');
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''');
|
||||||
|
|
||||||
yield LicenseEntryWithLineBreaks(["flaticons"], "Icons made by Freepik (https://www.freepik.com) from Flaticon (www.flaticon.com)");
|
yield LicenseEntryWithLineBreaks(["flaticons"], "Icons made by Freepik (https://www.freepik.com) from Flaticon (www.flaticon.com)");
|
||||||
|
|
||||||
|
yield LicenseEntryWithLineBreaks(["flutter_linkify", "linkify"], '''MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019/2020 Charles-William Crete
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.''');
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,10 @@ class Settings extends ChangeNotifier {
|
||||||
int _socksPort = -1;
|
int _socksPort = -1;
|
||||||
int _controlPort = -1;
|
int _controlPort = -1;
|
||||||
String _customTorAuth = "";
|
String _customTorAuth = "";
|
||||||
|
bool _useTorCache = false;
|
||||||
|
String _torCacheDir = "";
|
||||||
|
|
||||||
|
String get torCacheDir => _torCacheDir;
|
||||||
|
|
||||||
void setTheme(String themeId, String mode) {
|
void setTheme(String themeId, String mode) {
|
||||||
theme = getTheme(themeId, mode);
|
theme = getTheme(themeId, mode);
|
||||||
|
@ -99,6 +103,8 @@ class Settings extends ChangeNotifier {
|
||||||
_customTorConfig = settings["CustomTorrc"] ?? "";
|
_customTorConfig = settings["CustomTorrc"] ?? "";
|
||||||
_socksPort = settings["CustomSocksPort"] ?? -1;
|
_socksPort = settings["CustomSocksPort"] ?? -1;
|
||||||
_controlPort = settings["CustomControlPort"] ?? -1;
|
_controlPort = settings["CustomControlPort"] ?? -1;
|
||||||
|
_useTorCache = settings["UseTorCache"] ?? false;
|
||||||
|
_torCacheDir = settings["TorCacheDir"] ?? "";
|
||||||
|
|
||||||
// Push the experimental settings to Consumers of Settings
|
// Push the experimental settings to Consumers of Settings
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -252,6 +258,12 @@ class Settings extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get useTorCache => _useTorCache;
|
||||||
|
set useTorCache(bool useTorCache) {
|
||||||
|
_useTorCache = useTorCache;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
// Settings / Gettings for setting the custom tor config..
|
// Settings / Gettings for setting the custom tor config..
|
||||||
String get torConfig => _customTorConfig;
|
String get torConfig => _customTorConfig;
|
||||||
set torConfig(String torConfig) {
|
set torConfig(String torConfig) {
|
||||||
|
@ -304,6 +316,8 @@ class Settings extends ChangeNotifier {
|
||||||
"CustomSocksPort": _socksPort,
|
"CustomSocksPort": _socksPort,
|
||||||
"CustomControlPort": _controlPort,
|
"CustomControlPort": _controlPort,
|
||||||
"CustomAuth": _customTorAuth,
|
"CustomAuth": _customTorAuth,
|
||||||
|
"UseTorCache": _useTorCache,
|
||||||
|
"TorCacheDir": _torCacheDir
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,399 @@
|
||||||
|
// Code Originally taken from https://github.com/Cretezy/flutter_linkify/blob/201e147e0b07b7ca5c543da8167d712d81760753/lib/flutter_linkify.dart
|
||||||
|
//
|
||||||
|
// Now uses local `linkify`
|
||||||
|
//
|
||||||
|
// Original License for this code:
|
||||||
|
// MIT License
|
||||||
|
// Copyright (c) 2020 Charles-William Crete
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
|
import 'linkify.dart';
|
||||||
|
|
||||||
|
export 'linkify.dart' show LinkifyElement, LinkifyOptions, LinkableElement, TextElement, Linkifier, UrlElement, UrlLinkifier;
|
||||||
|
|
||||||
|
/// Callback clicked link
|
||||||
|
typedef LinkCallback = void Function(LinkableElement link);
|
||||||
|
|
||||||
|
/// Turns URLs into links
|
||||||
|
class Linkify extends StatelessWidget {
|
||||||
|
/// Text to be linkified
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
/// Linkifiers to be used for linkify
|
||||||
|
final List<Linkifier> linkifiers;
|
||||||
|
|
||||||
|
/// Callback for tapping a link
|
||||||
|
final LinkCallback? onOpen;
|
||||||
|
|
||||||
|
/// linkify's options.
|
||||||
|
final LinkifyOptions options;
|
||||||
|
|
||||||
|
// TextSpan
|
||||||
|
|
||||||
|
/// Style for non-link text
|
||||||
|
final TextStyle? style;
|
||||||
|
|
||||||
|
/// Style of link text
|
||||||
|
final TextStyle? linkStyle;
|
||||||
|
|
||||||
|
// Text.rich
|
||||||
|
|
||||||
|
/// How the text should be aligned horizontally.
|
||||||
|
final TextAlign textAlign;
|
||||||
|
|
||||||
|
/// Text direction of the text
|
||||||
|
final TextDirection? textDirection;
|
||||||
|
|
||||||
|
/// The maximum number of lines for the text to span, wrapping if necessary
|
||||||
|
final int? maxLines;
|
||||||
|
|
||||||
|
/// How visual overflow should be handled.
|
||||||
|
final TextOverflow overflow;
|
||||||
|
|
||||||
|
/// The number of font pixels for each logical pixel
|
||||||
|
final double textScaleFactor;
|
||||||
|
|
||||||
|
/// Whether the text should break at soft line breaks.
|
||||||
|
final bool softWrap;
|
||||||
|
|
||||||
|
/// The strut style used for the vertical layout
|
||||||
|
final StrutStyle? strutStyle;
|
||||||
|
|
||||||
|
/// Used to select a font when the same Unicode character can
|
||||||
|
/// be rendered differently, depending on the locale
|
||||||
|
final Locale? locale;
|
||||||
|
|
||||||
|
/// Defines how to measure the width of the rendered text.
|
||||||
|
final TextWidthBasis textWidthBasis;
|
||||||
|
|
||||||
|
/// Defines how the paragraph will apply TextStyle.height to the ascent of the first line and descent of the last line.
|
||||||
|
final TextHeightBehavior? textHeightBehavior;
|
||||||
|
|
||||||
|
const Linkify({
|
||||||
|
Key? key,
|
||||||
|
required this.text,
|
||||||
|
this.linkifiers = defaultLinkifiers,
|
||||||
|
this.onOpen,
|
||||||
|
this.options = const LinkifyOptions(),
|
||||||
|
// TextSpan
|
||||||
|
this.style,
|
||||||
|
this.linkStyle,
|
||||||
|
// RichText
|
||||||
|
this.textAlign = TextAlign.start,
|
||||||
|
this.textDirection,
|
||||||
|
this.maxLines,
|
||||||
|
this.overflow = TextOverflow.clip,
|
||||||
|
this.textScaleFactor = 1.0,
|
||||||
|
this.softWrap = true,
|
||||||
|
this.strutStyle,
|
||||||
|
this.locale,
|
||||||
|
this.textWidthBasis = TextWidthBasis.parent,
|
||||||
|
this.textHeightBehavior,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final elements = linkify(
|
||||||
|
text,
|
||||||
|
options: options,
|
||||||
|
linkifiers: linkifiers,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Text.rich(
|
||||||
|
buildTextSpan(
|
||||||
|
elements,
|
||||||
|
style: Theme.of(context).textTheme.bodyText2?.merge(style),
|
||||||
|
onOpen: onOpen,
|
||||||
|
useMouseRegion: true,
|
||||||
|
linkStyle: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyText2
|
||||||
|
?.merge(style)
|
||||||
|
.copyWith(
|
||||||
|
color: Colors.blueAccent,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
)
|
||||||
|
.merge(linkStyle),
|
||||||
|
),
|
||||||
|
textAlign: textAlign,
|
||||||
|
textDirection: textDirection,
|
||||||
|
maxLines: maxLines,
|
||||||
|
overflow: overflow,
|
||||||
|
textScaleFactor: textScaleFactor,
|
||||||
|
softWrap: softWrap,
|
||||||
|
strutStyle: strutStyle,
|
||||||
|
locale: locale,
|
||||||
|
textWidthBasis: textWidthBasis,
|
||||||
|
textHeightBehavior: textHeightBehavior,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turns URLs into links
|
||||||
|
class SelectableLinkify extends StatelessWidget {
|
||||||
|
/// Text to be linkified
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
/// The number of font pixels for each logical pixel
|
||||||
|
final textScaleFactor;
|
||||||
|
|
||||||
|
/// Linkifiers to be used for linkify
|
||||||
|
final List<Linkifier> linkifiers;
|
||||||
|
|
||||||
|
/// Callback for tapping a link
|
||||||
|
final LinkCallback? onOpen;
|
||||||
|
|
||||||
|
/// linkify's options.
|
||||||
|
final LinkifyOptions options;
|
||||||
|
|
||||||
|
// TextSpan
|
||||||
|
|
||||||
|
/// Style for non-link text
|
||||||
|
final TextStyle? style;
|
||||||
|
|
||||||
|
/// Style of link text
|
||||||
|
final TextStyle? linkStyle;
|
||||||
|
|
||||||
|
// Text.rich
|
||||||
|
|
||||||
|
/// How the text should be aligned horizontally.
|
||||||
|
final TextAlign? textAlign;
|
||||||
|
|
||||||
|
/// Text direction of the text
|
||||||
|
final TextDirection? textDirection;
|
||||||
|
|
||||||
|
/// The minimum number of lines to occupy when the content spans fewer lines.
|
||||||
|
final int? minLines;
|
||||||
|
|
||||||
|
/// The maximum number of lines for the text to span, wrapping if necessary
|
||||||
|
final int? maxLines;
|
||||||
|
|
||||||
|
/// The strut style used for the vertical layout
|
||||||
|
final StrutStyle? strutStyle;
|
||||||
|
|
||||||
|
/// Defines how to measure the width of the rendered text.
|
||||||
|
final TextWidthBasis? textWidthBasis;
|
||||||
|
|
||||||
|
// SelectableText.rich
|
||||||
|
|
||||||
|
/// Defines the focus for this widget.
|
||||||
|
final FocusNode? focusNode;
|
||||||
|
|
||||||
|
/// Whether to show cursor
|
||||||
|
final bool showCursor;
|
||||||
|
|
||||||
|
/// Whether this text field should focus itself if nothing else is already focused.
|
||||||
|
final bool autofocus;
|
||||||
|
|
||||||
|
/// Configuration of toolbar options
|
||||||
|
final ToolbarOptions? toolbarOptions;
|
||||||
|
|
||||||
|
/// How thick the cursor will be
|
||||||
|
final double cursorWidth;
|
||||||
|
|
||||||
|
/// How rounded the corners of the cursor should be
|
||||||
|
final Radius? cursorRadius;
|
||||||
|
|
||||||
|
/// The color to use when painting the cursor
|
||||||
|
final Color? cursorColor;
|
||||||
|
|
||||||
|
/// Determines the way that drag start behavior is handled
|
||||||
|
final DragStartBehavior dragStartBehavior;
|
||||||
|
|
||||||
|
/// If true, then long-pressing this TextField will select text and show the cut/copy/paste menu,
|
||||||
|
/// and tapping will move the text caret
|
||||||
|
final bool enableInteractiveSelection;
|
||||||
|
|
||||||
|
/// Called when the user taps on this selectable text (not link)
|
||||||
|
final GestureTapCallback? onTap;
|
||||||
|
|
||||||
|
final ScrollPhysics? scrollPhysics;
|
||||||
|
|
||||||
|
/// Defines how the paragraph will apply TextStyle.height to the ascent of the first line and descent of the last line.
|
||||||
|
final TextHeightBehavior? textHeightBehavior;
|
||||||
|
|
||||||
|
/// How tall the cursor will be.
|
||||||
|
final double? cursorHeight;
|
||||||
|
|
||||||
|
/// Optional delegate for building the text selection handles and toolbar.
|
||||||
|
final TextSelectionControls? selectionControls;
|
||||||
|
|
||||||
|
/// Called when the user changes the selection of text (including the cursor location).
|
||||||
|
final SelectionChangedCallback? onSelectionChanged;
|
||||||
|
|
||||||
|
const SelectableLinkify({
|
||||||
|
Key? key,
|
||||||
|
required this.text,
|
||||||
|
this.linkifiers = defaultLinkifiers,
|
||||||
|
this.onOpen,
|
||||||
|
this.options = const LinkifyOptions(),
|
||||||
|
// TextSpan
|
||||||
|
this.style,
|
||||||
|
this.linkStyle,
|
||||||
|
// RichText
|
||||||
|
this.textAlign,
|
||||||
|
this.textDirection,
|
||||||
|
this.minLines,
|
||||||
|
this.maxLines,
|
||||||
|
// SelectableText
|
||||||
|
this.focusNode,
|
||||||
|
this.textScaleFactor = 1.0,
|
||||||
|
this.strutStyle,
|
||||||
|
this.showCursor = false,
|
||||||
|
this.autofocus = false,
|
||||||
|
this.toolbarOptions,
|
||||||
|
this.cursorWidth = 2.0,
|
||||||
|
this.cursorRadius,
|
||||||
|
this.cursorColor,
|
||||||
|
this.dragStartBehavior = DragStartBehavior.start,
|
||||||
|
this.enableInteractiveSelection = true,
|
||||||
|
this.onTap,
|
||||||
|
this.scrollPhysics,
|
||||||
|
this.textWidthBasis,
|
||||||
|
this.textHeightBehavior,
|
||||||
|
this.cursorHeight,
|
||||||
|
this.selectionControls,
|
||||||
|
this.onSelectionChanged,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final elements = linkify(
|
||||||
|
text,
|
||||||
|
options: options,
|
||||||
|
linkifiers: linkifiers,
|
||||||
|
);
|
||||||
|
|
||||||
|
return SelectableText.rich(
|
||||||
|
buildTextSpan(
|
||||||
|
elements,
|
||||||
|
style: Theme.of(context).textTheme.bodyText2?.merge(style),
|
||||||
|
onOpen: onOpen,
|
||||||
|
linkStyle: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyText2
|
||||||
|
?.merge(style)
|
||||||
|
.copyWith(
|
||||||
|
color: Colors.blueAccent,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
)
|
||||||
|
.merge(linkStyle),
|
||||||
|
),
|
||||||
|
textAlign: textAlign,
|
||||||
|
textDirection: textDirection,
|
||||||
|
minLines: minLines,
|
||||||
|
maxLines: maxLines,
|
||||||
|
focusNode: focusNode,
|
||||||
|
strutStyle: strutStyle,
|
||||||
|
showCursor: showCursor,
|
||||||
|
textScaleFactor: textScaleFactor,
|
||||||
|
autofocus: autofocus,
|
||||||
|
toolbarOptions: toolbarOptions,
|
||||||
|
cursorWidth: cursorWidth,
|
||||||
|
cursorRadius: cursorRadius,
|
||||||
|
cursorColor: cursorColor,
|
||||||
|
dragStartBehavior: dragStartBehavior,
|
||||||
|
enableInteractiveSelection: enableInteractiveSelection,
|
||||||
|
onTap: onTap,
|
||||||
|
scrollPhysics: scrollPhysics,
|
||||||
|
textWidthBasis: textWidthBasis,
|
||||||
|
textHeightBehavior: textHeightBehavior,
|
||||||
|
cursorHeight: cursorHeight,
|
||||||
|
selectionControls: selectionControls,
|
||||||
|
onSelectionChanged: onSelectionChanged,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LinkableSpan extends WidgetSpan {
|
||||||
|
LinkableSpan({
|
||||||
|
required MouseCursor mouseCursor,
|
||||||
|
required InlineSpan inlineSpan,
|
||||||
|
}) : super(
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: mouseCursor,
|
||||||
|
child: Text.rich(
|
||||||
|
inlineSpan,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Raw TextSpan builder for more control on the RichText
|
||||||
|
TextSpan buildTextSpan(
|
||||||
|
List<LinkifyElement> elements, {
|
||||||
|
TextStyle? style,
|
||||||
|
TextStyle? linkStyle,
|
||||||
|
LinkCallback? onOpen,
|
||||||
|
bool useMouseRegion = false,
|
||||||
|
}) {
|
||||||
|
return TextSpan(
|
||||||
|
children: elements.map<InlineSpan>(
|
||||||
|
(element) {
|
||||||
|
if (element is LinkableElement) {
|
||||||
|
if (useMouseRegion) {
|
||||||
|
return TooltipSpan(
|
||||||
|
message: element.url,
|
||||||
|
inlineSpan: LinkableSpan(
|
||||||
|
mouseCursor: SystemMouseCursors.click,
|
||||||
|
inlineSpan: TextSpan(
|
||||||
|
text: element.text,
|
||||||
|
style: linkStyle,
|
||||||
|
recognizer: onOpen != null ? (TapGestureRecognizer()..onTap = () => onOpen(element)) : null,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return TooltipSpan(
|
||||||
|
message: element.url,
|
||||||
|
inlineSpan: TextSpan(
|
||||||
|
text: element.text,
|
||||||
|
style: linkStyle,
|
||||||
|
recognizer: onOpen != null ? (TapGestureRecognizer()..onTap = () => onOpen(element)) : null,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return TextSpan(
|
||||||
|
text: element.text,
|
||||||
|
style: style,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show a tooltip over an inlined element in a Rich Text widget.
|
||||||
|
class TooltipSpan extends WidgetSpan {
|
||||||
|
TooltipSpan({
|
||||||
|
required String message,
|
||||||
|
required InlineSpan inlineSpan,
|
||||||
|
}) : super(
|
||||||
|
child: Tooltip(
|
||||||
|
message: message,
|
||||||
|
child: Text.rich(
|
||||||
|
inlineSpan,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
// Originally from linkify https://github.com/Cretezy/linkify/blob/ba536fa85e7e3a16e580f153616f399458986183/lib/linkify.dart
|
||||||
|
// Removed options `removeWWW` and `humanize`
|
||||||
|
//
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2019 Charles-William Crete
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
import 'uri.dart';
|
||||||
|
export 'uri.dart' show UrlLinkifier, UrlElement;
|
||||||
|
|
||||||
|
abstract class LinkifyElement {
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
LinkifyElement(this.text);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(other) => equals(other);
|
||||||
|
|
||||||
|
bool equals(other) => other is LinkifyElement && other.text == text;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LinkableElement extends LinkifyElement {
|
||||||
|
final String url;
|
||||||
|
|
||||||
|
LinkableElement(String? text, this.url) : super(text ?? url);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(other) => equals(other);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool equals(other) => other is LinkableElement && super.equals(other) && other.url == url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents an element containing text
|
||||||
|
class TextElement extends LinkifyElement {
|
||||||
|
TextElement(String text) : super(text);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return "TextElement: '$text'";
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(other) => equals(other);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool equals(other) => other is TextElement && super.equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Linkifier {
|
||||||
|
const Linkifier();
|
||||||
|
|
||||||
|
List<LinkifyElement> parse(List<LinkifyElement> elements, LinkifyOptions options);
|
||||||
|
}
|
||||||
|
|
||||||
|
class LinkifyOptions {
|
||||||
|
/// Enables loose URL parsing (any string with "." is a URL).
|
||||||
|
final bool looseUrl;
|
||||||
|
|
||||||
|
/// When used with [looseUrl], default to `https` instead of `http`.
|
||||||
|
final bool defaultToHttps;
|
||||||
|
|
||||||
|
/// Excludes `.` at end of URLs.
|
||||||
|
final bool excludeLastPeriod;
|
||||||
|
|
||||||
|
const LinkifyOptions({
|
||||||
|
this.looseUrl = false,
|
||||||
|
this.defaultToHttps = false,
|
||||||
|
this.excludeLastPeriod = true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const _urlLinkifier = UrlLinkifier();
|
||||||
|
const defaultLinkifiers = [_urlLinkifier];
|
||||||
|
|
||||||
|
/// Turns [text] into a list of [LinkifyElement]
|
||||||
|
///
|
||||||
|
/// Use [humanize] to remove http/https from the start of the URL shown.
|
||||||
|
/// Will default to `false` (if `null`)
|
||||||
|
///
|
||||||
|
/// Uses [linkTypes] to enable some types of links (URL, email).
|
||||||
|
/// Will default to all (if `null`).
|
||||||
|
List<LinkifyElement> linkify(
|
||||||
|
String text, {
|
||||||
|
LinkifyOptions options = const LinkifyOptions(),
|
||||||
|
List<Linkifier> linkifiers = defaultLinkifiers,
|
||||||
|
}) {
|
||||||
|
var list = <LinkifyElement>[TextElement(text)];
|
||||||
|
|
||||||
|
if (text.isEmpty) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (linkifiers.isEmpty) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
linkifiers.forEach((linkifier) {
|
||||||
|
list = linkifier.parse(list, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
// Originally from linkify: https://github.com/Cretezy/linkify/blob/dfb3e43b0e56452bad584ddb0bf9b73d8db0589f/lib/src/url.dart
|
||||||
|
//
|
||||||
|
// Removed handling of `removeWWW` and `humanize`.
|
||||||
|
// Removed auto-appending of `http(s)://` to the readable url
|
||||||
|
//
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2019 Charles-William Crete
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
import 'linkify.dart';
|
||||||
|
|
||||||
|
final _urlRegex = RegExp(
|
||||||
|
r'^(.*?)((?:https?:\/\/|www\.)[^\s/$.?#].[^\s]*)',
|
||||||
|
caseSensitive: false,
|
||||||
|
dotAll: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
final _looseUrlRegex = RegExp(
|
||||||
|
r'^(.*?)((https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*))',
|
||||||
|
caseSensitive: false,
|
||||||
|
dotAll: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
final _protocolIdentifierRegex = RegExp(
|
||||||
|
r'^(https?:\/\/)',
|
||||||
|
caseSensitive: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
class UrlLinkifier extends Linkifier {
|
||||||
|
const UrlLinkifier();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<LinkifyElement> parse(elements, options) {
|
||||||
|
final list = <LinkifyElement>[];
|
||||||
|
|
||||||
|
elements.forEach((element) {
|
||||||
|
if (element is TextElement) {
|
||||||
|
var match = options.looseUrl ? _looseUrlRegex.firstMatch(element.text) : _urlRegex.firstMatch(element.text);
|
||||||
|
|
||||||
|
if (match == null) {
|
||||||
|
list.add(element);
|
||||||
|
} else {
|
||||||
|
final text = element.text.replaceFirst(match.group(0)!, '');
|
||||||
|
|
||||||
|
if (match.group(1)?.isNotEmpty == true) {
|
||||||
|
list.add(TextElement(match.group(1)!));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match.group(2)?.isNotEmpty == true) {
|
||||||
|
var originalUrl = match.group(2)!;
|
||||||
|
String? end;
|
||||||
|
|
||||||
|
if ((options.excludeLastPeriod) && originalUrl[originalUrl.length - 1] == ".") {
|
||||||
|
end = ".";
|
||||||
|
originalUrl = originalUrl.substring(0, originalUrl.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = originalUrl;
|
||||||
|
|
||||||
|
// If protocol has not been specified then append a protocol
|
||||||
|
// to the start of the URL so that it can be opened...
|
||||||
|
if (!url.startsWith("https://") && !url.startsWith("http://")) {
|
||||||
|
url = "https://" + url;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.add(UrlElement(url, originalUrl));
|
||||||
|
|
||||||
|
if (end != null) {
|
||||||
|
list.add(TextElement(end));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.isNotEmpty) {
|
||||||
|
list.addAll(parse([TextElement(text)], options));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
list.add(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents an element containing a link
|
||||||
|
class UrlElement extends LinkableElement {
|
||||||
|
UrlElement(String url, [String? text]) : super(text, url);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return "LinkElement: '$url' ($text)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(other) => equals(other);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool equals(other) => other is UrlElement && super.equals(other);
|
||||||
|
}
|
|
@ -76,6 +76,18 @@ class _TorStatusView extends State<TorStatusView> {
|
||||||
subtitle: SelectableText(torStatus.version),
|
subtitle: SelectableText(torStatus.version),
|
||||||
leading: Icon(CwtchIcons.info_24px, color: settings.current().mainTextColor),
|
leading: Icon(CwtchIcons.info_24px, color: settings.current().mainTextColor),
|
||||||
),
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text(AppLocalizations.of(context)!.torSettingsEnableCache),
|
||||||
|
subtitle: Text(AppLocalizations.of(context)!.torSettingsEnabledCacheDescription),
|
||||||
|
value: settings.useTorCache,
|
||||||
|
onChanged: (bool value) {
|
||||||
|
settings.useTorCache = value;
|
||||||
|
saveSettings(context);
|
||||||
|
},
|
||||||
|
activeTrackColor: settings.theme.defaultButtonColor,
|
||||||
|
inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
|
||||||
|
secondary: Icon(Icons.cached, color: settings.current().mainTextColor),
|
||||||
|
),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: Text(AppLocalizations.of(context)!.torSettingsEnabledAdvanced),
|
title: Text(AppLocalizations.of(context)!.torSettingsEnabledAdvanced),
|
||||||
subtitle: Text(AppLocalizations.of(context)!.torSettingsEnabledAdvancedDescription),
|
subtitle: Text(AppLocalizations.of(context)!.torSettingsEnabledAdvancedDescription),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:cwtch/models/message.dart';
|
import 'package:cwtch/models/message.dart';
|
||||||
|
import 'package:cwtch/third_party/linkify/flutter_linkify.dart';
|
||||||
import 'package:cwtch/widgets/malformedbubble.dart';
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
@ -8,7 +9,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../model.dart';
|
import '../model.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
import '../settings.dart';
|
import '../settings.dart';
|
||||||
|
@ -67,7 +67,7 @@ class MessageBubbleState extends State<MessageBubble> {
|
||||||
wdgMessage = SelectableLinkify(
|
wdgMessage = SelectableLinkify(
|
||||||
text: widget.content + '\u202F',
|
text: widget.content + '\u202F',
|
||||||
// TODO: onOpen breaks the "selectable" functionality. Maybe something to do with gesture handler?
|
// TODO: onOpen breaks the "selectable" functionality. Maybe something to do with gesture handler?
|
||||||
options: LinkifyOptions(humanize: false, removeWww: false, looseUrl: true, defaultToHttps: true),
|
options: LinkifyOptions(looseUrl: true, defaultToHttps: true),
|
||||||
linkifiers: [UrlLinkifier()],
|
linkifiers: [UrlLinkifier()],
|
||||||
onOpen: (link) {
|
onOpen: (link) {
|
||||||
_modalOpenLink(context, link);
|
_modalOpenLink(context, link);
|
||||||
|
|
14
pubspec.lock
14
pubspec.lock
|
@ -125,13 +125,6 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
flutter_linkify:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: flutter_linkify
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "5.0.2"
|
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -196,13 +189,6 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.3"
|
version: "0.6.3"
|
||||||
linkify:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: linkify
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "4.1.0"
|
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -43,7 +43,6 @@ dependencies:
|
||||||
scrollable_positioned_list: ^0.2.0-nullsafety.0
|
scrollable_positioned_list: ^0.2.0-nullsafety.0
|
||||||
file_picker: ^4.0.1
|
file_picker: ^4.0.1
|
||||||
file_picker_desktop: ^1.1.0
|
file_picker_desktop: ^1.1.0
|
||||||
flutter_linkify: ^5.0.2
|
|
||||||
url_launcher: ^6.0.12
|
url_launcher: ^6.0.12
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|
Loading…
Reference in New Issue