Allow Tor Caching + Our Own Linkify
continuous-integration/drone/pr Build is pending
Details
continuous-integration/drone/pr Build is pending
Details
This commit is contained in:
parent
9d10b9ea8d
commit
c6e64a3a5f
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"@@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",
|
||||
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
||||
"labelACNCircuitInfo": "ACN Circuit Info",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"@@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",
|
||||
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
||||
"labelACNCircuitInfo": "ACN Circuit Info",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"@@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",
|
||||
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
||||
"labelACNCircuitInfo": "ACN Circuit Info",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"@@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",
|
||||
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
||||
"labelACNCircuitInfo": "ACN Circuit Info",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"@@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",
|
||||
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
||||
"labelACNCircuitInfo": "ACN Circuit Info",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"@@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",
|
||||
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
||||
"labelACNCircuitInfo": "ACN Circuit Info",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"@@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",
|
||||
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
||||
"labelACNCircuitInfo": "ACN Circuit Info",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"@@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",
|
||||
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
|
||||
"labelACNCircuitInfo": "ACN Circuit Info",
|
||||
|
|
|
@ -45,6 +45,10 @@ class Settings extends ChangeNotifier {
|
|||
int _socksPort = -1;
|
||||
int _controlPort = -1;
|
||||
String _customTorAuth = "";
|
||||
bool _useTorCache = false;
|
||||
String _torCacheDir = "";
|
||||
|
||||
String get torCacheDir => _torCacheDir;
|
||||
|
||||
void setTheme(String themeId, String mode) {
|
||||
theme = getTheme(themeId, mode);
|
||||
|
@ -99,6 +103,8 @@ class Settings extends ChangeNotifier {
|
|||
_customTorConfig = settings["CustomTorrc"] ?? "";
|
||||
_socksPort = settings["CustomSocksPort"] ?? -1;
|
||||
_controlPort = settings["CustomControlPort"] ?? -1;
|
||||
_useTorCache = settings["UseTorCache"] ?? false;
|
||||
_torCacheDir = settings["TorCacheDir"] ?? "";
|
||||
|
||||
// Push the experimental settings to Consumers of Settings
|
||||
notifyListeners();
|
||||
|
@ -252,6 +258,12 @@ class Settings extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
bool get useTorCache => _useTorCache;
|
||||
set useTorCache(bool useTorCache) {
|
||||
_useTorCache = useTorCache;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Settings / Gettings for setting the custom tor config..
|
||||
String get torConfig => _customTorConfig;
|
||||
set torConfig(String torConfig) {
|
||||
|
@ -304,6 +316,10 @@ class Settings extends ChangeNotifier {
|
|||
"CustomSocksPort": _socksPort,
|
||||
"CustomControlPort": _controlPort,
|
||||
"CustomAuth": _customTorAuth,
|
||||
"UseTorCache": _useTorCache,
|
||||
"TorCacheDir": _torCacheDir
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,380 @@
|
|||
//
|
||||
// Code Originally taken from https://github.com/Cretezy/flutter_linkify/ and
|
||||
// subsequently modified...
|
||||
// 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';
|
||||
|
||||
|
||||
|
||||
/// 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 LinkableSpan(
|
||||
mouseCursor: SystemMouseCursors.click,
|
||||
inlineSpan: TextSpan(
|
||||
text: element.text,
|
||||
style: linkStyle,
|
||||
recognizer: onOpen != null ? (TapGestureRecognizer()..onTap = () => onOpen(element)) : null,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return TextSpan(
|
||||
text: element.text,
|
||||
style: linkStyle,
|
||||
recognizer: onOpen != null ? (TapGestureRecognizer()..onTap = () => onOpen(element)) : null,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return TextSpan(
|
||||
text: element.text,
|
||||
style: style,
|
||||
);
|
||||
}
|
||||
},
|
||||
).toList(),
|
||||
);
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
// Originally from linkify
|
||||
// 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 'package:cwtch/third_party/linkify/uri.dart';
|
||||
|
||||
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 {
|
||||
/// Removes http/https from shown URLs.
|
||||
final bool humanize;
|
||||
|
||||
/// Removes www. from shown URLs.
|
||||
final bool removeWww;
|
||||
|
||||
/// 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.humanize = true,
|
||||
this.removeWww = false,
|
||||
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,127 @@
|
|||
// Originally from linkify
|
||||
// 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 'package:cwtch/third_party/linkify/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;
|
||||
|
||||
// We do not, ever, change the original text of a message.
|
||||
if (options.defaultToHttps) {
|
||||
url = url.replaceFirst('http://', 'https://');
|
||||
}
|
||||
|
||||
// These options are intended for the human-readable portion of
|
||||
// the URI
|
||||
if (options.humanize) {
|
||||
originalUrl = originalUrl.replaceFirst(RegExp(r'https?://'), '');
|
||||
}
|
||||
|
||||
if (options.removeWww) {
|
||||
originalUrl = originalUrl.replaceFirst(RegExp(r'www\.'), '');
|
||||
}
|
||||
|
||||
list.add(UrlElement(originalUrl, url));
|
||||
|
||||
|
||||
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),
|
||||
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(
|
||||
title: Text(AppLocalizations.of(context)!.torSettingsEnabledAdvanced),
|
||||
subtitle: Text(AppLocalizations.of(context)!.torSettingsEnabledAdvancedDescription),
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:cwtch/models/message.dart';
|
||||
import 'package:cwtch/third_party/linkify/flutter_linkify.dart';
|
||||
import 'package:cwtch/third_party/linkify/linkify.dart';
|
||||
import 'package:cwtch/third_party/linkify/uri.dart';
|
||||
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
@ -8,7 +11,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import '../model.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../settings.dart';
|
||||
|
|
14
pubspec.lock
14
pubspec.lock
|
@ -125,13 +125,6 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
|
@ -196,13 +189,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.3"
|
||||
linkify:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: linkify
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -43,7 +43,6 @@ dependencies:
|
|||
scrollable_positioned_list: ^0.2.0-nullsafety.0
|
||||
file_picker: ^4.0.1
|
||||
file_picker_desktop: ^1.1.0
|
||||
flutter_linkify: ^5.0.2
|
||||
url_launcher: ^6.0.12
|
||||
|
||||
dev_dependencies:
|
||||
|
|
Loading…
Reference in New Issue