improved tor ui
This commit is contained in:
+17
-1
@@ -43,6 +43,7 @@ import 'package:weblibre/features/geckoview/features/browser/presentation/widget
|
||||
import 'package:weblibre/features/geckoview/features/find_in_page/presentation/controllers/find_in_page.dart';
|
||||
import 'package:weblibre/features/geckoview/features/readerview/presentation/controllers/readerable.dart';
|
||||
import 'package:weblibre/features/geckoview/features/readerview/presentation/widgets/reader_button.dart';
|
||||
import 'package:weblibre/features/tor/domain/services/tor_proxy.dart';
|
||||
import 'package:weblibre/features/user/data/models/general_settings.dart';
|
||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||
import 'package:weblibre/presentation/hooks/menu_controller.dart';
|
||||
@@ -327,7 +328,22 @@ class BrowserBottomAppBar extends HookConsumerWidget {
|
||||
await TorProxyRoute().push(context);
|
||||
},
|
||||
leadingIcon: const Icon(TorIcons.onionAlt),
|
||||
child: const Text('Tor™ Proxy'),
|
||||
child: Consumer(
|
||||
child: const Text('Tor™ Proxy'),
|
||||
builder: (context, ref, child) {
|
||||
final torConnected = ref.watch(
|
||||
torProxyServiceProvider.select(
|
||||
(value) => value.value != null,
|
||||
),
|
||||
);
|
||||
|
||||
return Badge(
|
||||
isLabelVisible: torConnected,
|
||||
backgroundColor: const Color(0xFF68B030),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
MenuItemButton(
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:fading_scroll/fading_scroll.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@@ -49,286 +50,176 @@ class TorProxyScreen extends HookConsumerWidget {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Tor™ Proxy')),
|
||||
body: SafeArea(
|
||||
child: ColoredBox(
|
||||
color: const Color(0xFF7D4698),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView(
|
||||
children: [
|
||||
const Icon(TorIcons.onionAlt, size: 84),
|
||||
const SizedBox(height: 16),
|
||||
ListTileTheme(
|
||||
iconColor: Colors.white,
|
||||
textColor: Colors.white,
|
||||
child: SwitchListTile.adaptive(
|
||||
inactiveThumbColor: Colors.white,
|
||||
activeColor: const Color(0xFF68B030),
|
||||
trackColor: WidgetStateProperty.resolveWith<Color?>((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.isEmpty) {
|
||||
return const Color(0xFF333A41);
|
||||
}
|
||||
return null; // Use the default color.
|
||||
}),
|
||||
trackOutlineColor:
|
||||
WidgetStateProperty.resolveWith<Color?>((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.isEmpty) {
|
||||
return Colors.white;
|
||||
}
|
||||
return null; // Use the default color.
|
||||
}),
|
||||
thumbIcon: WidgetStateProperty.resolveWith<Icon?>((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return const Icon(MdiIcons.axisArrowLock);
|
||||
}
|
||||
return null; // Use the default color.
|
||||
}),
|
||||
value: torProxyPort.value != null,
|
||||
title: const Text('Tor™ Proxy'),
|
||||
secondary: const Icon(MdiIcons.power),
|
||||
onChanged: torProxyPort.isLoading
|
||||
? null
|
||||
: (value) async {
|
||||
if (value) {
|
||||
await ref
|
||||
.read(torProxyServiceProvider.notifier)
|
||||
.startOrReconfigure();
|
||||
} else {
|
||||
await ref
|
||||
.read(torProxyServiceProvider.notifier)
|
||||
.disconnect();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
ListTileTheme(
|
||||
iconColor: Colors.white,
|
||||
textColor: Colors.white,
|
||||
child: SwitchListTile.adaptive(
|
||||
inactiveThumbColor: Colors.white,
|
||||
activeColor: const Color(0xFF68B030),
|
||||
trackColor: WidgetStateProperty.resolveWith<Color?>((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.isEmpty) {
|
||||
return const Color(0xFF333A41);
|
||||
}
|
||||
return null; // Use the default color.
|
||||
}),
|
||||
trackOutlineColor:
|
||||
WidgetStateProperty.resolveWith<Color?>((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.isEmpty) {
|
||||
return Colors.white;
|
||||
}
|
||||
return null; // Use the default color.
|
||||
}),
|
||||
thumbIcon: WidgetStateProperty.resolveWith<Icon?>((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return const Icon(MdiIcons.incognito);
|
||||
}
|
||||
return null; // Use the default color.
|
||||
}),
|
||||
value: torSettings.proxyPrivateTabsTor,
|
||||
title: const Text('Proxy Private Tabs'),
|
||||
subtitle: const Text(
|
||||
'When enabled, all Private Tabs will be tunneled through Tor',
|
||||
),
|
||||
secondary: const Icon(MdiIcons.incognito),
|
||||
onChanged: (value) async {
|
||||
await ref
|
||||
.read(saveTorSettingsControllerProvider.notifier)
|
||||
.save(
|
||||
(currentSettings) => currentSettings.copyWith
|
||||
.proxyPrivateTabsTor(value),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
ListTileTheme(
|
||||
iconColor: Colors.white,
|
||||
textColor: Colors.white,
|
||||
child: SwitchListTile.adaptive(
|
||||
inactiveThumbColor: Colors.white,
|
||||
activeColor: const Color(0xFF68B030),
|
||||
trackColor: WidgetStateProperty.resolveWith<Color?>((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.isEmpty) {
|
||||
return const Color(0xFF333A41);
|
||||
}
|
||||
return null; // Use the default color.
|
||||
}),
|
||||
trackOutlineColor:
|
||||
WidgetStateProperty.resolveWith<Color?>((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.isEmpty) {
|
||||
return Colors.white;
|
||||
}
|
||||
return null; // Use the default color.
|
||||
}),
|
||||
thumbIcon: WidgetStateProperty.resolveWith<Icon?>((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return const Icon(MdiIcons.arrowDecisionAuto);
|
||||
}
|
||||
return null; // Use the default color.
|
||||
}),
|
||||
value: torSettings.config == TorConnectionConfig.auto,
|
||||
title: const Text('Auto Configure Transport'),
|
||||
subtitle: const Text(
|
||||
'From some locations, it is necessary to use a pluggable transport to connect to Tor',
|
||||
),
|
||||
secondary: const Icon(MdiIcons.arrowDecisionAuto),
|
||||
onChanged: torProxyPort.isLoading
|
||||
? null
|
||||
: (value) async {
|
||||
await ref
|
||||
.read(
|
||||
saveTorSettingsControllerProvider
|
||||
.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) =>
|
||||
currentSettings.copyWith.config(
|
||||
value
|
||||
? TorConnectionConfig.auto
|
||||
: TorConnectionConfig.direct,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (torSettings.config == TorConnectionConfig.auto) ...[
|
||||
SwitchListTile.adaptive(
|
||||
value: torSettings.requireBridge,
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 56,
|
||||
right: 24,
|
||||
),
|
||||
onChanged: torProxyPort.isLoading
|
||||
? null
|
||||
: (value) async {
|
||||
await ref
|
||||
.read(
|
||||
saveTorSettingsControllerProvider
|
||||
.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) => currentSettings
|
||||
.copyWith
|
||||
.requireBridge(value),
|
||||
);
|
||||
},
|
||||
title: const Text(
|
||||
"I'm sure I cannot connect without a bridge",
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
RadioGroup(
|
||||
groupValue: torSettings.config,
|
||||
onChanged: (value) async {
|
||||
if (value != null) {
|
||||
await ref
|
||||
.read(
|
||||
saveTorSettingsControllerProvider.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) =>
|
||||
currentSettings.copyWith.config(value),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: RadioListTile.adaptive(
|
||||
value: TorConnectionConfig.direct,
|
||||
enabled: !torProxyPort.isLoading,
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 56,
|
||||
right: 24,
|
||||
),
|
||||
title: const Text('Direct Connection'),
|
||||
subtitle: const Text(
|
||||
'The best way to connect to Tor if Tor is not blocked',
|
||||
),
|
||||
),
|
||||
),
|
||||
RadioGroup(
|
||||
groupValue: torSettings.config,
|
||||
onChanged: (value) async {
|
||||
if (value != null) {
|
||||
await ref
|
||||
.read(
|
||||
saveTorSettingsControllerProvider.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) =>
|
||||
currentSettings.copyWith.config(value),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: RadioListTile.adaptive(
|
||||
value: TorConnectionConfig.obfs4,
|
||||
enabled: !torProxyPort.isLoading,
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 56,
|
||||
right: 24,
|
||||
),
|
||||
title: const Text('obfs4'),
|
||||
subtitle: const Text(
|
||||
'Suitable for light censorship and high bandwidth needs',
|
||||
),
|
||||
),
|
||||
),
|
||||
RadioGroup(
|
||||
groupValue: torSettings.config,
|
||||
onChanged: (value) async {
|
||||
if (value != null) {
|
||||
await ref
|
||||
.read(
|
||||
saveTorSettingsControllerProvider.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) =>
|
||||
currentSettings.copyWith.config(value),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: RadioListTile.adaptive(
|
||||
value: TorConnectionConfig.snowflake,
|
||||
enabled: !torProxyPort.isLoading,
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 56,
|
||||
right: 24,
|
||||
),
|
||||
title: const Text('Snowflake'),
|
||||
subtitle: const Text('Suitable for heavy censorship'),
|
||||
),
|
||||
),
|
||||
CheckboxListTile.adaptive(
|
||||
value: torSettings.fetchRemoteBridges,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
enabled:
|
||||
torSettings.config != TorConnectionConfig.direct,
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 56,
|
||||
right: 24,
|
||||
),
|
||||
onChanged: torProxyPort.isLoading
|
||||
? null
|
||||
: (value) async {
|
||||
if (value != null) {
|
||||
body: Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
listTileTheme: ListTileTheme.of(
|
||||
context,
|
||||
).copyWith(iconColor: Colors.white, textColor: Colors.white),
|
||||
switchTheme: SwitchTheme.of(context).copyWith(
|
||||
trackColor: WidgetStateProperty.resolveWith<Color?>((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.isEmpty) {
|
||||
return const Color(0xFF333A41);
|
||||
}
|
||||
return null; // Use the default color.
|
||||
}),
|
||||
trackOutlineColor: WidgetStateProperty.resolveWith<Color?>((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.isEmpty) {
|
||||
return Colors.white;
|
||||
}
|
||||
return null; // Use the default color.
|
||||
}),
|
||||
),
|
||||
radioTheme: RadioTheme.of(context).copyWith(
|
||||
fillColor: WidgetStateColor.resolveWith((states) {
|
||||
return Colors.white;
|
||||
}),
|
||||
),
|
||||
checkboxTheme: CheckboxTheme.of(context).copyWith(
|
||||
fillColor: WidgetStateColor.resolveWith((states) {
|
||||
return Colors.white;
|
||||
}),
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: ColoredBox(
|
||||
color: const Color(0xFF7D4698),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
const Icon(TorIcons.onionAlt, size: 84),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 24),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.white),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: SwitchListTile.adaptive(
|
||||
inactiveThumbColor: Colors.white,
|
||||
activeThumbColor: const Color(0xFF68B030),
|
||||
thumbIcon: WidgetStateProperty.resolveWith<Icon?>((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return const Icon(MdiIcons.axisArrowLock);
|
||||
}
|
||||
return null; // Use the default color.
|
||||
}),
|
||||
value: torProxyPort.value != null,
|
||||
title: const Text('Tor™ Proxy'),
|
||||
secondary: const Icon(MdiIcons.power),
|
||||
onChanged: torProxyPort.isLoading
|
||||
? null
|
||||
: (value) async {
|
||||
if (value) {
|
||||
await ref
|
||||
.read(torProxyServiceProvider.notifier)
|
||||
.startOrReconfigure();
|
||||
} else {
|
||||
await ref
|
||||
.read(torProxyServiceProvider.notifier)
|
||||
.disconnect();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: FadingScroll(
|
||||
fadingSize: 25,
|
||||
builder: (context, controller) {
|
||||
return FadingScroll(
|
||||
controller: controller,
|
||||
fadingSize: 25,
|
||||
builder: (context, controller) {
|
||||
return ListView(
|
||||
controller: controller,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 24.0),
|
||||
child: Text(
|
||||
'Routing',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
RadioGroup(
|
||||
groupValue: torSettings.proxyRegularTabsMode,
|
||||
onChanged: (value) async {
|
||||
if (value != null) {
|
||||
await ref
|
||||
.read(
|
||||
saveTorSettingsControllerProvider
|
||||
.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) => currentSettings
|
||||
.copyWith
|
||||
.proxyRegularTabsMode(value),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const RadioListTile.adaptive(
|
||||
value: TorRegularTabProxyMode.container,
|
||||
title: Text('Container-Based Routing'),
|
||||
subtitle: Text(
|
||||
'Route only tabs in Tor containers through the Tor network. Private tabs remain unaffected.',
|
||||
),
|
||||
),
|
||||
),
|
||||
RadioGroup(
|
||||
groupValue: torSettings.proxyRegularTabsMode,
|
||||
onChanged: (value) async {
|
||||
if (value != null) {
|
||||
await ref
|
||||
.read(
|
||||
saveTorSettingsControllerProvider
|
||||
.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) => currentSettings
|
||||
.copyWith
|
||||
.proxyRegularTabsMode(value),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const RadioListTile.adaptive(
|
||||
value: TorRegularTabProxyMode.all,
|
||||
title: Text('Global Routing'),
|
||||
subtitle: Text(
|
||||
'Route all regular tabs through the Tor network. Private tabs remain unaffected.',
|
||||
),
|
||||
),
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
inactiveThumbColor: Colors.white,
|
||||
activeThumbColor: const Color(0xFF68B030),
|
||||
thumbIcon:
|
||||
WidgetStateProperty.resolveWith<Icon?>((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.contains(
|
||||
WidgetState.selected,
|
||||
)) {
|
||||
return const Icon(MdiIcons.incognito);
|
||||
}
|
||||
return null; // Use the default color.
|
||||
}),
|
||||
value: torSettings.proxyPrivateTabsTor,
|
||||
title: const Text('Proxy Private Tabs'),
|
||||
subtitle: const Text(
|
||||
'When enabled, all Private Tabs will be tunneled through Tor',
|
||||
),
|
||||
secondary: const Icon(MdiIcons.incognito),
|
||||
onChanged: (value) async {
|
||||
await ref
|
||||
.read(
|
||||
saveTorSettingsControllerProvider
|
||||
@@ -337,77 +228,287 @@ class TorProxyScreen extends HookConsumerWidget {
|
||||
.save(
|
||||
(currentSettings) => currentSettings
|
||||
.copyWith
|
||||
.fetchRemoteBridges(value),
|
||||
.proxyPrivateTabsTor(value),
|
||||
);
|
||||
}
|
||||
},
|
||||
title: const Text(
|
||||
"Fetch fresh Bridges before connecting",
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 24.0),
|
||||
child: Text(
|
||||
'Circumvention',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
inactiveThumbColor: Colors.white,
|
||||
activeThumbColor: const Color(0xFF68B030),
|
||||
thumbIcon:
|
||||
WidgetStateProperty.resolveWith<Icon?>((
|
||||
Set<WidgetState> states,
|
||||
) {
|
||||
if (states.contains(
|
||||
WidgetState.selected,
|
||||
)) {
|
||||
return const Icon(
|
||||
MdiIcons.arrowDecisionAuto,
|
||||
);
|
||||
}
|
||||
return null; // Use the default color.
|
||||
}),
|
||||
value:
|
||||
torSettings.config ==
|
||||
TorConnectionConfig.auto,
|
||||
title: const Text('Auto Configure Transport'),
|
||||
subtitle: const Text(
|
||||
'From some locations, it is necessary to use a pluggable transport to connect to Tor',
|
||||
),
|
||||
secondary: const Icon(
|
||||
MdiIcons.arrowDecisionAuto,
|
||||
),
|
||||
onChanged: torProxyPort.isLoading
|
||||
? null
|
||||
: (value) async {
|
||||
await ref
|
||||
.read(
|
||||
saveTorSettingsControllerProvider
|
||||
.notifier,
|
||||
)
|
||||
.save(
|
||||
(
|
||||
currentSettings,
|
||||
) => currentSettings.copyWith
|
||||
.config(
|
||||
value
|
||||
? TorConnectionConfig
|
||||
.auto
|
||||
: TorConnectionConfig
|
||||
.direct,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (torSettings.config ==
|
||||
TorConnectionConfig.auto) ...[
|
||||
SwitchListTile.adaptive(
|
||||
inactiveThumbColor: Colors.white,
|
||||
activeThumbColor: const Color(0xFF68B030),
|
||||
value: torSettings.requireBridge,
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 56,
|
||||
right: 24,
|
||||
),
|
||||
onChanged: torProxyPort.isLoading
|
||||
? null
|
||||
: (value) async {
|
||||
await ref
|
||||
.read(
|
||||
saveTorSettingsControllerProvider
|
||||
.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) =>
|
||||
currentSettings.copyWith
|
||||
.requireBridge(value),
|
||||
);
|
||||
},
|
||||
title: const Text(
|
||||
"I'm sure I cannot connect without a bridge",
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
RadioGroup(
|
||||
groupValue: torSettings.config,
|
||||
onChanged: (value) async {
|
||||
if (value != null) {
|
||||
await ref
|
||||
.read(
|
||||
saveTorSettingsControllerProvider
|
||||
.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) => currentSettings
|
||||
.copyWith
|
||||
.config(value),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: RadioListTile.adaptive(
|
||||
value: TorConnectionConfig.direct,
|
||||
enabled: !torProxyPort.isLoading,
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 56,
|
||||
right: 24,
|
||||
),
|
||||
title: const Text('Direct Connection'),
|
||||
subtitle: const Text(
|
||||
'The best way to connect to Tor if Tor is not blocked',
|
||||
),
|
||||
),
|
||||
),
|
||||
RadioGroup(
|
||||
groupValue: torSettings.config,
|
||||
onChanged: (value) async {
|
||||
if (value != null) {
|
||||
await ref
|
||||
.read(
|
||||
saveTorSettingsControllerProvider
|
||||
.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) => currentSettings
|
||||
.copyWith
|
||||
.config(value),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: RadioListTile.adaptive(
|
||||
value: TorConnectionConfig.obfs4,
|
||||
enabled: !torProxyPort.isLoading,
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 56,
|
||||
right: 24,
|
||||
),
|
||||
title: const Text('obfs4'),
|
||||
subtitle: const Text(
|
||||
'Suitable for light censorship and high bandwidth needs',
|
||||
),
|
||||
),
|
||||
),
|
||||
RadioGroup(
|
||||
groupValue: torSettings.config,
|
||||
onChanged: (value) async {
|
||||
if (value != null) {
|
||||
await ref
|
||||
.read(
|
||||
saveTorSettingsControllerProvider
|
||||
.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) => currentSettings
|
||||
.copyWith
|
||||
.config(value),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: RadioListTile.adaptive(
|
||||
value: TorConnectionConfig.snowflake,
|
||||
enabled: !torProxyPort.isLoading,
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 56,
|
||||
right: 24,
|
||||
),
|
||||
title: const Text('Snowflake'),
|
||||
subtitle: const Text(
|
||||
'Suitable for heavy censorship',
|
||||
),
|
||||
),
|
||||
),
|
||||
CheckboxListTile.adaptive(
|
||||
value: torSettings.fetchRemoteBridges,
|
||||
controlAffinity:
|
||||
ListTileControlAffinity.leading,
|
||||
enabled:
|
||||
torSettings.config !=
|
||||
TorConnectionConfig.direct,
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 56,
|
||||
right: 24,
|
||||
),
|
||||
onChanged: torProxyPort.isLoading
|
||||
? null
|
||||
: (value) async {
|
||||
if (value != null) {
|
||||
await ref
|
||||
.read(
|
||||
saveTorSettingsControllerProvider
|
||||
.notifier,
|
||||
)
|
||||
.save(
|
||||
(currentSettings) =>
|
||||
currentSettings.copyWith
|
||||
.fetchRemoteBridges(
|
||||
value,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
title: const Text(
|
||||
"Fetch fresh Bridges before connecting",
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (torProxyPort.isLoading)
|
||||
const Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(height: 8),
|
||||
LinearProgressIndicator(
|
||||
backgroundColor: Color(0xFF333A41),
|
||||
color: Color(0xFF68B030),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
'Establishing connection...',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (torProxyPort.value != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (torProxyPort.isLoading)
|
||||
const Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
MdiIcons.shieldLockOutline,
|
||||
SizedBox(height: 8),
|
||||
LinearProgressIndicator(
|
||||
backgroundColor: Color(0xFF333A41),
|
||||
color: Color(0xFF68B030),
|
||||
size: 32,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
'Connected to the Tor Network',
|
||||
style: Theme.of(context).textTheme.titleMedium
|
||||
?.copyWith(color: const Color(0xFF68B030)),
|
||||
'Establishing connection...',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
if (torProxyPort.value != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
MdiIcons.shieldLockOutline,
|
||||
color: Color(0xFF68B030),
|
||||
size: 32,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'Connected to the Tor Network',
|
||||
style: Theme.of(context).textTheme.titleMedium
|
||||
?.copyWith(color: const Color(0xFF68B030)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 8.0,
|
||||
left: 8.0,
|
||||
bottom: 8.0,
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 8.0,
|
||||
left: 8.0,
|
||||
bottom: 8.0,
|
||||
),
|
||||
child: Text(
|
||||
'Tor is a trademark of The Tor Project; all rights reserved. WebLibre is not endorsed or sponsored by, or affiliated with, the Tor Project.',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'Tor is a trademark of The Tor Project; all rights reserved. WebLibre is not endorsed or sponsored by, or affiliated with, the Tor Project.',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall?.copyWith(fontStyle: FontStyle.italic),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user