From a8f18e5357d16f6aa5be7476ed9e2b04364913b3 Mon Sep 17 00:00:00 2001 From: Jigar-f Date: Tue, 24 Mar 2026 18:11:18 +0530 Subject: [PATCH 1/5] added back sys tray changes --- .../provider/system_tray_notifier.dart | 250 +++++++++++++----- .../provider/system_tray_notifier.g.dart | 2 +- .../provider/server_location_notifier.dart | 45 ++-- .../provider/server_location_notifier.g.dart | 22 +- lib/features/vpn/provider/vpn_notifier.g.dart | 2 +- lib/lantern/lantern_service_notifier.g.dart | 2 +- 6 files changed, 233 insertions(+), 90 deletions(-) diff --git a/lib/features/system_tray/provider/system_tray_notifier.dart b/lib/features/system_tray/provider/system_tray_notifier.dart index ee91cfc0f2..bd30627fd0 100644 --- a/lib/features/system_tray/provider/system_tray_notifier.dart +++ b/lib/features/system_tray/provider/system_tray_notifier.dart @@ -1,8 +1,10 @@ import 'dart:io'; +import 'package:lantern/core/models/app_setting.dart'; import 'package:lantern/core/models/available_servers.dart'; import 'package:lantern/core/models/macos_extension_state.dart'; import 'package:lantern/core/models/server_location.dart'; +import 'package:lantern/features/home/provider/app_setting_notifier.dart'; import 'package:lantern/features/vpn/provider/available_servers_notifier.dart'; import 'package:lantern/features/vpn/provider/vpn_notifier.dart'; import 'package:lantern/features/window/provider/window_notifier.dart'; @@ -21,9 +23,15 @@ class SystemTrayNotifier extends _$SystemTrayNotifier with TrayListener { VPNStatus _currentStatus = VPNStatus.disconnected; bool _isUserPro = false; List _locations = []; + RoutingMode _currentRoutingMode = RoutingMode.full; + ServerLocation? _serverLocation; bool get isConnected => _currentStatus == VPNStatus.connected; + bool get _isAutoLocation => + _serverLocation?.serverType.toServerLocationType == + ServerLocationType.auto; + @override Future build() async { if (!PlatformUtils.isDesktop) return; @@ -42,48 +50,62 @@ class SystemTrayNotifier extends _$SystemTrayNotifier with TrayListener { void _initializeState() { _currentStatus = ref.read(vpnProvider); _isUserPro = ref.read(isUserProProvider); + _currentRoutingMode = ref.read(appSettingProvider).routingMode; + _serverLocation = ref.read(serverLocationProvider); } void _setupListeners() { _listenToVPNStatus(); _listenToProStatus(); _listenToAvailableServers(); + _listenToServerLocation(); + _listenToRoutingMode(); } void _listenToVPNStatus() { - ref.listen( - vpnProvider, - (previous, next) async { - _currentStatus = next; - await updateTrayMenu(); - }, - ); + ref.listen(vpnProvider, (previous, next) async { + _currentStatus = next; + await updateTrayMenu(); + }); } void _listenToProStatus() { - ref.listen( - isUserProProvider, - (previous, next) async { - _isUserPro = next; - await updateTrayMenu(); - }, - ); + ref.listen(isUserProProvider, (previous, next) async { + _isUserPro = next; + await updateTrayMenu(); + }); } void _listenToAvailableServers() { - ref.listen>( - availableServersProvider, - (previous, next) async { - final data = next.value; - _locations = data?.lantern.locations.values.toList() ?? []; - _locations.sort((a, b) { - final cmp = a.country.compareTo(b.country); - if (cmp != 0) return cmp; - return a.city.compareTo(b.city); - }); + ref.listen>(availableServersProvider, ( + previous, + next, + ) async { + final data = next.value; + _locations = data?.lantern.locations.values.toList() ?? []; + _locations.sort((a, b) { + final cmp = a.country.compareTo(b.country); + if (cmp != 0) return cmp; + return a.city.compareTo(b.city); + }); + await updateTrayMenu(); + }); + } + + void _listenToServerLocation() { + ref.listen(serverLocationProvider, (previous, next) async { + _serverLocation = next; + await updateTrayMenu(); + }); + } + + void _listenToRoutingMode() { + ref.listen(appSettingProvider, (previous, next) async { + if (previous?.routingMode != next.routingMode) { + _currentRoutingMode = next.routingMode; await updateTrayMenu(); - }, - ); + } + }); } Future toggleVPN() async { @@ -97,43 +119,96 @@ class SystemTrayNotifier extends _$SystemTrayNotifier with TrayListener { /// Handle location selection from tray menu Future _onLocationSelected(Location_ location) async { - /// Check if extension is installed and up to date before connecting + if (!_checkMacOSExtension()) return; + + final result = await ref + .read(vpnProvider.notifier) + .connectToServer(ServerLocationType.lanternLocation, location.tag); + result.fold( + (failure) => appLogger.error( + 'Failed to connect: ${failure.localizedErrorMessage}', + ), + (success) { + appLogger.info('Connecting to ${location.country} - ${location.city}'); + _saveServerLocation(location); + }, + ); + } + + /// Handle smart location selection from tray menu + Future _onSmartLocationSelected() async { + if (!_checkMacOSExtension()) return; + + await ref + .read(serverLocationProvider.notifier) + .updateServerLocation(initialServerLocation()); + await ref.read(vpnProvider.notifier).startVPN(force: true); + } + + /// Handle routing mode selection from tray menu + Future _onRoutingModeSelected(RoutingMode mode) async { + await ref.read(appSettingProvider.notifier).setRoutingMode(mode); + } + + /// Returns true if OK to proceed, false if blocked by missing extension + bool _checkMacOSExtension() { if (PlatformUtils.isMacOS) { final systemExtensionStatus = ref.read(macosExtensionProvider); if (systemExtensionStatus.status != SystemExtensionStatus.installed && systemExtensionStatus.status != SystemExtensionStatus.activated) { windowManager.show(); appRouter.push(const MacOSExtensionDialog()); - return; + return false; } } - - final result = await ref.read(vpnProvider.notifier).connectToServer( - ServerLocationType.lanternLocation, - location.tag, - ); - result.fold( - (failure) => appLogger - .error('Failed to connect: ${failure.localizedErrorMessage}'), - (success) { - appLogger.info('Connecting to ${location.country} - ${location.city}'); - _saveServerLocation(location); - }, - ); + return true; } Future _saveServerLocation(Location_ location) async { - final serverLocation = ServerLocation.fromLanternLocation( - server: location, - ); + final serverLocation = ServerLocation.fromLanternLocation(server: location); await ref .read(serverLocationProvider.notifier) .updateServerLocation(serverLocation); } + /// Build the current location display string (flag emoji + city) + /// shown when connected + String get _currentLocationDisplay { + try { + if (_serverLocation == null) return ''; + + final loc = _serverLocation!; + String countryCode = ''; + String displayName = ''; + + if (loc.serverType.toServerLocationType == ServerLocationType.auto) { + /// For auto location, we use the autoLocation info which contains the actual connected server details + final auto_ = loc.autoLocation; + if (auto_ == null) return ''; + countryCode = auto_.countryCode; + displayName = auto_.displayName; + } else { + countryCode = loc.countryCode; + displayName = loc.displayName; + } + + if (displayName.isEmpty) return ''; + + final flag = _countryCodeToFlagEmoji(countryCode); + return flag.isNotEmpty ? '$flag $displayName' : displayName; + } catch (e) { + appLogger.error('Error building location display', e); + return ''; + } + } + Future updateTrayMenu() async { + final locationDisplay = _currentLocationDisplay; + final menu = Menu( items: [ + MenuItem.separator(), + // Status: Connected / Disconnected (greyed out, non-clickable) MenuItem( key: 'status_label', disabled: true, @@ -141,34 +216,79 @@ class SystemTrayNotifier extends _$SystemTrayNotifier with TrayListener { ? 'status_on'.i18n : 'status_off'.i18n, ), + + if (isConnected && locationDisplay.isNotEmpty) + MenuItem( + key: 'current_location', + disabled: true, + label: locationDisplay, + ), + MenuItem.separator(), + MenuItem( key: 'toggle', label: _currentStatus == VPNStatus.connected ? 'disconnect'.i18n : 'connect'.i18n, - disabled: _currentStatus == VPNStatus.connecting || + disabled: + _currentStatus == VPNStatus.connecting || _currentStatus == VPNStatus.disconnecting, onClick: (_) => toggleVPN(), ), MenuItem.separator(), + if (_isUserPro && _locations.isNotEmpty) MenuItem.submenu( key: 'select_location', label: 'select_location'.i18n, + disabled: + _currentStatus == VPNStatus.connecting || + _currentStatus == VPNStatus.disconnecting, submenu: Menu( - items: _locations.map((location) { - final displayName = location.city.isNotEmpty - ? '${location.country} - ${location.city}' - : location.country; - return MenuItem( - key: 'location_${location.tag}', - label: displayName, - icon: AppImagePaths.safeFlagPath(location.countryCode), - onClick: (_) => _onLocationSelected(location), - ); - }).toList(), + items: [ + // Smart Location as first option with checkmark + MenuItem.checkbox( + key: 'smart_location', + label: 'smart_location'.i18n, + checked: _isAutoLocation, + onClick: (_) => _onSmartLocationSelected(), + ), + MenuItem.separator(), + // Server list + ..._locations.map((location) { + final displayName = location.city.isNotEmpty + ? '${location.country} - ${location.city}' + : location.country; + return MenuItem( + key: 'location_${location.tag}', + label: displayName, + icon: AppImagePaths.safeFlagPath(location.countryCode), + onClick: (_) => _onLocationSelected(location), + ); + }), + ], ), ), + MenuItem.submenu( + key: 'routing_mode', + label: 'routing_mode'.i18n, + submenu: Menu( + items: [ + MenuItem.checkbox( + key: 'smart_routing', + label: 'smart_routing'.i18n, + checked: _currentRoutingMode == RoutingMode.smart, + onClick: (_) => _onRoutingModeSelected(RoutingMode.smart), + ), + MenuItem.checkbox( + key: 'full_tunnel', + label: 'full_tunnel'.i18n, + checked: _currentRoutingMode == RoutingMode.full, + onClick: (_) => _onRoutingModeSelected(RoutingMode.full), + ), + ], + ), + ), if (!_isUserPro) MenuItem( key: 'upgrade_to_pro', @@ -183,7 +303,6 @@ class SystemTrayNotifier extends _$SystemTrayNotifier with TrayListener { key: 'join_server', label: 'join_server'.i18n, onClick: (_) { - // Open Lantern and navigate to the join server page ref.read(windowProvider.notifier).open(focus: true); appRouter.push(JoinPrivateServer()); }, @@ -209,8 +328,10 @@ class SystemTrayNotifier extends _$SystemTrayNotifier with TrayListener { ); await trayManager.setContextMenu(menu); - trayManager.setIcon(_trayIconPath(isConnected), - isTemplate: Platform.isMacOS); + trayManager.setIcon( + _trayIconPath(isConnected), + isTemplate: Platform.isMacOS, + ); trayManager.setToolTip('app_name'.i18n); } @@ -244,3 +365,14 @@ class SystemTrayNotifier extends _$SystemTrayNotifier with TrayListener { await trayManager.popUpContextMenu(); } } + +/// Converts a 2-letter ISO country code to a flag emoji +/// e.g. "US" β†’ "πŸ‡ΊπŸ‡Έ", "GB" β†’ "πŸ‡¬πŸ‡§" +String _countryCodeToFlagEmoji(String countryCode) { + final code = countryCode.toUpperCase(); + if (code.length != 2) return ''; + // Ensure both characters are ASCII letters A–Z before computing the emoji. + final isAsciiLetters = code.codeUnits.every((c) => c >= 0x41 && c <= 0x5A); + if (!isAsciiLetters) return ''; + return String.fromCharCodes(code.codeUnits.map((c) => c - 0x41 + 0x1F1E6)); +} diff --git a/lib/features/system_tray/provider/system_tray_notifier.g.dart b/lib/features/system_tray/provider/system_tray_notifier.g.dart index dd00082804..26939f7b7d 100644 --- a/lib/features/system_tray/provider/system_tray_notifier.g.dart +++ b/lib/features/system_tray/provider/system_tray_notifier.g.dart @@ -34,7 +34,7 @@ final class SystemTrayNotifierProvider } String _$systemTrayNotifierHash() => - r'df4cb92a49f9fe51b0b54b16e51da661f000f7c6'; + r'63fb171a34e7ef783d7bb6675e511dec7638f041'; abstract class _$SystemTrayNotifier extends $AsyncNotifier { FutureOr build(); diff --git a/lib/features/vpn/provider/server_location_notifier.dart b/lib/features/vpn/provider/server_location_notifier.dart index cd266b4a33..efaba86b76 100644 --- a/lib/features/vpn/provider/server_location_notifier.dart +++ b/lib/features/vpn/provider/server_location_notifier.dart @@ -13,7 +13,7 @@ class ServerLocationNotifier extends _$ServerLocationNotifier { LocalStorageService get _storage => sl(); @override - Future build() async { + ServerLocation build() { return _storage.getServerLocation() ?? _defaultLocation(); } @@ -38,8 +38,9 @@ class ServerLocationNotifier extends _$ServerLocationNotifier { if (status == VPNStatus.connected && current != null && current.serverType.toServerLocationType == ServerLocationType.auto) { - final result = - await ref.read(lanternServiceProvider).getAutoServerLocation(); + final result = await ref + .read(lanternServiceProvider) + .getAutoServerLocation(); result.fold( (error) => appLogger.error("Failed to fetch auto server location: $error"), @@ -47,29 +48,31 @@ class ServerLocationNotifier extends _$ServerLocationNotifier { final countryName = autoLocation.location!.country; final cityName = autoLocation.location!.city; - updateServerLocation(ServerLocation( - serverType: ServerLocationType.auto.name, - serverName: '', - displayName: '', - protocol: '', - city: cityName, - autoLocation: AutoLocation( - countryCode: autoLocation.location!.countryCode, - country: countryName, - displayName: '$countryName - $cityName', - tag: autoLocation.tag, + updateServerLocation( + ServerLocation( + serverType: ServerLocationType.auto.name, + serverName: '', + displayName: '', + protocol: '', + city: cityName, + autoLocation: AutoLocation( + countryCode: autoLocation.location!.countryCode, + country: countryName, + displayName: '$countryName - $cityName', + tag: autoLocation.tag, + ), ), - )); + ); }, ); } } static ServerLocation _defaultLocation() => ServerLocation( - serverType: ServerLocationType.auto.name, - serverName: '', - displayName: '', - protocol: '', - city: '', - ); + serverType: ServerLocationType.auto.name, + serverName: '', + displayName: '', + protocol: '', + city: '', + ); } diff --git a/lib/features/vpn/provider/server_location_notifier.g.dart b/lib/features/vpn/provider/server_location_notifier.g.dart index d8ed9c3647..9c8d5922bf 100644 --- a/lib/features/vpn/provider/server_location_notifier.g.dart +++ b/lib/features/vpn/provider/server_location_notifier.g.dart @@ -13,7 +13,7 @@ part of 'server_location_notifier.dart'; final serverLocationProvider = ServerLocationNotifierProvider._(); final class ServerLocationNotifierProvider - extends $AsyncNotifierProvider { + extends $NotifierProvider { ServerLocationNotifierProvider._() : super( from: null, @@ -31,22 +31,30 @@ final class ServerLocationNotifierProvider @$internal @override ServerLocationNotifier create() => ServerLocationNotifier(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(ServerLocation value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } } String _$serverLocationNotifierHash() => - r'9b7c13306682f80e6ce31eea5a9f9a5e74baedbc'; + r'cf58012d44d48e3d21c9a56c90c4ae80d724aec6'; -abstract class _$ServerLocationNotifier extends $AsyncNotifier { - FutureOr build(); +abstract class _$ServerLocationNotifier extends $Notifier { + ServerLocation build(); @$mustCallSuper @override void runBuild() { - final ref = this.ref as $Ref, ServerLocation>; + final ref = this.ref as $Ref; final element = ref.element as $ClassProviderElement< - AnyNotifier, ServerLocation>, - AsyncValue, + AnyNotifier, + ServerLocation, Object?, Object? >; diff --git a/lib/features/vpn/provider/vpn_notifier.g.dart b/lib/features/vpn/provider/vpn_notifier.g.dart index 9ea941ffac..bd3f283e98 100644 --- a/lib/features/vpn/provider/vpn_notifier.g.dart +++ b/lib/features/vpn/provider/vpn_notifier.g.dart @@ -41,7 +41,7 @@ final class VpnNotifierProvider } } -String _$vpnNotifierHash() => r'575b3455ebab8d413e060fc644b7ace6b36a2236'; +String _$vpnNotifierHash() => r'9d5685dcb24bd12386049fdbc1f7aea0db2ebe46'; abstract class _$VpnNotifier extends $Notifier { VPNStatus build(); diff --git a/lib/lantern/lantern_service_notifier.g.dart b/lib/lantern/lantern_service_notifier.g.dart index 63cc421061..6104a14e14 100644 --- a/lib/lantern/lantern_service_notifier.g.dart +++ b/lib/lantern/lantern_service_notifier.g.dart @@ -48,4 +48,4 @@ final class LanternServiceProvider } } -String _$lanternServiceHash() => r'3adf724e5fa29199106b8e0999b9fc21c0c9f721'; +String _$lanternServiceHash() => r'3e4ab1e15ccf41a3d913e21f1dd6414a29c8a840'; From fcabfe78b11ab789551db5a82a0b1de72e4d1141 Mon Sep 17 00:00:00 2001 From: Jigar-f Date: Tue, 24 Mar 2026 18:28:03 +0530 Subject: [PATCH 2/5] server location changes --- lib/features/home/home.dart | 6 +- lib/features/home/provider/home_notifier.dart | 20 +-- lib/features/vpn/location_setting.dart | 135 +++++++----------- .../provider/server_location_notifier.dart | 11 +- lib/features/vpn/provider/vpn_notifier.dart | 15 +- lib/features/vpn/server_selection.dart | 17 +-- lib/lantern/lantern_core_service.dart | 2 + lib/lantern/lantern_ffi_service.dart | 13 ++ lib/lantern/lantern_generated_bindings.dart | 13 ++ lib/lantern/lantern_platform_service.dart | 10 ++ lib/lantern/lantern_service.dart | 8 ++ 11 files changed, 128 insertions(+), 122 deletions(-) diff --git a/lib/features/home/home.dart b/lib/features/home/home.dart index 1f77c95344..22a9ca4266 100644 --- a/lib/features/home/home.dart +++ b/lib/features/home/home.dart @@ -145,11 +145,9 @@ class _HomeState extends ConsumerState { } Widget _buildBody(WidgetRef ref, bool isUserPro) { - final serverLocationAsync = ref.watch(serverLocationProvider); + final serverLocation = ref.watch(serverLocationProvider); - // Choose a safe default while loading/error - final serverLocation = serverLocationAsync.value; - final serverType = (serverLocation?.serverType ?? '').toServerLocationType; + final serverType = serverLocation.serverType.toServerLocationType; return Padding( padding: EdgeInsets.symmetric(horizontal: defaultSize), diff --git a/lib/features/home/provider/home_notifier.dart b/lib/features/home/provider/home_notifier.dart index a1b968705d..0e121719aa 100644 --- a/lib/features/home/provider/home_notifier.dart +++ b/lib/features/home/provider/home_notifier.dart @@ -97,20 +97,14 @@ class HomeNotifier extends _$HomeNotifier { /// if user logs out or downgrade to free plan /// we need to reset the server location set to smart location void resetServerLocation() { - final serverLocationAsync = ref.read(serverLocationProvider); + final serverLocation = ref.read(serverLocationProvider); - serverLocationAsync.when( - data: (serverLocation) { - if (serverLocation.serverType.toServerLocationType == - ServerLocationType.lanternLocation) { - ref - .read(serverLocationProvider.notifier) - .updateServerLocation(initialServerLocation()); - } - }, - loading: () {}, - error: (_, __) {}, - ); + if (serverLocation.serverType.toServerLocationType == + ServerLocationType.lanternLocation) { + ref + .read(serverLocationProvider.notifier) + .updateServerLocation(initialServerLocation()); + } } /// Fetches the latest user data from the server if not cached locally. diff --git a/lib/features/vpn/location_setting.dart b/lib/features/vpn/location_setting.dart index dbde135ed7..71d09db9db 100644 --- a/lib/features/vpn/location_setting.dart +++ b/lib/features/vpn/location_setting.dart @@ -10,95 +10,66 @@ class LocationSetting extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final serverLocationAsync = ref.watch(serverLocationProvider); + final serverLocation = ref.watch(serverLocationProvider); + final serverType = serverLocation.serverType.toServerLocationType; - return serverLocationAsync.when( - loading: () => SettingTile( - label: 'selected_location'.i18n, - value: 'loading'.i18n, - subtitle: '', - icon: AppImage(path: AppImagePaths.location), - actions: const [], - onTap: null, - ), - error: (err, stack) => SettingTile( - label: 'selected_location'.i18n, - value: 'error'.i18n, - subtitle: '', - icon: AppImage(path: AppImagePaths.location), - actions: [ - IconButton( - onPressed: () => appRouter.push(const ServerSelection()), - icon: AppImage(path: AppImagePaths.arrowForward), - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - visualDensity: VisualDensity.compact, - ), - ], - onTap: () => appRouter.push(const ServerSelection()), - ), - data: (serverLocation) { - final serverType = serverLocation.serverType.toServerLocationType; - - String title = ''; - String value = ''; - String flag = ''; - String protocol = ''; + String title = ''; + String value = ''; + String flag = ''; + String protocol = ''; - switch (serverType) { - case ServerLocationType.auto: - title = 'smart_location'.i18n; - final autoLoc = serverLocation.autoLocation; + switch (serverType) { + case ServerLocationType.auto: + title = 'smart_location'.i18n; + final autoLoc = serverLocation.autoLocation; - value = autoLoc != null && autoLoc.displayName.isNotEmpty - ? autoLoc.displayName - : 'fastest_server'.i18n; + value = autoLoc != null && autoLoc.displayName.isNotEmpty + ? autoLoc.displayName + : 'fastest_server'.i18n; - flag = autoLoc?.countryCode ?? ''; - protocol = autoLoc?.protocol ?? ''; - break; + flag = autoLoc?.countryCode ?? ''; + protocol = autoLoc?.protocol ?? ''; + break; - case ServerLocationType.lanternLocation: - title = 'selected_location'.i18n; - value = serverLocation.displayName; - flag = serverLocation.countryCode; - protocol = serverLocation.protocol; - break; + case ServerLocationType.lanternLocation: + title = 'selected_location'.i18n; + value = serverLocation.displayName; + flag = serverLocation.countryCode; + protocol = serverLocation.protocol; + break; - case ServerLocationType.privateServer: - title = serverLocation.serverName; - value = serverLocation.displayName; - flag = serverLocation.countryCode; - protocol = serverLocation.protocol; - break; - } + case ServerLocationType.privateServer: + title = serverLocation.serverName; + value = serverLocation.displayName; + flag = serverLocation.countryCode; + protocol = serverLocation.protocol; + break; + } - return SettingTile( - label: title, - value: value.i18n, - subtitle: protocol, - icon: flag.isEmpty ? AppImagePaths.location : Flag(countryCode: flag), - actions: [ - if (serverType == ServerLocationType.auto) - AppImage( - path: AppImagePaths.blot, - useThemeColor: false, - ), - const SizedBox(width: 8), - IconButton( - onPressed: () => appRouter.push(const ServerSelection()), - style: ElevatedButton.styleFrom( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - icon: AppImage(path: AppImagePaths.arrowForward), - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - visualDensity: VisualDensity.compact, - ), - ], - onTap: () => appRouter.push(const ServerSelection()), - ); - }, + return SettingTile( + label: title, + value: value.i18n, + subtitle: protocol, + icon: flag.isEmpty ? AppImagePaths.location : Flag(countryCode: flag), + actions: [ + if (serverType == ServerLocationType.auto) + AppImage( + path: AppImagePaths.blot, + useThemeColor: false, + ), + const SizedBox(width: 8), + IconButton( + onPressed: () => appRouter.push(const ServerSelection()), + style: ElevatedButton.styleFrom( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + icon: AppImage(path: AppImagePaths.arrowForward), + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + visualDensity: VisualDensity.compact, + ), + ], + onTap: () => appRouter.push(const ServerSelection()), ); } } diff --git a/lib/features/vpn/provider/server_location_notifier.dart b/lib/features/vpn/provider/server_location_notifier.dart index efaba86b76..341cb67170 100644 --- a/lib/features/vpn/provider/server_location_notifier.dart +++ b/lib/features/vpn/provider/server_location_notifier.dart @@ -18,25 +18,24 @@ class ServerLocationNotifier extends _$ServerLocationNotifier { } Future updateServerLocation(ServerLocation entity) async { - final current = state.value; + final current = state; if (entity.serverType != ServerLocationType.auto.name) { //Preserve auto location metadata when switching to a non-auto server, // so we can show user smart location - final updated = entity.copyWith(autoLocation: current?.autoLocation); - state = AsyncData(updated); + final updated = entity.copyWith(autoLocation: current.autoLocation); + state = updated; await _storage.saveServerLocation(updated); } else { - state = AsyncData(entity); + state = entity; await _storage.saveServerLocation(entity); } } Future ifNeededGetAutoServerLocation() async { final status = ref.read(vpnProvider); - final current = state.value; + final current = state; if (status == VPNStatus.connected && - current != null && current.serverType.toServerLocationType == ServerLocationType.auto) { final result = await ref .read(lanternServiceProvider) diff --git a/lib/features/vpn/provider/vpn_notifier.dart b/lib/features/vpn/provider/vpn_notifier.dart index 4242e738ee..2514a2faf3 100644 --- a/lib/features/vpn/provider/vpn_notifier.dart +++ b/lib/features/vpn/provider/vpn_notifier.dart @@ -76,12 +76,7 @@ class VpnNotifier extends _$VpnNotifier { Future> startVPN({bool force = false}) async { final lantern = ref.read(lanternServiceProvider); - final serverLocation = ref.read(serverLocationProvider).value; - - if (serverLocation == null) { - appLogger.debug('No cached server location, starting VPN with auto'); - return lantern.startVPN(); - } + final serverLocation = ref.read(serverLocationProvider); final type = serverLocation.serverType.toServerLocationType; if (type == ServerLocationType.auto || force) { @@ -90,7 +85,13 @@ class VpnNotifier extends _$VpnNotifier { return lantern.startVPN(); } - return connectToServer(type, serverLocation.serverName); + final tag = serverLocation.serverName; + final tagAvailable = await lantern.isTagAvailable(tag); + if (!tagAvailable) { + appLogger.debug('Server tag "$tag" not available, falling back to auto VPN'); + return lantern.startVPN(); + } + return connectToServer(type, tag); } /// Connects to a specific server location. diff --git a/lib/features/vpn/server_selection.dart b/lib/features/vpn/server_selection.dart index 5c79d4b57c..02788f072d 100644 --- a/lib/features/vpn/server_selection.dart +++ b/lib/features/vpn/server_selection.dart @@ -48,7 +48,7 @@ class _ServerSelectionState extends ConsumerState { ], ); - if (selected.isLoading || availableServers.isLoading) { + if (availableServers.isLoading) { return BaseScreen( title: '', appBar: appBar, @@ -56,7 +56,7 @@ class _ServerSelectionState extends ConsumerState { ); } - final err = selected.asError ?? availableServers.asError; + final err = availableServers.asError; if (err != null) { return BaseScreen( title: '', @@ -67,7 +67,7 @@ class _ServerSelectionState extends ConsumerState { ); } - final selectedServer = selected.requireValue; + final selectedServer = selected; final isPrivateServerFound = availableServers.requireValue.user.outbounds.isNotEmpty; @@ -269,10 +269,7 @@ class _ServerLocationListViewState const verticalSpacing = 12.0; - final selectedTag = selected.maybeWhen( - data: (s) => (s.serverName).toString(), - orElse: () => '', - ); + final selectedTag = selected.serverName; return SafeArea( child: Column( @@ -553,11 +550,11 @@ class _PrivateServerLocationListViewState final availableServers = ref.watch(availableServersProvider); final selected = ref.watch(serverLocationProvider); - if (availableServers.isLoading || selected.isLoading) { + if (availableServers.isLoading) { return const Center(child: Spinner()); } - final err = availableServers.asError ?? selected.asError; + final err = availableServers.asError; if (err != null) { return Center( child: Text(err.error.toString(), textAlign: TextAlign.center), @@ -567,7 +564,7 @@ class _PrivateServerLocationListViewState final userLocations = availableServers.requireValue.user.locations.values .toList(); - final selectedTag = selected.requireValue.serverName; + final selectedTag = selected.serverName; if (userLocations.isEmpty) { return Column( diff --git a/lib/lantern/lantern_core_service.dart b/lib/lantern/lantern_core_service.dart index c7ddfae219..a4d2b43e8a 100644 --- a/lib/lantern/lantern_core_service.dart +++ b/lib/lantern/lantern_core_service.dart @@ -35,6 +35,8 @@ abstract class LanternCoreService { Future> connectToServer(String location, String tag); + Future isTagAvailable(String tag); + Stream watchVPNStatus(); Stream> watchLogs(String path); diff --git a/lib/lantern/lantern_ffi_service.dart b/lib/lantern/lantern_ffi_service.dart index d5b42bb1ae..151b945267 100644 --- a/lib/lantern/lantern_ffi_service.dart +++ b/lib/lantern/lantern_ffi_service.dart @@ -619,6 +619,19 @@ class LanternFFIService implements LanternCoreService { } } + @override + Future isTagAvailable(String tag) async { + try { + final result = await runInBackground(() async { + return _ffiService.isTagAvailable(tag.toCharPtr).toDartString(); + }); + return result == 'true'; + } catch (e, st) { + appLogger.error('Error checking tag availability, assuming available', e, st); + return true; + } + } + /// connectToServer is used to connect to a server /// this will work with lantern customer and private server /// requires location and tag diff --git a/lib/lantern/lantern_generated_bindings.dart b/lib/lantern/lantern_generated_bindings.dart index bc0c34bfc7..b2caf2cb30 100644 --- a/lib/lantern/lantern_generated_bindings.dart +++ b/lib/lantern/lantern_generated_bindings.dart @@ -3312,6 +3312,19 @@ class LanternBindings { ffi.Pointer Function(ffi.Pointer, ffi.Pointer, ffi.Pointer)>(); + ffi.Pointer isTagAvailable( + ffi.Pointer _tag, + ) { + return _isTagAvailable(_tag); + } + + late final _isTagAvailablePtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer)>>('isTagAvailable'); + late final _isTagAvailable = _isTagAvailablePtr + .asFunction Function(ffi.Pointer)>(); + ffi.Pointer stopVPN() { return _stopVPN(); } diff --git a/lib/lantern/lantern_platform_service.dart b/lib/lantern/lantern_platform_service.dart index 082419dcf6..8421348cd2 100644 --- a/lib/lantern/lantern_platform_service.dart +++ b/lib/lantern/lantern_platform_service.dart @@ -138,6 +138,16 @@ class LanternPlatformService implements LanternCoreService { } @override + @override + Future isTagAvailable(String tag) async { + try { + final result = await _methodChannel.invokeMethod('isTagAvailable', tag); + return result ?? true; + } catch (e) { + return true; + } + } + Future> stopVPN() async { try { final _ = await _methodChannel.invokeMethod('stopVPN'); diff --git a/lib/lantern/lantern_service.dart b/lib/lantern/lantern_service.dart index 01d9c0abc8..b002a1e1a2 100644 --- a/lib/lantern/lantern_service.dart +++ b/lib/lantern/lantern_service.dart @@ -46,6 +46,14 @@ class LanternService implements LanternCoreService { return _platformService.startVPN(); } + @override + Future isTagAvailable(String tag) { + if (PlatformUtils.isFFISupported) { + return _ffiService.isTagAvailable(tag); + } + return _platformService.isTagAvailable(tag); + } + @override Future> stopVPN() { if (PlatformUtils.isFFISupported) { From 68d5fce120763a8f033186e24f61fd1d94787925 Mon Sep 17 00:00:00 2001 From: Jigar-f Date: Tue, 24 Mar 2026 18:38:48 +0530 Subject: [PATCH 3/5] add flags only on desktop versions. --- pubspec.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 34c38d7e2f..96c5f02664 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -148,7 +148,11 @@ flutter: assets: - assets/ - assets/images/ - - assets/images/flags/ + - path: assets/images/flags/ + platforms: + - windows + - macos + - linux - assets/locales/ - app.env From 53b6944114ce425172d2bd765430d1c805f3ebc3 Mon Sep 17 00:00:00 2001 From: atavism Date: Tue, 24 Mar 2026 06:56:54 -0700 Subject: [PATCH 4/5] code review updates (#8569) --- .../vpn/provider/server_location_notifier.dart | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/features/vpn/provider/server_location_notifier.dart b/lib/features/vpn/provider/server_location_notifier.dart index 341cb67170..a405a8347b 100644 --- a/lib/features/vpn/provider/server_location_notifier.dart +++ b/lib/features/vpn/provider/server_location_notifier.dart @@ -40,14 +40,15 @@ class ServerLocationNotifier extends _$ServerLocationNotifier { final result = await ref .read(lanternServiceProvider) .getAutoServerLocation(); - result.fold( - (error) => - appLogger.error("Failed to fetch auto server location: $error"), - (autoLocation) { + await result.fold( + (error) async { + appLogger.error("Failed to fetch auto server location: $error"); + }, + (autoLocation) async { final countryName = autoLocation.location!.country; final cityName = autoLocation.location!.city; - updateServerLocation( + await updateServerLocation( ServerLocation( serverType: ServerLocationType.auto.name, serverName: '', From 9775b286b861f692e08c638b5e9d902900744376 Mon Sep 17 00:00:00 2001 From: Jigar-f Date: Tue, 24 Mar 2026 19:38:01 +0530 Subject: [PATCH 5/5] Update lantern_platform_service.dart --- lib/lantern/lantern_platform_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/lantern/lantern_platform_service.dart b/lib/lantern/lantern_platform_service.dart index 8421348cd2..f5ad2d8f20 100644 --- a/lib/lantern/lantern_platform_service.dart +++ b/lib/lantern/lantern_platform_service.dart @@ -137,13 +137,13 @@ class LanternPlatformService implements LanternCoreService { } } - @override @override Future isTagAvailable(String tag) async { try { final result = await _methodChannel.invokeMethod('isTagAvailable', tag); return result ?? true; } catch (e) { + appLogger.error('Error checking if tag is available', e); return true; } }