improved tor ui

This commit is contained in:
Fabian Freund
2025-09-25 09:39:22 +02:00
parent dd16332c2d
commit d633106076
2 changed files with 458 additions and 341 deletions
@@ -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/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/controllers/readerable.dart';
import 'package:weblibre/features/geckoview/features/readerview/presentation/widgets/reader_button.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/data/models/general_settings.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
import 'package:weblibre/presentation/hooks/menu_controller.dart'; import 'package:weblibre/presentation/hooks/menu_controller.dart';
@@ -327,7 +328,22 @@ class BrowserBottomAppBar extends HookConsumerWidget {
await TorProxyRoute().push(context); await TorProxyRoute().push(context);
}, },
leadingIcon: const Icon(TorIcons.onionAlt), leadingIcon: const Icon(TorIcons.onionAlt),
child: Consumer(
child: const Text('Tor™ Proxy'), 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(), const Divider(),
MenuItemButton( MenuItemButton(
@@ -17,6 +17,7 @@
* You should have received a copy of the GNU Affero General Public License * 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/>. * 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.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -49,22 +50,12 @@ class TorProxyScreen extends HookConsumerWidget {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('Tor™ Proxy')), appBar: AppBar(title: const Text('Tor™ Proxy')),
body: SafeArea( body: Theme(
child: ColoredBox( data: Theme.of(context).copyWith(
color: const Color(0xFF7D4698), listTileTheme: ListTileTheme.of(
child: Column( context,
children: [ ).copyWith(iconColor: Colors.white, textColor: Colors.white),
Expanded( switchTheme: SwitchTheme.of(context).copyWith(
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?>(( trackColor: WidgetStateProperty.resolveWith<Color?>((
Set<WidgetState> states, Set<WidgetState> states,
) { ) {
@@ -73,8 +64,7 @@ class TorProxyScreen extends HookConsumerWidget {
} }
return null; // Use the default color. return null; // Use the default color.
}), }),
trackOutlineColor: trackOutlineColor: WidgetStateProperty.resolveWith<Color?>((
WidgetStateProperty.resolveWith<Color?>((
Set<WidgetState> states, Set<WidgetState> states,
) { ) {
if (states.isEmpty) { if (states.isEmpty) {
@@ -82,6 +72,35 @@ class TorProxyScreen extends HookConsumerWidget {
} }
return null; // Use the default color. 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?>(( thumbIcon: WidgetStateProperty.resolveWith<Icon?>((
Set<WidgetState> states, Set<WidgetState> states,
) { ) {
@@ -108,33 +127,88 @@ class TorProxyScreen extends HookConsumerWidget {
}, },
), ),
), ),
ListTileTheme( const SizedBox(height: 16),
iconColor: Colors.white, Expanded(
textColor: Colors.white, child: FadingScroll(
child: SwitchListTile.adaptive( 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, inactiveThumbColor: Colors.white,
activeColor: const Color(0xFF68B030), activeThumbColor: const Color(0xFF68B030),
trackColor: WidgetStateProperty.resolveWith<Color?>(( thumbIcon:
WidgetStateProperty.resolveWith<Icon?>((
Set<WidgetState> states, Set<WidgetState> states,
) { ) {
if (states.isEmpty) { if (states.contains(
return const Color(0xFF333A41); WidgetState.selected,
} )) {
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 const Icon(MdiIcons.incognito);
} }
return null; // Use the default color. return null; // Use the default color.
@@ -147,51 +221,55 @@ 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(saveTorSettingsControllerProvider.notifier) .read(
saveTorSettingsControllerProvider
.notifier,
)
.save( .save(
(currentSettings) => currentSettings.copyWith (currentSettings) => currentSettings
.copyWith
.proxyPrivateTabsTor(value), .proxyPrivateTabsTor(value),
); );
}, },
), ),
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,
), ),
ListTileTheme( ),
iconColor: Colors.white, ),
textColor: Colors.white, SwitchListTile.adaptive(
child: SwitchListTile.adaptive(
inactiveThumbColor: Colors.white, inactiveThumbColor: Colors.white,
activeColor: const Color(0xFF68B030), activeThumbColor: const Color(0xFF68B030),
trackColor: WidgetStateProperty.resolveWith<Color?>(( thumbIcon:
WidgetStateProperty.resolveWith<Icon?>((
Set<WidgetState> states, Set<WidgetState> states,
) { ) {
if (states.isEmpty) { if (states.contains(
return const Color(0xFF333A41); WidgetState.selected,
)) {
return const Icon(
MdiIcons.arrowDecisionAuto,
);
} }
return null; // Use the default color. return null; // Use the default color.
}), }),
trackOutlineColor: value:
WidgetStateProperty.resolveWith<Color?>(( torSettings.config ==
Set<WidgetState> states, TorConnectionConfig.auto,
) {
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'), 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(MdiIcons.arrowDecisionAuto), secondary: const Icon(
MdiIcons.arrowDecisionAuto,
),
onChanged: torProxyPort.isLoading onChanged: torProxyPort.isLoading
? null ? null
: (value) async { : (value) async {
@@ -201,18 +279,24 @@ class TorProxyScreen extends HookConsumerWidget {
.notifier, .notifier,
) )
.save( .save(
(currentSettings) => (
currentSettings.copyWith.config( currentSettings,
) => currentSettings.copyWith
.config(
value value
? TorConnectionConfig.auto ? TorConnectionConfig
: TorConnectionConfig.direct, .auto
: TorConnectionConfig
.direct,
), ),
); );
}, },
), ),
), if (torSettings.config ==
if (torSettings.config == TorConnectionConfig.auto) ...[ TorConnectionConfig.auto) ...[
SwitchListTile.adaptive( SwitchListTile.adaptive(
inactiveThumbColor: Colors.white,
activeThumbColor: const Color(0xFF68B030),
value: torSettings.requireBridge, value: torSettings.requireBridge,
contentPadding: const EdgeInsets.only( contentPadding: const EdgeInsets.only(
left: 56, left: 56,
@@ -227,8 +311,8 @@ class TorProxyScreen extends HookConsumerWidget {
.notifier, .notifier,
) )
.save( .save(
(currentSettings) => currentSettings (currentSettings) =>
.copyWith currentSettings.copyWith
.requireBridge(value), .requireBridge(value),
); );
}, },
@@ -243,11 +327,13 @@ class TorProxyScreen extends HookConsumerWidget {
if (value != null) { if (value != null) {
await ref await ref
.read( .read(
saveTorSettingsControllerProvider.notifier, saveTorSettingsControllerProvider
.notifier,
) )
.save( .save(
(currentSettings) => (currentSettings) => currentSettings
currentSettings.copyWith.config(value), .copyWith
.config(value),
); );
} }
}, },
@@ -270,11 +356,13 @@ class TorProxyScreen extends HookConsumerWidget {
if (value != null) { if (value != null) {
await ref await ref
.read( .read(
saveTorSettingsControllerProvider.notifier, saveTorSettingsControllerProvider
.notifier,
) )
.save( .save(
(currentSettings) => (currentSettings) => currentSettings
currentSettings.copyWith.config(value), .copyWith
.config(value),
); );
} }
}, },
@@ -297,11 +385,13 @@ class TorProxyScreen extends HookConsumerWidget {
if (value != null) { if (value != null) {
await ref await ref
.read( .read(
saveTorSettingsControllerProvider.notifier, saveTorSettingsControllerProvider
.notifier,
) )
.save( .save(
(currentSettings) => (currentSettings) => currentSettings
currentSettings.copyWith.config(value), .copyWith
.config(value),
); );
} }
}, },
@@ -313,14 +403,18 @@ class TorProxyScreen extends HookConsumerWidget {
right: 24, right: 24,
), ),
title: const Text('Snowflake'), title: const Text('Snowflake'),
subtitle: const Text('Suitable for heavy censorship'), subtitle: const Text(
'Suitable for heavy censorship',
),
), ),
), ),
CheckboxListTile.adaptive( CheckboxListTile.adaptive(
value: torSettings.fetchRemoteBridges, value: torSettings.fetchRemoteBridges,
controlAffinity: ListTileControlAffinity.leading, controlAffinity:
ListTileControlAffinity.leading,
enabled: enabled:
torSettings.config != TorConnectionConfig.direct, torSettings.config !=
TorConnectionConfig.direct,
contentPadding: const EdgeInsets.only( contentPadding: const EdgeInsets.only(
left: 56, left: 56,
right: 24, right: 24,
@@ -335,9 +429,11 @@ class TorProxyScreen extends HookConsumerWidget {
.notifier, .notifier,
) )
.save( .save(
(currentSettings) => currentSettings (currentSettings) =>
.copyWith currentSettings.copyWith
.fetchRemoteBridges(value), .fetchRemoteBridges(
value,
),
); );
} }
}, },
@@ -347,6 +443,10 @@ class TorProxyScreen extends HookConsumerWidget {
), ),
], ],
], ],
);
},
);
},
), ),
), ),
Padding( Padding(
@@ -402,15 +502,16 @@ class TorProxyScreen extends HookConsumerWidget {
child: Text( 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.', '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, textAlign: TextAlign.center,
style: Theme.of( style: Theme.of(context).textTheme.bodySmall?.copyWith(
context, fontStyle: FontStyle.italic,
).textTheme.bodySmall?.copyWith(fontStyle: FontStyle.italic), ),
), ),
), ),
], ],
), ),
), ),
), ),
),
); );
} }
} }