diff --git a/README.md b/README.md index 09a650a4e..79a8582e8 100644 --- a/README.md +++ b/README.md @@ -47,13 +47,28 @@ on Mobile: ### Android +WiFi ssid policy + +```yaml +proxy-groups: + - name: "🚀 节点选择" + type: select + proxies: [...] + ssid-policy: + "ip:192.168.10.": "home" # 匹配 192.168.10.x(家里) + "ip:10.10.": "office" # 匹配 10.10.x.x(公司) + "HomeWiFi": "DIRECT" # SSID + "cellular": "hk" # 移动网络 + "default": "auto" # 默认规则 +``` + Support the following actions ```bash com.follow.clash.action.START - + com.follow.clash.action.STOP - + com.follow.clash.action.TOGGLE ``` diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 4cef3e8ad..5b115c274 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -38,7 +38,7 @@ android { defaultConfig { applicationId = "com.follow.clash" - minSdk = flutter.minSdkVersion + minSdk = libs.versions.minSdk.get().toInt() targetSdk = libs.versions.targetSdk.get().toInt() versionCode = flutter.versionCode versionName = flutter.versionName diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0c4c9c85f..0b0d43639 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -16,6 +16,10 @@ + + + + applySsidPolicy( + String networkType, + String ssid, + String wifiIp, + ) async { + try { + // Check if proxy is running + if (globalState.isStart != true) { + return; + } + + // Get the current running config from the core + final currentProfileId = _ref.read(currentProfileIdProvider); + if (currentProfileId == null) { + return; + } + + // Get the config that's currently loaded (after scripts have processed it) + final configMap = await coreController.getConfig(currentProfileId); + + // Extract proxy-groups from config + final proxyGroups = configMap['proxy-groups'] as List?; + if (proxyGroups == null || proxyGroups.isEmpty) { + return; + } + + // Determine network key for policy lookup + final networkKey = networkType == 'wifi' ? ssid : networkType; + + commonPrint.log( + 'Applying SSID policy for network: $networkKey${networkType == 'wifi' && wifiIp.isNotEmpty ? " (IP: $wifiIp)" : ""}', + ); + + // Get current proxy groups to validate target proxies exist + final currentGroups = groups; + + // Apply policy for each proxy group that has ssid-policy defined + for (final groupData in proxyGroups) { + if (groupData is! Map) continue; + + final groupName = groupData['name'] as String?; + final ssidPolicyData = groupData['ssid-policy'] as Map?; + + if (groupName == null || + ssidPolicyData == null || + ssidPolicyData.isEmpty) { + continue; + } + + // Convert ssidPolicyData to Map + final policy = ssidPolicyData.map( + (key, value) => MapEntry(key.toString(), value.toString()), + ); + + // Look up target proxy with priority: IP segment match > SSID/network > default + String? targetProxy; + String? matchedKey; + + // For WiFi, try IP segment match first (e.g., 'ip:192.168.2' matches '192.168.2.100') + if (networkType == 'wifi' && wifiIp.isNotEmpty) { + for (final key in policy.keys) { + if (key.startsWith('ip:')) { + final ipSegment = key.substring(3); // Remove 'ip:' prefix + if (wifiIp.startsWith(ipSegment)) { + targetProxy = policy[key]; + matchedKey = key; + break; + } + } + } + } + + // If no IP match, try SSID/network match + if (targetProxy == null && policy.containsKey(networkKey)) { + targetProxy = policy[networkKey]; + matchedKey = networkKey; + } + + // Finally try default + if (targetProxy == null) { + targetProxy = policy['default']; + matchedKey = 'default'; + } + + if (targetProxy != null && targetProxy.isNotEmpty) { + // Find the group to validate the proxy exists + final group = currentGroups + .where((g) => g.name == groupName) + .firstOrNull; + + if (group == null || group.type != GroupType.Selector) { + commonPrint.log( + 'SSID Policy: Group "$groupName" not found or not a Selector type, skipping', + ); + continue; + } + + // Check if target proxy exists in the group's proxy list + final proxyExists = group.all.any( + (proxy) => proxy.name == targetProxy, + ); + + if (!proxyExists) { + commonPrint.log( + 'SSID Policy: Proxy "$targetProxy" not found in group "$groupName", skipping', + ); + continue; + } + + commonPrint.log( + 'SSID Policy: Switching $groupName to $targetProxy (matched: $matchedKey)', + ); + updateCurrentSelectedMap(groupName, targetProxy); + changeProxyDebounce(groupName, targetProxy); + } + } + } catch (e) { + commonPrint.log( + 'Error applying SSID policy: $e', + logLevel: LogLevel.warning, + ); + } + } } extension SetupControllerExt on AppController { @@ -565,6 +690,9 @@ extension SetupControllerExt on AppController { } await globalState.handleStart([updateRunTime, updateTraffic]); applyProfileDebounce(force: true, silence: true); + + // Start network monitoring and apply initial SSID policy + _startNetworkMonitoring(); } else { globalState.needInitStatus = false; await applyProfile( @@ -573,6 +701,9 @@ extension SetupControllerExt on AppController { await globalState.handleStart([updateRunTime, updateTraffic]); }, ); + + // Start network monitoring and apply initial SSID policy + _startNetworkMonitoring(); } } else { await globalState.handleStop(); @@ -581,7 +712,32 @@ extension SetupControllerExt on AppController { _ref.read(totalTrafficProvider.notifier).value = Traffic(); _ref.read(runTimeProvider.notifier).value = null; addCheckIp(); + + // Stop network monitoring when proxy stops + _stopNetworkMonitoring(); + } + } + + void _startNetworkMonitoring() { + // Only support SSID policy on Android and macOS + if (!Platform.isAndroid && !Platform.isMacOS) { + return; } + + final monitor = NetworkStateMonitor(); + monitor.onNetworkStateChanged = (networkType, ssid, wifiIp) { + applySsidPolicy(networkType, ssid, wifiIp); + }; + monitor.startMonitoring(); + } + + void _stopNetworkMonitoring() { + // Only support SSID policy on Android and macOS + if (!Platform.isAndroid && !Platform.isMacOS) { + return; + } + + NetworkStateMonitor().stopMonitoring(); } Future needSetup() async { diff --git a/lib/core/network_monitor.dart b/lib/core/network_monitor.dart new file mode 100644 index 000000000..948a116e1 --- /dev/null +++ b/lib/core/network_monitor.dart @@ -0,0 +1,265 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:fl_clash/common/common.dart'; +import 'package:flutter/services.dart'; +import 'package:network_info_plus/network_info_plus.dart'; +import 'package:permission_handler/permission_handler.dart'; + +/// Network state monitor for SSID-based policy switching +class NetworkStateMonitor { + static final NetworkStateMonitor _instance = NetworkStateMonitor._internal(); + factory NetworkStateMonitor() => _instance; + NetworkStateMonitor._internal(); + + final _connectivity = Connectivity(); + final _networkInfo = NetworkInfo(); + StreamSubscription>? _subscription; + static const _macLocationChannel = MethodChannel( + 'com.flclash/location_permission', + ); + + String _currentNetworkType = ''; + String _currentSsid = ''; + String _currentWifiIp = ''; + bool _hasLocationPermission = false; + + Function(String networkType, String ssid, String wifiIp)? + onNetworkStateChanged; + + /// Check and request location permission (required for getting WiFi SSID) + Future _checkAndRequestPermission() async { + try { + // On macOS Sonoma+, CWWiFiClient.ssid() requires CLLocationManager + // authorization. We use a native MethodChannel to request it. + if (Platform.isMacOS) { + return await _requestMacOSLocationPermission(); + } + + // Android permission flow + // First check locationAlways permission (includes locationWhenInUse) + var alwaysStatus = await Permission.locationAlways.status; + + if (alwaysStatus.isGranted) { + _hasLocationPermission = true; + commonPrint.log( + 'NetworkStateMonitor: Location Always permission granted', + ); + return true; + } + + // Check if we have locationWhenInUse permission + var whenInUseStatus = await Permission.locationWhenInUse.status; + + if (whenInUseStatus.isGranted) { + // We have When In Use, now request Always + commonPrint.log( + 'NetworkStateMonitor: Requesting location Always permission', + ); + alwaysStatus = await Permission.locationAlways.request(); + + if (alwaysStatus.isGranted) { + _hasLocationPermission = true; + commonPrint.log( + 'NetworkStateMonitor: Location Always permission granted', + ); + return true; + } else { + // Even if Always is denied, we still have When In Use + _hasLocationPermission = true; + commonPrint.log( + 'NetworkStateMonitor: Location When In Use permission granted (Always denied)', + ); + return true; + } + } + + // Request When In Use permission first + if (whenInUseStatus.isDenied) { + commonPrint.log( + 'NetworkStateMonitor: Requesting location When In Use permission', + ); + whenInUseStatus = await Permission.locationWhenInUse.request(); + + if (whenInUseStatus.isGranted) { + _hasLocationPermission = true; + commonPrint.log( + 'NetworkStateMonitor: Location When In Use permission granted', + ); + + // Now request Always permission + commonPrint.log( + 'NetworkStateMonitor: Requesting location Always permission', + ); + alwaysStatus = await Permission.locationAlways.request(); + + if (alwaysStatus.isGranted) { + commonPrint.log( + 'NetworkStateMonitor: Location Always permission granted', + ); + } else { + commonPrint.log( + 'NetworkStateMonitor: Location Always permission denied, using When In Use', + ); + } + + return true; + } else if (whenInUseStatus.isPermanentlyDenied) { + commonPrint.log( + 'NetworkStateMonitor: Location permission permanently denied. ' + 'Please enable it in Settings.', + ); + return false; + } else { + commonPrint.log( + 'NetworkStateMonitor: Location When In Use permission denied', + ); + return false; + } + } + + if (whenInUseStatus.isPermanentlyDenied) { + commonPrint.log( + 'NetworkStateMonitor: Location permission permanently denied. ' + 'Please enable it in Settings.', + ); + return false; + } + + return false; + } catch (e) { + commonPrint.log('NetworkStateMonitor: Error checking permission: $e'); + return false; + } + } + + /// Request location permission on macOS via native CLLocationManager + Future _requestMacOSLocationPermission() async { + try { + final status = await _macLocationChannel.invokeMethod( + 'requestLocationPermission', + ); + commonPrint.log( + 'NetworkStateMonitor: macOS location permission status: $status', + ); + _hasLocationPermission = status == 'granted'; + if (!_hasLocationPermission) { + commonPrint.log( + 'NetworkStateMonitor: macOS location permission not granted ($status). ' + 'WiFi SSID will not be available. ' + 'Please grant location permission in System Settings > ' + 'Privacy & Security > Location Services.', + ); + } + return _hasLocationPermission; + } catch (e) { + commonPrint.log( + 'NetworkStateMonitor: Error requesting macOS location permission: $e', + ); + return false; + } + } + + /// Start monitoring network state changes + Future startMonitoring() async { + commonPrint.log('NetworkStateMonitor: Starting network monitoring'); + + // Check and request location permission first + await _checkAndRequestPermission(); + + // Get initial state + await _updateNetworkState(); + + // Listen to connectivity changes + _subscription = _connectivity.onConnectivityChanged.listen((results) { + commonPrint.log('NetworkStateMonitor: Connectivity changed: $results'); + _updateNetworkState(); + }); + } + + /// Stop monitoring + void stopMonitoring() { + commonPrint.log('NetworkStateMonitor: Stopping network monitoring'); + _subscription?.cancel(); + _subscription = null; + } + + /// Update network state and notify if changed + Future _updateNetworkState() async { + try { + final results = await _connectivity.checkConnectivity(); + final networkType = _getNetworkType(results); + final ssid = await _getSsid(results); + final wifiIp = await _getWifiIp(results); + + if (networkType != _currentNetworkType || + ssid != _currentSsid || + wifiIp != _currentWifiIp) { + _currentNetworkType = networkType; + _currentSsid = ssid; + _currentWifiIp = wifiIp; + onNetworkStateChanged?.call(networkType, ssid, wifiIp); + } + } catch (e) { + commonPrint.log('NetworkStateMonitor: Error updating network state: $e'); + } + } + + /// Convert ConnectivityResult to network type string + String _getNetworkType(List results) { + if (results.isEmpty || results.contains(ConnectivityResult.none)) { + return 'none'; + } + if (results.contains(ConnectivityResult.wifi)) { + return 'wifi'; + } + if (results.contains(ConnectivityResult.mobile)) { + return 'cellular'; + } + if (results.contains(ConnectivityResult.ethernet)) { + return 'ethernet'; + } + return 'other'; + } + + /// Get SSID for WiFi networks + Future _getSsid(List results) async { + if (!results.contains(ConnectivityResult.wifi)) { + return ''; + } + + // On macOS, system handles permission automatically + // On Android/iOS, check if we have location permission + if (!Platform.isMacOS && !_hasLocationPermission) { + commonPrint.log( + 'NetworkStateMonitor: Cannot get SSID without location permission', + ); + return ''; + } + + try { + final wifiName = await _networkInfo.getWifiName(); + // Remove quotes if present (iOS returns SSID with quotes) + return wifiName?.replaceAll('"', '') ?? ''; + } catch (e) { + commonPrint.log('NetworkStateMonitor: Error getting SSID: $e'); + return ''; + } + } + + /// Get WiFi IP (local IP address) + Future _getWifiIp(List results) async { + if (!results.contains(ConnectivityResult.wifi)) { + return ''; + } + + try { + await Future.delayed(Duration(milliseconds: 360)); + final wifiIp = await utils.getLocalIpAddress(); + return wifiIp ?? ''; + } catch (e) { + commonPrint.log('NetworkStateMonitor: Error getting WiFi IP: $e'); + return ''; + } + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 4d3a298fd..78880bd9d 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -14,8 +14,8 @@ import file_selector_macos import flutter_js import hotkey_manager_macos import mobile_scanner +import network_info_plus import package_info_plus -import path_provider_foundation import screen_retriever_macos import shared_preferences_foundation import sqflite_darwin @@ -35,8 +35,8 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterJsPlugin.register(with: registry.registrar(forPlugin: "FlutterJsPlugin")) HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) + NetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "NetworkInfoPlusPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) - PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 8cfb75eac..bb4918fd9 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + F5LOCPER2024A1A10003CAFE /* LocationPermissionPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5LOCPER2024A1A00003CAFE /* LocationPermissionPlugin.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; @@ -84,6 +85,7 @@ 33CC10ED2044A3C60003C045 /* FlClash.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FlClash.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + F5LOCPER2024A1A00003CAFE /* LocationPermissionPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationPermissionPlugin.swift; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; @@ -193,6 +195,7 @@ children = ( 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + F5LOCPER2024A1A00003CAFE /* LocationPermissionPlugin.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, 33CC11242044D66E0003C045 /* Resources */, @@ -459,6 +462,7 @@ 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + F5LOCPER2024A1A10003CAFE /* LocationPermissionPlugin.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements index f8697e31c..3264fe0ae 100755 --- a/macos/Runner/DebugProfile.entitlements +++ b/macos/Runner/DebugProfile.entitlements @@ -6,5 +6,7 @@ com.apple.security.files.user-selected.read-write + com.apple.security.personal-information.location + diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist index df57c8d84..24980b945 100755 --- a/macos/Runner/Info.plist +++ b/macos/Runner/Info.plist @@ -43,5 +43,9 @@ MainMenu NSPrincipalClass NSApplication + NSLocationWhenInUseUsageDescription + FlClash needs location access to detect WiFi SSID for automatic proxy switching based on network. + NSLocationUsageDescription + FlClash needs location access to detect WiFi SSID for automatic proxy switching based on network. diff --git a/macos/Runner/LocationPermissionPlugin.swift b/macos/Runner/LocationPermissionPlugin.swift new file mode 100644 index 000000000..131df8870 --- /dev/null +++ b/macos/Runner/LocationPermissionPlugin.swift @@ -0,0 +1,111 @@ +import Cocoa +import FlutterMacOS +import CoreLocation + +class LocationPermissionPlugin: NSObject, FlutterPlugin, CLLocationManagerDelegate { + private let locationManager = CLLocationManager() + private var pendingResult: FlutterResult? + + static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel( + name: "com.flclash/location_permission", + binaryMessenger: registrar.messenger + ) + let instance = LocationPermissionPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + override init() { + super.init() + locationManager.delegate = self + } + + func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "requestLocationPermission": + requestLocationPermission(result: result) + case "checkLocationPermission": + checkLocationPermission(result: result) + default: + result(FlutterMethodNotImplemented) + } + } + + private func requestLocationPermission(result: @escaping FlutterResult) { + let status: CLAuthorizationStatus + if #available(macOS 11.0, *) { + status = locationManager.authorizationStatus + } else { + status = CLLocationManager.authorizationStatus() + } + + switch status { + case .authorizedAlways: + result("granted") + case .denied: + result("denied") + case .restricted: + result("restricted") + case .notDetermined: + // Store result to respond after authorization callback + pendingResult = result + if #available(macOS 10.15, *) { + locationManager.requestWhenInUseAuthorization() + } else { + // On older macOS, just start updating which triggers the prompt + locationManager.startUpdatingLocation() + locationManager.stopUpdatingLocation() + } + @unknown default: + result("unknown") + } + } + + private func checkLocationPermission(result: @escaping FlutterResult) { + let status: CLAuthorizationStatus + if #available(macOS 11.0, *) { + status = locationManager.authorizationStatus + } else { + status = CLLocationManager.authorizationStatus() + } + + switch status { + case .authorizedAlways: + result("granted") + case .denied: + result("denied") + case .restricted: + result("restricted") + case .notDetermined: + result("notDetermined") + @unknown default: + result("unknown") + } + } + + // CLLocationManagerDelegate + func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { + guard let result = pendingResult else { return } + pendingResult = nil + + let status: CLAuthorizationStatus + if #available(macOS 11.0, *) { + status = manager.authorizationStatus + } else { + status = CLLocationManager.authorizationStatus() + } + + switch status { + case .authorizedAlways: + result("granted") + case .denied: + result("denied") + case .restricted: + result("restricted") + case .notDetermined: + result("notDetermined") + @unknown default: + result("unknown") + } + } +} diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift index aee339ecf..b24132b77 100755 --- a/macos/Runner/MainFlutterWindow.swift +++ b/macos/Runner/MainFlutterWindow.swift @@ -9,7 +9,7 @@ class MainFlutterWindow: NSWindow { let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) - + FlutterMethodChannel( name: "launch_at_startup", binaryMessenger: flutterViewController.engine.binaryMessenger ) @@ -26,8 +26,11 @@ class MainFlutterWindow: NSWindow { result(FlutterMethodNotImplemented) } } - + RegisterGeneratedPlugins(registry: flutterViewController) + LocationPermissionPlugin.register( + with: flutterViewController.registrar(forPlugin: "LocationPermissionPlugin") + ) super.awakeFromNib() } override public func order(_ place: NSWindow.OrderingMode, relativeTo otherWin: Int) { diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index 080e1e075..46ab511be 100755 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -4,5 +4,7 @@ com.apple.security.files.user-selected.read-write + com.apple.security.personal-information.location + diff --git a/pubspec.yaml b/pubspec.yaml index cca65ac8e..16fbf676e 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -46,6 +46,8 @@ dependencies: uni_platform: ^0.1.3 device_info_plus: ^12.2.0 connectivity_plus: ^7.0.0 + network_info_plus: ^6.0.2 + permission_handler: ^12.0.0 screen_retriever: ^0.2.0 defer_pointer: ^0.0.2 flutter_riverpod: ^3.0.0 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 3cac9d06f..32440743a 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FlutterJsPlugin")); HotkeyManagerWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("HotkeyManagerWindowsPluginCApi")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); ProxyPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("ProxyPluginCApi")); ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 7f1cdbd69..9eb6be912 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -9,6 +9,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows flutter_js hotkey_manager_windows + permission_handler_windows proxy screen_retriever_windows sqlite3_flutter_libs