fix proxy name resolution
This commit is contained in:
+1
-1
@@ -42,7 +42,7 @@ final class ProxySettingsReplicationProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$proxySettingsReplicationHash() =>
|
String _$proxySettingsReplicationHash() =>
|
||||||
r'3a7f15c1e09e304d355dada2de80112dec0adbf4';
|
r'42dcd4ec7bc5a00804f729178b99c56e8c917850';
|
||||||
|
|
||||||
abstract class _$ProxySettingsReplication extends $Notifier<void> {
|
abstract class _$ProxySettingsReplication extends $Notifier<void> {
|
||||||
void build();
|
void build();
|
||||||
|
|||||||
+22
-2
@@ -38,6 +38,7 @@ import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors
|
|||||||
import 'package:weblibre/features/geckoview/features/tabs/utils/container_icons.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/utils/container_icons.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';
|
||||||
|
|
||||||
enum _DialogMode { create, edit }
|
enum _DialogMode { create, edit }
|
||||||
|
|
||||||
@@ -80,6 +81,11 @@ class ContainerEditScreen extends HookConsumerWidget {
|
|||||||
final colorScheme = theme.colorScheme;
|
final colorScheme = theme.colorScheme;
|
||||||
|
|
||||||
final proxyOptions = ref.watch(proxyConnectionOptionsProvider);
|
final proxyOptions = ref.watch(proxyConnectionOptionsProvider);
|
||||||
|
final proxyOptionsLoading = ref.watch(
|
||||||
|
singboxProxyProfilesRepositoryProvider.select(
|
||||||
|
(value) => value.isLoading && !value.hasValue,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final selectedColor = useState(initialContainer.color);
|
final selectedColor = useState(initialContainer.color);
|
||||||
final selectedIcon = useState(initialContainer.metadata.iconData);
|
final selectedIcon = useState(initialContainer.metadata.iconData);
|
||||||
@@ -383,7 +389,11 @@ class ContainerEditScreen extends HookConsumerWidget {
|
|||||||
leading: const Icon(Icons.route_outlined),
|
leading: const Icon(Icons.route_outlined),
|
||||||
title: const Text('Proxy Connection'),
|
title: const Text('Proxy Connection'),
|
||||||
subtitle: Text(switch (proxyConnectionId.value) {
|
subtitle: Text(switch (proxyConnectionId.value) {
|
||||||
final id? => proxyConnectionTitle(proxyOptions, id),
|
final id? => proxyConnectionTitle(
|
||||||
|
proxyOptions,
|
||||||
|
id,
|
||||||
|
isLoading: proxyOptionsLoading,
|
||||||
|
),
|
||||||
null => 'None',
|
null => 'None',
|
||||||
}),
|
}),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
@@ -401,6 +411,12 @@ class ContainerEditScreen extends HookConsumerWidget {
|
|||||||
uuid.v4();
|
uuid.v4();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final optionsLoaded = ref
|
||||||
|
.read(
|
||||||
|
singboxProxyProfilesRepositoryProvider,
|
||||||
|
)
|
||||||
|
.hasValue;
|
||||||
|
|
||||||
final outcome =
|
final outcome =
|
||||||
await showModalBottomSheet<
|
await showModalBottomSheet<
|
||||||
_ProxyPickerOutcome
|
_ProxyPickerOutcome
|
||||||
@@ -410,6 +426,7 @@ class ContainerEditScreen extends HookConsumerWidget {
|
|||||||
builder: (context) {
|
builder: (context) {
|
||||||
return _ProxyConnectionPickerSheet(
|
return _ProxyConnectionPickerSheet(
|
||||||
options: proxyOptions,
|
options: proxyOptions,
|
||||||
|
optionsLoaded: optionsLoaded,
|
||||||
selectedProxyConnectionId:
|
selectedProxyConnectionId:
|
||||||
proxyConnectionId.value,
|
proxyConnectionId.value,
|
||||||
);
|
);
|
||||||
@@ -586,10 +603,12 @@ class _ProxyPickerSelected extends _ProxyPickerOutcome {
|
|||||||
|
|
||||||
class _ProxyConnectionPickerSheet extends StatelessWidget {
|
class _ProxyConnectionPickerSheet extends StatelessWidget {
|
||||||
final List<ProxyConnectionOption> options;
|
final List<ProxyConnectionOption> options;
|
||||||
|
final bool optionsLoaded;
|
||||||
final ProxyConnectionId? selectedProxyConnectionId;
|
final ProxyConnectionId? selectedProxyConnectionId;
|
||||||
|
|
||||||
const _ProxyConnectionPickerSheet({
|
const _ProxyConnectionPickerSheet({
|
||||||
required this.options,
|
required this.options,
|
||||||
|
required this.optionsLoaded,
|
||||||
required this.selectedProxyConnectionId,
|
required this.selectedProxyConnectionId,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -597,7 +616,8 @@ class _ProxyConnectionPickerSheet extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final hasUnknownSelectedProxy =
|
final hasUnknownSelectedProxy =
|
||||||
selectedProxyConnectionId != null &&
|
selectedProxyConnectionId != null &&
|
||||||
!options.any((option) => option.id == selectedProxyConnectionId);
|
optionsLoaded &&
|
||||||
|
!proxyConnectionOptionExists(options, selectedProxyConnectionId!);
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: RadioGroup<ProxyConnectionId?>(
|
child: RadioGroup<ProxyConnectionId?>(
|
||||||
|
|||||||
+5
@@ -32,6 +32,7 @@ import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/c
|
|||||||
import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/utils/container_icons.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/utils/container_icons.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/presentation/controllers/ensure_proxy_started.dart';
|
import 'package:weblibre/features/proxy/presentation/controllers/ensure_proxy_started.dart';
|
||||||
import 'package:weblibre/presentation/widgets/failure_widget.dart';
|
import 'package:weblibre/presentation/widgets/failure_widget.dart';
|
||||||
|
|
||||||
@@ -183,6 +184,9 @@ class _ContainerCard extends HookConsumerWidget {
|
|||||||
final tabCount = container.tabCount ?? 0;
|
final tabCount = container.tabCount ?? 0;
|
||||||
final palette = ContainerColors.palette(context, containerColor);
|
final palette = ContainerColors.palette(context, containerColor);
|
||||||
final proxyOptions = ref.watch(proxyConnectionOptionsProvider);
|
final proxyOptions = ref.watch(proxyConnectionOptionsProvider);
|
||||||
|
final proxyOptionsState = ref.watch(singboxProxyProfilesRepositoryProvider);
|
||||||
|
final proxyOptionsLoading =
|
||||||
|
proxyOptionsState.isLoading && !proxyOptionsState.hasValue;
|
||||||
|
|
||||||
return AnimatedContainer(
|
return AnimatedContainer(
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
@@ -253,6 +257,7 @@ class _ContainerCard extends HookConsumerWidget {
|
|||||||
label: proxyConnectionTitle(
|
label: proxyConnectionTitle(
|
||||||
proxyOptions,
|
proxyOptions,
|
||||||
container.metadata.proxyConnectionId!,
|
container.metadata.proxyConnectionId!,
|
||||||
|
isLoading: proxyOptionsLoading,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (container.metadata.clearDataOnExit)
|
if (container.metadata.clearDataOnExit)
|
||||||
|
|||||||
+5
@@ -35,6 +35,7 @@ import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/c
|
|||||||
import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/utils/container_icons.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/utils/container_icons.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/presentation/widgets/failure_widget.dart';
|
import 'package:weblibre/presentation/widgets/failure_widget.dart';
|
||||||
|
|
||||||
class ContainerSelectionScreen extends HookConsumerWidget {
|
class ContainerSelectionScreen extends HookConsumerWidget {
|
||||||
@@ -242,6 +243,9 @@ class _SelectionContainerCard extends ConsumerWidget {
|
|||||||
final palette = ContainerColors.palette(context, containerColor);
|
final palette = ContainerColors.palette(context, containerColor);
|
||||||
|
|
||||||
final proxyOptions = ref.watch(proxyConnectionOptionsProvider);
|
final proxyOptions = ref.watch(proxyConnectionOptionsProvider);
|
||||||
|
final proxyOptionsState = ref.watch(singboxProxyProfilesRepositoryProvider);
|
||||||
|
final proxyOptionsLoading =
|
||||||
|
proxyOptionsState.isLoading && !proxyOptionsState.hasValue;
|
||||||
|
|
||||||
return AnimatedContainer(
|
return AnimatedContainer(
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
@@ -315,6 +319,7 @@ class _SelectionContainerCard extends ConsumerWidget {
|
|||||||
label: proxyConnectionTitle(
|
label: proxyConnectionTitle(
|
||||||
proxyOptions,
|
proxyOptions,
|
||||||
container.metadata.proxyConnectionId!,
|
container.metadata.proxyConnectionId!,
|
||||||
|
isLoading: proxyOptionsLoading,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (container.metadata.clearDataOnExit)
|
if (container.metadata.clearDataOnExit)
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ import 'package:weblibre/features/proxy/domain/repositories/singbox_proxy_profil
|
|||||||
|
|
||||||
part 'proxy_connection_options.g.dart';
|
part 'proxy_connection_options.g.dart';
|
||||||
|
|
||||||
|
const loadingProxyTitle = 'Loading proxy...';
|
||||||
|
const unknownProxyTitle = 'Unknown proxy';
|
||||||
|
|
||||||
class ProxyConnectionOption with FastEquatable {
|
class ProxyConnectionOption with FastEquatable {
|
||||||
final ProxyConnectionId id;
|
final ProxyConnectionId id;
|
||||||
final String title;
|
final String title;
|
||||||
@@ -72,13 +75,21 @@ List<ProxyConnectionOption> proxyConnectionOptions(Ref ref) {
|
|||||||
|
|
||||||
String proxyConnectionTitle(
|
String proxyConnectionTitle(
|
||||||
List<ProxyConnectionOption> options,
|
List<ProxyConnectionOption> options,
|
||||||
ProxyConnectionId proxyConnectionId,
|
ProxyConnectionId proxyConnectionId, {
|
||||||
) {
|
bool isLoading = false,
|
||||||
|
}) {
|
||||||
for (final option in options) {
|
for (final option in options) {
|
||||||
if (option.id == proxyConnectionId) {
|
if (option.id == proxyConnectionId) {
|
||||||
return option.title;
|
return option.title;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'Unknown proxy';
|
return isLoading ? loadingProxyTitle : unknownProxyTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool proxyConnectionOptionExists(
|
||||||
|
List<ProxyConnectionOption> options,
|
||||||
|
ProxyConnectionId proxyConnectionId,
|
||||||
|
) {
|
||||||
|
return options.any((option) => option.id == proxyConnectionId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ final class SingboxProxyRuntimeRepositoryProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$singboxProxyRuntimeRepositoryHash() =>
|
String _$singboxProxyRuntimeRepositoryHash() =>
|
||||||
r'bb84ab57abd3b7261105da85d3e047cef1b31a44';
|
r'660543fd3f82a5fcc5c3c85b11d0a98d472638af';
|
||||||
|
|
||||||
abstract class _$SingboxProxyRuntimeRepository
|
abstract class _$SingboxProxyRuntimeRepository
|
||||||
extends $AsyncNotifier<SingboxProxyRuntimeState> {
|
extends $AsyncNotifier<SingboxProxyRuntimeState> {
|
||||||
|
|||||||
+32
-2
@@ -24,6 +24,7 @@ import 'package:weblibre/core/logger.dart';
|
|||||||
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.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_runtime.dart';
|
import 'package:weblibre/features/proxy/domain/repositories/singbox_proxy_runtime.dart';
|
||||||
import 'package:weblibre/features/tor/presentation/controllers/start_tor_proxy.dart';
|
import 'package:weblibre/features/tor/presentation/controllers/start_tor_proxy.dart';
|
||||||
import 'package:weblibre/features/tor/presentation/widgets/tor_dialog.dart';
|
import 'package:weblibre/features/tor/presentation/widgets/tor_dialog.dart';
|
||||||
@@ -112,8 +113,8 @@ Future<bool> _maybeStartSingboxProxy(
|
|||||||
|
|
||||||
if (isRunning) return true;
|
if (isRunning) return true;
|
||||||
|
|
||||||
final proxyTitle = proxyConnectionTitle(
|
final proxyTitle = await _proxyConnectionTitleForPrompt(
|
||||||
ref.read(proxyConnectionOptionsProvider),
|
ref,
|
||||||
proxyConnectionId,
|
proxyConnectionId,
|
||||||
);
|
);
|
||||||
final shouldStart = await showDialog<bool>(
|
final shouldStart = await showDialog<bool>(
|
||||||
@@ -157,6 +158,35 @@ Future<bool> _maybeStartSingboxProxy(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> _proxyConnectionTitleForPrompt(
|
||||||
|
WidgetRef ref,
|
||||||
|
ProxyConnectionId proxyConnectionId,
|
||||||
|
) async {
|
||||||
|
final options = ref.read(proxyConnectionOptionsProvider);
|
||||||
|
final title = proxyConnectionTitle(options, proxyConnectionId);
|
||||||
|
|
||||||
|
if (title != unknownProxyTitle) {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proxyConnectionId is SingboxProxyConnectionId) {
|
||||||
|
try {
|
||||||
|
final profile = await ref
|
||||||
|
.read(singboxProxyProfilesRepositoryProvider.notifier)
|
||||||
|
.findProfile(proxyConnectionId.profileId);
|
||||||
|
if (profile != null) return profile.name;
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
logger.w(
|
||||||
|
'Failed to resolve sing-box proxy profile ${proxyConnectionId.profileId} for prompt title',
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxyConnectionTitle(options, proxyConnectionId);
|
||||||
|
}
|
||||||
|
|
||||||
Future<SingboxProxyRuntimeState> _resolveRuntimeStateForPrompt(
|
Future<SingboxProxyRuntimeState> _resolveRuntimeStateForPrompt(
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
) async {
|
) async {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.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/settings/presentation/widgets/settings_detail.dart';
|
import 'package:weblibre/features/settings/presentation/widgets/settings_detail.dart';
|
||||||
import 'package:weblibre/features/user/data/models/proxy_routing_settings.dart';
|
import 'package:weblibre/features/user/data/models/proxy_routing_settings.dart';
|
||||||
import 'package:weblibre/features/user/domain/repositories/proxy_routing_settings.dart';
|
import 'package:weblibre/features/user/domain/repositories/proxy_routing_settings.dart';
|
||||||
@@ -129,8 +130,10 @@ class _GlobalRoutingProxySection extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final options = ref.watch(proxyConnectionOptionsProvider);
|
final options = ref.watch(proxyConnectionOptionsProvider);
|
||||||
|
final optionsState = ref.watch(singboxProxyProfilesRepositoryProvider);
|
||||||
return _ProxyConnectionPicker(
|
return _ProxyConnectionPicker(
|
||||||
options: options,
|
options: options,
|
||||||
|
optionsLoaded: optionsState.hasValue,
|
||||||
selectedId: settings.regularTabsProxyConnectionId,
|
selectedId: settings.regularTabsProxyConnectionId,
|
||||||
onChanged: (id) => ref
|
onChanged: (id) => ref
|
||||||
.read(proxyRoutingSettingsRepositoryProvider.notifier)
|
.read(proxyRoutingSettingsRepositoryProvider.notifier)
|
||||||
@@ -148,8 +151,10 @@ class _PrivateTabsProxySection extends ConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final settings = ref.watch(proxyRoutingSettingsWithDefaultsProvider);
|
final settings = ref.watch(proxyRoutingSettingsWithDefaultsProvider);
|
||||||
final options = ref.watch(proxyConnectionOptionsProvider);
|
final options = ref.watch(proxyConnectionOptionsProvider);
|
||||||
|
final optionsState = ref.watch(singboxProxyProfilesRepositoryProvider);
|
||||||
return _ProxyConnectionPicker(
|
return _ProxyConnectionPicker(
|
||||||
options: options,
|
options: options,
|
||||||
|
optionsLoaded: optionsState.hasValue,
|
||||||
selectedId: settings.privateTabsProxyConnectionId,
|
selectedId: settings.privateTabsProxyConnectionId,
|
||||||
onChanged: (id) => ref
|
onChanged: (id) => ref
|
||||||
.read(proxyRoutingSettingsRepositoryProvider.notifier)
|
.read(proxyRoutingSettingsRepositoryProvider.notifier)
|
||||||
@@ -162,11 +167,13 @@ class _PrivateTabsProxySection extends ConsumerWidget {
|
|||||||
|
|
||||||
class _ProxyConnectionPicker extends StatelessWidget {
|
class _ProxyConnectionPicker extends StatelessWidget {
|
||||||
final List<ProxyConnectionOption> options;
|
final List<ProxyConnectionOption> options;
|
||||||
|
final bool optionsLoaded;
|
||||||
final ProxyConnectionId? selectedId;
|
final ProxyConnectionId? selectedId;
|
||||||
final ValueChanged<ProxyConnectionId?> onChanged;
|
final ValueChanged<ProxyConnectionId?> onChanged;
|
||||||
|
|
||||||
const _ProxyConnectionPicker({
|
const _ProxyConnectionPicker({
|
||||||
required this.options,
|
required this.options,
|
||||||
|
required this.optionsLoaded,
|
||||||
required this.selectedId,
|
required this.selectedId,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
});
|
});
|
||||||
@@ -174,7 +181,9 @@ class _ProxyConnectionPicker extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final hasUnknownSelection =
|
final hasUnknownSelection =
|
||||||
selectedId != null && !options.any((option) => option.id == selectedId);
|
selectedId != null &&
|
||||||
|
optionsLoaded &&
|
||||||
|
!proxyConnectionOptionExists(options, selectedId!);
|
||||||
|
|
||||||
return RadioGroup<ProxyConnectionId?>(
|
return RadioGroup<ProxyConnectionId?>(
|
||||||
groupValue: selectedId,
|
groupValue: selectedId,
|
||||||
|
|||||||
+68
-1
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@@ -5,6 +7,9 @@ import 'package:weblibre/features/geckoview/features/tabs/data/models/container_
|
|||||||
import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/container_edit.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/container_edit.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/user/data/database/definitions.drift.dart'
|
||||||
|
show ProxyProfile;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets(
|
testWidgets(
|
||||||
@@ -16,6 +21,9 @@ void main() {
|
|||||||
proxyConnectionOptionsProvider.overrideWith(
|
proxyConnectionOptionsProvider.overrideWith(
|
||||||
(ref) => const <ProxyConnectionOption>[],
|
(ref) => const <ProxyConnectionOption>[],
|
||||||
),
|
),
|
||||||
|
singboxProxyProfilesRepositoryProvider.overrideWith(
|
||||||
|
() => _LoadedProfilesRepository(const []),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: MaterialApp(
|
child: MaterialApp(
|
||||||
home: ContainerEditScreen.create(
|
home: ContainerEditScreen.create(
|
||||||
@@ -24,7 +32,9 @@ void main() {
|
|||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
orderKey: 'a',
|
orderKey: 'a',
|
||||||
metadata: ContainerMetadata.withDefaults(
|
metadata: ContainerMetadata.withDefaults(
|
||||||
proxyConnectionId: const SingboxProxyConnectionId('missing-proxy'),
|
proxyConnectionId: const SingboxProxyConnectionId(
|
||||||
|
'missing-proxy',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -56,6 +66,9 @@ void main() {
|
|||||||
proxyConnectionOptionsProvider.overrideWith(
|
proxyConnectionOptionsProvider.overrideWith(
|
||||||
(ref) => const <ProxyConnectionOption>[],
|
(ref) => const <ProxyConnectionOption>[],
|
||||||
),
|
),
|
||||||
|
singboxProxyProfilesRepositoryProvider.overrideWith(
|
||||||
|
() => _LoadedProfilesRepository(const []),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: MaterialApp(
|
child: MaterialApp(
|
||||||
home: ContainerEditScreen.create(
|
home: ContainerEditScreen.create(
|
||||||
@@ -83,4 +96,58 @@ void main() {
|
|||||||
expect(find.text('New Container'), findsOneWidget);
|
expect(find.text('New Container'), findsOneWidget);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
testWidgets('does not offer to clear selected proxy while profiles load', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
ProviderScope(
|
||||||
|
overrides: [
|
||||||
|
proxyConnectionOptionsProvider.overrideWith(
|
||||||
|
(ref) => const <ProxyConnectionOption>[],
|
||||||
|
),
|
||||||
|
singboxProxyProfilesRepositoryProvider.overrideWith(
|
||||||
|
() => _LoadingProfilesRepository(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: MaterialApp(
|
||||||
|
home: ContainerEditScreen.create(
|
||||||
|
initialContainer: ContainerData(
|
||||||
|
id: 'container-1',
|
||||||
|
color: Colors.blue,
|
||||||
|
orderKey: 'a',
|
||||||
|
metadata: ContainerMetadata.withDefaults(
|
||||||
|
proxyConnectionId: const SingboxProxyConnectionId('profile-1'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('Loading proxy...'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.text('Proxy Connection'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('Unknown proxy'), findsNothing);
|
||||||
|
expect(find.text('Clear'), findsNothing);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoadedProfilesRepository extends SingboxProxyProfilesRepository {
|
||||||
|
final List<ProxyProfile> profiles;
|
||||||
|
|
||||||
|
_LoadedProfilesRepository(this.profiles);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<List<ProxyProfile>> build() => Stream.value(profiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoadingProfilesRepository extends SingboxProxyProfilesRepository {
|
||||||
|
@override
|
||||||
|
Stream<List<ProxyProfile>> build() async* {
|
||||||
|
await Completer<void>().future;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+75
-4
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
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';
|
||||||
@@ -5,8 +7,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.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_runtime.dart';
|
import 'package:weblibre/features/proxy/domain/repositories/singbox_proxy_runtime.dart';
|
||||||
import 'package:weblibre/features/proxy/presentation/controllers/ensure_proxy_started.dart';
|
import 'package:weblibre/features/proxy/presentation/controllers/ensure_proxy_started.dart';
|
||||||
|
import 'package:weblibre/features/user/data/database/definitions.drift.dart'
|
||||||
|
show ProxyProfile;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets(
|
testWidgets(
|
||||||
@@ -47,10 +52,7 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.text('Start Proxy Connection?'), findsOneWidget);
|
expect(find.text('Start Proxy Connection?'), findsOneWidget);
|
||||||
expect(
|
expect(find.textContaining('This tab needs Mullvad'), findsOneWidget);
|
||||||
find.textContaining('This tab needs Mullvad'),
|
|
||||||
findsOneWidget,
|
|
||||||
);
|
|
||||||
|
|
||||||
await tester.tap(find.text('Start'));
|
await tester.tap(find.text('Start'));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
@@ -59,6 +61,56 @@ void main() {
|
|||||||
expect(find.text('result:true'), findsOneWidget);
|
expect(find.text('result:true'), findsOneWidget);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
testWidgets('resolves sing-box prompt title while profile options load', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final runtimeRepository = _ErrorRuntimeRepository();
|
||||||
|
final profilesRepository = _LoadingProfilesRepository([
|
||||||
|
_profile(id: 'profile-1', name: 'Mullvad'),
|
||||||
|
]);
|
||||||
|
final container = ContainerData(
|
||||||
|
id: 'container-1',
|
||||||
|
color: Colors.blue,
|
||||||
|
orderKey: 'a',
|
||||||
|
metadata: ContainerMetadata.withDefaults(
|
||||||
|
proxyConnectionId: const SingboxProxyConnectionId('profile-1'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
ProviderScope(
|
||||||
|
overrides: [
|
||||||
|
singboxProxyRuntimeRepositoryProvider.overrideWith(
|
||||||
|
() => runtimeRepository,
|
||||||
|
),
|
||||||
|
singboxProxyProfilesRepositoryProvider.overrideWith(
|
||||||
|
() => profilesRepository,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: MaterialApp(home: _EnsureProxyHarness(container: container)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.text('Open container'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.textContaining('This tab needs Mullvad'), findsOneWidget);
|
||||||
|
expect(find.textContaining('Unknown proxy'), findsNothing);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ProxyProfile _profile({required String id, required String name}) {
|
||||||
|
final createdAt = DateTime(2026);
|
||||||
|
return ProxyProfile(
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
type: SingboxProxyProfileType.customOutbound,
|
||||||
|
configJson: '{"type":"socks"}',
|
||||||
|
createdAt: createdAt,
|
||||||
|
updatedAt: createdAt,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EnsureProxyHarness extends ConsumerStatefulWidget {
|
class _EnsureProxyHarness extends ConsumerStatefulWidget {
|
||||||
@@ -128,3 +180,22 @@ class _ErrorRuntimeRepository extends SingboxProxyRuntimeRepository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _LoadingProfilesRepository extends SingboxProxyProfilesRepository {
|
||||||
|
final List<ProxyProfile> profiles;
|
||||||
|
|
||||||
|
_LoadingProfilesRepository(this.profiles);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<List<ProxyProfile>> build() async* {
|
||||||
|
await Completer<void>().future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<ProxyProfile?> findProfile(String id) async {
|
||||||
|
for (final profile in profiles) {
|
||||||
|
if (profile.id == id) return profile;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user