Allow Tor Caching + Our Own Linkify
continuous-integration/drone/pr Build is pending Details

This commit is contained in:
Sarah Jamie Lewis 2022-01-18 13:17:27 -08:00
parent 9d10b9ea8d
commit c6e64a3a5f
16 changed files with 690 additions and 24 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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
};
}
}

View File

@ -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(),
);
}

128
lib/third_party/linkify/linkify.dart vendored Normal file
View File

@ -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;
}

127
lib/third_party/linkify/uri.dart vendored Normal file
View File

@ -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);
}

View File

@ -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),

View File

@ -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';

View File

@ -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:

View File

@ -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: