branding fixes

This commit is contained in:
Fabian Freund
2026-05-31 12:16:30 +02:00
parent 8277be500d
commit 88f128621f
21 changed files with 127 additions and 43 deletions
@@ -0,0 +1,7 @@
const String torBrand = 'Tor™';
const String torProxyLabel = '$torBrand Proxy';
const String torServiceLabel = '$torBrand Service';
const String torNetworkLabel = '$torBrand network';
const String wireGuardBrand = 'WireGuard®';
const String wireGuardConfigLabel = '$wireGuardBrand config';
@@ -35,6 +35,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:nullability/nullability.dart'; import 'package:nullability/nullability.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import 'package:skeletonizer/skeletonizer.dart'; import 'package:skeletonizer/skeletonizer.dart';
import 'package:weblibre/core/branding/proxy_brands.dart';
import 'package:weblibre/core/design/app_colors.dart'; import 'package:weblibre/core/design/app_colors.dart';
import 'package:weblibre/core/logger.dart'; import 'package:weblibre/core/logger.dart';
import 'package:weblibre/core/providers/persisted_bool.dart'; import 'package:weblibre/core/providers/persisted_bool.dart';
@@ -2043,7 +2044,7 @@ class _ConnectionCard extends ConsumerWidget {
final rows = <Widget>[ final rows = <Widget>[
_ConnectionRow( _ConnectionRow(
icon: TorIcons.onionAlt, icon: TorIcons.onionAlt,
label: 'Tor™ Proxy', label: torProxyLabel,
active: isTorActive, active: isTorActive,
enabled: !isTorBusy, enabled: !isTorBusy,
onToggle: () async { onToggle: () async {
@@ -20,6 +20,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:fast_equatable/fast_equatable.dart'; import 'package:fast_equatable/fast_equatable.dart';
import 'package:weblibre/core/branding/proxy_brands.dart';
import 'package:weblibre/features/proxy/data/parsers/host_port.dart'; import 'package:weblibre/features/proxy/data/parsers/host_port.dart';
class WireguardConfigImport with FastEquatable { class WireguardConfigImport with FastEquatable {
@@ -42,7 +43,7 @@ class WireguardConfigImport with FastEquatable {
final peer = sections['peer']; final peer = sections['peer'];
if (interface == null || peer == null) { if (interface == null || peer == null) {
throw const FormatException( throw const FormatException(
'WireGuard config must contain [Interface] and [Peer] sections.', '$wireGuardBrand config must contain [Interface] and [Peer] sections.',
); );
} }
@@ -51,7 +52,7 @@ class WireguardConfigImport with FastEquatable {
? (host: '', port: '') ? (host: '', port: '')
: parseHostPort( : parseHostPort(
rawEndpoint, rawEndpoint,
invalidMessage: 'WireGuard endpoint must be host:port.', invalidMessage: '$wireGuardBrand endpoint must be host:port.',
); );
final mtu = interface['mtu']?.trim(); final mtu = interface['mtu']?.trim();
@@ -103,7 +104,9 @@ Map<String, Map<String, String>> _parseWireguardConfig(String configText) {
if (line.startsWith('[') && line.endsWith(']')) { if (line.startsWith('[') && line.endsWith(']')) {
currentSection = line.substring(1, line.length - 1).trim().toLowerCase(); currentSection = line.substring(1, line.length - 1).trim().toLowerCase();
if (currentSection.isEmpty) { if (currentSection.isEmpty) {
throw const FormatException('WireGuard config contains empty section.'); throw const FormatException(
'$wireGuardBrand config contains empty section.',
);
} }
sections.putIfAbsent(currentSection, () => <String, String>{}); sections.putIfAbsent(currentSection, () => <String, String>{});
continue; continue;
@@ -111,19 +114,19 @@ Map<String, Map<String, String>> _parseWireguardConfig(String configText) {
if (currentSection == null) { if (currentSection == null) {
throw const FormatException( throw const FormatException(
'WireGuard config entries must be inside a section.', '$wireGuardBrand config entries must be inside a section.',
); );
} }
final separatorIndex = line.indexOf('='); final separatorIndex = line.indexOf('=');
if (separatorIndex < 1) { if (separatorIndex < 1) {
throw FormatException('Invalid WireGuard config line: $rawLine'); throw FormatException('Invalid $wireGuardBrand config line: $rawLine');
} }
final key = line.substring(0, separatorIndex).trim().toLowerCase(); final key = line.substring(0, separatorIndex).trim().toLowerCase();
final value = line.substring(separatorIndex + 1).trim(); final value = line.substring(separatorIndex + 1).trim();
if (key.isEmpty) { if (key.isEmpty) {
throw FormatException('Invalid WireGuard config line: $rawLine'); throw FormatException('Invalid $wireGuardBrand config line: $rawLine');
} }
sections[currentSection]![key] = value; sections[currentSection]![key] = value;
} }
@@ -19,6 +19,8 @@
*/ */
import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart'; import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart';
import 'package:weblibre/core/branding/proxy_brands.dart';
extension SingboxProxyProfileTypeExt on SingboxProxyProfileType { extension SingboxProxyProfileTypeExt on SingboxProxyProfileType {
/// Long human label for menus, dialogs, subtitles. /// Long human label for menus, dialogs, subtitles.
String get label => switch (this) { String get label => switch (this) {
@@ -33,7 +35,7 @@ extension SingboxProxyProfileTypeExt on SingboxProxyProfileType {
SingboxProxyProfileType.hysteria2 => 'Hysteria2', SingboxProxyProfileType.hysteria2 => 'Hysteria2',
SingboxProxyProfileType.tuic => 'TUIC', SingboxProxyProfileType.tuic => 'TUIC',
SingboxProxyProfileType.ssh => 'SSH', SingboxProxyProfileType.ssh => 'SSH',
SingboxProxyProfileType.wireguard => 'WireGuard', SingboxProxyProfileType.wireguard => wireGuardBrand,
SingboxProxyProfileType.shadowTls => 'ShadowTLS', SingboxProxyProfileType.shadowTls => 'ShadowTLS',
SingboxProxyProfileType.anyTls => 'AnyTLS', SingboxProxyProfileType.anyTls => 'AnyTLS',
SingboxProxyProfileType.customOutbound => 'Custom Outbound', SingboxProxyProfileType.customOutbound => 'Custom Outbound',
@@ -19,6 +19,7 @@
*/ */
import 'package:fast_equatable/fast_equatable.dart'; import 'package:fast_equatable/fast_equatable.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/core/branding/proxy_brands.dart';
import 'package:weblibre/core/logger.dart'; import 'package:weblibre/core/logger.dart';
import 'package:weblibre/features/proxy/data/models/singbox_proxy_profile.dart'; import 'package:weblibre/features/proxy/data/models/singbox_proxy_profile.dart';
import 'package:weblibre/features/proxy/data/proxy_connection.dart'; import 'package:weblibre/features/proxy/data/proxy_connection.dart';
@@ -61,8 +62,8 @@ List<ProxyConnectionOption> proxyConnectionOptions(Ref ref) {
return [ return [
ProxyConnectionOption( ProxyConnectionOption(
id: const TorProxyConnectionId(), id: const TorProxyConnectionId(),
title: 'Tor', title: torBrand,
subtitle: 'Route through the Tor network', subtitle: 'Route through the $torNetworkLabel',
), ),
for (final profile in singboxProfiles) for (final profile in singboxProfiles)
ProxyConnectionOption( ProxyConnectionOption(
@@ -22,6 +22,7 @@ import 'dart:async';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:synchronized/synchronized.dart'; import 'package:synchronized/synchronized.dart';
import 'package:weblibre/core/branding/proxy_brands.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/models/site_assignment.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/models/site_assignment.dart';
import 'package:weblibre/features/proxy/data/proxy_connection.dart'; import 'package:weblibre/features/proxy/data/proxy_connection.dart';
@@ -57,7 +58,7 @@ class ContainerProxyRepository extends _$ContainerProxyRepository {
return _service.upsertProxy( return _service.upsertProxy(
GeckoProxySettings( GeckoProxySettings(
id: const TorProxyConnectionId().encode(), id: const TorProxyConnectionId().encode(),
title: 'Tor', title: torBrand,
type: 'socks', type: 'socks',
host: '127.0.0.1', host: '127.0.0.1',
port: port, port: port,
@@ -22,6 +22,7 @@ import 'dart:convert';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart'; import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/core/branding/proxy_brands.dart';
import 'package:weblibre/core/logger.dart'; import 'package:weblibre/core/logger.dart';
import 'package:weblibre/features/proxy/data/forms/singbox_form_specs.dart'; import 'package:weblibre/features/proxy/data/forms/singbox_form_specs.dart';
import 'package:weblibre/features/proxy/data/models/proxy_profile_seed.dart'; import 'package:weblibre/features/proxy/data/models/proxy_profile_seed.dart';
@@ -70,8 +71,8 @@ class ProxyInputConsumer extends _$ProxyInputConsumer {
if (_looksLikeWireguardConf(trimmed)) { if (_looksLikeWireguardConf(trimmed)) {
return _seedOutcome( return _seedOutcome(
() => _seedFromWireguardConf(trimmed, fileName: 'WireGuard'), () => _seedFromWireguardConf(trimmed, fileName: wireGuardBrand),
logMessage: 'Failed to parse WireGuard configuration', logMessage: 'Failed to parse $wireGuardBrand configuration',
); );
} }
@@ -26,6 +26,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart'; import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:socks5_proxy/socks_client.dart'; import 'package:socks5_proxy/socks_client.dart';
import 'package:weblibre/core/branding/proxy_brands.dart';
import 'package:weblibre/features/proxy/data/proxy_connection.dart'; import 'package:weblibre/features/proxy/data/proxy_connection.dart';
import 'package:weblibre/features/proxy/domain/repositories/singbox_proxy_runtime.dart'; import 'package:weblibre/features/proxy/domain/repositories/singbox_proxy_runtime.dart';
import 'package:weblibre/features/tor/domain/extensions/tor_status_x.dart'; import 'package:weblibre/features/tor/domain/extensions/tor_status_x.dart';
@@ -118,7 +119,7 @@ class ProxyLatencyResults extends _$ProxyLatencyResults {
if (socksPort == null) { if (socksPort == null) {
_set( _set(
const TorProxyConnectionId(), const TorProxyConnectionId(),
AsyncError('Tor is not ready', StackTrace.current), AsyncError('$torBrand is not ready', StackTrace.current),
); );
return; return;
} }
@@ -23,6 +23,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import 'package:weblibre/core/branding/proxy_brands.dart';
import 'package:weblibre/features/proxy/data/models/proxy_log_message.dart'; import 'package:weblibre/features/proxy/data/models/proxy_log_message.dart';
import 'package:weblibre/features/proxy/domain/repositories/singbox_proxy_logs.dart'; import 'package:weblibre/features/proxy/domain/repositories/singbox_proxy_logs.dart';
import 'package:weblibre/utils/ui_helper.dart'; import 'package:weblibre/utils/ui_helper.dart';
@@ -202,7 +203,7 @@ class _EmptyLogs extends StatelessWidget {
child: Text( child: Text(
hasFilter hasFilter
? 'No log lines match the current filter.' ? 'No log lines match the current filter.'
: 'No log lines yet. Start a proxy or Tor to see output here.', : 'No log lines yet. Start a proxy or $torBrand to see output here.',
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
@@ -22,6 +22,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart'; import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/branding/proxy_brands.dart';
import 'package:weblibre/features/proxy/data/forms/singbox_form_specs.dart'; import 'package:weblibre/features/proxy/data/forms/singbox_form_specs.dart';
import 'package:weblibre/features/proxy/data/models/proxy_profile_seed.dart'; import 'package:weblibre/features/proxy/data/models/proxy_profile_seed.dart';
import 'package:weblibre/features/proxy/domain/extensions/singbox_proxy_profile_type_x.dart'; import 'package:weblibre/features/proxy/domain/extensions/singbox_proxy_profile_type_x.dart';
@@ -161,6 +163,10 @@ class _Editor extends ConsumerWidget {
?.copyWith(color: scheme.onSurfaceVariant), ?.copyWith(color: scheme.onSurfaceVariant),
), ),
), ),
if (draft.type == SingboxProxyProfileType.wireguard) ...[
const SizedBox(height: 12),
const _WireGuardDisclaimer(),
],
]), ]),
), ),
), ),
@@ -173,6 +179,26 @@ class _Editor extends ConsumerWidget {
} }
} }
class _WireGuardDisclaimer extends StatelessWidget {
const _WireGuardDisclaimer();
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Text(
'$wireGuardBrand is a registered trademark of Jason A. Donenfeld; all '
'rights reserved. WebLibre is not endorsed or sponsored by, or '
'affiliated with, Jason A. Donenfeld.',
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(color: scheme.onSurfaceVariant),
),
);
}
}
class _GeneralSection extends HookConsumerWidget { class _GeneralSection extends HookConsumerWidget {
final ProxyProfileDraftProvider draftProvider; final ProxyProfileDraftProvider draftProvider;
final ProxyProfileDraftState draft; final ProxyProfileDraftState draft;
@@ -22,6 +22,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart'; import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart';
import 'package:weblibre/core/branding/proxy_brands.dart';
import 'package:weblibre/features/proxy/data/models/proxy_profile_seed.dart'; import 'package:weblibre/features/proxy/data/models/proxy_profile_seed.dart';
import 'package:weblibre/features/proxy/domain/services/proxy_input_consumer.dart'; import 'package:weblibre/features/proxy/domain/services/proxy_input_consumer.dart';
import 'package:weblibre/features/qr_scanner/presentation/dialogs/qr_scanner_dialog.dart'; import 'package:weblibre/features/qr_scanner/presentation/dialogs/qr_scanner_dialog.dart';
@@ -274,7 +275,7 @@ class _FileKindPicker extends StatelessWidget {
), ),
ListTile( ListTile(
leading: const Icon(Icons.vpn_lock), leading: const Icon(Icons.vpn_lock),
title: const Text('WireGuard config'), title: const Text(wireGuardConfigLabel),
subtitle: const Text('.conf file with [Interface]/[Peer]'), subtitle: const Text('.conf file with [Interface]/[Peer]'),
onTap: () => onTap: () =>
Navigator.of(context).pop(ProxyFileImportKind.wireguardConf), Navigator.of(context).pop(ProxyFileImportKind.wireguardConf),
@@ -22,6 +22,7 @@ import 'dart:convert';
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:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/branding/proxy_brands.dart';
import 'package:weblibre/features/proxy/presentation/controllers/proxy_profile_draft_controller.dart'; import 'package:weblibre/features/proxy/presentation/controllers/proxy_profile_draft_controller.dart';
import 'package:weblibre/features/user/data/models/proxy_dns_override.dart'; import 'package:weblibre/features/user/data/models/proxy_dns_override.dart';
@@ -87,7 +88,7 @@ class ProfileDnsOverrideSection extends HookConsumerWidget {
children: [ children: [
Text( Text(
'Resolve names through a server reachable inside this profile ' 'Resolve names through a server reachable inside this profile '
'(e.g. an internal DoH server behind a corporate WireGuard). ' '(e.g. an internal DoH server behind a corporate $wireGuardBrand tunnel). '
'Leave off to use automatic DNS handling.', 'Leave off to use automatic DNS handling.',
style: Theme.of(context).textTheme.bodySmall, style: Theme.of(context).textTheme.bodySmall,
), ),
@@ -19,6 +19,7 @@
*/ */
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/branding/proxy_brands.dart';
import 'package:weblibre/core/logger.dart'; import 'package:weblibre/core/logger.dart';
import 'package:weblibre/core/routing/routes.dart'; import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/proxy/data/proxy_connection.dart'; import 'package:weblibre/features/proxy/data/proxy_connection.dart';
@@ -72,7 +73,7 @@ class TorProfileTile extends ConsumerWidget {
child: Icon(TorIcons.onionAlt, color: foreground, size: 24), child: Icon(TorIcons.onionAlt, color: foreground, size: 24),
), ),
title: Text( title: Text(
'Tor', torBrand,
style: TextStyle( style: TextStyle(
fontWeight: isRunning ? FontWeight.w600 : FontWeight.w500, fontWeight: isRunning ? FontWeight.w600 : FontWeight.w500,
), ),
@@ -145,8 +146,8 @@ class TorProfileTile extends ConsumerWidget {
showErrorMessage( showErrorMessage(
context, context,
isRunning isRunning
? 'Failed to stop Tor: $error' ? 'Failed to stop $torBrand: $error'
: 'Failed to start Tor: $error', : 'Failed to start $torBrand: $error',
); );
} }
} }
@@ -23,6 +23,7 @@ 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';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:nullability/nullability.dart'; import 'package:nullability/nullability.dart';
import 'package:weblibre/core/branding/proxy_brands.dart';
import 'package:weblibre/core/routing/routes.dart'; import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart'; import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart';
import 'package:weblibre/features/settings/presentation/widgets/settings_detail.dart'; import 'package:weblibre/features/settings/presentation/widgets/settings_detail.dart';
@@ -41,8 +42,8 @@ const List<SettingsSectionDefinition> torProxySettingsSections = [
keywords: ['power', 'start', 'stop'], keywords: ['power', 'start', 'stop'],
entries: [ entries: [
SettingsEntryDefinition( SettingsEntryDefinition(
title: 'Tor™ Service', title: torServiceLabel,
subtitle: 'Start or stop the Tor service', subtitle: 'Start or stop the $torBrand service',
keywords: ['enable', 'connect'], keywords: ['enable', 'connect'],
child: _TorServiceTile(), child: _TorServiceTile(),
), ),
@@ -68,7 +69,7 @@ const List<SettingsSectionDefinition> torProxySettingsSections = [
SettingsEntryDefinition( SettingsEntryDefinition(
title: 'Transport', title: 'Transport',
subtitle: subtitle:
'Choose how to reach the Tor network when not auto-configured', 'Choose how to reach the $torNetworkLabel when not auto-configured',
keywords: ['direct', 'obfs4', 'snowflake'], keywords: ['direct', 'obfs4', 'snowflake'],
child: _TransportSection(), child: _TransportSection(),
), ),
@@ -103,7 +104,7 @@ const List<SettingsSectionDefinition> torProxySettingsSections = [
leading: Icon(Icons.info_outline), leading: Icon(Icons.info_outline),
title: Text('Trademark'), title: Text('Trademark'),
subtitle: Text( subtitle: Text(
'Tor is a trademark of The Tor Project; all rights reserved. ' '$torBrand is a trademark of The Tor Project; all rights reserved. '
'WebLibre is not endorsed or sponsored by, or affiliated with, ' 'WebLibre is not endorsed or sponsored by, or affiliated with, '
'the Tor Project.', 'the Tor Project.',
), ),
@@ -133,7 +134,7 @@ class TorProxyScreen extends HookConsumerWidget {
}); });
return const SettingsDetailScaffold( return const SettingsDetailScaffold(
title: 'Tor™ Proxy', title: torProxyLabel,
subtitle: subtitle:
'Onion routing, pluggable transports, bridges and country restrictions.', 'Onion routing, pluggable transports, bridges and country restrictions.',
icon: TorIcons.onionAlt, icon: TorIcons.onionAlt,
@@ -177,8 +178,8 @@ class _TorServiceTile extends HookConsumerWidget {
children: [ children: [
SwitchListTile.adaptive( SwitchListTile.adaptive(
secondary: const Icon(MdiIcons.power), secondary: const Icon(MdiIcons.power),
title: const Text('Tor™ Service'), title: const Text(torServiceLabel),
subtitle: const Text('Start or stop the Tor service'), subtitle: const Text('Start or stop the $torBrand service'),
value: pendingRequest.value ?? isRunning, value: pendingRequest.value ?? isRunning,
onChanged: isBusy onChanged: isBusy
? null ? null
@@ -225,7 +226,7 @@ class _RequestNewIdentityTile extends ConsumerWidget {
onTap: () async { onTap: () async {
await ref.read(torProxyServiceProvider.notifier).requestNewIdentity(); await ref.read(torProxyServiceProvider.notifier).requestNewIdentity();
if (context.mounted) { if (context.mounted) {
showInfoMessage(context, 'Requesting new Tor identity...'); showInfoMessage(context, 'Requesting new $torBrand identity...');
} }
}, },
); );
@@ -248,7 +249,7 @@ class _AutoConfigureTransportTile extends ConsumerWidget {
secondary: const Icon(MdiIcons.arrowDecisionAuto), secondary: const Icon(MdiIcons.arrowDecisionAuto),
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 $torBrand',
), ),
value: torSettings.config == TorConnectionConfig.auto, value: torSettings.config == TorConnectionConfig.auto,
onChanged: isBusy onChanged: isBusy
@@ -323,7 +324,7 @@ class _TransportSection extends ConsumerWidget {
enabled: !isBusy, enabled: !isBusy,
title: const Text('Direct Connection'), title: const Text('Direct Connection'),
subtitle: const Text( subtitle: const Text(
'The best way to connect to Tor if Tor is not blocked', 'The best way to connect to $torBrand if $torBrand is not blocked',
), ),
), ),
RadioListTile<TorConnectionConfig>.adaptive( RadioListTile<TorConnectionConfig>.adaptive(
@@ -19,6 +19,7 @@
*/ */
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:weblibre/core/branding/proxy_brands.dart';
import 'package:weblibre/core/design/app_colors.dart'; import 'package:weblibre/core/design/app_colors.dart';
import 'package:weblibre/presentation/icons/tor_icons.dart'; import 'package:weblibre/presentation/icons/tor_icons.dart';
@@ -30,9 +31,9 @@ class TorDialog extends StatelessWidget {
final appColors = AppColors.of(context); final appColors = AppColors.of(context);
return AlertDialog( return AlertDialog(
icon: Icon(TorIcons.onionAlt, color: appColors.torPurple), icon: Icon(TorIcons.onionAlt, color: appColors.torPurple),
title: const Text('Tor™ Proxy'), title: const Text(torProxyLabel),
content: const Text( content: const Text(
'This container requires a Tor proxy for secure connections, which is not currently running.', 'This container requires a $torBrand proxy for secure connections, which is not currently running.',
), ),
actions: [ actions: [
TextButton( TextButton(
@@ -19,6 +19,7 @@
*/ */
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/branding/proxy_brands.dart';
import 'package:weblibre/core/design/app_colors.dart'; import 'package:weblibre/core/design/app_colors.dart';
import 'package:weblibre/features/geckoview/domain/controllers/overlay.dart'; import 'package:weblibre/features/geckoview/domain/controllers/overlay.dart';
import 'package:weblibre/features/tor/domain/extensions/tor_status_x.dart'; import 'package:weblibre/features/tor/domain/extensions/tor_status_x.dart';
@@ -68,7 +69,7 @@ class TorNotification extends HookConsumerWidget {
), ),
Expanded( Expanded(
child: Text( child: Text(
'Tor Proxy is connecting...', '$torProxyLabel is connecting...',
style: Theme.of( style: Theme.of(
context, context,
).textTheme.bodyMedium?.copyWith(color: Colors.white), ).textTheme.bodyMedium?.copyWith(color: Colors.white),
@@ -5,6 +5,7 @@ import 'dart:typed_data';
import 'package:riverpod/riverpod.dart'; import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:search_client/search_client.dart'; import 'package:search_client/search_client.dart';
import 'package:weblibre/core/branding/proxy_brands.dart';
import 'package:weblibre/domain/services/generic_website.dart'; import 'package:weblibre/domain/services/generic_website.dart';
import 'package:weblibre/features/search_credits/domain/controllers/search_token_issuance_controller.dart'; import 'package:weblibre/features/search_credits/domain/controllers/search_token_issuance_controller.dart';
import 'package:weblibre/features/search_credits/domain/providers.dart'; import 'package:weblibre/features/search_credits/domain/providers.dart';
@@ -302,7 +303,7 @@ class MetaSearchController extends _$MetaSearchController {
state = state.copyWith( state = state.copyWith(
status: WebSearchStatus.error, status: WebSearchStatus.error,
errorMessage: errorMessage:
'Could not start Tor for the search. Disable the Tor toggle or try again.', 'Could not start $torBrand for the search. Disable the $torBrand toggle or try again.',
hasOpenSession: false, hasOpenSession: false,
); );
return; return;
@@ -19,6 +19,7 @@
*/ */
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/core/branding/proxy_brands.dart';
import 'package:weblibre/core/design/app_colors.dart'; import 'package:weblibre/core/design/app_colors.dart';
import 'package:weblibre/features/search_credits/domain/providers/proxy_client.dart'; import 'package:weblibre/features/search_credits/domain/providers/proxy_client.dart';
import 'package:weblibre/features/search_credits/domain/repositories/web_search_settings.dart'; import 'package:weblibre/features/search_credits/domain/repositories/web_search_settings.dart';
@@ -121,7 +122,7 @@ class RouteThroughTorToggle extends HookConsumerWidget {
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
Text( Text(
routeThroughTor ? 'Tor on' : 'Tor off', routeThroughTor ? '$torBrand on' : '$torBrand off',
style: textTheme.labelLarge?.copyWith( style: textTheme.labelLarge?.copyWith(
color: routeThroughTor color: routeThroughTor
? colorScheme.onPrimaryContainer ? colorScheme.onPrimaryContainer
@@ -1,6 +1,7 @@
import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart'; import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod/riverpod.dart'; import 'package:riverpod/riverpod.dart';
import 'package:weblibre/core/branding/proxy_brands.dart';
import 'package:weblibre/features/proxy/data/proxy_connection.dart'; import 'package:weblibre/features/proxy/data/proxy_connection.dart';
import 'package:weblibre/features/proxy/domain/providers/proxy_connection_options.dart'; import 'package:weblibre/features/proxy/domain/providers/proxy_connection_options.dart';
import 'package:weblibre/features/proxy/domain/repositories/singbox_proxy_profiles.dart'; import 'package:weblibre/features/proxy/domain/repositories/singbox_proxy_profiles.dart';
@@ -41,7 +42,38 @@ void main() {
const TorProxyConnectionId(), const TorProxyConnectionId(),
const SingboxProxyConnectionId('profile-1'), const SingboxProxyConnectionId('profile-1'),
]); ]);
expect(options.map((option) => option.title), ['Tor', 'Mullvad']); expect(options.map((option) => option.title), [torBrand, 'Mullvad']);
});
test('uses standardized WireGuard branding for subtitles', () async {
final createdAt = DateTime(2026);
final container = ProviderContainer(
overrides: [
singboxProxyProfilesRepositoryProvider.overrideWith(
() => _FakeProfilesRepository([
ProxyProfile(
id: 'profile-1',
name: 'Home Tunnel',
type: SingboxProxyProfileType.wireguard,
configJson: '{"type":"wireguard"}',
createdAt: createdAt,
updatedAt: createdAt,
),
]),
),
],
);
addTearDown(container.dispose);
container.listen(
singboxProxyProfilesRepositoryProvider,
(_, _) {},
fireImmediately: true,
);
await Future<void>.delayed(Duration.zero);
final options = container.read(proxyConnectionOptionsProvider);
expect(options[1].subtitle, wireGuardBrand);
}); });
test('labels unknown proxy ids explicitly', () { test('labels unknown proxy ids explicitly', () {
@@ -23,7 +23,7 @@
android:foregroundServiceType="specialUse"> android:foregroundServiceType="specialUse">
<property <property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Tor proxy service for anonymous networking" /> android:value="Tor proxy service for anonymous networking" />
</service> </service>
</application> </application>
</manifest> </manifest>
@@ -57,7 +57,7 @@ class TorService : Service() {
ACTION_START -> { ACTION_START -> {
// startForegroundService() must promote the service promptly, before // startForegroundService() must promote the service promptly, before
// the later binder call reaches startTor(). // the later binder call reaches startTor().
startForeground(NOTIFICATION_ID, createNotification("Tor is connecting...")) startForeground(NOTIFICATION_ID, createNotification("Tor is connecting..."))
} }
ACTION_STOP -> { ACTION_STOP -> {
scope.launch { scope.launch {
@@ -92,13 +92,13 @@ class TorService : Service() {
suspend fun startTor(config: TorConfiguration): Int { suspend fun startTor(config: TorConfiguration): Int {
Log.d(TAG, "Starting Tor...") Log.d(TAG, "Starting Tor...")
startForeground(NOTIFICATION_ID, createNotification("Tor is connecting...")) startForeground(NOTIFICATION_ID, createNotification("Tor is connecting..."))
val manager = torManager ?: throw IllegalStateException("Service not initialized") val manager = torManager ?: throw IllegalStateException("Service not initialized")
try { try {
val socksPort = manager.start(config) val socksPort = manager.start(config)
updateNotification("Tor connected") updateNotification("Tor connected")
return socksPort return socksPort
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Failed to start Tor", e) Log.e(TAG, "Failed to start Tor", e)
@@ -162,7 +162,7 @@ class TorService : Service() {
) )
return NotificationCompat.Builder(this, CHANNEL_ID) return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("WebLibre Tor") .setContentTitle("WebLibre Tor")
.setContentText(text) .setContentText(text)
.setSmallIcon(R.drawable.ic_onion) .setSmallIcon(R.drawable.ic_onion)
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
@@ -178,10 +178,10 @@ class TorService : Service() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel( val channel = NotificationChannel(
CHANNEL_ID, CHANNEL_ID,
"Tor Service", "Tor Service",
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
).apply { ).apply {
description = "Keeps Tor running in the background" description = "Keeps Tor running in the background"
setShowBadge(false) setShowBadge(false)
} }