diff --git a/apps/weblibre/lib/domain/services/generic_website.g.dart b/apps/weblibre/lib/domain/services/generic_website.g.dart index e7e35286..e9a66b9f 100644 --- a/apps/weblibre/lib/domain/services/generic_website.g.dart +++ b/apps/weblibre/lib/domain/services/generic_website.g.dart @@ -130,7 +130,7 @@ final class GenericWebsiteServiceProvider } String _$genericWebsiteServiceHash() => - r'83c62a15492606a7ffe4a7990fc1f9bdbfee87cc'; + r'189cbd17a06be382859c697221355cd623608123'; abstract class _$GenericWebsiteService extends $Notifier { void build(); diff --git a/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.g.dart b/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.g.dart index d0ca8897..072fd461 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.g.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.g.dart @@ -1186,7 +1186,7 @@ final class GroupedTabListItemsProvider } String _$groupedTabListItemsHash() => - r'15106bfa42146270245c7178928a834237042c1c'; + r'dbb510f22c00858fcbadfb94547d46841221fca4'; /// Grouped flat-list rendering for the list and grid views. /// diff --git a/apps/weblibre/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.dart b/apps/weblibre/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.dart index 5ebc9622..0a8d497a 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.dart @@ -26,9 +26,7 @@ import 'package:weblibre/features/geckoview/features/tabs/data/models/container_ import 'package:weblibre/features/geckoview/features/tabs/data/providers.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart'; -import 'package:weblibre/features/proxy/data/proxy_connection.dart'; import 'package:weblibre/features/proxy/domain/repositories/container_proxy.dart'; -import 'package:weblibre/features/proxy/domain/repositories/singbox_proxy_runtime.dart'; import 'package:weblibre/features/tor/domain/services/tor_proxy.dart'; import 'package:weblibre/features/user/data/models/proxy_routing_settings.dart'; import 'package:weblibre/features/user/domain/repositories/proxy_routing_settings.dart'; @@ -43,25 +41,6 @@ class ProxySettingsReplication extends _$ProxySettingsReplication { final _recomputeLock = Lock(); var _recomputeDirty = false; - Future _ensureProxyConnectionAvailable( - ProxyConnectionId proxyConnectionId, - ) async { - if (proxyConnectionId is! SingboxProxyConnectionId) return; - - try { - await ref - .read(singboxProxyRuntimeRepositoryProvider.notifier) - .ensureProxyConnectionAvailable(proxyConnectionId); - } catch (error, stackTrace) { - logger.e( - 'Failed to make proxy connection $proxyConnectionId available; ' - 'assigned traffic will remain blocked', - error: error, - stackTrace: stackTrace, - ); - } - } - Future _queueIsolatedProxyAliasesRecompute() async { _recomputeDirty = true; if (_recomputeLock.inLock) return; @@ -187,7 +166,6 @@ class ProxySettingsReplication extends _$ProxySettingsReplication { await containerProxy.clearContainerProxy('private'); } else { await containerProxy.setContainerProxy('private', next.encode()); - await _ensureProxyConnectionAvailable(next); } }, onError: (error, stackTrace) { @@ -220,7 +198,6 @@ class ProxySettingsReplication extends _$ProxySettingsReplication { 'general', proxyConnectionId.encode(), ); - await _ensureProxyConnectionAvailable(proxyConnectionId); } } }, @@ -315,10 +292,6 @@ class ProxySettingsReplication extends _$ProxySettingsReplication { } try { if (value != null) { - final proxyConnectionId = ProxyConnectionId.decode(value); - if (proxyConnectionId != null) { - await _ensureProxyConnectionAvailable(proxyConnectionId); - } await repo.setContainerProxy(key, value); } else { await repo.clearContainerProxy(key); diff --git a/apps/weblibre/lib/features/geckoview/features/browser/presentation/screens/browser.dart b/apps/weblibre/lib/features/geckoview/features/browser/presentation/screens/browser.dart index 9c7d6af1..e30d5bf0 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/presentation/screens/browser.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/presentation/screens/browser.dart @@ -58,14 +58,19 @@ import 'package:weblibre/features/geckoview/features/find_in_page/presentation/c import 'package:weblibre/features/geckoview/features/find_in_page/presentation/widgets/find_in_page.dart'; import 'package:weblibre/features/geckoview/features/readerview/presentation/controllers/readerable.dart'; import 'package:weblibre/features/geckoview/features/search/domain/providers/search_autofocus.dart'; +import 'package:weblibre/features/geckoview/features/tabs/data/entities/tab_mode.dart'; +import 'package:weblibre/features/geckoview/features/tabs/data/providers.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/tab.dart'; +import 'package:weblibre/features/proxy/data/proxy_connection.dart'; import 'package:weblibre/features/proxy/presentation/controllers/ensure_proxy_started.dart'; import 'package:weblibre/features/small_web/presentation/controllers/small_web_mode_controller.dart'; import 'package:weblibre/features/small_web/presentation/widgets/small_web_browser_overlay.dart'; import 'package:weblibre/features/sync/domain/repositories/sync.dart'; import 'package:weblibre/features/user/data/models/general_settings.dart'; +import 'package:weblibre/features/user/data/models/proxy_routing_settings.dart'; import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; +import 'package:weblibre/features/user/domain/repositories/proxy_routing_settings.dart'; import 'package:weblibre/utils/move_to_background.dart'; import 'package:weblibre/utils/ui_helper.dart' as ui_helper; @@ -230,6 +235,86 @@ class _TabBar extends HookConsumerWidget { } } +Future _proxyConnectionIdForLoadError( + WidgetRef ref, { + required String tabId, + required String? contextId, +}) async { + final routing = ref.read(proxyRoutingSettingsWithDefaultsProvider); + final tabState = ref.read(tabStateProvider(tabId)); + + if (contextId == 'private' || tabState?.tabMode is PrivateTabMode) { + return routing.privateTabsProxyConnectionId; + } + + if (contextId != null && contextId.isNotEmpty && contextId != 'general') { + final isolatedContextProxyConnectionId = + await _isolatedContextProxyConnectionId(ref, contextId); + if (isolatedContextProxyConnectionId != null) { + return isolatedContextProxyConnectionId; + } + + final contextContainer = await ref + .read(containerRepositoryProvider.notifier) + .getContainerByContextualIdentity(contextId); + final contextProxyConnectionId = + contextContainer?.metadata.proxyConnectionId; + if (contextProxyConnectionId != null) { + return contextProxyConnectionId; + } + } + + final tabContainer = await ref + .read(tabDataRepositoryProvider.notifier) + .getTabContainerData(tabId); + final tabProxyConnectionId = tabContainer?.metadata.proxyConnectionId; + if (tabProxyConnectionId != null) { + return tabProxyConnectionId; + } + + if (routing.regularTabsMode == ProxyRegularTabRoutingMode.all) { + return routing.regularTabsProxyConnectionId; + } + + return null; +} + +Future _isolatedContextProxyConnectionId( + WidgetRef ref, + String contextId, +) async { + final db = ref.read(tabDatabaseProvider); + final pairs = await db.tabDao.isolatedContextContainerPairs().get(); + final containerIds = pairs + .where((pair) => pair.isolationContextId == contextId) + .map((pair) => pair.containerId) + .nonNulls + .toSet(); + + if (containerIds.isEmpty) return null; + + final containers = await ref + .read(containerRepositoryProvider.notifier) + .getAllContainersWithCount(); + final proxyIds = + containers + .where((container) => containerIds.contains(container.id)) + .map((container) => container.metadata.proxyConnectionId?.encode()) + .nonNulls + .toSet() + .toList() + ..sort(); + + if (proxyIds.isEmpty) return null; + return ProxyConnectionId.decode(proxyIds.first); +} + +typedef _PendingProxyLoadError = ({ + String? contextId, + String errorType, + String? url, +}); + class BrowserScreen extends HookConsumerWidget { const BrowserScreen({super.key}); @@ -238,52 +323,89 @@ class BrowserScreen extends HookConsumerWidget { final eventService = ref.watch(eventServiceProvider); final viewportService = ref.watch(viewportServiceProvider); final activeProxyPromptKeys = useRef({}); + final pendingProxyLoadErrors = useRef({}); + final selectedTabIdForProxyPrompt = ref.watch(selectedTabProvider); + + Future handleProxyLoadError({ + required String tabId, + required String? contextId, + required String? url, + required String errorType, + }) async { + final promptKey = '$tabId:${url ?? errorType}'; + if (!activeProxyPromptKeys.value.add(promptKey)) return; + + try { + final proxyConnectionId = await _proxyConnectionIdForLoadError( + ref, + tabId: tabId, + contextId: contextId, + ); + + if (!context.mounted || proxyConnectionId == null) return; + + final isProxyStarted = await ensureProxyStartedForConnection( + context, + ref, + proxyConnectionId, + ); + + if (isProxyStarted && + context.mounted && + ref.read(selectedTabProvider) == tabId) { + await ref.read(tabSessionProvider(tabId: tabId).notifier).reload(); + } + } catch (error, stackTrace) { + logger.e( + 'Failed to handle proxy load error', + error: error, + stackTrace: stackTrace, + ); + } finally { + activeProxyPromptKeys.value.remove(promptKey); + } + } + + useEffect(() { + final tabId = selectedTabIdForProxyPrompt; + if (tabId == null) return null; + + final pending = pendingProxyLoadErrors.value.remove(tabId); + if (pending == null) return null; + + unawaited( + handleProxyLoadError( + tabId: tabId, + contextId: pending.contextId, + url: pending.url, + errorType: pending.errorType, + ), + ); + return null; + }, [selectedTabIdForProxyPrompt]); useOnStreamChange( eventService.proxyLoadErrorEvents, onData: (event) async { final selectedTabId = ref.read(selectedTabProvider); final tabId = event.tabId ?? selectedTabId; - if (tabId == null || tabId != selectedTabId) return; + if (tabId == null) return; - final promptKey = '$tabId:${event.url ?? event.errorType}'; - if (!activeProxyPromptKeys.value.add(promptKey)) return; - - try { - final contextId = event.contextId; - final contextContainer = contextId != null && contextId.isNotEmpty - ? await ref - .read(containerRepositoryProvider.notifier) - .getContainerByContextualIdentity(contextId) - : null; - final container = - contextContainer ?? - await ref - .read(tabDataRepositoryProvider.notifier) - .getTabContainerData(tabId); - - if (!context.mounted || container == null) return; - - final isProxyStarted = await ensureProxyStartedForContainer( - context, - ref, - container, + if (tabId != selectedTabId) { + pendingProxyLoadErrors.value[tabId] = ( + contextId: event.contextId, + errorType: event.errorType, + url: event.url, ); - - if (isProxyStarted && - context.mounted && - ref.read(selectedTabProvider) == tabId) { - await ref.read(tabSessionProvider(tabId: tabId).notifier).reload(); - } - } catch (error, stackTrace) { - logger.e( - 'Failed to handle proxy load error', - error: error, - stackTrace: stackTrace, - ); - } finally { - activeProxyPromptKeys.value.remove(promptKey); + return; } + + await handleProxyLoadError( + tabId: tabId, + contextId: event.contextId, + url: event.url, + errorType: event.errorType, + ); }, ); diff --git a/apps/weblibre/lib/features/geckoview/features/pwa/domain/providers.g.dart b/apps/weblibre/lib/features/geckoview/features/pwa/domain/providers.g.dart index 29f9abb3..94605202 100644 --- a/apps/weblibre/lib/features/geckoview/features/pwa/domain/providers.g.dart +++ b/apps/weblibre/lib/features/geckoview/features/pwa/domain/providers.g.dart @@ -272,7 +272,7 @@ final class InstallCurrentWebAppProvider } String _$installCurrentWebAppHash() => - r'0e0013fac7b70441983f7af1e6db3c6470c23900'; + r'6eeed094b9ea9ade946dab77c691f271c92f89e1'; /// Installs the current tab as a PWA, embedding profile and container context /// in the shortcut intent so the PWA reopens with the same isolation. @@ -460,7 +460,7 @@ final class InstallBasicShortcutProvider } String _$installBasicShortcutHash() => - r'fcb5bec79f375b32a7a0953ad22859a0db222167'; + r'7155266e635d0016e4d3ed407bc6d8af61434b0f'; /// Creates a basic bookmark shortcut on the home screen for the current tab. diff --git a/apps/weblibre/lib/features/proxy/domain/repositories/singbox_proxy_runtime.dart b/apps/weblibre/lib/features/proxy/domain/repositories/singbox_proxy_runtime.dart index 7095c027..bc54eb24 100644 --- a/apps/weblibre/lib/features/proxy/domain/repositories/singbox_proxy_runtime.dart +++ b/apps/weblibre/lib/features/proxy/domain/repositories/singbox_proxy_runtime.dart @@ -139,23 +139,6 @@ class SingboxProxyRuntimeRepository extends _$SingboxProxyRuntimeRepository { }); } - Future ensureProxyConnectionAvailable( - SingboxProxyConnectionId connectionId, - ) async { - await _lock.synchronized(() async { - final currentState = await _stateSnapshotUnlocked(); - final isRunning = currentState.endpoints.any( - (endpoint) => endpoint.profileId == connectionId.encode(), - ); - if (isRunning) return; - - final activeProfileIds = _activeProfileIds(currentState); - await _startProfilesUnlocked( - {...activeProfileIds, connectionId.profileId}.toList(), - ); - }); - } - Set _activeProfileIds(SingboxProxyRuntimeState runtimeState) { return runtimeState.endpoints .map((endpoint) => ProxyConnectionId.decode(endpoint.profileId)) diff --git a/apps/weblibre/lib/features/proxy/domain/repositories/singbox_proxy_runtime.g.dart b/apps/weblibre/lib/features/proxy/domain/repositories/singbox_proxy_runtime.g.dart index f3c7fcf9..52be74f3 100644 --- a/apps/weblibre/lib/features/proxy/domain/repositories/singbox_proxy_runtime.g.dart +++ b/apps/weblibre/lib/features/proxy/domain/repositories/singbox_proxy_runtime.g.dart @@ -87,7 +87,7 @@ final class SingboxProxyRuntimeRepositoryProvider } String _$singboxProxyRuntimeRepositoryHash() => - r'665908eaff34569f7a19dc5328a067711d1937f2'; + r'bb84ab57abd3b7261105da85d3e047cef1b31a44'; abstract class _$SingboxProxyRuntimeRepository extends $AsyncNotifier { diff --git a/apps/weblibre/lib/features/proxy/domain/services/proxy_input_consumer.g.dart b/apps/weblibre/lib/features/proxy/domain/services/proxy_input_consumer.g.dart index ca27fb3f..d05f2bc2 100644 --- a/apps/weblibre/lib/features/proxy/domain/services/proxy_input_consumer.g.dart +++ b/apps/weblibre/lib/features/proxy/domain/services/proxy_input_consumer.g.dart @@ -42,7 +42,7 @@ final class ProxyInputConsumerProvider } String _$proxyInputConsumerHash() => - r'fed529f253a9bdf72d0b1c23a298765b681d07b8'; + r'8958cb3ff089779c1991390a7d6062cef74b0588'; abstract class _$ProxyInputConsumer extends $Notifier { void build(); diff --git a/apps/weblibre/lib/features/proxy/domain/services/singbox_proxy_endpoint_sync.g.dart b/apps/weblibre/lib/features/proxy/domain/services/singbox_proxy_endpoint_sync.g.dart index db579c0e..be90deca 100644 --- a/apps/weblibre/lib/features/proxy/domain/services/singbox_proxy_endpoint_sync.g.dart +++ b/apps/weblibre/lib/features/proxy/domain/services/singbox_proxy_endpoint_sync.g.dart @@ -81,7 +81,7 @@ final class SingboxProxyEndpointSyncProvider } String _$singboxProxyEndpointSyncHash() => - r'2d6b0641db33638b0b339b510dd63cdddab7f7cf'; + r'8aeb4097d02c2f6ea37fdb2de26b399d2e6042c2'; /// Mirrors the sing-box runtime's active SOCKS endpoints into Gecko's /// container-proxy registry. Listens to [singboxProxyRuntimeRepositoryProvider] diff --git a/apps/weblibre/lib/features/proxy/presentation/controllers/ensure_proxy_started.dart b/apps/weblibre/lib/features/proxy/presentation/controllers/ensure_proxy_started.dart index aee35410..09b5eae5 100644 --- a/apps/weblibre/lib/features/proxy/presentation/controllers/ensure_proxy_started.dart +++ b/apps/weblibre/lib/features/proxy/presentation/controllers/ensure_proxy_started.dart @@ -37,7 +37,18 @@ Future ensureProxyStartedForContainer( WidgetRef ref, ContainerData container, ) async { - final proxyConnectionId = container.metadata.proxyConnectionId; + return ensureProxyStartedForConnection( + context, + ref, + container.metadata.proxyConnectionId, + ); +} + +Future ensureProxyStartedForConnection( + BuildContext context, + WidgetRef ref, + ProxyConnectionId? proxyConnectionId, +) async { if (proxyConnectionId == null) return true; if (proxyConnectionId is TorProxyConnectionId) { @@ -45,7 +56,7 @@ Future ensureProxyStartedForContainer( } if (proxyConnectionId is SingboxProxyConnectionId) { - return await _maybeStartSingboxProxyForContainer(context, ref, container); + return await _maybeStartSingboxProxy(context, ref, proxyConnectionId); } return true; @@ -71,16 +82,11 @@ Future _ensureTorStarted(BuildContext context, WidgetRef ref) async { return false; } -Future _maybeStartSingboxProxyForContainer( +Future _maybeStartSingboxProxy( BuildContext context, WidgetRef ref, - ContainerData container, + SingboxProxyConnectionId proxyConnectionId, ) async { - final proxyConnectionId = container.metadata.proxyConnectionId; - if (proxyConnectionId == null) return true; - - if (proxyConnectionId is! SingboxProxyConnectionId) return true; - // Await any start/stop in flight so we don't prompt the user a second time // while a start they already triggered is still resolving. If the runtime // provider is already in AsyncError, keep treating that as "not running" so @@ -116,7 +122,7 @@ Future _maybeStartSingboxProxyForContainer( icon: const Icon(Icons.route_outlined), title: const Text('Start Proxy Connection?'), content: Text( - 'This container uses $proxyTitle, but that connection is not running. Start it now?', + 'This tab needs $proxyTitle, but that connection is not running. Start it now?', ), actions: [ TextButton( diff --git a/apps/weblibre/lib/features/search_credits/domain/providers.g.dart b/apps/weblibre/lib/features/search_credits/domain/providers.g.dart index a741a0e7..25a08f8d 100644 --- a/apps/weblibre/lib/features/search_credits/domain/providers.g.dart +++ b/apps/weblibre/lib/features/search_credits/domain/providers.g.dart @@ -54,7 +54,7 @@ final class SearchBackendEndpointsProvider } String _$searchBackendEndpointsHash() => - r'f7249075bdf7a83271f7f9030dd12a3a9d2bd483'; + r'e2f367530cac69a2833b75520d1ac66605822f80'; @ProviderFor(searchClientLogger) final searchClientLoggerProvider = SearchClientLoggerProvider._(); diff --git a/apps/weblibre/lib/features/share_intent/domain/services/sharing_intent.g.dart b/apps/weblibre/lib/features/share_intent/domain/services/sharing_intent.g.dart index f48864ed..fb4291ee 100644 --- a/apps/weblibre/lib/features/share_intent/domain/services/sharing_intent.g.dart +++ b/apps/weblibre/lib/features/share_intent/domain/services/sharing_intent.g.dart @@ -110,7 +110,7 @@ final class SharingIntentStreamProvider } String _$sharingIntentStreamHash() => - r'02cef0c00bcb9416f8daf420977f58764c8efaca'; + r'62e9e54bfa168e7400c4c496d0aca0972ccdf4c1'; /// Stream of account callback handoff codes extracted from deep link intents. diff --git a/apps/weblibre/lib/features/web_search/domain/controllers/sandbox_capture_controller.g.dart b/apps/weblibre/lib/features/web_search/domain/controllers/sandbox_capture_controller.g.dart index 9a2ce309..9a129971 100644 --- a/apps/weblibre/lib/features/web_search/domain/controllers/sandbox_capture_controller.g.dart +++ b/apps/weblibre/lib/features/web_search/domain/controllers/sandbox_capture_controller.g.dart @@ -374,7 +374,7 @@ final class SandboxCaptureErrorsProvider } String _$sandboxCaptureErrorsHash() => - r'd6ef9ebba2515f8177a380b822f344128e4ea8c4'; + r'da9021ba394d2769140408577983bbcf45b0e0fe'; /// Orchestrates sandbox capture browsing: /// @@ -440,7 +440,7 @@ final class SandboxCaptureControllerProvider } String _$sandboxCaptureControllerHash() => - r'fb582cb09504dafd1b9addc1bfc0e6f58fabc9b1'; + r'6ddcb0df7194f3e35ac0eee3c182204a990f9651'; /// Orchestrates sandbox capture browsing: /// diff --git a/apps/weblibre/lib/features/web_search/domain/controllers/search_controller.g.dart b/apps/weblibre/lib/features/web_search/domain/controllers/search_controller.g.dart index 34d17ea2..898ec4c6 100644 --- a/apps/weblibre/lib/features/web_search/domain/controllers/search_controller.g.dart +++ b/apps/weblibre/lib/features/web_search/domain/controllers/search_controller.g.dart @@ -42,7 +42,7 @@ final class MetaSearchControllerProvider } String _$metaSearchControllerHash() => - r'afb6600f78717df124a6afdb80710eba74666d23'; + r'74ea7c616213dae53d3ac18b2677b797f5f87d53'; abstract class _$MetaSearchController extends $Notifier { MetaSearchState build(); @@ -108,7 +108,7 @@ final class WebSearchScrollOffsetProvider } String _$webSearchScrollOffsetHash() => - r'8d622d93aba7712b2bf0e6da7cbde6c90ee00c07'; + r'404573c2e5e52a0f260d182b7947f4878df45820'; /// Persisted vertical scroll offset of the web-search results list. Kept /// alive (like [MetaSearchController]) so returning to the search screen diff --git a/apps/weblibre/test/features/geckoview/features/bookmarks/utils/bookmark_html_utils_test.mocks.dart b/apps/weblibre/test/features/geckoview/features/bookmarks/utils/bookmark_html_utils_test.mocks.dart index 914afa5b..6112b7dc 100644 --- a/apps/weblibre/test/features/geckoview/features/bookmarks/utils/bookmark_html_utils_test.mocks.dart +++ b/apps/weblibre/test/features/geckoview/features/bookmarks/utils/bookmark_html_utils_test.mocks.dart @@ -12,6 +12,7 @@ import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i5; // ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use diff --git a/apps/weblibre/test/features/geckoview/features/bookmarks/utils/bookmark_json_utils_test.mocks.dart b/apps/weblibre/test/features/geckoview/features/bookmarks/utils/bookmark_json_utils_test.mocks.dart index 7e546fba..b5a7657a 100644 --- a/apps/weblibre/test/features/geckoview/features/bookmarks/utils/bookmark_json_utils_test.mocks.dart +++ b/apps/weblibre/test/features/geckoview/features/bookmarks/utils/bookmark_json_utils_test.mocks.dart @@ -12,6 +12,7 @@ import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i5; // ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use diff --git a/apps/weblibre/test/features/geckoview/features/browser/domain/services/proxy_settings_replication_test.dart b/apps/weblibre/test/features/geckoview/features/browser/domain/services/proxy_settings_replication_test.dart index 365e6718..9b685104 100644 --- a/apps/weblibre/test/features/geckoview/features/browser/domain/services/proxy_settings_replication_test.dart +++ b/apps/weblibre/test/features/geckoview/features/browser/domain/services/proxy_settings_replication_test.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:ui'; import 'package:drift/native.dart'; -import 'package:flutter_singbox_proxy/flutter_singbox_proxy.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_tor/flutter_tor.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -17,7 +16,6 @@ import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart' import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart'; import 'package:weblibre/features/proxy/data/proxy_connection.dart'; import 'package:weblibre/features/proxy/domain/repositories/container_proxy.dart'; -import 'package:weblibre/features/proxy/domain/repositories/singbox_proxy_runtime.dart'; import 'package:weblibre/features/tor/domain/services/tor_proxy.dart'; import 'package:weblibre/features/user/data/models/proxy_routing_settings.dart'; import 'package:weblibre/features/user/domain/repositories/proxy_routing_settings.dart'; @@ -26,7 +24,7 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); test( - 'container assignments ensure sing-box profiles are running before syncing', + 'container assignments sync without starting stopped sing-box profiles', () async { const profileId = SingboxProxyConnectionId('profile-1'); final assignedContainer = _container( @@ -44,7 +42,6 @@ void main() { ); final containerProxyRepository = _FakeContainerProxyRepository(); final containerRepository = _FakeContainerRepository([assignedContainer]); - final runtimeRepository = _FakeSingboxProxyRuntimeRepository(); final container = ProviderContainer( overrides: [ tabDatabaseProvider.overrideWith((ref) => db), @@ -52,9 +49,6 @@ void main() { () => containerProxyRepository, ), containerRepositoryProvider.overrideWith(() => containerRepository), - singboxProxyRuntimeRepositoryProvider.overrideWith( - () => runtimeRepository, - ), torProxyServiceProvider.overrideWith(_FakeTorProxyService.new), proxyRoutingSettingsWithDefaultsProvider.overrideWith( (ref) => ProxyRoutingSettings.withDefaults(), @@ -83,7 +77,6 @@ void main() { addTearDown(subscription.close); await pumpEventQueue(); - expect(runtimeRepository.ensuredProxyConnectionIds, [profileId]); expect(containerProxyRepository.setContainerProxyCalls, [ ('context-a', profileId.encode()), ]); @@ -144,25 +137,6 @@ class _FakeContainerRepository extends ContainerRepository { void build() {} } -class _FakeSingboxProxyRuntimeRepository extends SingboxProxyRuntimeRepository { - final ensuredProxyConnectionIds = []; - - @override - Future ensureProxyConnectionAvailable( - SingboxProxyConnectionId connectionId, - ) async { - ensuredProxyConnectionIds.add(connectionId); - } - - @override - Future build() async { - return SingboxProxyRuntimeState( - status: SingboxProxyRuntimeStatus.stopped, - endpoints: [], - ); - } -} - class _FakeTorProxyService extends TorProxyService { @override Stream build() async* { diff --git a/apps/weblibre/test/features/proxy/domain/repositories/singbox_proxy_runtime_test.dart b/apps/weblibre/test/features/proxy/domain/repositories/singbox_proxy_runtime_test.dart index e8075bc1..78f700e4 100644 --- a/apps/weblibre/test/features/proxy/domain/repositories/singbox_proxy_runtime_test.dart +++ b/apps/weblibre/test/features/proxy/domain/repositories/singbox_proxy_runtime_test.dart @@ -22,37 +22,6 @@ import 'package:weblibre/features/user/domain/repositories/proxy_routing_setting void main() { TestWidgetsFlutterBinding.ensureInitialized(); - test( - 'ensureProxyConnectionAvailable starts an assigned stopped profile', - () async { - final profile = _profile(id: 'profile-1', name: 'First'); - final client = _FakeSingboxProxyClient(_state(const [])); - final container = _container( - client: client, - profilesRepository: _FakeProfilesRepository([profile]), - ); - addTearDown(container.dispose); - - await container.read(singboxProxyRuntimeRepositoryProvider.future); - final repository = container.read( - singboxProxyRuntimeRepositoryProvider.notifier, - ); - - await repository.ensureProxyConnectionAvailable( - SingboxProxyConnectionId(profile.id), - ); - await repository.ensureProxyConnectionAvailable( - SingboxProxyConnectionId(profile.id), - ); - - expect(client.startCalls, hasLength(1)); - expect( - client.startCalls.single.map((runtimeProfile) => runtimeProfile.id), - [profile.proxyConnectionId], - ); - }, - ); - test('startProfile preserves already-running profiles', () async { final profile1 = _profile(id: 'profile-1', name: 'First'); final profile2 = _profile(id: 'profile-2', name: 'Second'); diff --git a/apps/weblibre/test/features/proxy/presentation/controllers/ensure_proxy_started_test.dart b/apps/weblibre/test/features/proxy/presentation/controllers/ensure_proxy_started_test.dart index d31627a5..e0979cac 100644 --- a/apps/weblibre/test/features/proxy/presentation/controllers/ensure_proxy_started_test.dart +++ b/apps/weblibre/test/features/proxy/presentation/controllers/ensure_proxy_started_test.dart @@ -48,7 +48,7 @@ void main() { expect(find.text('Start Proxy Connection?'), findsOneWidget); expect( - find.textContaining('This container uses Mullvad'), + find.textContaining('This tab needs Mullvad'), findsOneWidget, ); diff --git a/apps/weblibre/test/features/settings/presentation/widgets/settings_detail_test.dart b/apps/weblibre/test/features/settings/presentation/widgets/settings_detail_test.dart index 206adab8..cd129a7a 100644 --- a/apps/weblibre/test/features/settings/presentation/widgets/settings_detail_test.dart +++ b/apps/weblibre/test/features/settings/presentation/widgets/settings_detail_test.dart @@ -17,8 +17,10 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:weblibre/features/settings/domain/providers/pending_settings_highlight.dart'; import 'package:weblibre/features/settings/presentation/widgets/settings_detail.dart'; void main() { @@ -78,4 +80,45 @@ void main() { ); }); }); + + testWidgets('clears a pending highlight after the target entry handles it', ( + tester, + ) async { + final container = ProviderContainer(); + addTearDown(container.dispose); + + container.read(pendingSettingsHighlightProvider.notifier).set('Theme'); + + await tester.pumpWidget( + UncontrolledProviderScope( + container: container, + child: MaterialApp( + home: SettingsDetailScaffold( + title: 'Appearance', + subtitle: 'Configure app appearance', + icon: Icons.palette, + sections: const [ + SettingsSectionDefinition( + title: 'Display', + entries: [ + SettingsEntryDefinition( + title: 'Theme', + child: SizedBox(height: 48, child: Text('Theme')), + ), + ], + ), + ], + ), + ), + ), + ); + + await tester.pump(); + expect(container.read(pendingSettingsHighlightProvider), isNull); + + await tester.pumpWidget(const SizedBox.shrink()); + await tester.pump(); + + expect(container.read(pendingSettingsHighlightProvider), isNull); + }); }