improve tor ui

This commit is contained in:
Fabian Freund
2026-01-09 09:23:15 +01:00
parent ea015f9415
commit 87e18309d8
@@ -19,7 +19,6 @@
*/ */
import 'package:country_codes/country_codes.dart'; import 'package:country_codes/country_codes.dart';
import 'package:country_flags/country_flags.dart'; import 'package:country_flags/country_flags.dart';
import 'package:fading_scroll/fading_scroll.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
@@ -31,7 +30,6 @@ import 'package:weblibre/features/tor/domain/services/tor_proxy.dart';
import 'package:weblibre/features/user/data/models/tor_settings.dart'; import 'package:weblibre/features/user/data/models/tor_settings.dart';
import 'package:weblibre/features/user/domain/repositories/tor_settings.dart'; import 'package:weblibre/features/user/domain/repositories/tor_settings.dart';
import 'package:weblibre/presentation/hooks/on_initialization.dart'; import 'package:weblibre/presentation/hooks/on_initialization.dart';
import 'package:weblibre/presentation/icons/tor_icons.dart';
import 'package:weblibre/utils/ui_helper.dart'; import 'package:weblibre/utils/ui_helper.dart';
class TorProxyScreen extends HookConsumerWidget { class TorProxyScreen extends HookConsumerWidget {
@@ -95,11 +93,7 @@ class TorProxyScreen extends HookConsumerWidget {
final countryDropdownEntries = useMemoized( final countryDropdownEntries = useMemoized(
() => [ () => [
const DropdownMenuEntry( const DropdownMenuEntry(value: null, label: 'Automatic'),
value: null,
label: 'Automatic',
leadingIcon: Icon(MdiIcons.lightbulbAuto),
),
...CountryCodes.countryCodes().map((country) { ...CountryCodes.countryCodes().map((country) {
final label = final label =
country.localizedName ?? country.localizedName ??
@@ -111,12 +105,6 @@ class TorProxyScreen extends HookConsumerWidget {
return DropdownMenuEntry( return DropdownMenuEntry(
value: country.alpha2Code, value: country.alpha2Code,
label: label, label: label,
leadingIcon: country.alpha2Code.mapNotNull(
(code) => CountryFlag.fromCountryCode(
code,
theme: const ImageTheme(width: 24, height: 16),
),
),
labelWidget: Text( labelWidget: Text(
label, label,
style: const TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
@@ -127,7 +115,6 @@ class TorProxyScreen extends HookConsumerWidget {
); );
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('Tor™ Proxy')),
body: Theme( body: Theme(
data: Theme.of(context).copyWith( data: Theme.of(context).copyWith(
listTileTheme: ListTileTheme.of( listTileTheme: ListTileTheme.of(
@@ -160,25 +147,18 @@ class TorProxyScreen extends HookConsumerWidget {
fillColor: WidgetStateColor.resolveWith((states) { fillColor: WidgetStateColor.resolveWith((states) {
return Colors.white; return Colors.white;
}), }),
checkColor: WidgetStateProperty.all(AppColors.torPurple),
), ),
iconTheme: const IconThemeData(color: Colors.white),
), ),
child: SafeArea( child: SafeArea(
child: ColoredBox( child: ColoredBox(
color: AppColors.torPurple, color: AppColors.torPurple,
child: Column( child: CustomScrollView(
children: [ slivers: [
const SizedBox(height: 16), SliverAppBar(
const Icon(TorIcons.onionAlt, size: 84), pinned: true,
const SizedBox(height: 16), title: SwitchListTile.adaptive(
Container(
margin: const EdgeInsets.symmetric(horizontal: 24),
decoration: BoxDecoration(
border: Border.all(color: Colors.white),
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
SwitchListTile.adaptive(
inactiveThumbColor: Colors.white, inactiveThumbColor: Colors.white,
activeThumbColor: AppColors.torActiveGreen, activeThumbColor: AppColors.torActiveGreen,
thumbIcon: WidgetStateProperty.resolveWith<Icon?>(( thumbIcon: WidgetStateProperty.resolveWith<Icon?>((
@@ -190,7 +170,7 @@ class TorProxyScreen extends HookConsumerWidget {
return null; // Use the default color. return null; // Use the default color.
}), }),
value: torPendingRequest.value ?? torIsRunning, value: torPendingRequest.value ?? torIsRunning,
title: const Text('Tor™ Proxy'), title: const Text('Tor™ Service'),
secondary: const Icon(MdiIcons.power), secondary: const Icon(MdiIcons.power),
onChanged: torIsBusy onChanged: torIsBusy
? null ? null
@@ -212,20 +192,36 @@ class TorProxyScreen extends HookConsumerWidget {
} }
}, },
), ),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(4 + 40 + 8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (torPendingRequest.value != false && torIsBusy)
LinearProgressIndicator(
backgroundColor: AppColors.torBackgroundGrey,
color: AppColors.torActiveGreen,
value: bootstrapProgress / 100,
),
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
right: 16.0, top: 4.0,
right: 16,
left: 16, left: 16,
bottom: 16, bottom: 4,
), ),
child: SizedBox( child: SizedBox(
width: double.infinity, width: double.infinity,
child: FilledButton.icon( child: FilledButton.icon(
onPressed: onPressed:
torIsRunning && torIsBootstrapped && !torIsBusy torIsRunning &&
torIsBootstrapped &&
!torIsBusy
? () async { ? () async {
await ref await ref
.read(torProxyServiceProvider.notifier) .read(
torProxyServiceProvider.notifier,
)
.requestNewIdentity(); .requestNewIdentity();
if (context.mounted) { if (context.mounted) {
@@ -244,17 +240,8 @@ class TorProxyScreen extends HookConsumerWidget {
], ],
), ),
), ),
const SizedBox(height: 16), ),
Expanded( SliverList.list(
child: FadingScroll(
fadingSize: 25,
builder: (context, controller) {
return FadingScroll(
controller: controller,
fadingSize: 25,
builder: (context, controller) {
return ListView(
controller: controller,
children: [ children: [
const SizedBox(height: 16), const SizedBox(height: 16),
const Padding( const Padding(
@@ -273,13 +260,9 @@ class TorProxyScreen extends HookConsumerWidget {
onChanged: (value) async { onChanged: (value) async {
if (value != null) { if (value != null) {
await ref await ref
.read( .read(saveTorSettingsControllerProvider.notifier)
saveTorSettingsControllerProvider
.notifier,
)
.save( .save(
(currentSettings) => currentSettings (currentSettings) => currentSettings.copyWith
.copyWith
.proxyRegularTabsMode(value), .proxyRegularTabsMode(value),
); );
} }
@@ -307,13 +290,10 @@ class TorProxyScreen extends HookConsumerWidget {
SwitchListTile.adaptive( SwitchListTile.adaptive(
inactiveThumbColor: Colors.white, inactiveThumbColor: Colors.white,
activeThumbColor: AppColors.torActiveGreen, activeThumbColor: AppColors.torActiveGreen,
thumbIcon: thumbIcon: WidgetStateProperty.resolveWith<Icon?>((
WidgetStateProperty.resolveWith<Icon?>((
Set<WidgetState> states, Set<WidgetState> states,
) { ) {
if (states.contains( if (states.contains(WidgetState.selected)) {
WidgetState.selected,
)) {
return const Icon(MdiIcons.incognito); return const Icon(MdiIcons.incognito);
} }
return null; // Use the default color. return null; // Use the default color.
@@ -326,13 +306,9 @@ class TorProxyScreen extends HookConsumerWidget {
secondary: const Icon(MdiIcons.incognito), secondary: const Icon(MdiIcons.incognito),
onChanged: (value) async { onChanged: (value) async {
await ref await ref
.read( .read(saveTorSettingsControllerProvider.notifier)
saveTorSettingsControllerProvider
.notifier,
)
.save( .save(
(currentSettings) => currentSettings (currentSettings) => currentSettings.copyWith
.copyWith
.proxyPrivateTabsTor(value), .proxyPrivateTabsTor(value),
); );
}, },
@@ -352,53 +328,38 @@ class TorProxyScreen extends HookConsumerWidget {
SwitchListTile.adaptive( SwitchListTile.adaptive(
inactiveThumbColor: Colors.white, inactiveThumbColor: Colors.white,
activeThumbColor: AppColors.torActiveGreen, activeThumbColor: AppColors.torActiveGreen,
thumbIcon: thumbIcon: WidgetStateProperty.resolveWith<Icon?>((
WidgetStateProperty.resolveWith<Icon?>((
Set<WidgetState> states, Set<WidgetState> states,
) { ) {
if (states.contains( if (states.contains(WidgetState.selected)) {
WidgetState.selected, return const Icon(MdiIcons.arrowDecisionAuto);
)) {
return const Icon(
MdiIcons.arrowDecisionAuto,
);
} }
return null; // Use the default color. return null; // Use the default color.
}), }),
value: value: torSettings.config == TorConnectionConfig.auto,
torSettings.config ==
TorConnectionConfig.auto,
title: const Text('Auto Configure Transport'), title: const Text('Auto Configure Transport'),
subtitle: const Text( subtitle: const Text(
'From some locations, it is necessary to use a pluggable transport to connect to Tor', 'From some locations, it is necessary to use a pluggable transport to connect to Tor',
), ),
secondary: const Icon( secondary: const Icon(MdiIcons.arrowDecisionAuto),
MdiIcons.arrowDecisionAuto,
),
onChanged: torIsBusy onChanged: torIsBusy
? null ? null
: (value) async { : (value) async {
await ref await ref
.read( .read(
saveTorSettingsControllerProvider saveTorSettingsControllerProvider.notifier,
.notifier,
) )
.save( .save(
( (currentSettings) =>
currentSettings, currentSettings.copyWith.config(
) => currentSettings.copyWith
.config(
value value
? TorConnectionConfig ? TorConnectionConfig.auto
.auto : TorConnectionConfig.direct,
: TorConnectionConfig
.direct,
), ),
); );
}, },
), ),
if (torSettings.config == if (torSettings.config == TorConnectionConfig.auto) ...[
TorConnectionConfig.auto) ...[
SwitchListTile.adaptive( SwitchListTile.adaptive(
inactiveThumbColor: Colors.white, inactiveThumbColor: Colors.white,
activeThumbColor: AppColors.torActiveGreen, activeThumbColor: AppColors.torActiveGreen,
@@ -416,8 +377,8 @@ class TorProxyScreen extends HookConsumerWidget {
.notifier, .notifier,
) )
.save( .save(
(currentSettings) => (currentSettings) => currentSettings
currentSettings.copyWith .copyWith
.requireBridge(value), .requireBridge(value),
); );
}, },
@@ -432,13 +393,11 @@ class TorProxyScreen extends HookConsumerWidget {
if (value != null) { if (value != null) {
await ref await ref
.read( .read(
saveTorSettingsControllerProvider saveTorSettingsControllerProvider.notifier,
.notifier,
) )
.save( .save(
(currentSettings) => currentSettings (currentSettings) =>
.copyWith currentSettings.copyWith.config(value),
.config(value),
); );
} }
}, },
@@ -485,11 +444,9 @@ class TorProxyScreen extends HookConsumerWidget {
), ),
CheckboxListTile.adaptive( CheckboxListTile.adaptive(
value: torSettings.fetchRemoteBridges, value: torSettings.fetchRemoteBridges,
controlAffinity: controlAffinity: ListTileControlAffinity.leading,
ListTileControlAffinity.leading,
enabled: enabled:
torSettings.config != torSettings.config != TorConnectionConfig.direct,
TorConnectionConfig.direct,
contentPadding: const EdgeInsets.only( contentPadding: const EdgeInsets.only(
left: 56, left: 56,
right: 24, right: 24,
@@ -504,11 +461,9 @@ class TorProxyScreen extends HookConsumerWidget {
.notifier, .notifier,
) )
.save( .save(
(currentSettings) => (currentSettings) => currentSettings
currentSettings.copyWith .copyWith
.fetchRemoteBridges( .fetchRemoteBridges(value),
value,
),
); );
} }
}, },
@@ -536,29 +491,33 @@ class TorProxyScreen extends HookConsumerWidget {
top: 8.0, top: 8.0,
), ),
child: DropdownMenu( child: DropdownMenu(
initialSelection: enabled: !torIsBusy,
torSettings.entryNodeCountry, initialSelection: torSettings.entryNodeCountry,
menuHeight: 400, menuHeight: 400,
requestFocusOnTap: true, requestFocusOnTap: true,
label: const Text('Entry Country'), label: const Text('Entry Country'),
textStyle: const TextStyle( textStyle: const TextStyle(color: Colors.white),
color: Colors.white, leadingIcon: torSettings.entryNodeCountry.mapNotNull(
), (code) => Padding(
inputDecorationTheme: padding: const EdgeInsets.all(8.0),
const InputDecorationTheme( child: CountryFlag.fromCountryCode(
labelStyle: TextStyle( code,
theme: const EmojiTheme(size: 28),
),
),
),
trailingIcon: const Icon(
MdiIcons.chevronDown,
color: Colors.white, color: Colors.white,
), ),
inputDecorationTheme: const InputDecorationTheme(
labelStyle: TextStyle(color: Colors.white),
iconColor: Colors.white, iconColor: Colors.white,
enabledBorder: UnderlineInputBorder( enabledBorder: UnderlineInputBorder(
borderSide: BorderSide( borderSide: BorderSide(color: Colors.white),
color: Colors.white,
),
), ),
focusedBorder: UnderlineInputBorder( focusedBorder: UnderlineInputBorder(
borderSide: BorderSide( borderSide: BorderSide(color: Colors.white),
color: Colors.white,
),
), ),
), ),
// menuStyle: MenuStyle( // menuStyle: MenuStyle(
@@ -570,13 +529,9 @@ class TorProxyScreen extends HookConsumerWidget {
dropdownMenuEntries: countryDropdownEntries, dropdownMenuEntries: countryDropdownEntries,
onSelected: (value) async { onSelected: (value) async {
await ref await ref
.read( .read(saveTorSettingsControllerProvider.notifier)
saveTorSettingsControllerProvider
.notifier,
)
.save( .save(
(currentSettings) => currentSettings (currentSettings) => currentSettings.copyWith
.copyWith
.entryNodeCountry(value), .entryNodeCountry(value),
); );
}, },
@@ -589,28 +544,33 @@ class TorProxyScreen extends HookConsumerWidget {
top: 8.0, top: 8.0,
), ),
child: DropdownMenu( child: DropdownMenu(
enabled: !torIsBusy,
initialSelection: torSettings.exitNodeCountry, initialSelection: torSettings.exitNodeCountry,
menuHeight: 400, menuHeight: 400,
requestFocusOnTap: true, requestFocusOnTap: true,
label: const Text('Exit Country'), label: const Text('Exit Country'),
textStyle: const TextStyle( textStyle: const TextStyle(color: Colors.white),
color: Colors.white, leadingIcon: torSettings.exitNodeCountry.mapNotNull(
), (code) => Padding(
inputDecorationTheme: padding: const EdgeInsets.all(8.0),
const InputDecorationTheme( child: CountryFlag.fromCountryCode(
labelStyle: TextStyle( code,
theme: const EmojiTheme(size: 28),
),
),
),
trailingIcon: const Icon(
MdiIcons.chevronDown,
color: Colors.white, color: Colors.white,
), ),
inputDecorationTheme: const InputDecorationTheme(
labelStyle: TextStyle(color: Colors.white),
iconColor: Colors.white, iconColor: Colors.white,
enabledBorder: UnderlineInputBorder( enabledBorder: UnderlineInputBorder(
borderSide: BorderSide( borderSide: BorderSide(color: Colors.white),
color: Colors.white,
),
), ),
focusedBorder: UnderlineInputBorder( focusedBorder: UnderlineInputBorder(
borderSide: BorderSide( borderSide: BorderSide(color: Colors.white),
color: Colors.white,
),
), ),
), ),
// menuStyle: MenuStyle( // menuStyle: MenuStyle(
@@ -622,74 +582,20 @@ class TorProxyScreen extends HookConsumerWidget {
dropdownMenuEntries: countryDropdownEntries, dropdownMenuEntries: countryDropdownEntries,
onSelected: (value) async { onSelected: (value) async {
await ref await ref
.read( .read(saveTorSettingsControllerProvider.notifier)
saveTorSettingsControllerProvider
.notifier,
)
.save( .save(
(currentSettings) => currentSettings (currentSettings) => currentSettings.copyWith
.copyWith
.exitNodeCountry(value), .exitNodeCountry(value),
); );
}, },
), ),
), ),
], ],
);
},
);
},
), ),
), SliverToBoxAdapter(
Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (torPendingRequest.value != false && torIsBusy)
Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 8),
LinearProgressIndicator(
backgroundColor: AppColors.torBackgroundGrey,
color: AppColors.torActiveGreen,
value: bootstrapProgress / 100,
),
const SizedBox(height: 8),
const Text(
'Establishing connection...',
style: TextStyle(color: Colors.white),
),
],
)
else if (torPendingRequest.value != false &&
torIsRunning &&
torIsBootstrapped)
Padding(
padding: const EdgeInsets.only(top: 24.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
MdiIcons.shieldLockOutline,
color: AppColors.torActiveGreen,
size: 32,
),
const SizedBox(width: 12),
Text(
'Connected to the Tor Network',
style: Theme.of(context).textTheme.titleMedium
?.copyWith(color: AppColors.torActiveGreen),
),
],
),
),
],
),
),
Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 32.0,
right: 12.0, right: 12.0,
left: 12.0, left: 12.0,
bottom: 8.0, bottom: 8.0,
@@ -699,6 +605,8 @@ class TorProxyScreen extends HookConsumerWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodySmall?.copyWith( style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
color: Colors.white,
),
), ),
), ),
), ),