diff --git a/README.md b/README.md index c9bdc18..f3f3aa4 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,7 @@ Need help or have any questions? We're here to assist: ## Contributors - [Jamalianpour](https://github.com/Jamalianpour) - Responsible for adding full macOS support and performing a comprehensive refactor, including code cleanup, structural improvements, and cross-platform compatibility enhancements. + Responsible for adding full macOS support and refactor. --- diff --git a/lib/binding.dart b/lib/binding.dart index a66d9d8..a1f021b 100644 --- a/lib/binding.dart +++ b/lib/binding.dart @@ -3,6 +3,7 @@ import 'package:netshift/controller/blocked_apps_controller.dart'; import 'package:netshift/controller/check_for_update_controller.dart'; import 'package:netshift/controller/foreground_controller.dart'; import 'package:netshift/controller/glow_controller.dart'; +import 'package:netshift/controller/main_wrapper_controller.dart'; import 'package:netshift/controller/netshift_engine_controller.dart'; import 'package:netshift/controller/splash_controller.dart'; import 'package:netshift/controller/stop_watch_controller.dart'; @@ -17,6 +18,6 @@ class InitialBindings extends Bindings { Get.lazyPut(() => StopWatchController(), fenix: true); Get.lazyPut(() => GlowController(), fenix: true); Get.lazyPut(() => BlockedAppsController(), fenix: true); + Get.lazyPut(() => MainWrapperController(), fenix: true); } } - diff --git a/lib/controller/blocked_apps_controller.dart b/lib/controller/blocked_apps_controller.dart index a82da8b..5951e06 100644 --- a/lib/controller/blocked_apps_controller.dart +++ b/lib/controller/blocked_apps_controller.dart @@ -5,9 +5,9 @@ import 'package:installed_apps/app_info.dart'; import 'package:installed_apps/installed_apps.dart'; class BlockedAppsController extends GetxController { - GetStorage box2 = GetStorage(); - var blockedApps = [].obs; - var apps = [].obs; + final GetStorage box2 = GetStorage(); + RxList blockedApps = [].obs; + RxList apps = [].obs; RxBool isSystemApp = false.obs; @override void onInit() { diff --git a/lib/controller/check_for_update_controller.dart b/lib/controller/check_for_update_controller.dart index 4409e2e..5b6888a 100644 --- a/lib/controller/check_for_update_controller.dart +++ b/lib/controller/check_for_update_controller.dart @@ -28,7 +28,9 @@ class CheckForUpdateController extends GetxController { log("No update available"); updateIsAvailable.value = false; } else { - log("Update available version: ${updateVersion.value} with status code ${response.statusCode}"); + log( + "Update available version: ${updateVersion.value} with status code ${response.statusCode}", + ); updateIsAvailable.value = true; } } else { @@ -55,7 +57,9 @@ class CheckForUpdateController extends GetxController { log("No update available"); updateIsAvailable.value = false; } else { - log("Update available version: ${updateVersion.value} with status code ${response.statusCode}"); + log( + "Update available version: ${updateVersion.value} with status code ${response.statusCode}", + ); updateIsAvailable.value = true; } } else { @@ -82,7 +86,9 @@ class CheckForUpdateController extends GetxController { log("No update available"); updateIsAvailable.value = false; } else { - log("Update available version: ${updateVersion.value} with status code ${response.statusCode}"); + log( + "Update available version: ${updateVersion.value} with status code ${response.statusCode}", + ); updateIsAvailable.value = true; } } else { diff --git a/lib/controller/dio_config.dart b/lib/controller/dio_config.dart index bf5c508..839197f 100644 --- a/lib/controller/dio_config.dart +++ b/lib/controller/dio_config.dart @@ -11,4 +11,3 @@ void configureDio() { log("Connect timeout: ${dio.options.connectTimeout!.inSeconds} Seconds"); log("Receive timeout: ${dio.options.receiveTimeout!.inSeconds} Seconds"); } - diff --git a/lib/controller/encrypt_data.dart b/lib/controller/encrypt_data.dart index 0ac4069..62c546b 100644 --- a/lib/controller/encrypt_data.dart +++ b/lib/controller/encrypt_data.dart @@ -5,7 +5,7 @@ // class EncryptionData { // static final String keyString = generateRandomKey(32); // static final encrypt.Key key = encrypt.Key.fromUtf8(keyString); -// static final encrypt.IV iv = encrypt.IV.fromLength(16); +// static final encrypt.IV iv = encrypt.IV.fromLength(16); // static final encrypt.Encrypter encrypter = encrypt.Encrypter(encrypt.AES(key)); // static String encryptData(String plainText) { @@ -23,4 +23,4 @@ // final List values = List.generate(length, (i) => random.nextInt(256)); // return base64Url.encode(values).substring(0, length); // } -// } \ No newline at end of file +// } diff --git a/lib/controller/foreground_controller.dart b/lib/controller/foreground_controller.dart index a92d58c..b936f97 100644 --- a/lib/controller/foreground_controller.dart +++ b/lib/controller/foreground_controller.dart @@ -10,16 +10,18 @@ import 'package:netshift/controller/stop_watch_controller.dart'; class ForegroundController extends GetxController { static const platform = MethodChannel("com.netshift.dnschanger/netdns"); RxBool isRunning = false.obs; - static const EventChannel statusChannel = - EventChannel("com.netshift.dnschanger/netdnsStatus"); + static const EventChannel statusChannel = EventChannel( + "com.netshift.dnschanger/netdnsStatus", + ); String foregroundStatus = "none"; RxString download = "0.00 MB".obs; RxString upload = "0.00 MB".obs; RxBool serviceStatus = false.obs; StreamSubscription? dataUsageSubscription; final status = GetStorage(); - NetshiftEngineController netshiftEngineController = - Get.put(NetshiftEngineController()); + NetshiftEngineController netshiftEngineController = Get.put( + NetshiftEngineController(), + ); StopWatchController stopWatchController = Get.put(StopWatchController()); Future startService(String contextText) async { log("Foreground Service Started"); @@ -32,7 +34,6 @@ class ForegroundController extends GetxController { } } - Future stopService() async { isRunning.value = false; // statusService.write('isServiceRunning', false); @@ -47,21 +48,19 @@ class ForegroundController extends GetxController { } void listenToServiceStatus() { - platform.setMethodCallHandler( - (call) async { - if (call.method == "serviceStatusUpdate") { - serviceStatus.value = call.arguments as bool; - log("Service status updated: ${serviceStatus.value}"); - loadServiceStatus(); - } else if (call.method == 'dataUsageUpdate') { - final Map data = call.arguments; - download.value = - "${(data['download'] / (1024 * 1024)).toStringAsFixed(2)} MB"; - upload.value = - "${(data['upload'] / (1024 * 1024)).toStringAsFixed(2)} MB"; - } - }, - ); + platform.setMethodCallHandler((call) async { + if (call.method == "serviceStatusUpdate") { + serviceStatus.value = call.arguments as bool; + log("Service status updated: ${serviceStatus.value}"); + loadServiceStatus(); + } else if (call.method == 'dataUsageUpdate') { + final Map data = call.arguments; + download.value = + "${(data['download'] / (1024 * 1024)).toStringAsFixed(2)} MB"; + upload.value = + "${(data['upload'] / (1024 * 1024)).toStringAsFixed(2)} MB"; + } + }); } Future serviceStatusKotlin() async { @@ -78,7 +77,9 @@ class ForegroundController extends GetxController { serviceStatus.value = await serviceStatusKotlin(); loadServiceStatus(); log("****Service Status ananas : ${serviceStatus.value}****"); - log("****Service Status ananas1 : ${netshiftEngineController.isActive.value}****"); + log( + "****Service Status ananas1 : ${netshiftEngineController.isActive.value}****", + ); } void loadServiceStatus() { diff --git a/lib/controller/glow_controller.dart b/lib/controller/glow_controller.dart index 5fdb965..5b5894c 100644 --- a/lib/controller/glow_controller.dart +++ b/lib/controller/glow_controller.dart @@ -23,4 +23,4 @@ class GlowController extends GetxController with GetTickerProviderStateMixin { glowController.dispose(); super.onClose(); } -} \ No newline at end of file +} diff --git a/lib/controller/main_wrapper_controller.dart b/lib/controller/main_wrapper_controller.dart index 2de8889..717962c 100644 --- a/lib/controller/main_wrapper_controller.dart +++ b/lib/controller/main_wrapper_controller.dart @@ -7,7 +7,7 @@ import 'package:netshift/screens/home_page.dart'; import 'package:netshift/screens/settings_page.dart'; class MainWrapperController extends GetxController { - final netshiftEngineController = + final NetshiftEngineController netshiftEngineController = Get.find(); final List _pages = [ HomePage(), @@ -20,7 +20,8 @@ class MainWrapperController extends GetxController { super.onInit(); netshiftEngineController.getIpAddress(); } - var selectedIndex = 0.obs; + + RxInt selectedIndex = 0.obs; void onSelectPage(int index) { selectedIndex.value = index; diff --git a/lib/controller/netshift_engine_controller.dart b/lib/controller/netshift_engine_controller.dart index d0364a4..732d19b 100644 --- a/lib/controller/netshift_engine_controller.dart +++ b/lib/controller/netshift_engine_controller.dart @@ -1,444 +1,188 @@ -import 'dart:developer'; import 'dart:io'; -import 'package:flutter/services.dart'; import 'package:get/get.dart'; -import 'package:get_ip_address/get_ip_address.dart'; -import 'package:get_storage/get_storage.dart'; import 'package:netshift/controller/blocked_apps_controller.dart'; +import 'package:netshift/core/services/dns_platform_service.dart'; +import 'package:netshift/core/services/dns_storage_service.dart'; +import 'package:netshift/core/services/ip_address_service.dart'; import 'package:netshift/models/dns_model.dart'; class NetshiftEngineController extends GetxController { - static const platform = MethodChannel("com.netshift.dnschanger/netdns"); - static const EventChannel statusChannel = - EventChannel("com.netshift.dnschanger/netdnsStatus"); - String dnsStatus = "none"; - GetStorage storage = GetStorage(); - GetStorage interfaceNameStore = GetStorage(); - GetStorage dnsListPersonalStorage = GetStorage(); + late final DnsPlatformService _platformService; + final DnsStorageService _storageService = DnsStorageService(); + final IpAddressService _ipAddressService = IpAddressService(); + + // Connection state RxBool isActive = false.obs; RxBool isLoading = false.obs; RxBool isFetching = false.obs; RxBool isPermissionGiven = false.obs; - RxList newDnsList = RxList(); - RxMap interfaces = RxMap(); - RxList interfaceKeys = RxList(); - RxList interfaceValues = RxList(); - RxString interfaceName = 'Select Interface'.obs; RxBool isFlushing = false.obs; RxBool isIpAddress = false.obs; - RxString ipAddressString = "".obs; - - var dnsListNetShift = [].obs; + // DNS lists + RxList dnsListNetShift = [].obs; + RxList dnsListPersonal = [].obs; + RxList newDnsList = RxList(); - var dnsListPersonal = [].obs; + List get combinedListDns => [ + ...dnsListNetShift, + ...dnsListPersonal, + ]; - List get combinedListDns => - [...dnsListNetShift, ...dnsListPersonal]; - var selectedDns = DnsModel( + // Selected DNS + Rx selectedDns = DnsModel( name: 'NetShift DNS', primaryDNS: '178.22.122.100', secondaryDNS: '78.157.42.100', ).obs; + + // Network interfaces (for Windows/macOS) + RxMap interfaces = RxMap(); + RxList interfaceKeys = RxList(); + RxList interfaceValues = RxList(); + RxString interfaceName = 'Select Interface'.obs; + + // IP address + RxString ipAddressString = "".obs; + + // Dependencies final BlockedAppsController blockedAppsController = - Get.put(BlockedAppsController()); + Get.find(); + @override void onInit() { super.onInit(); - if (Platform.isAndroid) { - prepareDns(); - } - loadConnectButtonStatus(); - loadSelectedDnsValue(); - loadPersonalDns(); - loadPrepareService(); - if (Platform.isWindows) { - interfaceNameForWindows(); - loadInterfaceName(); - } - if (Platform.isMacOS) { - interfaceNameForMacOS(); - loadInterfaceName(); - } - } - - Future prepareDns() async { - isPermissionGiven.value = false; - try { - await platform.invokeMethod('prepareDns'); - isPermissionGiven.value = true; - savePrepareService(); - } on PlatformException catch (e) { - log("Service Failed to Start $e"); - } - } - - void savePrepareService() { - storage.write('prepareDns', isPermissionGiven.value); - } - - void loadPrepareService() { - isPermissionGiven.value = storage.read('prepareDns') ?? false; + _initializePlatformService(); + _loadSavedState(); } - Future startDnsForAndroid() async { - if (isPermissionGiven.value) { - prepareDns(); - } - isLoading.value = true; - isActive.value = true; - saveConnectButtonStatus(); - try { - await platform.invokeMethod('startDns', { - 'dns1': selectedDns.value.primaryDNS, - 'dns2': selectedDns.value.secondaryDNS, - 'disallowedApps': blockedAppsController.blockedApps.toList(), - }); - await Future.delayed(const Duration(seconds: 1)); - isLoading.value = false; - } on PlatformException catch (e) { - log("Service Failed to Start $e"); - } - } + void _initializePlatformService() { + _platformService = DnsPlatformService.create(); - Future stopDnsForAndroid() async { - isActive.value = false; - saveConnectButtonStatus(); - try { - await platform.invokeMethod('stopDns'); - } on PlatformException catch (e) { - log("Service Failed to Start $e"); + if (Platform.isAndroid) { + _prepareAndroidDns(); } - } - - // FOR WINDOWS START - Future startDnsForWindows() async { - isLoading.value = true; - isActive.value = true; - saveConnectButtonStatus(); - try { - final primaryDnsResult = await Process.run( - 'netsh', - [ - 'interface', - 'ipv4', - 'set', - 'dns', - 'name="${interfaceName.value}"', - 'static', - selectedDns.value.primaryDNS, - 'primary' - ], - ); - - if (primaryDnsResult.exitCode != 0) { - throw Exception( - 'Error setting primary DNS: ${primaryDnsResult.stderr}'); - } - - final secondaryDnsResult = await Process.run( - 'netsh', - [ - 'interface', - 'ipv4', - 'add', - 'dns', - 'name="${interfaceName.value}"', - selectedDns.value.secondaryDNS, - 'index=2' - ], - ); - - if (secondaryDnsResult.exitCode != 0) { - throw Exception( - 'Error setting secondary DNS: ${secondaryDnsResult.stderr}'); - } - log('DNS configured successfully.'); - } catch (e) { - log('Error configuring DNS: $e'); - } finally { - isLoading.value = false; + if (_platformService.requiresInterfaceSelection) { + _loadNetworkInterfaces(); } } - Future stopDnsForWindows() async { - isActive.value = false; - saveConnectButtonStatus(); - - try { - final resetDnsResult = await Process.run( - 'netsh', - [ - 'interface', - 'ipv4', - 'set', - 'dns', - 'name="${interfaceName.value}"', - 'source=dhcp', - ], - ); - - if (resetDnsResult.exitCode != 0) { - throw Exception('Error resetting DNS: ${resetDnsResult.stderr}'); - } + void _loadSavedState() { + isActive.value = _storageService.loadConnectionStatus(); + isPermissionGiven.value = _storageService.loadPrepareDnsPermission(); + interfaceName.value = _storageService.loadInterfaceName(); - log('DNS reset to default.'); - } catch (e) { - log('Error disabling DNS: $e'); + final DnsModel? savedDns = _storageService.loadSelectedDns(); + if (savedDns != null) { + selectedDns.value = savedDns; } - } - Future flushDnsForWindows() async { - isFlushing.value = true; - ProcessResult result = - await Process.run('ipconfig', ['/flushdns'], runInShell: true); - isFlushing.value = false; - log(result.stdout); - } - - void saveInterfaceName() { - interfaceNameStore.write('interfaceNameStore', interfaceName.value); + dnsListPersonal.value = _storageService.loadPersonalDnsList(); } - void loadInterfaceName() { - interfaceName.value = - storage.read('interfaceNameStore') ?? 'Select Interface'; - } - - Future interfaceNameForWindows() async { - ProcessResult result = await Process.run( - 'netsh', ['interface', 'show', 'interface'], - runInShell: true); - - String interfaceOutput = result.stdout; - Map allInterfaces = parseNetshOutput(interfaceOutput); - interfaces.value = allInterfaces; - interfaceKeys.addAll(interfaces.keys); - interfaceValues.addAll(interfaces.values); + Future _prepareAndroidDns() async { + isPermissionGiven.value = false; + bool success = await _platformService.prepareDns(); + isPermissionGiven.value = success; + if (success) { + _storageService.savePrepareDnsPermission(true); + } } - Map parseNetshOutput(String output) { - Map interfaceMap = {}; - List lines = output.split('\n'); - - int nameIndex = -1; - int stateIndex = -1; - - for (String line in lines) { - line = line.trim(); - if (line.toLowerCase().contains("admin state")) { - List headers = line.split(RegExp(r'\s{2,}')); - nameIndex = headers.indexOf("Interface Name"); - stateIndex = headers.indexOf("State"); - continue; - } + Future _loadNetworkInterfaces() async { + Map networkInterfaces = + await _platformService.getNetworkInterfaces(); - if (nameIndex != -1 && stateIndex != -1) { - List parts = line.split(RegExp(r'\s{2,}')); - if (parts.length > stateIndex) { - String name = parts[nameIndex].trim(); - String state = parts[stateIndex].trim(); + interfaces.value = networkInterfaces; + interfaceKeys.value = networkInterfaces.keys.toList(); + interfaceValues.value = networkInterfaces.values.toList(); - interfaceMap[name] = state; - } - } + // Auto-select first connected interface if none selected + if (interfaceName.value == 'Select Interface' && interfaceKeys.isNotEmpty) { + interfaceName.value = interfaceKeys.first; + saveInterfaceName(); } - - List> sortedEntries = interfaceMap.entries.toList() - ..sort((a, b) { - if (a.value == b.value) return 0; - return a.value == "Connected" ? -1 : 1; - }); - - return Map.fromEntries(sortedEntries); } - // FOR WINDOWS END + // DNS Control Methods + Future startDns() async { + if (Platform.isAndroid && !isPermissionGiven.value) { + await _prepareAndroidDns(); + } - // FOR MACOS START - Future startDnsForMacOS() async { isLoading.value = true; isActive.value = true; - saveConnectButtonStatus(); - try { - // Get the active network service name - String networkService = interfaceName.value.contains('Select Interface') - ? await _getMacOSNetworkService() - : interfaceName.value; - if (networkService.isEmpty) { - log('No active network service found'); - isLoading.value = false; - isActive.value = false; - return; - } + _storageService.saveConnectionStatus(true); - // Set DNS servers using osascript with administrator privileges - final result = await Process.run( - 'osascript', - [ - '-e', - 'do shell script "networksetup -setdnsservers \'$networkService\' ${selectedDns.value.primaryDNS} ${selectedDns.value.secondaryDNS}" with administrator privileges' - ], + try { + await _platformService.startDns( + primaryDns: selectedDns.value.primaryDNS, + secondaryDns: selectedDns.value.secondaryDNS, + interfaceName: interfaceName.value, + disallowedApps: blockedAppsController.blockedApps.toList(), ); - if (result.exitCode != 0) { - log('Error setting DNS: ${result.stderr}'); - isActive.value = false; - saveConnectButtonStatus(); - } else { - log('DNS configured successfully on $networkService'); + if (Platform.isAndroid) { + await Future.delayed(const Duration(seconds: 1)); } } catch (e) { - log('Error configuring DNS: $e'); isActive.value = false; - saveConnectButtonStatus(); + _storageService.saveConnectionStatus(false); } finally { isLoading.value = false; } } - Future stopDnsForMacOS() async { + Future stopDns() async { isActive.value = false; - saveConnectButtonStatus(); - try { - String networkService = interfaceName.value.contains('Select Interface') - ? await _getMacOSNetworkService() - : interfaceName.value; - if (networkService.isEmpty) { - log('No active network service found'); - return; - } - - // Reset DNS to empty (DHCP) using osascript with administrator privileges - final resetResult = await Process.run( - 'osascript', - [ - '-e', - 'do shell script "networksetup -setdnsservers \'$networkService\' Empty" with administrator privileges' - ], - ); + _storageService.saveConnectionStatus(false); - if (resetResult.exitCode != 0) { - log('Error resetting DNS: ${resetResult.stderr}'); - } else { - log('DNS reset to default on $networkService'); - } + try { + await _platformService.stopDns(interfaceName: interfaceName.value); } catch (e) { - log('Error disabling DNS: $e'); + // Error already logged in service } } - Future flushDnsForMacOS() async { + Future flushDns() async { isFlushing.value = true; try { - // Flush DNS cache - await Process.run('dscacheutil', ['-flushcache']); - // Restart mDNSResponder to fully clear DNS cache (requires admin privileges) - await Process.run('osascript', [ - '-e', - 'do shell script "killall -HUP mDNSResponder" with administrator privileges' - ]); - log('DNS cache flushed on macOS'); - } catch (e) { - log('Error flushing DNS cache: $e'); + await _platformService.flushDns(); + } finally { + isFlushing.value = false; } - isFlushing.value = false; } - Future _getMacOSNetworkService() async { - try { - final result = - await Process.run('networksetup', ['-listallnetworkservices']); - if (result.exitCode != 0) return 'Wi-Fi'; - - List services = result.stdout.toString().split('\n'); - for (String service in services.skip(1)) { - service = service.trim(); - if (service.isEmpty || service.startsWith('*')) continue; - - final ipResult = - await Process.run('networksetup', ['-getinfo', service]); - if (ipResult.stdout.toString().contains('IP address:') && - !ipResult.stdout.toString().contains('IP address: none')) { - return service; - } - } - return 'Wi-Fi'; - } catch (e) { - log('Error getting network service: $e'); - return 'Wi-Fi'; - } + // Interface management + void saveInterfaceName() { + _storageService.saveInterfaceName(interfaceName.value); } - Future interfaceNameForMacOS() async { - try { - final result = - await Process.run('networksetup', ['-listallnetworkservices']); - if (result.exitCode == 0) { - List services = result.stdout.toString().split('\n'); - interfaces.clear(); - interfaceKeys.clear(); - interfaceValues.clear(); - - for (String service in services.skip(1)) { - service = service.trim(); - if (service.isEmpty || service.startsWith('*')) continue; - - final ipResult = - await Process.run('networksetup', ['-getinfo', service]); - bool isConnected = - ipResult.stdout.toString().contains('IP address:') && - !ipResult.stdout.toString().contains('IP address: none'); - - interfaces[service] = isConnected ? 'Connected' : 'Disconnected'; - interfaceKeys.add(service); - interfaceValues.add(isConnected ? 'Connected' : 'Disconnected'); - } - - // Sort to put connected interfaces first - List> sortedEntries = - interfaces.entries.toList() - ..sort((a, b) { - if (a.value == b.value) return 0; - return a.value == "Connected" ? -1 : 1; - }); - interfaces.value = Map.fromEntries(sortedEntries); - - // Auto-select first connected interface - if (interfaceName.value == 'Select Interface' && - interfaceKeys.isNotEmpty) { - interfaceName.value = interfaceKeys.first; - saveInterfaceName(); - } - } - } catch (e) { - log('Error getting macOS network services: $e'); - } + Future refreshNetworkInterfaces() async { + await _loadNetworkInterfaces(); } - // FOR MACOS END - - void addDNS(String dnsName, primaryDNS, secondaryDNS) { - dnsListPersonal.add( - DnsModel( - name: dnsName, - primaryDNS: primaryDNS, - secondaryDNS: secondaryDNS, - ), - ); - selectedDns.value = DnsModel( + + // DNS List Management + void addDNS(String dnsName, String primaryDNS, String secondaryDNS) { + DnsModel newDns = DnsModel( name: dnsName, primaryDNS: primaryDNS, secondaryDNS: secondaryDNS, ); - saveSelectedDnsValue(); + + dnsListPersonal.add(newDns); + selectedDns.value = newDns; + + _savePersonalDnsAndSelection(); } void deleteDns(DnsModel dns) { dnsListPersonal.remove(dns); - final isSelectedDns = selectedDns.value.name == dns.name && + bool isSelectedDns = + selectedDns.value.name == dns.name && selectedDns.value.primaryDNS == dns.primaryDNS && selectedDns.value.secondaryDNS == dns.secondaryDNS; @@ -447,17 +191,23 @@ class NetshiftEngineController extends GetxController { ? dnsListPersonal.first : dnsListNetShift.first; } - saveSelectedDnsValue(); + + _savePersonalDnsAndSelection(); } - void editDns(DnsModel oldDns, String newDnsName, String newPrimaryDNS, - String newSecondaryDNS) { + void editDns( + DnsModel oldDns, + String newDnsName, + String newPrimaryDNS, + String newSecondaryDNS, + ) { int index = dnsListPersonal.indexWhere( - (dns) => + (DnsModel dns) => dns.name == oldDns.name && dns.primaryDNS == oldDns.primaryDNS && dns.secondaryDNS == oldDns.secondaryDNS, ); + if (index != -1) { dnsListPersonal[index] = DnsModel( name: newDnsName, @@ -465,6 +215,7 @@ class NetshiftEngineController extends GetxController { secondaryDNS: newSecondaryDNS, ); } + if (selectedDns.value.name == oldDns.name && selectedDns.value.primaryDNS == oldDns.primaryDNS && selectedDns.value.secondaryDNS == oldDns.secondaryDNS) { @@ -475,64 +226,42 @@ class NetshiftEngineController extends GetxController { ); } - saveSelectedDnsValue(); - } - - void saveConnectButtonStatus() { - storage.write('connectionButtonStatus', isActive.value); + _savePersonalDnsAndSelection(); } - void loadConnectButtonStatus() { - isActive.value = storage.read('connectionButtonStatus') ?? false; - log("Your connection button state is : ${isActive.value.toString()}"); + void _savePersonalDnsAndSelection() { + _storageService.savePersonalDnsList(dnsListPersonal); + _storageService.saveSelectedDns(selectedDns.value); } void saveSelectedDnsValue() { - storage.write('selectedDnsValue', selectedDns.value.toJson()); - } - - void loadSelectedDnsValue() { - var dnsData = storage.read('selectedDnsValue'); - if (dnsData != null) { - selectedDns.value = DnsModel.fromJson(dnsData); - } - log("Your selected DNS is : ${selectedDns.value.name}"); + _storageService.saveSelectedDns(selectedDns.value); } void savePersonalDns() { - dnsListPersonalStorage.write( - 'dnsListPersonal', - dnsListPersonal.map((dns) => dns.toJson()).toList(), - ); + _storageService.savePersonalDnsList(dnsListPersonal); } - void loadPersonalDns() { - var dnsData = dnsListPersonalStorage.read('dnsListPersonal'); - if (dnsData != null) { - dnsListPersonal.value = (dnsData as List) - .map((item) => DnsModel.fromJson(Map.from(item))) - .toList(); - } - } + // IP Address + Future getIpAddress() async { + isIpAddress.value = true; - void getIpAddress() async { - try { - isIpAddress.value = true; - var ipAddress = IpAddress(type: RequestType.text); - dynamic data = await ipAddress.getIpAddress(); - bool isIPv6 = data.contains(':'); - if (isIPv6) { - ipAddressString.value = "Failed(IPv6)"; - log("Failed IPv6 detected"); - } else { - ipAddressString.value = data; - log(data.toString()); - } - isIpAddress.value = false; - } on IpAddressException catch (e) { - log(e.message); - ipAddressString.value = "Failed(Offline)"; - isIpAddress.value = false; - } + IpAddressResult result = await _ipAddressService.getIpAddress(); + ipAddressString.value = result.ipAddress; + + isIpAddress.value = false; } + + // Legacy method names for backward compatibility + Future startDnsForAndroid() => startDns(); + Future stopDnsForAndroid() => stopDns(); + Future startDnsForWindows() => startDns(); + Future stopDnsForWindows() => stopDns(); + Future startDnsForMacOS() => startDns(); + Future stopDnsForMacOS() => stopDns(); + Future flushDnsForWindows() => flushDns(); + Future flushDnsForMacOS() => flushDns(); + Future prepareDns() => _prepareAndroidDns(); + Future interfaceNameForWindows() => _loadNetworkInterfaces(); + Future interfaceNameForMacOS() => _loadNetworkInterfaces(); } diff --git a/lib/controller/random_dns_generator_controller.dart b/lib/controller/random_dns_generator_controller.dart index b69a77e..0dda684 100644 --- a/lib/controller/random_dns_generator_controller.dart +++ b/lib/controller/random_dns_generator_controller.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'dart:math'; -import 'package:dio/dio.dart'; +import 'package:dio/dio.dart' as dio_pkg; import 'package:get/get.dart'; import 'package:netshift/controller/dio_config.dart'; import 'package:netshift/core/services/url_constant.dart'; @@ -11,7 +11,7 @@ import 'package:netshift/models/dns_model.dart'; class RandomDnsGeneratorController extends GetxController { final NetshiftEngineController netshiftEngineController = Get.find(); - var generatedDnsList = []; + List generatedDnsList = []; final Random random = Random(); RxBool isGenerating = false.obs; @@ -20,24 +20,28 @@ class RandomDnsGeneratorController extends GetxController { isGenerating.value = true; try { - final response = - await dio.get(UrlConstant.baseUrl + UrlConstant.ananas1); + final dio_pkg.Response response = await dio.get( + UrlConstant.baseUrl + UrlConstant.ananas1, + ); if (response.statusCode == 200) { generatedDnsList.clear(); - final String ananas4 = response.data['status']; - final int ananas5 = response.data['code']; - print("Generated DNS Status : $ananas4"); - print("Generated DNS Code : ${ananas5.toString()}"); - var ananas3 = jsonDecode(response.data['data']); - ananas3['validateDNS'].forEach((json) { + final String status = response.data['status']; + final int code = response.data['code']; + print("Generated DNS Status : $status"); + print("Generated DNS Code : ${code.toString()}"); + final Map validatedDnsData = jsonDecode( + response.data['data'], + ); + validatedDnsData['validateDNS'].forEach((dynamic json) { generatedDnsList.add(DnsModel.fromJson(json)); }); //TODO Will add dnsCounter later - for (var dnsCount = 0; dnsCount < 1; dnsCount++) { + for (int dnsCount = 0; dnsCount < 1; dnsCount++) { if (generatedDnsList.isNotEmpty) { - netshiftEngineController.dnsListPersonal - .add(generatedDnsList[random.nextInt(generatedDnsList.length)]); + netshiftEngineController.dnsListPersonal.add( + generatedDnsList[random.nextInt(generatedDnsList.length)], + ); netshiftEngineController.savePersonalDns(); print("DNS Generated successfully"); } else { @@ -49,7 +53,7 @@ class RandomDnsGeneratorController extends GetxController { } else { print("Unexpected server response: ${response.statusCode}"); } - } on DioException catch (e) { + } on dio_pkg.DioException catch (e) { print("Error generating DNS: ${e.message}"); } finally { isGenerating.value = false; diff --git a/lib/controller/restart_engine.dart b/lib/controller/restart_engine.dart index 3d02911..9544e12 100644 --- a/lib/controller/restart_engine.dart +++ b/lib/controller/restart_engine.dart @@ -6,11 +6,11 @@ import 'package:netshift/controller/netshift_engine_controller.dart'; import 'package:netshift/controller/single_dns_ping_controller.dart'; import 'package:netshift/controller/stop_watch_controller.dart'; import 'package:netshift/core/services/windows_local_notif.dart'; -import 'package:netshift/core/widgets/flutter_toast.dart'; +import 'package:netshift/core/widgets/common/flutter_toast.dart'; class RestartEngine extends GetxController { - final dnsPingController = Get.find(); - final NetshiftEngineController netshiftEngineController = Get.find(); + final dnsPingController = Get.find(); + final NetshiftEngineController netshiftEngineController = Get.find(); final StopWatchController stopWatchController = Get.find(); final ForegroundController foregroundController = Get.find(); @@ -22,8 +22,9 @@ class RestartEngine extends GetxController { stopWatchController.stopWatchTime(); await netshiftEngineController.startDnsForAndroid(); - await foregroundController - .startService(netshiftEngineController.selectedDns.value.name); + await foregroundController.startService( + netshiftEngineController.selectedDns.value.name, + ); stopWatchController.startWatchTime(); FlutterToast(message: "Service Restarted Successfully").flutterToast(); @@ -31,8 +32,9 @@ class RestartEngine extends GetxController { void startEngineAndroid() { netshiftEngineController.startDnsForAndroid(); - foregroundController - .startService(netshiftEngineController.selectedDns.value.name); + foregroundController.startService( + netshiftEngineController.selectedDns.value.name, + ); stopWatchController.startWatchTime(); netshiftEngineController.isActive.value = true; FlutterToast(message: "Service Started Successfully").flutterToast(); @@ -43,7 +45,10 @@ class RestartEngine extends GetxController { stopWatchController.stopWatchTime(); await netshiftEngineController.startDnsForWindows(); stopWatchController.startWatchTime(); - WindowsLocalNotif(body: "Service Restarted Successfully", title: "NetShift"); + WindowsLocalNotif( + body: "Service Restarted Successfully", + title: "NetShift", + ); } void startEngineWindows() { diff --git a/lib/controller/single_dns_ping_controller.dart b/lib/controller/single_dns_ping_controller.dart index 756c39f..24602b5 100644 --- a/lib/controller/single_dns_ping_controller.dart +++ b/lib/controller/single_dns_ping_controller.dart @@ -3,7 +3,7 @@ import 'package:get/get.dart'; import 'package:netshift/controller/netshift_engine_controller.dart'; class SingleDnsPingController extends GetxController { -final NetshiftEngineController netshiftEngineController = Get.find(); + final NetshiftEngineController netshiftEngineController = Get.find(); RxBool isPingP = false.obs; RxBool isPingS = false.obs; RxString resultPingP = ''.obs; @@ -18,12 +18,13 @@ final NetshiftEngineController netshiftEngineController = Get.find(); Future pingPrimaryDns() async { isPingP.value = true; final primaryPingResult = await Ping( - netshiftEngineController.selectedDns.value.primaryDNS, - count: 1) - .stream - .first; - if (primaryPingResult.response != null && primaryPingResult.response!.time != null) { - resultPingP.value = primaryPingResult.response!.time!.inMilliseconds.toString(); + netshiftEngineController.selectedDns.value.primaryDNS, + count: 1, + ).stream.first; + if (primaryPingResult.response != null && + primaryPingResult.response!.time != null) { + resultPingP.value = primaryPingResult.response!.time!.inMilliseconds + .toString(); } else { resultPingP.value = "-1"; } @@ -33,12 +34,13 @@ final NetshiftEngineController netshiftEngineController = Get.find(); Future pingSecondaryDns() async { isPingS.value = true; final secondaryPingResult = await Ping( - netshiftEngineController.selectedDns.value.secondaryDNS, - count: 1) - .stream - .first; - if (secondaryPingResult.response != null && secondaryPingResult.response!.time != null) { - resultPingS.value = secondaryPingResult.response!.time!.inMilliseconds.toString(); + netshiftEngineController.selectedDns.value.secondaryDNS, + count: 1, + ).stream.first; + if (secondaryPingResult.response != null && + secondaryPingResult.response!.time != null) { + resultPingS.value = secondaryPingResult.response!.time!.inMilliseconds + .toString(); } else { resultPingS.value = "-1"; } diff --git a/lib/controller/sorted_dns_ping_controller.dart b/lib/controller/sorted_dns_ping_controller.dart index e9af87c..97d22e1 100644 --- a/lib/controller/sorted_dns_ping_controller.dart +++ b/lib/controller/sorted_dns_ping_controller.dart @@ -11,19 +11,19 @@ class SortedDnsPingController extends GetxController { RxBool isPinging = false.obs; RxMap> pingResultMap = >{}.obs; - RxList ananas = [].obs; - RxList ananas1 = [].obs; - RxList ananas2 = [].obs; - RxList ananas3 = [].obs; - RxList ananas4 = [].obs; - RxList ananas5 = [].obs; + RxList dnsNames = [].obs; + RxList primaryDnsList = [].obs; + RxList secondaryDnsList = [].obs; + RxList avgPingList = [].obs; + RxList primaryPingList = [].obs; + RxList secondaryPingList = [].obs; RxInt completedCount = 0.obs; RxInt totalCount = 0.obs; List get combinedList => [ - ...netshiftEngineController.dnsListNetShift, - ...netshiftEngineController.dnsListPersonal - ]; + ...netshiftEngineController.dnsListNetShift, + ...netshiftEngineController.dnsListPersonal, + ]; @override void onInit() { @@ -37,8 +37,8 @@ class SortedDnsPingController extends GetxController { totalCount.value = combinedList.length; // Launch all pings in parallel - List> pingFutures = []; - for (var item in combinedList) { + List> pingFutures = >[]; + for (DnsModel item in combinedList) { pingFutures.add(_pingDns(item)); } @@ -61,10 +61,9 @@ class SortedDnsPingController extends GetxController { int primaryPing = results[0]; int secondaryPing = results[1]; - int avgPing = - (primaryPing == -1 || secondaryPing == -1) - ? -1 - : (primaryPing + secondaryPing) ~/ 2; + int avgPing = (primaryPing == -1 || secondaryPing == -1) + ? -1 + : (primaryPing + secondaryPing) ~/ 2; pingResultMap[item.name] = { "ping": avgPing.toString(), @@ -115,32 +114,35 @@ class SortedDnsPingController extends GetxController { void _sortAndUpdateLists() { // Sort by ping (fastest first, timeouts last) - var sortedEntries = pingResultMap.entries.toList() - ..sort((a, b) { - int pingA = int.parse(a.value['ping']!); - int pingB = int.parse(b.value['ping']!); - - if (pingA == -1 && pingB == -1) return 0; - if (pingA == -1) return 1; - if (pingB == -1) return -1; - return pingA.compareTo(pingB); - }); + List>> sortedEntries = + pingResultMap.entries.toList()..sort(( + MapEntry> a, + MapEntry> b, + ) { + int pingA = int.parse(a.value['ping']!); + int pingB = int.parse(b.value['ping']!); + + if (pingA == -1 && pingB == -1) return 0; + if (pingA == -1) return 1; + if (pingB == -1) return -1; + return pingA.compareTo(pingB); + }); // Clear and rebuild display lists - ananas.clear(); - ananas1.clear(); - ananas2.clear(); - ananas3.clear(); - ananas4.clear(); - ananas5.clear(); - - for (var entry in sortedEntries) { - ananas.add(entry.key); - ananas1.add(entry.value['primary']!); - ananas2.add(entry.value['secondary']!); - ananas3.add(entry.value['ping']!); - ananas4.add(entry.value['ping1']!); - ananas5.add(entry.value['ping2']!); + dnsNames.clear(); + primaryDnsList.clear(); + secondaryDnsList.clear(); + avgPingList.clear(); + primaryPingList.clear(); + secondaryPingList.clear(); + + for (MapEntry> entry in sortedEntries) { + dnsNames.add(entry.key); + primaryDnsList.add(entry.value['primary']!); + secondaryDnsList.add(entry.value['secondary']!); + avgPingList.add(entry.value['ping']!); + primaryPingList.add(entry.value['ping1']!); + secondaryPingList.add(entry.value['ping2']!); } // Force UI update @@ -149,12 +151,12 @@ class SortedDnsPingController extends GetxController { Future refreshDnsPingResults() async { pingResultMap.clear(); - ananas.clear(); - ananas1.clear(); - ananas2.clear(); - ananas3.clear(); - ananas4.clear(); - ananas5.clear(); + dnsNames.clear(); + primaryDnsList.clear(); + secondaryDnsList.clear(); + avgPingList.clear(); + primaryPingList.clear(); + secondaryPingList.clear(); await sortedPing(); } } diff --git a/lib/controller/splash_controller.dart b/lib/controller/splash_controller.dart index 2a0a4a5..e3c8bdc 100644 --- a/lib/controller/splash_controller.dart +++ b/lib/controller/splash_controller.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'dart:developer'; import 'dart:io'; import 'dart:isolate'; -import 'package:dio/dio.dart'; +import 'package:dio/dio.dart' as dio_pkg; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; @@ -14,7 +14,7 @@ import 'package:netshift/core/services/url_constant.dart'; import 'package:netshift/controller/netshift_engine_controller.dart'; import 'package:netshift/main_wrapper.dart'; import 'package:netshift/models/dns_model.dart'; -import 'package:netshift/core/widgets/check_for_update_widget.dart'; +import 'package:netshift/core/widgets/settings/check_for_update_widget.dart'; class SplashScreenController extends GetxController with GetSingleTickerProviderStateMixin { @@ -32,69 +32,90 @@ class SplashScreenController extends GetxController final ReceivePort receivePort = ReceivePort(); List newOfflineDnsList = [ DnsModel( - name: 'NetShift DNS', - primaryDNS: "178.22.122.100", - secondaryDNS: "78.157.42.100"), - DnsModel( - name: 'Electro DNS', - primaryDNS: "78.157.42.100", - secondaryDNS: "78.157.42.101"), - DnsModel( - name: 'Radar DNS', - primaryDNS: "10.202.10.10", - secondaryDNS: "10.202.10.11"), - DnsModel( - name: 'Shelter DNS', - primaryDNS: "78.157.60.6", - secondaryDNS: "78.157.60.242"), - DnsModel( - name: 'Google DNS', primaryDNS: "8.8.8.8", secondaryDNS: "8.8.4.4"), - DnsModel( - name: 'CloudFlare DNS', primaryDNS: "1.1.1.1", secondaryDNS: "1.0.0.1"), - DnsModel( - name: 'Open DNS', - primaryDNS: "208.67.222.222", - secondaryDNS: "208.67.220.220"), - DnsModel( - name: 'Quad9 DNS (AD)', - primaryDNS: "9.9.9.9", - secondaryDNS: "149.112.112.112"), - DnsModel( - name: 'Beshkan DNS', - primaryDNS: "181.41.194.177", - secondaryDNS: "181.41.194.186"), - DnsModel( - name: 'Shecan DNS', - primaryDNS: "178.22.122.100", - secondaryDNS: "185.51.200.2"), - DnsModel( - name: '403 DNS', - primaryDNS: "10.202.10.202", - secondaryDNS: "10.202.10.102"), - DnsModel( - name: 'Begzar DNS', - primaryDNS: "185.55.226.26", - secondaryDNS: "185.55.226.25"), - DnsModel( - name: 'Shatel DNS', - primaryDNS: "85.15.1.14", - secondaryDNS: "85.15.1.15"), - DnsModel( - name: 'Pishgaman DNS', - primaryDNS: "5.202.100.100", - secondaryDNS: "5.202.100.101"), - DnsModel( - name: 'Level3 DNS', - primaryDNS: "209.244.0.3", - secondaryDNS: "209.244.0.4"), - DnsModel( - name: 'Comodo Secure DNS', - primaryDNS: "8.26.56.26", - secondaryDNS: "8.20.247.20"), - DnsModel( - name: 'Verisign DNS', - primaryDNS: "64.6.64.6", - secondaryDNS: "64.6.65.6"), + name: 'NetShift DNS', + primaryDNS: "178.22.122.100", + secondaryDNS: "78.157.42.100", + ), + DnsModel( + name: 'Electro DNS', + primaryDNS: "78.157.42.100", + secondaryDNS: "78.157.42.101", + ), + DnsModel( + name: 'Radar DNS', + primaryDNS: "10.202.10.10", + secondaryDNS: "10.202.10.11", + ), + DnsModel( + name: 'Shelter DNS', + primaryDNS: "78.157.60.6", + secondaryDNS: "78.157.60.242", + ), + DnsModel( + name: 'Google DNS', + primaryDNS: "8.8.8.8", + secondaryDNS: "8.8.4.4", + ), + DnsModel( + name: 'CloudFlare DNS', + primaryDNS: "1.1.1.1", + secondaryDNS: "1.0.0.1", + ), + DnsModel( + name: 'Open DNS', + primaryDNS: "208.67.222.222", + secondaryDNS: "208.67.220.220", + ), + DnsModel( + name: 'Quad9 DNS (AD)', + primaryDNS: "9.9.9.9", + secondaryDNS: "149.112.112.112", + ), + DnsModel( + name: 'Beshkan DNS', + primaryDNS: "181.41.194.177", + secondaryDNS: "181.41.194.186", + ), + DnsModel( + name: 'Shecan DNS', + primaryDNS: "178.22.122.100", + secondaryDNS: "185.51.200.2", + ), + DnsModel( + name: '403 DNS', + primaryDNS: "10.202.10.202", + secondaryDNS: "10.202.10.102", + ), + DnsModel( + name: 'Begzar DNS', + primaryDNS: "185.55.226.26", + secondaryDNS: "185.55.226.25", + ), + DnsModel( + name: 'Shatel DNS', + primaryDNS: "85.15.1.14", + secondaryDNS: "85.15.1.15", + ), + DnsModel( + name: 'Pishgaman DNS', + primaryDNS: "5.202.100.100", + secondaryDNS: "5.202.100.101", + ), + DnsModel( + name: 'Level3 DNS', + primaryDNS: "209.244.0.3", + secondaryDNS: "209.244.0.4", + ), + DnsModel( + name: 'Comodo Secure DNS', + primaryDNS: "8.26.56.26", + secondaryDNS: "8.20.247.20", + ), + DnsModel( + name: 'Verisign DNS', + primaryDNS: "64.6.64.6", + secondaryDNS: "64.6.65.6", + ), ]; @override @@ -124,8 +145,10 @@ class SplashScreenController extends GetxController } else if (isOffline.value) { getDnsListOffline(); } - progressController = - Tween(begin: 0, end: 0.8).animate(animationController); + progressController = Tween( + begin: 0, + end: 0.8, + ).animate(animationController); } Future getDnsListOnline() async { @@ -136,21 +159,23 @@ class SplashScreenController extends GetxController isOnline.value = true; saveOnline(); try { - var response = await dio.get(UrlConstant.baseUrl + UrlConstant.ananas); + final dio_pkg.Response response = await dio.get( + UrlConstant.baseUrl + UrlConstant.ananas, + ); if (response.statusCode == 200) { newOnlineDnsList.clear(); netshiftEngineController.dnsListNetShift.clear(); - var ananas2 = jsonDecode(response.data['data']); - ananas2['dns_providers'].forEach((json) { + final Map dnsProvidersData = jsonDecode( + response.data['data'], + ); + dnsProvidersData['dns_providers'].forEach((dynamic json) { newOnlineDnsList.add(DnsModel.fromJson(json)); }); - newOnlineDnsList.sort( - (a, b) { - if (a.name.contains('**') && !b.name.contains('**')) return 1; - if (!a.name.contains('**') && b.name.contains('**')) return -1; - return 0; - }, - ); + newOnlineDnsList.sort((a, b) { + if (a.name.contains('**') && !b.name.contains('**')) return 1; + if (!a.name.contains('**') && b.name.contains('**')) return -1; + return 0; + }); netshiftEngineController.dnsListNetShift.addAll(newOnlineDnsList); await Future.delayed(const Duration(milliseconds: 500)); if (checkForUpdateController.updateIsAvailable.value) { @@ -163,7 +188,7 @@ class SplashScreenController extends GetxController isError.value = true; update(); } - } on DioException catch (e) { + } on dio_pkg.DioException catch (e) { isError.value = true; update(); log("Error: $e"); diff --git a/lib/controller/stop_watch_controller.dart b/lib/controller/stop_watch_controller.dart index 81afc3d..82a3809 100644 --- a/lib/controller/stop_watch_controller.dart +++ b/lib/controller/stop_watch_controller.dart @@ -4,29 +4,28 @@ import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; class StopWatchController extends GetxController { - final timerBox = GetStorage(); + final GetStorage timerBox = GetStorage(); Timer? timer; DateTime? startTime; - var elapsedTime = Duration.zero.obs; - var isRunning = false.obs; + Rx elapsedTime = Duration.zero.obs; + RxBool isRunning = false.obs; @override void onInit() { super.onInit(); loadSavedTime(); } + @override void onClose() { timer?.cancel(); super.onClose(); } + void startTimer() { - timer = Timer.periodic( - const Duration(seconds: 1), - (timer) { - elapsedTime.value = DateTime.now().difference(startTime!); - }, - ); + timer = Timer.periodic(const Duration(seconds: 1), (timer) { + elapsedTime.value = DateTime.now().difference(startTime!); + }); } void startWatchTime() { @@ -62,6 +61,4 @@ class StopWatchController extends GetxController { final seconds = twoDigits(duration.inSeconds.remainder(60)); return '$hours:$minutes:$seconds'; } - - } diff --git a/lib/controller/system_chrome_controller.dart b/lib/controller/system_chrome_controller.dart index 3eab1f4..fa02b1a 100644 --- a/lib/controller/system_chrome_controller.dart +++ b/lib/controller/system_chrome_controller.dart @@ -10,11 +10,15 @@ class SystemChromeController { statusBarColor: themeController.isDarkMode ? const Color(0xFF1A1B21) : const Color.fromARGB(255, 231, 218, 250), - statusBarIconBrightness: - themeController.isDarkMode ? Brightness.light : Brightness.dark, - statusBarBrightness: themeController.isDarkMode ? Brightness.dark : Brightness.light, - systemNavigationBarIconBrightness: - themeController.isDarkMode ? Brightness.light : Brightness.dark, + statusBarIconBrightness: themeController.isDarkMode + ? Brightness.light + : Brightness.dark, + statusBarBrightness: themeController.isDarkMode + ? Brightness.dark + : Brightness.light, + systemNavigationBarIconBrightness: themeController.isDarkMode + ? Brightness.light + : Brightness.dark, ), ); } diff --git a/lib/core/resources/app_colors.dart b/lib/core/resources/app_colors.dart index 56af128..e7dc334 100644 --- a/lib/core/resources/app_colors.dart +++ b/lib/core/resources/app_colors.dart @@ -138,8 +138,9 @@ class AppColors { static Color get interfaceClose => isDarkMode ? Colors.greenAccent : Colors.indigo; - static Color get interfaceCloseHover => - isDarkMode ? Colors.greenAccent.withValues(alpha: 0.3) : Colors.indigo.withValues(alpha: 0.3); + static Color get interfaceCloseHover => isDarkMode + ? Colors.greenAccent.withValues(alpha: 0.3) + : Colors.indigo.withValues(alpha: 0.3); // ------------------ DNS PAGE COLORS ------------------ @@ -265,10 +266,8 @@ class AppColors { isDarkMode ? Colors.black : Colors.indigo; static Color - get dnsSelectorSheetPersonalCancelDeleteDnsElevatedButtonShadow => - isDarkMode - ? Colors.greenAccent.withValues(alpha: 0.4) - : Colors.indigo; + get dnsSelectorSheetPersonalCancelDeleteDnsElevatedButtonShadow => + isDarkMode ? Colors.greenAccent.withValues(alpha: 0.4) : Colors.indigo; static Color get dnsSelectionContainerCopySnackBarText => isDarkMode ? Colors.white : Colors.indigo; @@ -287,8 +286,8 @@ class AppColors { static Color get dnsSelectionCustomFloatingAddDnsActionButtonBackground => isDarkMode - ? const Color(0xFF1E2025) - : const Color.fromARGB(255, 196, 180, 221); + ? const Color(0xFF1E2025) + : const Color.fromARGB(255, 196, 180, 221); static Color get dnsSelectionCustomButton => isDarkMode ? const Color(0xFF16725C) : Colors.indigo; @@ -535,11 +534,7 @@ class GradientAppColors { static LinearGradient get mainWrapperBottomNavContainer => isDarkMode ? const LinearGradient( - colors: [ - Color(0xFF1A6B57), - Color(0xFF1BA87D), - Color(0xFF196856), - ], + colors: [Color(0xFF1A6B57), Color(0xFF1BA87D), Color(0xFF196856)], begin: AlignmentDirectional.topStart, end: AlignmentDirectional.bottomEnd, ) diff --git a/lib/core/resources/extention_sized.dart b/lib/core/resources/extention_sized.dart index 6020c8e..3c83322 100644 --- a/lib/core/resources/extention_sized.dart +++ b/lib/core/resources/extention_sized.dart @@ -1,10 +1,6 @@ import 'package:flutter/material.dart'; extension SizedBoxExt on num { - SizedBox get height => SizedBox( - height: toDouble(), - ); - SizedBox get width => SizedBox( - width: toDouble(), - ); -} \ No newline at end of file + SizedBox get height => SizedBox(height: toDouble()); + SizedBox get width => SizedBox(width: toDouble()); +} diff --git a/lib/core/services/android_dns_service.dart b/lib/core/services/android_dns_service.dart new file mode 100644 index 0000000..a63e4d8 --- /dev/null +++ b/lib/core/services/android_dns_service.dart @@ -0,0 +1,65 @@ +import 'dart:developer'; + +import 'package:flutter/services.dart'; +import 'package:netshift/core/services/dns_platform_service.dart'; + +/// Android-specific DNS service implementation using VPN method channel. +class AndroidDnsService implements DnsPlatformService { + static const MethodChannel _platform = + MethodChannel("com.netshift.dnschanger/netdns"); + + @override + bool get requiresInterfaceSelection => false; + + @override + Future prepareDns() async { + try { + await _platform.invokeMethod('prepareDns'); + return true; + } on PlatformException catch (e) { + log("Service Failed to Start $e"); + return false; + } + } + + @override + Future startDns({ + required String primaryDns, + required String secondaryDns, + required String interfaceName, + List disallowedApps = const [], + }) async { + try { + await _platform.invokeMethod('startDns', { + 'dns1': primaryDns, + 'dns2': secondaryDns, + 'disallowedApps': disallowedApps, + }); + } on PlatformException catch (e) { + log("Service Failed to Start $e"); + rethrow; + } + } + + @override + Future stopDns({required String interfaceName}) async { + try { + await _platform.invokeMethod('stopDns'); + } on PlatformException catch (e) { + log("Service Failed to Stop $e"); + rethrow; + } + } + + @override + Future flushDns() async { + // Android doesn't support DNS flushing in the same way + log('DNS flush not supported on Android'); + } + + @override + Future> getNetworkInterfaces() async { + // Android doesn't require interface selection + return {}; + } +} diff --git a/lib/core/services/dns_platform_service.dart b/lib/core/services/dns_platform_service.dart new file mode 100644 index 0000000..6b93692 --- /dev/null +++ b/lib/core/services/dns_platform_service.dart @@ -0,0 +1,44 @@ +import 'dart:io'; + +import 'package:netshift/core/services/android_dns_service.dart'; +import 'package:netshift/core/services/macos_dns_service.dart'; +import 'package:netshift/core/services/windows_dns_service.dart'; + +/// Abstract service for platform-specific DNS operations. +/// Use [DnsPlatformService.create] to get the appropriate implementation. +abstract class DnsPlatformService { + /// Factory constructor that returns the appropriate platform implementation. + factory DnsPlatformService.create() { + if (Platform.isAndroid) { + return AndroidDnsService(); + } else if (Platform.isWindows) { + return WindowsDnsService(); + } else if (Platform.isMacOS) { + return MacOsDnsService(); + } + throw UnsupportedError('Platform not supported'); + } + + /// Start DNS with the given primary and secondary DNS servers. + Future startDns({ + required String primaryDns, + required String secondaryDns, + required String interfaceName, + List disallowedApps = const [], + }); + + /// Stop DNS and reset to default. + Future stopDns({required String interfaceName}); + + /// Flush DNS cache. + Future flushDns(); + + /// Get available network interfaces. + Future> getNetworkInterfaces(); + + /// Prepare DNS service (Android-specific, no-op on other platforms). + Future prepareDns() async => true; + + /// Check if the platform requires interface selection. + bool get requiresInterfaceSelection; +} diff --git a/lib/core/services/dns_storage_service.dart b/lib/core/services/dns_storage_service.dart new file mode 100644 index 0000000..a0b4bc7 --- /dev/null +++ b/lib/core/services/dns_storage_service.dart @@ -0,0 +1,82 @@ +import 'dart:developer'; + +import 'package:get_storage/get_storage.dart'; +import 'package:netshift/models/dns_model.dart'; + +/// Service for persisting DNS-related data to local storage. +class DnsStorageService { + final GetStorage _storage = GetStorage(); + final GetStorage _interfaceStorage = GetStorage(); + final GetStorage _personalDnsStorage = GetStorage(); + + // Storage keys + static const String _keyConnectionStatus = 'connectionButtonStatus'; + static const String _keySelectedDns = 'selectedDnsValue'; + static const String _keyPersonalDnsList = 'dnsListPersonal'; + static const String _keyInterfaceName = 'interfaceNameStore'; + static const String _keyPrepareDns = 'prepareDns'; + + // Connection status + void saveConnectionStatus(bool isActive) { + _storage.write(_keyConnectionStatus, isActive); + } + + bool loadConnectionStatus() { + bool status = _storage.read(_keyConnectionStatus) ?? false; + log("Your connection button state is: $status"); + return status; + } + + // Selected DNS + void saveSelectedDns(DnsModel dns) { + _storage.write(_keySelectedDns, dns.toJson()); + } + + DnsModel? loadSelectedDns() { + final dynamic dnsData = _storage.read(_keySelectedDns); + if (dnsData != null) { + DnsModel dns = DnsModel.fromJson(dnsData); + log("Your selected DNS is: ${dns.name}"); + return dns; + } + return null; + } + + // Personal DNS list + void savePersonalDnsList(List dnsList) { + _personalDnsStorage.write( + _keyPersonalDnsList, + dnsList.map((DnsModel dns) => dns.toJson()).toList(), + ); + } + + List loadPersonalDnsList() { + final dynamic dnsData = _personalDnsStorage.read(_keyPersonalDnsList); + if (dnsData != null) { + return (dnsData as List) + .map( + (dynamic item) => DnsModel.fromJson(Map.from(item)), + ) + .toList(); + } + return []; + } + + // Interface name + void saveInterfaceName(String interfaceName) { + _interfaceStorage.write(_keyInterfaceName, interfaceName); + } + + String loadInterfaceName() { + return _storage.read(_keyInterfaceName) ?? 'Select Interface'; + } + + // Prepare DNS permission (Android) + void savePrepareDnsPermission(bool isPermissionGiven) { + _storage.write(_keyPrepareDns, isPermissionGiven); + } + + bool loadPrepareDnsPermission() { + return _storage.read(_keyPrepareDns) ?? false; + } +} diff --git a/lib/core/services/ip_address_service.dart b/lib/core/services/ip_address_service.dart new file mode 100644 index 0000000..acee559 --- /dev/null +++ b/lib/core/services/ip_address_service.dart @@ -0,0 +1,51 @@ +import 'dart:developer'; + +import 'package:get_ip_address/get_ip_address.dart'; + +/// Service for fetching and managing IP address information. +class IpAddressService { + /// Fetches the current public IP address. + /// Returns the IP address string, or an error message if failed. + Future getIpAddress() async { + try { + final IpAddress ipAddress = IpAddress(type: RequestType.text); + final dynamic data = await ipAddress.getIpAddress(); + bool isIPv6 = data.toString().contains(':'); + + if (isIPv6) { + log("Failed: IPv6 detected"); + return IpAddressResult( + success: false, + ipAddress: "Failed(IPv6)", + errorMessage: "IPv6 detected", + ); + } + + log("IP Address: $data"); + return IpAddressResult( + success: true, + ipAddress: data.toString(), + ); + } on IpAddressException catch (e) { + log("IP Address Error: ${e.message}"); + return IpAddressResult( + success: false, + ipAddress: "Failed(Offline)", + errorMessage: e.message, + ); + } + } +} + +/// Result class for IP address fetching operations. +class IpAddressResult { + final bool success; + final String ipAddress; + final String? errorMessage; + + const IpAddressResult({ + required this.success, + required this.ipAddress, + this.errorMessage, + }); +} diff --git a/lib/core/services/macos_dns_service.dart b/lib/core/services/macos_dns_service.dart new file mode 100644 index 0000000..40ec37f --- /dev/null +++ b/lib/core/services/macos_dns_service.dart @@ -0,0 +1,166 @@ +import 'dart:developer'; +import 'dart:io'; + +import 'package:netshift/core/services/dns_platform_service.dart'; + +/// macOS-specific DNS service implementation using networksetup commands. +class MacOsDnsService implements DnsPlatformService { + @override + bool get requiresInterfaceSelection => true; + + @override + Future prepareDns() async => true; + + @override + Future startDns({ + required String primaryDns, + required String secondaryDns, + required String interfaceName, + List disallowedApps = const [], + }) async { + try { + String networkService = interfaceName.contains('Select Interface') + ? await _getActiveNetworkService() + : interfaceName; + + if (networkService.isEmpty) { + log('No active network service found'); + throw Exception('No active network service found'); + } + + final ProcessResult result = await Process.run('osascript', [ + '-e', + 'do shell script "networksetup -setdnsservers \'$networkService\' $primaryDns $secondaryDns" with administrator privileges', + ]); + + if (result.exitCode != 0) { + log('Error setting DNS: ${result.stderr}'); + throw Exception('Error setting DNS: ${result.stderr}'); + } + + log('DNS configured successfully on $networkService'); + } catch (e) { + log('Error configuring DNS: $e'); + rethrow; + } + } + + @override + Future stopDns({required String interfaceName}) async { + try { + String networkService = interfaceName.contains('Select Interface') + ? await _getActiveNetworkService() + : interfaceName; + + if (networkService.isEmpty) { + log('No active network service found'); + return; + } + + final ProcessResult resetResult = await Process.run('osascript', [ + '-e', + 'do shell script "networksetup -setdnsservers \'$networkService\' Empty" with administrator privileges', + ]); + + if (resetResult.exitCode != 0) { + log('Error resetting DNS: ${resetResult.stderr}'); + throw Exception('Error resetting DNS: ${resetResult.stderr}'); + } + + log('DNS reset to default on $networkService'); + } catch (e) { + log('Error disabling DNS: $e'); + rethrow; + } + } + + @override + Future flushDns() async { + try { + await Process.run('dscacheutil', ['-flushcache']); + await Process.run('osascript', [ + '-e', + 'do shell script "killall -HUP mDNSResponder" with administrator privileges', + ]); + log('DNS cache flushed on macOS'); + } catch (e) { + log('Error flushing DNS cache: $e'); + rethrow; + } + } + + @override + Future> getNetworkInterfaces() async { + Map interfaces = {}; + + try { + final ProcessResult result = await Process.run('networksetup', [ + '-listallnetworkservices', + ]); + + if (result.exitCode != 0) { + return {'Wi-Fi': 'Unknown'}; + } + + List services = result.stdout.toString().split('\n'); + + for (String service in services.skip(1)) { + service = service.trim(); + if (service.isEmpty || service.startsWith('*')) continue; + + final ProcessResult ipResult = await Process.run('networksetup', [ + '-getinfo', + service, + ]); + + bool isConnected = + ipResult.stdout.toString().contains('IP address:') && + !ipResult.stdout.toString().contains('IP address: none'); + + interfaces[service] = isConnected ? 'Connected' : 'Disconnected'; + } + + // Sort to put connected interfaces first + List> sortedEntries = interfaces.entries.toList() + ..sort((MapEntry a, MapEntry b) { + if (a.value == b.value) return 0; + return a.value == "Connected" ? -1 : 1; + }); + + return Map.fromEntries(sortedEntries); + } catch (e) { + log('Error getting macOS network services: $e'); + return {'Wi-Fi': 'Unknown'}; + } + } + + Future _getActiveNetworkService() async { + try { + final ProcessResult result = await Process.run('networksetup', [ + '-listallnetworkservices', + ]); + + if (result.exitCode != 0) return 'Wi-Fi'; + + List services = result.stdout.toString().split('\n'); + for (String service in services.skip(1)) { + service = service.trim(); + if (service.isEmpty || service.startsWith('*')) continue; + + final ProcessResult ipResult = await Process.run('networksetup', [ + '-getinfo', + service, + ]); + + if (ipResult.stdout.toString().contains('IP address:') && + !ipResult.stdout.toString().contains('IP address: none')) { + return service; + } + } + return 'Wi-Fi'; + } catch (e) { + log('Error getting network service: $e'); + return 'Wi-Fi'; + } + } +} diff --git a/lib/core/services/macos_title_bar_box.dart b/lib/core/services/macos_title_bar_box.dart index 23df4cc..56d15a9 100644 --- a/lib/core/services/macos_title_bar_box.dart +++ b/lib/core/services/macos_title_bar_box.dart @@ -21,11 +21,7 @@ class MacOSTitleBarBox extends StatelessWidget { onPanStart: (_) => windowManager.startDragging(), child: Row( children: [ - Image.asset( - Assets.png.tray, - width: 16, - height: 16, - ), + Image.asset(Assets.png.tray, width: 16, height: 16), const SizedBox(width: 8), const Text( 'NetShift', diff --git a/lib/core/services/tray_manager_service.dart b/lib/core/services/tray_manager_service.dart index 4795da4..f5c84e9 100644 --- a/lib/core/services/tray_manager_service.dart +++ b/lib/core/services/tray_manager_service.dart @@ -7,14 +7,8 @@ Future initTray() async { await TrayManager.instance.setIcon(Assets.png.tray); List items = [ - MenuItem( - key: 'show', - label: 'Show App', - ), - MenuItem( - key: 'exit', - label: 'Exit', - ), + MenuItem(key: 'show', label: 'Show App'), + MenuItem(key: 'exit', label: 'Exit'), ]; await TrayManager.instance.setContextMenu(Menu(items: items)); diff --git a/lib/core/services/url_constant.dart b/lib/core/services/url_constant.dart index 03bb700..f49553a 100644 --- a/lib/core/services/url_constant.dart +++ b/lib/core/services/url_constant.dart @@ -1,19 +1,11 @@ class UrlConstant { static const String baseUrl = ''; - static const String ananas = - ''; - static const String ananas1 = - ''; - static const String ananas2 = - ''; - static const String ananas3 = - ''; - static const String ananas4 = - ''; - static const String ananas5 = - ''; - static const String ananas6 = - ''; - static const String ananas7 = - ''; + static const String ananas = ''; + static const String ananas1 = ''; + static const String ananas2 = ''; + static const String ananas3 = ''; + static const String ananas4 = ''; + static const String ananas5 = ''; + static const String ananas6 = ''; + static const String ananas7 = ''; } diff --git a/lib/core/services/windows_dns_service.dart b/lib/core/services/windows_dns_service.dart new file mode 100644 index 0000000..30a6d62 --- /dev/null +++ b/lib/core/services/windows_dns_service.dart @@ -0,0 +1,140 @@ +import 'dart:developer'; +import 'dart:io'; + +import 'package:netshift/core/services/dns_platform_service.dart'; + +/// Windows-specific DNS service implementation using netsh commands. +class WindowsDnsService implements DnsPlatformService { + @override + bool get requiresInterfaceSelection => true; + + @override + Future prepareDns() async => true; + + @override + Future startDns({ + required String primaryDns, + required String secondaryDns, + required String interfaceName, + List disallowedApps = const [], + }) async { + try { + final ProcessResult primaryDnsResult = await Process.run('netsh', [ + 'interface', + 'ipv4', + 'set', + 'dns', + 'name="$interfaceName"', + 'static', + primaryDns, + 'primary', + ]); + + if (primaryDnsResult.exitCode != 0) { + throw Exception('Error setting primary DNS: ${primaryDnsResult.stderr}'); + } + + final ProcessResult secondaryDnsResult = await Process.run('netsh', [ + 'interface', + 'ipv4', + 'add', + 'dns', + 'name="$interfaceName"', + secondaryDns, + 'index=2', + ]); + + if (secondaryDnsResult.exitCode != 0) { + throw Exception( + 'Error setting secondary DNS: ${secondaryDnsResult.stderr}', + ); + } + + log('DNS configured successfully.'); + } catch (e) { + log('Error configuring DNS: $e'); + rethrow; + } + } + + @override + Future stopDns({required String interfaceName}) async { + try { + final ProcessResult resetDnsResult = await Process.run('netsh', [ + 'interface', + 'ipv4', + 'set', + 'dns', + 'name="$interfaceName"', + 'source=dhcp', + ]); + + if (resetDnsResult.exitCode != 0) { + throw Exception('Error resetting DNS: ${resetDnsResult.stderr}'); + } + + log('DNS reset to default.'); + } catch (e) { + log('Error disabling DNS: $e'); + rethrow; + } + } + + @override + Future flushDns() async { + ProcessResult result = await Process.run( + 'ipconfig', + ['/flushdns'], + runInShell: true, + ); + log(result.stdout.toString()); + } + + @override + Future> getNetworkInterfaces() async { + ProcessResult result = await Process.run( + 'netsh', + ['interface', 'show', 'interface'], + runInShell: true, + ); + + String interfaceOutput = result.stdout.toString(); + return _parseNetshOutput(interfaceOutput); + } + + Map _parseNetshOutput(String output) { + Map interfaceMap = {}; + List lines = output.split('\n'); + + int nameIndex = -1; + int stateIndex = -1; + + for (String line in lines) { + line = line.trim(); + if (line.toLowerCase().contains("admin state")) { + List headers = line.split(RegExp(r'\s{2,}')); + nameIndex = headers.indexOf("Interface Name"); + stateIndex = headers.indexOf("State"); + continue; + } + + if (nameIndex != -1 && stateIndex != -1) { + List parts = line.split(RegExp(r'\s{2,}')); + if (parts.length > stateIndex) { + String name = parts[nameIndex].trim(); + String state = parts[stateIndex].trim(); + + interfaceMap[name] = state; + } + } + } + + List> sortedEntries = interfaceMap.entries.toList() + ..sort((MapEntry a, MapEntry b) { + if (a.value == b.value) return 0; + return a.value == "Connected" ? -1 : 1; + }); + + return Map.fromEntries(sortedEntries); + } +} diff --git a/lib/core/services/windows_local_notif.dart b/lib/core/services/windows_local_notif.dart index 6407e4c..f8a74e4 100644 --- a/lib/core/services/windows_local_notif.dart +++ b/lib/core/services/windows_local_notif.dart @@ -3,10 +3,7 @@ import 'package:local_notifier/local_notifier.dart'; class WindowsLocalNotif { final String title; final String body; - WindowsLocalNotif({ - required this.body, - required this.title, - }); + WindowsLocalNotif({required this.body, required this.title}); void showNotification() { final notification = LocalNotification( title: title, diff --git a/lib/core/services/windows_title_bar_box.dart b/lib/core/services/windows_title_bar_box.dart index 83fd75b..ebcfa31 100644 --- a/lib/core/services/windows_title_bar_box.dart +++ b/lib/core/services/windows_title_bar_box.dart @@ -20,11 +20,7 @@ class WindowsTitleBarBox extends StatelessWidget { child: Row( children: [ const SizedBox(width: 10), - Image.asset( - Assets.png.tray, - width: 16, - height: 16, - ), + Image.asset(Assets.png.tray, width: 16, height: 16), const SizedBox(width: 8), const Text( 'NetShift', @@ -39,12 +35,7 @@ class WindowsTitleBarBox extends StatelessWidget { ), ), ), - const Row( - children: [ - MinimizeWindowButton(), - CloseWindowButton(), - ], - ), + const Row(children: [MinimizeWindowButton(), CloseWindowButton()]), ], ), ), @@ -77,9 +68,9 @@ class CloseWindowButton extends StatelessWidget { onPressed: () { appWindow.hide(); WindowsLocalNotif( - body: "NetShift is running in the background", - title: "Minimized to Tray") - .showNotification(); + body: "NetShift is running in the background", + title: "Minimized to Tray", + ).showNotification(); }, tooltip: 'Close', ); diff --git a/lib/core/widgets/action_card.dart b/lib/core/widgets/common/action_card.dart similarity index 100% rename from lib/core/widgets/action_card.dart rename to lib/core/widgets/common/action_card.dart diff --git a/lib/core/widgets/app_bar.dart b/lib/core/widgets/common/app_bar.dart similarity index 73% rename from lib/core/widgets/app_bar.dart rename to lib/core/widgets/common/app_bar.dart index 3bbe44a..82be704 100644 --- a/lib/core/widgets/app_bar.dart +++ b/lib/core/widgets/common/app_bar.dart @@ -26,18 +26,19 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { return AppBar( - centerTitle: centerTitle, - backgroundColor: AppColors.background, - title: Text( - title, - style: TextStyle( - fontFamily: fontFamily, - fontSize: 24, - fontWeight: fontWeight, - color: AppColors.textAppBar, - ), + centerTitle: centerTitle, + backgroundColor: AppColors.background, + title: Text( + title, + style: TextStyle( + fontFamily: fontFamily, + fontSize: 24, + fontWeight: fontWeight, + color: AppColors.textAppBar, ), - actions: actions); + ), + actions: actions, + ); } @override diff --git a/lib/core/widgets/custom_button.dart b/lib/core/widgets/common/custom_button.dart similarity index 77% rename from lib/core/widgets/custom_button.dart rename to lib/core/widgets/common/custom_button.dart index ff1c424..1b38eb1 100644 --- a/lib/core/widgets/custom_button.dart +++ b/lib/core/widgets/common/custom_button.dart @@ -4,19 +4,17 @@ import 'package:netshift/core/resources/app_colors.dart'; class CustomButton extends StatelessWidget { final String text; final Function() onTap; - const CustomButton({ - super.key, - required this.text, - required this.onTap, - }); + const CustomButton({super.key, required this.text, required this.onTap}); @override Widget build(BuildContext context) { return ElevatedButton( onPressed: onTap, style: ButtonStyle( - backgroundColor: - WidgetStatePropertyAll(AppColors.dnsSelectionCustomButton)), + backgroundColor: WidgetStatePropertyAll( + AppColors.dnsSelectionCustomButton, + ), + ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 22, vertical: 10), child: Text( diff --git a/lib/core/widgets/custom_floating_action_button.dart b/lib/core/widgets/common/custom_floating_action_button.dart similarity index 78% rename from lib/core/widgets/custom_floating_action_button.dart rename to lib/core/widgets/common/custom_floating_action_button.dart index bfdc84f..393e86a 100644 --- a/lib/core/widgets/custom_floating_action_button.dart +++ b/lib/core/widgets/common/custom_floating_action_button.dart @@ -15,10 +15,9 @@ class CustomFloatingActionButton extends StatelessWidget { Widget build(BuildContext context) { return ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: AppColors.dnsSelectionCustomFloatingActionButtonBackground, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), + backgroundColor: + AppColors.dnsSelectionCustomFloatingActionButtonBackground, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.all(18), elevation: 2, minimumSize: const Size(60, 60), diff --git a/lib/core/widgets/custom_snack_bar.dart b/lib/core/widgets/common/custom_snack_bar.dart similarity index 94% rename from lib/core/widgets/custom_snack_bar.dart rename to lib/core/widgets/common/custom_snack_bar.dart index 0fc32d8..fb6f688 100644 --- a/lib/core/widgets/custom_snack_bar.dart +++ b/lib/core/widgets/common/custom_snack_bar.dart @@ -40,11 +40,7 @@ class CustomSnackBar { fontWeight: FontWeight.w600, ), ), - icon: Icon( - icon, - color: iconColor, - size: 32, - ), + icon: Icon(icon, color: iconColor, size: 32), snackPosition: SnackPosition.TOP, margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), borderRadius: 10, diff --git a/lib/core/widgets/custom_textfield.dart b/lib/core/widgets/common/custom_textfield.dart similarity index 86% rename from lib/core/widgets/custom_textfield.dart rename to lib/core/widgets/common/custom_textfield.dart index 4b932d4..2eaebc8 100644 --- a/lib/core/widgets/custom_textfield.dart +++ b/lib/core/widgets/common/custom_textfield.dart @@ -3,14 +3,11 @@ import 'package:flutter/services.dart'; import 'package:flutter_svg/svg.dart'; import 'package:netshift/gen/assets.gen.dart'; import 'package:netshift/core/resources/app_colors.dart'; -import 'package:netshift/core/widgets/custom_snack_bar.dart'; +import 'package:netshift/core/widgets/common/custom_snack_bar.dart'; class CustomTextContainer extends StatelessWidget { final String dns; - const CustomTextContainer({ - super.key, - required this.dns, - }); + const CustomTextContainer({super.key, required this.dns}); void copyToClipboard(BuildContext context, String dns) { Clipboard.setData(ClipboardData(text: dns)); @@ -50,8 +47,10 @@ class CustomTextContainer extends StatelessWidget { IconButton( icon: SvgPicture.asset( Assets.svg.copy, - colorFilter: - ColorFilter.mode(AppColors.dnsSelectionCopy, BlendMode.srcIn), + colorFilter: ColorFilter.mode( + AppColors.dnsSelectionCopy, + BlendMode.srcIn, + ), ), onPressed: () => copyToClipboard(context, dns), ), diff --git a/lib/core/widgets/dis.dart b/lib/core/widgets/common/dis.dart similarity index 79% rename from lib/core/widgets/dis.dart rename to lib/core/widgets/common/dis.dart index 83e3716..585cd96 100644 --- a/lib/core/widgets/dis.dart +++ b/lib/core/widgets/common/dis.dart @@ -9,10 +9,7 @@ class Dis extends StatelessWidget { return GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => Navigator.of(context).pop(), - child: GestureDetector( - onTap: () {}, - child: child, - ), + child: GestureDetector(onTap: () {}, child: child), ); } } diff --git a/lib/core/widgets/double_tap_to_exit.dart b/lib/core/widgets/common/double_tap_to_exit.dart similarity index 87% rename from lib/core/widgets/double_tap_to_exit.dart rename to lib/core/widgets/common/double_tap_to_exit.dart index d9e2f2f..a5c9183 100644 --- a/lib/core/widgets/double_tap_to_exit.dart +++ b/lib/core/widgets/common/double_tap_to_exit.dart @@ -1,14 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:netshift/core/widgets/flutter_toast.dart'; +import 'package:netshift/core/widgets/common/flutter_toast.dart'; class DoubleTapToExit extends StatefulWidget { final Widget child; - const DoubleTapToExit({ - super.key, - required this.child, - }); + const DoubleTapToExit({super.key, required this.child}); @override State createState() => _DoubleTapToExitState(); @@ -19,7 +16,6 @@ class _DoubleTapToExitState extends State { @override Widget build(BuildContext context) { - // ignore: deprecated_member_use return WillPopScope( child: widget.child, diff --git a/lib/core/widgets/flutter_toast.dart b/lib/core/widgets/common/flutter_toast.dart similarity index 88% rename from lib/core/widgets/flutter_toast.dart rename to lib/core/widgets/common/flutter_toast.dart index bf8cda0..3da12fc 100644 --- a/lib/core/widgets/flutter_toast.dart +++ b/lib/core/widgets/common/flutter_toast.dart @@ -5,9 +5,7 @@ import 'package:flutter/services.dart'; class FlutterToast { final String message; static const platform = MethodChannel('com.netshift.dnschanger/toast'); - FlutterToast({ - required this.message, - }); + FlutterToast({required this.message}); Future flutterToast() async { try { await platform.invokeMethod('showToast', {'message': message}); diff --git a/lib/core/widgets/nav_bar.dart b/lib/core/widgets/common/nav_bar.dart similarity index 90% rename from lib/core/widgets/nav_bar.dart rename to lib/core/widgets/common/nav_bar.dart index f6e1174..5a140ac 100644 --- a/lib/core/widgets/nav_bar.dart +++ b/lib/core/widgets/common/nav_bar.dart @@ -1,4 +1,3 @@ - import 'package:flutter/material.dart'; import 'package:netshift/core/resources/app_colors.dart'; @@ -34,7 +33,7 @@ class NavBar extends StatelessWidget { blurRadius: 8, offset: const Offset(1, 6), spreadRadius: 3, - ) + ), ] : [], ), @@ -43,11 +42,7 @@ class NavBar extends StatelessWidget { // colorFilter: ColorFilter.mode( // AppColors.mainWrapperNavBarIcons, BlendMode.srcIn), // ), - child: Icon( - icon, - color: AppColors.mainWrapperNavBarIcons, - size: 30, - ), + child: Icon(icon, color: AppColors.mainWrapperNavBarIcons, size: 30), ), ); } diff --git a/lib/core/widgets/offline_banner.dart b/lib/core/widgets/common/offline_banner.dart similarity index 97% rename from lib/core/widgets/offline_banner.dart rename to lib/core/widgets/common/offline_banner.dart index 0774f03..f56d986 100644 --- a/lib/core/widgets/offline_banner.dart +++ b/lib/core/widgets/common/offline_banner.dart @@ -6,7 +6,7 @@ import 'package:netshift/controller/splash_controller.dart'; import 'package:netshift/core/resources/app_colors.dart'; import 'package:netshift/core/resources/extention_sized.dart'; import 'package:netshift/core/services/windows_local_notif.dart'; -import 'package:netshift/core/widgets/flutter_toast.dart'; +import 'package:netshift/core/widgets/common/flutter_toast.dart'; import 'package:netshift/screens/splash_screen.dart'; class OfflineBanner extends StatelessWidget { diff --git a/lib/core/widgets/status_bar.dart b/lib/core/widgets/common/status_bar.dart similarity index 96% rename from lib/core/widgets/status_bar.dart rename to lib/core/widgets/common/status_bar.dart index cd2ed2b..e5e0072 100644 --- a/lib/core/widgets/status_bar.dart +++ b/lib/core/widgets/common/status_bar.dart @@ -16,7 +16,8 @@ class StatusIcon extends StatelessWidget { required this.label, required this.status, required this.color, - required this.onTap, required this.iconColor, + required this.onTap, + required this.iconColor, }); @override @@ -56,7 +57,7 @@ class StatusIcon extends StatelessWidget { fontWeight: FontWeight.w600, fontSize: 15, ), - ) + ), ], ), ), diff --git a/lib/core/widgets/desktop/desktop_settings_card.dart b/lib/core/widgets/desktop/desktop_settings_card.dart new file mode 100644 index 0000000..a19ab7a --- /dev/null +++ b/lib/core/widgets/desktop/desktop_settings_card.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:netshift/core/resources/app_colors.dart'; + +class DesktopSettingsCard extends StatelessWidget { + const DesktopSettingsCard({ + super.key, + required this.icon, + required this.title, + required this.subtitle, + this.trailing, + this.onTap, + }); + + final IconData icon; + final String title; + final String subtitle; + final Widget? trailing; + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + return MouseRegion( + cursor: onTap != null + ? SystemMouseCursors.click + : SystemMouseCursors.basic, + child: GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.isDarkMode + ? const Color(0xFF262626) + : const Color(0xFFF5F5F5), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: AppColors.isDarkMode + ? Colors.white.withValues(alpha: 0.05) + : Colors.indigo.withValues(alpha: 0.1), + ), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.isDarkMode + ? Colors.greenAccent.withValues(alpha: 0.1) + : Colors.indigo.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Icon( + icon, + color: AppColors.settingsIconColors, + size: 22, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontFamily: 'Poppins', + fontSize: 14, + fontWeight: FontWeight.w600, + color: AppColors.settingsCustomCardTitle, + ), + ), + const SizedBox(height: 2), + Text( + subtitle, + style: TextStyle( + fontFamily: 'Poppins', + fontSize: 12, + color: AppColors.settingsCustomCardSubTitle, + ), + ), + ], + ), + ), + if (trailing != null) trailing!, + if (onTap != null && trailing == null) + Icon( + Icons.chevron_right, + color: AppColors.isDarkMode + ? Colors.greenAccent.withValues(alpha: 0.5) + : Colors.indigo.withValues(alpha: 0.5), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/core/widgets/desktop_sidebar.dart b/lib/core/widgets/desktop/desktop_sidebar.dart similarity index 91% rename from lib/core/widgets/desktop_sidebar.dart rename to lib/core/widgets/desktop/desktop_sidebar.dart index 8a78097..580cc39 100644 --- a/lib/core/widgets/desktop_sidebar.dart +++ b/lib/core/widgets/desktop/desktop_sidebar.dart @@ -16,10 +16,7 @@ class DesktopSidebar extends StatelessWidget { decoration: BoxDecoration( gradient: LinearGradient( colors: AppColors.isDarkMode - ? [ - const Color(0xFF1A1B21), - const Color(0xFF1E2025), - ] + ? [const Color(0xFF1A1B21), const Color(0xFF1E2025)] : [ const Color.fromARGB(255, 231, 218, 250), const Color.fromARGB(255, 220, 205, 245), @@ -60,11 +57,7 @@ class DesktopSidebar extends StatelessWidget { ), ], ), - child: Image.asset( - Assets.png.tray, - width: 28, - height: 28, - ), + child: Image.asset(Assets.png.tray, width: 28, height: 28), ), const SizedBox(width: 12), Text( @@ -178,8 +171,8 @@ class _SidebarItemState extends State<_SidebarItem> { : null, color: isHovered && !widget.isActive ? (AppColors.isDarkMode - ? Colors.white.withValues(alpha: 0.05) - : Colors.indigo.withValues(alpha: 0.1)) + ? Colors.white.withValues(alpha: 0.05) + : Colors.indigo.withValues(alpha: 0.1)) : null, borderRadius: BorderRadius.circular(12), boxShadow: widget.isActive @@ -201,8 +194,8 @@ class _SidebarItemState extends State<_SidebarItem> { color: widget.isActive ? Colors.white : (AppColors.isDarkMode - ? Colors.white70 - : Colors.indigo.withValues(alpha: 0.8)), + ? Colors.white70 + : Colors.indigo.withValues(alpha: 0.8)), size: 22, ), const SizedBox(width: 12), @@ -211,13 +204,14 @@ class _SidebarItemState extends State<_SidebarItem> { style: TextStyle( fontFamily: 'Poppins', fontSize: 14, - fontWeight: - widget.isActive ? FontWeight.w600 : FontWeight.w500, + fontWeight: widget.isActive + ? FontWeight.w600 + : FontWeight.w500, color: widget.isActive ? Colors.white : (AppColors.isDarkMode - ? Colors.white70 - : Colors.indigo.withValues(alpha: 0.8)), + ? Colors.white70 + : Colors.indigo.withValues(alpha: 0.8)), ), ), ], diff --git a/lib/core/widgets/desktop_status_card.dart b/lib/core/widgets/desktop/desktop_status_card.dart similarity index 91% rename from lib/core/widgets/desktop_status_card.dart rename to lib/core/widgets/desktop/desktop_status_card.dart index 5d560d7..a69e0e1 100644 --- a/lib/core/widgets/desktop_status_card.dart +++ b/lib/core/widgets/desktop/desktop_status_card.dart @@ -72,7 +72,9 @@ class DesktopStatusCard extends StatelessWidget { fontFamily: 'Poppins', fontSize: 14, fontWeight: FontWeight.w600, - color: AppColors.isDarkMode ? Colors.white : Colors.black87, + color: AppColors.isDarkMode + ? Colors.white + : Colors.black87, ), ), const SizedBox(height: 4), @@ -81,7 +83,9 @@ class DesktopStatusCard extends StatelessWidget { style: TextStyle( fontFamily: 'Poppins', fontSize: 12, - color: AppColors.isDarkMode ? Colors.white70 : Colors.black54, + color: AppColors.isDarkMode + ? Colors.white70 + : Colors.black54, ), overflow: TextOverflow.ellipsis, ), diff --git a/lib/core/widgets/add_dns.dart b/lib/core/widgets/dns/add_dns.dart similarity index 74% rename from lib/core/widgets/add_dns.dart rename to lib/core/widgets/dns/add_dns.dart index c83c35f..32735db 100644 --- a/lib/core/widgets/add_dns.dart +++ b/lib/core/widgets/dns/add_dns.dart @@ -4,8 +4,8 @@ import 'package:netshift/core/resources/media_query_size.dart'; import 'package:netshift/controller/netshift_engine_controller.dart'; import 'package:netshift/controller/single_dns_ping_controller.dart'; import 'package:netshift/core/resources/app_colors.dart'; -import 'package:netshift/core/widgets/add_dns_text_field.dart'; -import 'package:netshift/core/widgets/custom_button.dart'; +import 'package:netshift/core/widgets/dns/add_dns_text_field.dart'; +import 'package:netshift/core/widgets/common/custom_button.dart'; class AddDnsBottomSheet extends StatefulWidget { const AddDnsBottomSheet({super.key}); @@ -21,9 +21,12 @@ class _AddDnsBottomSheetState extends State { final TextEditingController secondaryDnsController = TextEditingController(); - NetshiftEngineController netshiftEngineController = - Get.put(NetshiftEngineController()); - SingleDnsPingController dnsPingController = Get.put(SingleDnsPingController()); + NetshiftEngineController netshiftEngineController = Get.put( + NetshiftEngineController(), + ); + SingleDnsPingController dnsPingController = Get.put( + SingleDnsPingController(), + ); @override void dispose() { @@ -36,40 +39,33 @@ class _AddDnsBottomSheetState extends State { @override Widget build(BuildContext context) { return Padding( - padding: - EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), child: Container( width: double.infinity, height: ScreenSize.height * 0.4, decoration: BoxDecoration( color: AppColors.dnsSelectionCustomFloatingAddDnsActionButtonBackground, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(24), - ), + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ AddDNSTextField( label: "DNS Name", - suffixIcon: const Icon( - Icons.data_object_sharp, - ), + suffixIcon: const Icon(Icons.data_object_sharp), controller: dnsNameController, ), AddDNSTextField( label: "Primary DNS", - suffixIcon: const Icon( - Icons.dns_outlined, - ), + suffixIcon: const Icon(Icons.dns_outlined), controller: primaryDnsController, ), AddDNSTextField( label: "Secondary DNS", - suffixIcon: const Icon( - Icons.dns_outlined, - ), + suffixIcon: const Icon(Icons.dns_outlined), controller: secondaryDnsController, ), CustomButton( @@ -85,7 +81,7 @@ class _AddDnsBottomSheetState extends State { dnsPingController.pingSecondaryDns(); Navigator.of(context).pop(); }, - ) + ), ], ), ), diff --git a/lib/core/widgets/add_dns_text_field.dart b/lib/core/widgets/dns/add_dns_text_field.dart similarity index 100% rename from lib/core/widgets/add_dns_text_field.dart rename to lib/core/widgets/dns/add_dns_text_field.dart diff --git a/lib/core/widgets/delete_dns_alert_dialog.dart b/lib/core/widgets/dns/delete_dns_alert_dialog.dart similarity index 84% rename from lib/core/widgets/delete_dns_alert_dialog.dart rename to lib/core/widgets/dns/delete_dns_alert_dialog.dart index 94e3a47..fa904b7 100644 --- a/lib/core/widgets/delete_dns_alert_dialog.dart +++ b/lib/core/widgets/dns/delete_dns_alert_dialog.dart @@ -1,4 +1,3 @@ - import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:netshift/controller/netshift_engine_controller.dart'; @@ -14,20 +13,21 @@ class DeleteDNSAlertDialog extends StatelessWidget { final NetshiftEngineController netshiftEngineController; final dynamic dns; - final SingleDnsPingController dnsPingController = - Get.put(SingleDnsPingController()); + final SingleDnsPingController dnsPingController = Get.put( + SingleDnsPingController(), + ); @override Widget build(BuildContext context) { return AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), backgroundColor: AppColors.dnsSelectorSheetPersonalDeleteDnsBackground, title: Row( children: [ - Icon(Icons.warning_amber_rounded, - color: AppColors.dnsSelectorSheetPersonalDeleteDnsWarningIcon), + Icon( + Icons.warning_amber_rounded, + color: AppColors.dnsSelectorSheetPersonalDeleteDnsWarningIcon, + ), const SizedBox(width: 8), Text( "Delete DNS", @@ -64,10 +64,7 @@ class DeleteDNSAlertDialog extends StatelessWidget { onPressed: () { Navigator.of(context).pop(); }, - child: const Text( - "Cancel", - style: TextStyle(color: Colors.white), - ), + child: const Text("Cancel", style: TextStyle(color: Colors.white)), ), ElevatedButton( style: ElevatedButton.styleFrom( @@ -90,10 +87,7 @@ class DeleteDNSAlertDialog extends StatelessWidget { dnsPingController.pingSecondaryDns(); netshiftEngineController.savePersonalDns(); }, - child: const Text( - "Delete", - style: TextStyle(color: Colors.white), - ), + child: const Text("Delete", style: TextStyle(color: Colors.white)), ), ], ); diff --git a/lib/core/widgets/dns/dns_apply_button.dart b/lib/core/widgets/dns/dns_apply_button.dart new file mode 100644 index 0000000..bf6180d --- /dev/null +++ b/lib/core/widgets/dns/dns_apply_button.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:netshift/core/resources/app_colors.dart'; + +class DnsApplyButton extends StatelessWidget { + const DnsApplyButton({ + super.key, + required this.onPressed, + required this.isDisabled, + this.isDesktop = false, + }); + + final VoidCallback? onPressed; + final bool isDisabled; + final bool isDesktop; + + @override + Widget build(BuildContext context) { + if (isDesktop) { + return SizedBox( + width: double.infinity, + child: OutlinedButton( + style: OutlinedButton.styleFrom( + foregroundColor: isDisabled + ? Colors.grey + : AppColors.dnsPingApplyButtonForeground, + padding: const EdgeInsets.symmetric(vertical: 10), + side: BorderSide( + color: isDisabled + ? Colors.grey + : AppColors.dnsPingApplyButtonBorder, + width: 1.5, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + onPressed: isDisabled ? null : onPressed, + child: const Text("Apply DNS"), + ), + ); + } + + return OutlinedButton( + style: OutlinedButton.styleFrom( + foregroundColor: isDisabled + ? Colors.grey + : AppColors.dnsPingApplyButtonForeground, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + textStyle: const TextStyle(fontSize: 14, fontFamily: 'IRANSansX'), + side: BorderSide( + color: isDisabled ? Colors.grey : AppColors.dnsPingApplyButtonBorder, + width: 2, + ), + ), + onPressed: isDisabled ? null : onPressed, + child: const Text("Apply"), + ); + } +} diff --git a/lib/core/widgets/dns/dns_configuration_card.dart b/lib/core/widgets/dns/dns_configuration_card.dart new file mode 100644 index 0000000..09d0c41 --- /dev/null +++ b/lib/core/widgets/dns/dns_configuration_card.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:netshift/controller/netshift_engine_controller.dart'; +import 'package:netshift/core/resources/app_colors.dart'; +import 'package:netshift/core/widgets/common/custom_snack_bar.dart'; +import 'package:netshift/core/widgets/dns/dns_details_card.dart'; +import 'package:netshift/core/widgets/dns/dns_selection_container.dart'; +import 'package:netshift/core/widgets/dns/main_dns_selector.dart'; + +class DnsConfigurationCard extends StatelessWidget { + DnsConfigurationCard({super.key}); + + final NetshiftEngineController netshiftEngineController = Get.find(); + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "DNS Configuration", + style: TextStyle( + fontFamily: 'Poppins', + fontSize: 24, + fontWeight: FontWeight.bold, + color: AppColors.dnsText, + ), + ), + const SizedBox(height: 8), + Text( + "Select and configure your preferred DNS servers", + style: TextStyle( + fontFamily: 'Poppins', + fontSize: 14, + color: AppColors.dnsText.withValues(alpha: 0.7), + ), + ), + const SizedBox(height: 32), + DnsSelectionContainer( + onPressed: () => _handleDnsSelection(context), + color: AppColors.dnsSelectionContainerIcon, + ), + const SizedBox(height: 32), + DnsDetailsCard(), + ], + ), + ); + } + + void _handleDnsSelection(BuildContext context) { + if (netshiftEngineController.isActive.value) { + CustomSnackBar( + title: "Operation Failed", + message: "Failed to SELECT DNS, please stop the service first", + backColor: Colors.red.shade700.withValues(alpha: 0.9), + iconColor: Colors.white, + icon: Icons.error_outline, + textColor: Colors.white, + ).customSnackBar(); + return; + } + + showModalBottomSheet( + sheetAnimationStyle: AnimationStyle( + duration: const Duration(milliseconds: 400), + curve: Curves.easeInOut, + reverseDuration: const Duration(milliseconds: 300), + reverseCurve: Curves.easeInOut, + ), + context: context, + builder: (context) => MainDNSSelector(), + ); + } +} diff --git a/lib/core/widgets/dns/dns_details_card.dart b/lib/core/widgets/dns/dns_details_card.dart new file mode 100644 index 0000000..108832d --- /dev/null +++ b/lib/core/widgets/dns/dns_details_card.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:netshift/controller/netshift_engine_controller.dart'; +import 'package:netshift/controller/single_dns_ping_controller.dart'; +import 'package:netshift/core/resources/app_colors.dart'; +import 'package:netshift/core/widgets/dns/dns_info_row.dart'; + +class DnsDetailsCard extends StatelessWidget { + DnsDetailsCard({super.key}); + + final NetshiftEngineController netshiftEngineController = Get.find(); + + @override + Widget build(BuildContext context) { + Get.lazyPut(() => SingleDnsPingController()); + final SingleDnsPingController dnsPingController = Get.find(); + + return Obx( + () => Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: AppColors.isDarkMode + ? const Color(0xFF1E2025) + : Colors.white.withValues(alpha: 0.9), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColors.isDarkMode + ? Colors.black.withValues(alpha: 0.2) + : Colors.indigo.withValues(alpha: 0.1), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ], + ), + child: Column( + children: [ + DnsInfoRow( + label: "Primary DNS", + dns: netshiftEngineController.selectedDns.value.primaryDNS, + isPinging: dnsPingController.isPingP.value, + pingResult: dnsPingController.resultPingP, + ), + const SizedBox(height: 20), + Divider( + color: AppColors.isDarkMode + ? Colors.white12 + : Colors.indigo.withValues(alpha: 0.1), + ), + const SizedBox(height: 20), + DnsInfoRow( + label: "Secondary DNS", + dns: netshiftEngineController.selectedDns.value.secondaryDNS, + isPinging: dnsPingController.isPingS.value, + pingResult: dnsPingController.resultPingS, + ), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: () { + dnsPingController.pingPrimaryDns(); + dnsPingController.pingSecondaryDns(); + }, + icon: const Icon(Icons.speed, size: 20), + label: const Text("Test DNS Latency"), + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.isDarkMode + ? const Color(0xFF16725C) + : Colors.indigo, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/core/widgets/dns_generator_dialog.dart b/lib/core/widgets/dns/dns_generator_dialog.dart similarity index 88% rename from lib/core/widgets/dns_generator_dialog.dart rename to lib/core/widgets/dns/dns_generator_dialog.dart index 5938e24..783be87 100644 --- a/lib/core/widgets/dns_generator_dialog.dart +++ b/lib/core/widgets/dns/dns_generator_dialog.dart @@ -7,20 +7,17 @@ import 'package:netshift/controller/random_dns_generator_controller.dart'; import 'package:netshift/core/resources/app_colors.dart'; class DnsGeneratorDialog extends StatelessWidget { - DnsGeneratorDialog({ - super.key, - }); + DnsGeneratorDialog({super.key}); - final RandomDnsGeneratorController randomDnsGeneratorController = - Get.put(RandomDnsGeneratorController()); + final RandomDnsGeneratorController randomDnsGeneratorController = Get.put( + RandomDnsGeneratorController(), + ); @override Widget build(BuildContext context) { return Obx( () => AlertDialog( backgroundColor: AppColors.background, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), elevation: 10, title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -36,10 +33,7 @@ class DnsGeneratorDialog extends StatelessWidget { ), GestureDetector( onTap: () => Navigator.pop(context), - child: Icon( - Icons.close, - color: AppColors.dnsGeneratorTitle, - ), + child: Icon(Icons.close, color: AppColors.dnsGeneratorTitle), ), ], ), @@ -76,14 +70,14 @@ class DnsGeneratorDialog extends StatelessWidget { ), ) : Text( - "DNS Generated", - style: TextStyle( - fontFamily: 'Poppins', - fontWeight: FontWeight.w500, - fontSize: 18, - color: AppColors.dnsGeneratorText, + "DNS Generated", + style: TextStyle( + fontFamily: 'Poppins', + fontWeight: FontWeight.w500, + fontSize: 18, + color: AppColors.dnsGeneratorText, + ), ), - ), ], ), ), @@ -142,7 +136,7 @@ class DnsGeneratorDialog extends StatelessWidget { ), // 12.width, ], - ) + ), ], ), ); diff --git a/lib/core/widgets/dns_info_row.dart b/lib/core/widgets/dns/dns_info_row.dart similarity index 68% rename from lib/core/widgets/dns_info_row.dart rename to lib/core/widgets/dns/dns_info_row.dart index 7f0cf42..ca45329 100644 --- a/lib/core/widgets/dns_info_row.dart +++ b/lib/core/widgets/dns/dns_info_row.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:netshift/core/resources/app_colors.dart'; +import 'package:netshift/core/widgets/ping/ping_status_indicator.dart'; class DnsInfoRow extends StatelessWidget { final String label; @@ -60,33 +60,8 @@ class DnsInfoRow extends StatelessWidget { ], ), ), - _buildPingStatus(), + PingStatusIndicator(isPinging: isPinging, pingResult: pingResult), ], ); } - - Widget _buildPingStatus() { - if (isPinging) { - return SpinKitCircle(color: AppColors.spinKitColor, size: 24); - } - - return Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: BoxDecoration( - color: AppColors.isDarkMode - ? Colors.greenAccent.withValues(alpha: 0.15) - : Colors.indigo.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(20), - ), - child: Text( - "$pingResult ms", - style: TextStyle( - fontFamily: 'Poppins', - fontSize: 14, - fontWeight: FontWeight.w600, - color: AppColors.isDarkMode ? Colors.greenAccent : Colors.indigo, - ), - ), - ); - } } diff --git a/lib/core/widgets/dns/dns_list_item_netshift.dart b/lib/core/widgets/dns/dns_list_item_netshift.dart new file mode 100644 index 0000000..8b3a99b --- /dev/null +++ b/lib/core/widgets/dns/dns_list_item_netshift.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:netshift/models/dns_model.dart'; +import 'package:netshift/core/resources/app_colors.dart'; + +class DnsListItemNetshift extends StatelessWidget { + const DnsListItemNetshift({ + super.key, + required this.dns, + required this.onTap, + }); + + final DnsModel dns; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + color: AppColors.dnsSelectionContainerNetShiftContainer, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColors.dnsSelectionContainerNetShiftBorderContainer, + width: 2, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + dns.name, + style: TextStyle( + color: AppColors.dnsSelectionContainerNetShiftDnsName, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + "Primary: ${dns.primaryDNS}", + style: TextStyle( + color: AppColors.dnsSelectionContainerNetShiftDns, + fontSize: 14, + ), + ), + Text( + 'Secondary: ${dns.secondaryDNS}', + style: TextStyle( + color: AppColors.dnsSelectionContainerNetShiftDns, + fontSize: 14, + ), + ), + ], + ), + ), + dns.name.contains('*') + ? const Icon( + Icons.remove_circle, + color: Color.fromARGB(255, 255, 30, 0), + ) + : const Icon( + Icons.check_circle, + color: Color.fromARGB(255, 48, 136, 51), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/core/widgets/dns/dns_list_item_personal.dart b/lib/core/widgets/dns/dns_list_item_personal.dart new file mode 100644 index 0000000..56847e1 --- /dev/null +++ b/lib/core/widgets/dns/dns_list_item_personal.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:netshift/core/resources/media_query_size.dart'; +import 'package:netshift/models/dns_model.dart'; +import 'package:netshift/core/resources/app_colors.dart'; + +class DnsListItemPersonal extends StatelessWidget { + const DnsListItemPersonal({ + super.key, + required this.dns, + required this.onTap, + required this.onEdit, + required this.onDelete, + }); + + final DnsModel dns; + final VoidCallback onTap; + final VoidCallback onEdit; + final VoidCallback onDelete; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + color: AppColors.dnsSelectorSheetPersonalContainer, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColors.dnsSelectorSheetPersonalContainerBorder, + width: 2, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: ScreenSize.width * 0.55, + child: Text( + dns.name, + style: TextStyle( + color: AppColors.dnsSelectorSheetPersonalDnsName, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + const SizedBox(height: 4), + Text( + "Primary: ${dns.primaryDNS}", + style: TextStyle( + color: AppColors.dnsSelectorSheetPersonalDns, + fontSize: 14, + ), + ), + Text( + 'Secondary: ${dns.secondaryDNS}', + style: TextStyle( + color: AppColors.dnsSelectorSheetPersonalDns, + fontSize: 14, + ), + ), + ], + ), + ), + Row( + children: [ + IconButton( + onPressed: onEdit, + icon: Icon( + Icons.edit_outlined, + color: AppColors.dnsSelectorSheetPersonalDnsEdit, + size: 30, + ), + ), + IconButton( + onPressed: onDelete, + icon: Icon( + Icons.delete_sweep_outlined, + color: AppColors.dnsSelectorSheetPersonalDnsDelete, + size: 30, + ), + ), + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/core/widgets/dns/dns_ping_card.dart b/lib/core/widgets/dns/dns_ping_card.dart new file mode 100644 index 0000000..142df72 --- /dev/null +++ b/lib/core/widgets/dns/dns_ping_card.dart @@ -0,0 +1,175 @@ +import 'package:flutter/material.dart'; +import 'package:netshift/core/resources/app_colors.dart'; +import 'package:netshift/core/resources/extention_sized.dart'; +import 'package:netshift/core/widgets/dns/dns_apply_button.dart'; +import 'package:netshift/core/widgets/dns/dns_row_item.dart'; +import 'package:netshift/core/widgets/ping/ping_badge.dart'; + +class DnsPingCard extends StatelessWidget { + final String name; + final String primaryDns; + final String secondaryDns; + final String avgPing; + final String primaryPing; + final String secondaryPing; + final VoidCallback? onApply; + final bool isDesktop; + + const DnsPingCard({ + super.key, + required this.name, + required this.primaryDns, + required this.secondaryDns, + required this.avgPing, + required this.primaryPing, + required this.secondaryPing, + this.onApply, + this.isDesktop = false, + }); + + bool get isTimeout => avgPing == '-1'; + + @override + Widget build(BuildContext context) { + return isDesktop ? _buildDesktopCard() : _buildMobileCard(); + } + + Widget _buildDesktopCard() { + return AnimatedContainer( + duration: const Duration(milliseconds: 300), + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColors.isDarkMode + ? const Color(0xFF1E2025) + : Colors.white.withValues(alpha: 0.95), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: isTimeout + ? Colors.red.withValues(alpha: 0.5) + : AppColors.dnsPingContainerBorder.withValues(alpha: 0.5), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: AppColors.dnsPingContainerShadow.withValues(alpha: 0.2), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _DnsPingCardHeader(name: name, avgPing: avgPing), + const SizedBox(height: 12), + DnsRowItem(icon: Icons.dns, dns: primaryDns, ping: primaryPing), + const SizedBox(height: 6), + DnsRowItem( + icon: Icons.dns_outlined, + dns: secondaryDns, + ping: secondaryPing, + ), + const Spacer(), + DnsApplyButton( + onPressed: onApply, + isDisabled: isTimeout, + isDesktop: true, + ), + ], + ), + ); + } + + Widget _buildMobileCard() { + return AnimatedContainer( + duration: const Duration(milliseconds: 300), + margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.background, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: isTimeout + ? Colors.red.withValues(alpha: 0.5) + : AppColors.dnsPingContainerBorder, + width: 2, + ), + boxShadow: [ + BoxShadow( + color: AppColors.dnsPingContainerShadow, + blurRadius: 2, + spreadRadius: 1, + offset: const Offset(2, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _DnsPingCardHeader(name: name, avgPing: avgPing, isMobile: true), + Divider(color: AppColors.dnsPingDivider, thickness: 1.2, height: 20), + 8.height, + DnsRowItem( + icon: Icons.dns, + dns: primaryDns, + ping: primaryPing, + isMobile: true, + ), + 8.height, + DnsRowItem( + icon: Icons.dns_outlined, + dns: secondaryDns, + ping: secondaryPing, + isMobile: true, + ), + 16.height, + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + DnsApplyButton( + onPressed: onApply, + isDisabled: isTimeout, + isDesktop: false, + ), + ], + ), + ], + ), + ); + } +} + +class _DnsPingCardHeader extends StatelessWidget { + const _DnsPingCardHeader({ + required this.name, + required this.avgPing, + this.isMobile = false, + }); + + final String name; + final String avgPing; + final bool isMobile; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + name, + style: TextStyle( + color: AppColors.dnsPingName, + fontSize: isMobile ? 20 : 18, + fontFamily: isMobile ? 'IRANSansX' : 'Poppins', + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), + ), + PingBadge(avgPing: avgPing, isMobile: isMobile), + ], + ); + } +} diff --git a/lib/core/widgets/dns/dns_row_item.dart b/lib/core/widgets/dns/dns_row_item.dart new file mode 100644 index 0000000..2acc2d2 --- /dev/null +++ b/lib/core/widgets/dns/dns_row_item.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:netshift/core/resources/app_colors.dart'; +import 'package:netshift/core/resources/extention_sized.dart'; + +class DnsRowItem extends StatelessWidget { + const DnsRowItem({ + super.key, + required this.icon, + required this.dns, + required this.ping, + this.isMobile = false, + }); + + final IconData icon; + final String dns; + final String ping; + final bool isMobile; + + String get formattedPing => ping == '-1' ? 'Timeout' : '$ping ms'; + + @override + Widget build(BuildContext context) { + if (isMobile) { + return Row( + children: [ + Icon(icon, color: AppColors.dnsPing, size: 18), + 8.width, + Expanded( + child: Text( + "$dns -> $formattedPing", + style: TextStyle( + color: AppColors.dnsPingText, + fontSize: 14, + fontFamily: 'Poppins', + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ); + } + + return Row( + children: [ + Icon(icon, color: AppColors.dnsPing, size: 16), + 8.width, + Expanded( + child: Text( + "$dns ($formattedPing)", + style: TextStyle( + color: AppColors.dnsPingText, + fontSize: 12, + fontFamily: 'Poppins', + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ); + } +} diff --git a/lib/core/widgets/dns_selection.dart b/lib/core/widgets/dns/dns_selection.dart similarity index 100% rename from lib/core/widgets/dns_selection.dart rename to lib/core/widgets/dns/dns_selection.dart diff --git a/lib/core/widgets/dns_selection_container.dart b/lib/core/widgets/dns/dns_selection_container.dart similarity index 56% rename from lib/core/widgets/dns_selection_container.dart rename to lib/core/widgets/dns/dns_selection_container.dart index 6206c3d..83fe26c 100644 --- a/lib/core/widgets/dns_selection_container.dart +++ b/lib/core/widgets/dns/dns_selection_container.dart @@ -14,8 +14,9 @@ class DnsSelectionContainer extends StatelessWidget { required this.color, }); - final NetshiftEngineController netshiftEngineController = - Get.put(NetshiftEngineController()); + final NetshiftEngineController netshiftEngineController = Get.put( + NetshiftEngineController(), + ); @override Widget build(BuildContext context) { return OutlinedButton( @@ -23,33 +24,31 @@ class DnsSelectionContainer extends StatelessWidget { style: OutlinedButton.styleFrom( padding: const EdgeInsets.all(24), backgroundColor: AppColors.dnsSelectionContainer, - side: BorderSide( - color: color, - width: 2, - ), + side: BorderSide(color: color, width: 2), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), ), child: SizedBox( - width: ScreenSize.height * 80, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - netshiftEngineController.selectedDns.value.name, - style: TextStyle( - color: AppColors.dnsSelectionContainerName, - fontSize: 16, - fontFamily: 'Poppins', - fontWeight: FontWeight.w600, - ), - ), - 8.width, - Icon( - Icons.keyboard_arrow_down_outlined, + width: ScreenSize.height * 80, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + netshiftEngineController.selectedDns.value.name, + style: TextStyle( color: AppColors.dnsSelectionContainerName, + fontSize: 16, + fontFamily: 'Poppins', + fontWeight: FontWeight.w600, ), - ], - )), + ), + 8.width, + Icon( + Icons.keyboard_arrow_down_outlined, + color: AppColors.dnsSelectionContainerName, + ), + ], + ), + ), ); } } diff --git a/lib/core/widgets/dns/dns_selector_bottom_sheet_netshift.dart b/lib/core/widgets/dns/dns_selector_bottom_sheet_netshift.dart new file mode 100644 index 0000000..69ba788 --- /dev/null +++ b/lib/core/widgets/dns/dns_selector_bottom_sheet_netshift.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:netshift/controller/netshift_engine_controller.dart'; +import 'package:netshift/controller/single_dns_ping_controller.dart'; +import 'package:netshift/core/resources/app_colors.dart'; +import 'package:netshift/core/widgets/common/dis.dart'; +import 'package:netshift/core/widgets/dns/dns_list_item_netshift.dart'; + +class DNSSelectorBottomSheetNetShift extends StatelessWidget { + DNSSelectorBottomSheetNetShift({super.key}); + final NetshiftEngineController netshiftEngineController = Get.find(); + final SingleDnsPingController dnsPingController = Get.find(); + + @override + Widget build(BuildContext context) { + return Dis( + child: DraggableScrollableSheet( + initialChildSize: 0.6, + maxChildSize: 0.7, + minChildSize: 0.3, + builder: (BuildContext context, ScrollController scrollController) { + return Container( + decoration: BoxDecoration( + color: AppColors.dnsSelectionContainerNetShiftBackground, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(24), + ), + ), + child: Column( + children: [ + _DnsSelectorHeader(onClose: () => Navigator.pop(context)), + Expanded( + child: ListView.builder( + controller: scrollController, + itemCount: netshiftEngineController.dnsListNetShift.length, + itemBuilder: (context, index) { + final dns = + netshiftEngineController.dnsListNetShift[index]; + return DnsListItemNetshift( + dns: dns, + onTap: () { + netshiftEngineController.selectedDns.value = dns; + dnsPingController.pingPrimaryDns(); + dnsPingController.pingSecondaryDns(); + netshiftEngineController.saveSelectedDnsValue(); + Navigator.pop(context); + }, + ); + }, + ), + ), + ], + ), + ); + }, + ), + ); + } +} + +class _DnsSelectorHeader extends StatelessWidget { + const _DnsSelectorHeader({required this.onClose}); + + final VoidCallback onClose; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Select DNS', + style: TextStyle( + color: AppColors.dnsSelectionContainerNetShiftAppBarText, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + IconButton( + onPressed: onClose, + icon: Icon( + Icons.close, + color: AppColors.dnsSelectionContainerNetShiftCloseIcon, + ), + ), + ], + ), + ); + } +} diff --git a/lib/core/widgets/dns/dns_selector_bottom_sheet_personal.dart b/lib/core/widgets/dns/dns_selector_bottom_sheet_personal.dart new file mode 100644 index 0000000..b00814c --- /dev/null +++ b/lib/core/widgets/dns/dns_selector_bottom_sheet_personal.dart @@ -0,0 +1,214 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:get/get.dart'; +import 'package:netshift/core/resources/extention_sized.dart'; +import 'package:netshift/core/resources/media_query_size.dart'; +import 'package:netshift/controller/netshift_engine_controller.dart'; +import 'package:netshift/controller/single_dns_ping_controller.dart'; +import 'package:netshift/gen/assets.gen.dart'; +import 'package:netshift/models/dns_model.dart'; +import 'package:netshift/core/resources/app_colors.dart'; +import 'package:netshift/core/widgets/dns/add_dns_text_field.dart'; +import 'package:netshift/core/widgets/common/custom_button.dart'; +import 'package:netshift/core/widgets/dns/delete_dns_alert_dialog.dart'; +import 'package:netshift/core/widgets/common/dis.dart'; +import 'package:netshift/core/widgets/dns/dns_list_item_personal.dart'; + +class DNSSelectorBottomSheetPersonal extends StatelessWidget { + DNSSelectorBottomSheetPersonal({super.key}); + final TextEditingController dnsNameController = TextEditingController(); + final TextEditingController primaryDnsController = TextEditingController(); + final TextEditingController secondaryDnsController = TextEditingController(); + final NetshiftEngineController netshiftEngineController = Get.find(); + final SingleDnsPingController dnsPingController = Get.find(); + + @override + Widget build(BuildContext context) { + return Dis( + child: DraggableScrollableSheet( + initialChildSize: 0.6, + maxChildSize: 0.7, + minChildSize: 0.3, + builder: (BuildContext context, ScrollController scrollController) { + return Container( + decoration: BoxDecoration( + color: AppColors.dnsSelectorSheetPersonal, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(24), + ), + ), + child: Column( + children: [ + _DnsSelectorHeader(onClose: () => Navigator.pop(context)), + Expanded( + child: Obx( + () => netshiftEngineController.dnsListPersonal.isNotEmpty + ? ListView.builder( + controller: scrollController, + itemCount: + netshiftEngineController.dnsListPersonal.length, + itemBuilder: (context, index) { + final dns = netshiftEngineController + .dnsListPersonal[index]; + return DnsListItemPersonal( + dns: dns, + onTap: () { + netshiftEngineController.selectedDns.value = + dns; + dnsPingController.pingPrimaryDns(); + dnsPingController.pingSecondaryDns(); + netshiftEngineController + .saveSelectedDnsValue(); + Navigator.pop(context); + }, + onEdit: () => + _showEditDnsBottomSheet(context, dns), + onDelete: () { + showDialog( + context: context, + builder: (context) => DeleteDNSAlertDialog( + netshiftEngineController: + netshiftEngineController, + dns: dns, + ), + ); + }, + ); + }, + ) + : const _EmptyDnsState(), + ), + ), + ], + ), + ); + }, + ), + ); + } + + void _showEditDnsBottomSheet(BuildContext context, DnsModel dns) { + dnsNameController.text = dns.name; + primaryDnsController.text = dns.primaryDNS; + secondaryDnsController.text = dns.secondaryDNS; + showModalBottomSheet( + context: context, + sheetAnimationStyle: AnimationStyle( + duration: const Duration(milliseconds: 400), + curve: Curves.fastOutSlowIn, + reverseDuration: const Duration(milliseconds: 300), + reverseCurve: Curves.fastOutSlowIn, + ), + isScrollControlled: true, + builder: (context) { + return Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + child: Container( + width: double.infinity, + height: ScreenSize.height * 0.4, + decoration: BoxDecoration( + color: AppColors.background, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(24), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + AddDNSTextField( + label: "DNS Name", + suffixIcon: const Icon(Icons.data_object_sharp), + controller: dnsNameController, + ), + AddDNSTextField( + label: "Primary DNS", + suffixIcon: const Icon(Icons.dns_outlined), + controller: primaryDnsController, + ), + AddDNSTextField( + label: "Secondary DNS", + suffixIcon: const Icon(Icons.dns_outlined), + controller: secondaryDnsController, + ), + CustomButton( + text: "Edit DNS", + onTap: () { + netshiftEngineController.editDns( + dns, + dnsNameController.text, + primaryDnsController.text, + secondaryDnsController.text, + ); + dnsPingController.pingPrimaryDns(); + dnsPingController.pingSecondaryDns(); + netshiftEngineController.savePersonalDns(); + Navigator.of(context).pop(); + }, + ), + ], + ), + ), + ); + }, + ); + } +} + +class _DnsSelectorHeader extends StatelessWidget { + const _DnsSelectorHeader({required this.onClose}); + + final VoidCallback onClose; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Select DNS', + style: TextStyle( + color: AppColors.dnsSelectorSheetPersonalAppBarText, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + IconButton( + onPressed: onClose, + icon: Icon( + Icons.close, + color: AppColors.dnsSelectorSheetPersonalCloseIcon, + ), + ), + ], + ), + ); + } +} + +class _EmptyDnsState extends StatelessWidget { + const _EmptyDnsState(); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset(Assets.svg.trash, height: ScreenSize.height * 0.3), + 12.height, + Text( + "No DNS Added Yet!", + style: TextStyle( + color: AppColors.dnsSelectorSheetPersonalNoDnsAddedText, + fontSize: 18, + fontWeight: FontWeight.bold, + fontFamily: 'Poppins', + ), + ), + ], + ); + } +} diff --git a/lib/core/widgets/dns_status.dart b/lib/core/widgets/dns/dns_status.dart similarity index 93% rename from lib/core/widgets/dns_status.dart rename to lib/core/widgets/dns/dns_status.dart index 42f169a..f17ba64 100644 --- a/lib/core/widgets/dns_status.dart +++ b/lib/core/widgets/dns/dns_status.dart @@ -12,7 +12,7 @@ class DNSStatus extends StatefulWidget { } class _DNSStatusState extends State { -final NetshiftEngineController netshiftEngineController = Get.find(); + final NetshiftEngineController netshiftEngineController = Get.find(); bool isHovered = false; @override @@ -25,8 +25,8 @@ final NetshiftEngineController netshiftEngineController = Get.find(); padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 20), decoration: BoxDecoration( gradient: isHovered - ? GradientAppColors.dnsStatusBackgroundHover - : GradientAppColors.dnsStatusBackground, + ? GradientAppColors.dnsStatusBackgroundHover + : GradientAppColors.dnsStatusBackground, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( diff --git a/lib/core/widgets/main_dns_selector.dart b/lib/core/widgets/dns/main_dns_selector.dart similarity index 86% rename from lib/core/widgets/main_dns_selector.dart rename to lib/core/widgets/dns/main_dns_selector.dart index 24c6d29..be57227 100644 --- a/lib/core/widgets/main_dns_selector.dart +++ b/lib/core/widgets/dns/main_dns_selector.dart @@ -3,16 +3,15 @@ import 'package:get/get.dart'; import 'package:netshift/core/resources/media_query_size.dart'; import 'package:netshift/controller/netshift_engine_controller.dart'; import 'package:netshift/core/resources/app_colors.dart'; -import 'package:netshift/core/widgets/dns_selection.dart'; -import 'package:netshift/core/widgets/dns_selector_bottom_sheet_netshift.dart'; -import 'package:netshift/core/widgets/dns_selector_bottom_sheet_personal.dart'; +import 'package:netshift/core/widgets/dns/dns_selection.dart'; +import 'package:netshift/core/widgets/dns/dns_selector_bottom_sheet_netshift.dart'; +import 'package:netshift/core/widgets/dns/dns_selector_bottom_sheet_personal.dart'; class MainDNSSelector extends StatelessWidget { - MainDNSSelector({ - super.key, - }); - final NetshiftEngineController netshiftEngineController = - Get.put(NetshiftEngineController()); + MainDNSSelector({super.key}); + final NetshiftEngineController netshiftEngineController = Get.put( + NetshiftEngineController(), + ); @override Widget build(BuildContext context) { return Container( @@ -20,9 +19,7 @@ class MainDNSSelector extends StatelessWidget { width: double.infinity, decoration: BoxDecoration( color: AppColors.mainDnsSelectorSheet, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(24), - ), + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, diff --git a/lib/core/widgets/dns_ping_card.dart b/lib/core/widgets/dns_ping_card.dart deleted file mode 100644 index 4a32f66..0000000 --- a/lib/core/widgets/dns_ping_card.dart +++ /dev/null @@ -1,274 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:netshift/core/resources/app_colors.dart'; -import 'package:netshift/core/resources/extention_sized.dart'; - -class DnsPingCard extends StatelessWidget { - final String name; - final String primaryDns; - final String secondaryDns; - final String avgPing; - final String primaryPing; - final String secondaryPing; - final VoidCallback? onApply; - final bool isDesktop; - - const DnsPingCard({ - super.key, - required this.name, - required this.primaryDns, - required this.secondaryDns, - required this.avgPing, - required this.primaryPing, - required this.secondaryPing, - this.onApply, - this.isDesktop = false, - }); - - bool get isTimeout => avgPing == '-1'; - - Color get pingColor { - int pingValue = int.tryParse(avgPing) ?? -1; - if (pingValue == -1) return Colors.red; - if (pingValue < 50) return Colors.green; - if (pingValue < 150) return Colors.lightGreen; - if (pingValue < 300) return Colors.orange; - return Colors.deepOrange; - } - - String _formatPing(String ping) { - return ping == '-1' ? 'Timeout' : '$ping ms'; - } - - @override - Widget build(BuildContext context) { - return isDesktop ? _buildDesktopCard() : _buildMobileCard(); - } - - Widget _buildDesktopCard() { - return AnimatedContainer( - duration: const Duration(milliseconds: 300), - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: AppColors.isDarkMode - ? const Color(0xFF1E2025) - : Colors.white.withValues(alpha: 0.95), - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: isTimeout - ? Colors.red.withValues(alpha: 0.5) - : AppColors.dnsPingContainerBorder.withValues(alpha: 0.5), - width: 1, - ), - boxShadow: [ - BoxShadow( - color: AppColors.dnsPingContainerShadow.withValues(alpha: 0.2), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _buildHeader(), - const SizedBox(height: 12), - _buildDnsRow(Icons.dns, primaryDns, primaryPing), - const SizedBox(height: 6), - _buildDnsRow(Icons.dns_outlined, secondaryDns, secondaryPing), - const Spacer(), - _buildApplyButton(isDesktop: true), - ], - ), - ); - } - - Widget _buildMobileCard() { - return AnimatedContainer( - duration: const Duration(milliseconds: 300), - margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColors.background, - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: isTimeout - ? Colors.red.withValues(alpha: 0.5) - : AppColors.dnsPingContainerBorder, - width: 2, - ), - boxShadow: [ - BoxShadow( - color: AppColors.dnsPingContainerShadow, - blurRadius: 2, - spreadRadius: 1, - offset: const Offset(2, 2), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildMobileHeader(), - Divider( - color: AppColors.dnsPingDivider, - thickness: 1.2, - height: 20, - ), - 8.height, - _buildMobileDnsRow(Icons.dns, primaryDns, primaryPing), - 8.height, - _buildMobileDnsRow(Icons.dns_outlined, secondaryDns, secondaryPing), - 16.height, - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [_buildApplyButton(isDesktop: false)], - ), - ], - ), - ); - } - - Widget _buildHeader() { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - name, - style: TextStyle( - color: AppColors.dnsPingName, - fontSize: 18, - fontFamily: 'Poppins', - fontWeight: FontWeight.bold, - ), - overflow: TextOverflow.ellipsis, - ), - ), - _buildPingBadge(), - ], - ); - } - - Widget _buildMobileHeader() { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - name, - style: TextStyle( - color: AppColors.dnsPingName, - fontSize: 20, - fontFamily: 'IRANSansX', - fontWeight: FontWeight.bold, - ), - overflow: TextOverflow.ellipsis, - ), - ), - _buildPingBadge(isMobile: true), - ], - ); - } - - Widget _buildPingBadge({bool isMobile = false}) { - return Container( - padding: EdgeInsets.symmetric( - horizontal: isMobile ? 10 : 12, - vertical: isMobile ? 4 : 6, - ), - decoration: BoxDecoration( - color: pingColor.withValues(alpha: 0.15), - borderRadius: BorderRadius.circular(isMobile ? 16 : 20), - ), - child: Text( - isTimeout ? "Timeout" : "$avgPing ms", - style: TextStyle( - color: pingColor, - fontSize: 14, - fontFamily: 'Poppins', - fontWeight: FontWeight.w600, - ), - ), - ); - } - - Widget _buildDnsRow(IconData icon, String dns, String ping) { - return Row( - children: [ - Icon(icon, color: AppColors.dnsPing, size: 16), - 8.width, - Expanded( - child: Text( - "$dns (${_formatPing(ping)})", - style: TextStyle( - color: AppColors.dnsPingText, - fontSize: 12, - fontFamily: 'Poppins', - ), - overflow: TextOverflow.ellipsis, - ), - ), - ], - ); - } - - Widget _buildMobileDnsRow(IconData icon, String dns, String ping) { - return Row( - children: [ - Icon(icon, color: AppColors.dnsPing, size: 18), - 8.width, - Expanded( - child: Text( - "$dns -> ${_formatPing(ping)}", - style: TextStyle( - color: AppColors.dnsPingText, - fontSize: 14, - fontFamily: 'Poppins', - fontWeight: FontWeight.w500, - ), - ), - ), - ], - ); - } - - Widget _buildApplyButton({required bool isDesktop}) { - if (isDesktop) { - return SizedBox( - width: double.infinity, - child: OutlinedButton( - style: OutlinedButton.styleFrom( - foregroundColor: - isTimeout ? Colors.grey : AppColors.dnsPingApplyButtonForeground, - padding: const EdgeInsets.symmetric(vertical: 10), - side: BorderSide( - color: isTimeout ? Colors.grey : AppColors.dnsPingApplyButtonBorder, - width: 1.5, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - onPressed: isTimeout ? null : onApply, - child: const Text("Apply DNS"), - ), - ); - } - - return OutlinedButton( - style: OutlinedButton.styleFrom( - foregroundColor: - isTimeout ? Colors.grey : AppColors.dnsPingApplyButtonForeground, - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - textStyle: const TextStyle(fontSize: 14, fontFamily: 'IRANSansX'), - side: BorderSide( - color: isTimeout ? Colors.grey : AppColors.dnsPingApplyButtonBorder, - width: 2, - ), - ), - onPressed: isTimeout ? null : onApply, - child: const Text("Apply"), - ); - } -} diff --git a/lib/core/widgets/dns_selector_bottom_sheet_netshift.dart b/lib/core/widgets/dns_selector_bottom_sheet_netshift.dart deleted file mode 100644 index 023e2ea..0000000 --- a/lib/core/widgets/dns_selector_bottom_sheet_netshift.dart +++ /dev/null @@ -1,151 +0,0 @@ - -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:netshift/controller/netshift_engine_controller.dart'; -import 'package:netshift/controller/single_dns_ping_controller.dart'; -import 'package:netshift/core/resources/app_colors.dart'; -import 'package:netshift/core/widgets/dis.dart'; - -class DNSSelectorBottomSheetNetShift extends StatelessWidget { - DNSSelectorBottomSheetNetShift({ - super.key, - }); -final NetshiftEngineController netshiftEngineController = Get.find(); -final SingleDnsPingController dnsPingController = Get.find(); - @override - Widget build(BuildContext context) { - return Dis( - child: DraggableScrollableSheet( - initialChildSize: 0.6, - maxChildSize: 0.7, - minChildSize: 0.3, - builder: (BuildContext context, ScrollController scrollController) { - return Container( - decoration: BoxDecoration( - color: AppColors.dnsSelectionContainerNetShiftBackground, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(24), - ), - ), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, vertical: 8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Select DNS', - style: TextStyle( - color: - AppColors.dnsSelectionContainerNetShiftAppBarText, - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - IconButton( - onPressed: () => Navigator.pop(context), - icon: Icon( - Icons.close, - color: - AppColors.dnsSelectionContainerNetShiftCloseIcon, - ), - ), - ], - ), - ), - Expanded( - child: ListView.builder( - controller: scrollController, - itemCount: netshiftEngineController.dnsListNetShift.length, - itemBuilder: (context, index) { - final dns = - netshiftEngineController.dnsListNetShift[index]; - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 8.0, - ), - child: GestureDetector( - onTap: () { - netshiftEngineController.selectedDns.value = dns; - dnsPingController.pingPrimaryDns(); - dnsPingController.pingSecondaryDns(); - netshiftEngineController.saveSelectedDnsValue(); - Navigator.pop(context); - }, - child: Container( - padding: const EdgeInsets.all(16.0), - decoration: BoxDecoration( - color: AppColors - .dnsSelectionContainerNetShiftContainer, - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: AppColors - .dnsSelectionContainerNetShiftBorderContainer, - width: 2, - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - netshiftEngineController - .dnsListNetShift[index].name, - style: TextStyle( - color: AppColors - .dnsSelectionContainerNetShiftDnsName, - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 4), - Text( - "Primary: ${netshiftEngineController.dnsListNetShift[index].primaryDNS}", - style: TextStyle( - color: AppColors - .dnsSelectionContainerNetShiftDns, - fontSize: 14, - ), - ), - Text( - 'Secondary: ${netshiftEngineController.dnsListNetShift[index].secondaryDNS}', - style: TextStyle( - color: AppColors - .dnsSelectionContainerNetShiftDns, - fontSize: 14, - ), - ), - ], - ), - netshiftEngineController - .dnsListNetShift[index].name - .contains('*') - ? const Icon( - Icons.remove_circle, - color: Color.fromARGB(255, 255, 30, 0), - ) - : const Icon( - Icons.check_circle, - color: Color.fromARGB(255, 48, 136, 51), - ), - ], - ), - ), - ), - ); - }, - ), - ), - ], - ), - ); - }, - ), - ); - } -} diff --git a/lib/core/widgets/dns_selector_bottom_sheet_personal.dart b/lib/core/widgets/dns_selector_bottom_sheet_personal.dart deleted file mode 100644 index 8a442c9..0000000 --- a/lib/core/widgets/dns_selector_bottom_sheet_personal.dart +++ /dev/null @@ -1,295 +0,0 @@ - -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:get/get.dart'; -import 'package:netshift/core/resources/extention_sized.dart'; -import 'package:netshift/core/resources/media_query_size.dart'; -import 'package:netshift/controller/netshift_engine_controller.dart'; -import 'package:netshift/controller/single_dns_ping_controller.dart'; -import 'package:netshift/gen/assets.gen.dart'; -import 'package:netshift/models/dns_model.dart'; -import 'package:netshift/core/resources/app_colors.dart'; -import 'package:netshift/core/widgets/add_dns_text_field.dart'; -import 'package:netshift/core/widgets/custom_button.dart'; -import 'package:netshift/core/widgets/delete_dns_alert_dialog.dart'; -import 'package:netshift/core/widgets/dis.dart'; - -class DNSSelectorBottomSheetPersonal extends StatelessWidget { - DNSSelectorBottomSheetPersonal({ - super.key, - }); - final TextEditingController dnsNameController = TextEditingController(); - - final TextEditingController primaryDnsController = TextEditingController(); - - final TextEditingController secondaryDnsController = TextEditingController(); - -final NetshiftEngineController netshiftEngineController = Get.find(); -final SingleDnsPingController dnsPingController = Get.find(); - @override - Widget build(BuildContext context) { - return Dis( - child: DraggableScrollableSheet( - initialChildSize: 0.6, - maxChildSize: 0.7, - minChildSize: 0.3, - builder: (BuildContext context, ScrollController scrollController) { - return Container( - decoration: BoxDecoration( - color: AppColors.dnsSelectorSheetPersonal, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(24), - ), - ), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, vertical: 8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Select DNS', - style: TextStyle( - color: AppColors.dnsSelectorSheetPersonalAppBarText, - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - IconButton( - onPressed: () => Navigator.pop(context), - icon: Icon( - Icons.close, - color: AppColors.dnsSelectorSheetPersonalCloseIcon, - ), - ), - ], - ), - ), - Expanded( - child: Obx( - () => netshiftEngineController.dnsListPersonal.isNotEmpty - ? ListView.builder( - controller: scrollController, - itemCount: - netshiftEngineController.dnsListPersonal.length, - itemBuilder: (context, index) { - final dns = netshiftEngineController - .dnsListPersonal[index]; - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 8.0, - ), - child: GestureDetector( - onTap: () { - netshiftEngineController.selectedDns.value = - dns; - dnsPingController.pingPrimaryDns(); - dnsPingController.pingSecondaryDns(); - netshiftEngineController - .saveSelectedDnsValue(); - Navigator.pop(context); - }, - child: Container( - padding: const EdgeInsets.all(16.0), - decoration: BoxDecoration( - color: AppColors - .dnsSelectorSheetPersonalContainer, - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: AppColors - .dnsSelectorSheetPersonalContainerBorder, - width: 2, - ), - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - SizedBox( - width: ScreenSize.width * 0.55, - child: Text( - netshiftEngineController - .dnsListPersonal[index].name, - style: TextStyle( - color: AppColors - .dnsSelectorSheetPersonalDnsName, - fontSize: 16, - fontWeight: FontWeight.bold, - ), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - ), - const SizedBox(height: 4), - Text( - "Primary: ${netshiftEngineController.dnsListPersonal[index].primaryDNS}", - style: TextStyle( - color: AppColors - .dnsSelectorSheetPersonalDns, - fontSize: 14, - ), - ), - Text( - 'Secondary: ${netshiftEngineController.dnsListPersonal[index].secondaryDNS}', - style: TextStyle( - color: AppColors - .dnsSelectorSheetPersonalDns, - fontSize: 14, - ), - ), - ], - ), - Row( - children: [ - IconButton( - onPressed: () { - editDnsBottomSheet( - context, - dns, - ); - }, - icon: Icon( - Icons.edit_outlined, - color: AppColors - .dnsSelectorSheetPersonalDnsEdit, - size: 30, - ), - ), - IconButton( - onPressed: () { - showDialog( - context: context, - builder: (context) { - return DeleteDNSAlertDialog( - netshiftEngineController: - netshiftEngineController, - dns: dns, - ); - }, - ); - }, - icon: Icon( - Icons.delete_sweep_outlined, - color: AppColors - .dnsSelectorSheetPersonalDnsDelete, - size: 30, - ), - ), - ], - ) - ], - ), - ), - ), - ); - }, - ) - : Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - Assets.svg.trash, - height: ScreenSize.height * 0.3, - ), - 12.height, - Text( - "No DNS Added Yet!", - style: TextStyle( - color: AppColors - .dnsSelectorSheetPersonalNoDnsAddedText, - fontSize: 18, - fontWeight: FontWeight.bold, - fontFamily: 'Poppins', - ), - ) - ], - ), - ), - ), - ], - ), - ); - }, - ), - ); - } - - void editDnsBottomSheet(BuildContext context, DnsModel dns) { - dnsNameController.text = dns.name; - primaryDnsController.text = dns.primaryDNS; - secondaryDnsController.text = dns.secondaryDNS; - showModalBottomSheet( - context: context, - sheetAnimationStyle: AnimationStyle( - duration: const Duration(milliseconds: 400), - curve: Curves.fastOutSlowIn, - reverseDuration: const Duration(milliseconds: 300), - reverseCurve: Curves.fastOutSlowIn, - ), - isScrollControlled: true, - builder: (context) { - return Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom), - child: Container( - width: double.infinity, - height: ScreenSize.height * 0.4, - decoration: BoxDecoration( - color: AppColors.background, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(24), - ), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - AddDNSTextField( - label: "DNS Name", - suffixIcon: const Icon( - Icons.data_object_sharp, - ), - controller: dnsNameController, - ), - AddDNSTextField( - label: "Primary DNS", - suffixIcon: const Icon( - Icons.dns_outlined, - ), - controller: primaryDnsController, - ), - AddDNSTextField( - label: "Secondary DNS", - suffixIcon: const Icon( - Icons.dns_outlined, - ), - controller: secondaryDnsController, - ), - CustomButton( - text: "Edit DNS", - onTap: () { - netshiftEngineController.editDns( - dns, - dnsNameController.text, - primaryDnsController.text, - secondaryDnsController.text, - ); - dnsPingController.pingPrimaryDns(); - dnsPingController.pingSecondaryDns(); - netshiftEngineController.savePersonalDns(); - Navigator.of(context).pop(); - }, - ) - ], - ), - ), - ); - }); - } -} diff --git a/lib/core/widgets/connect_button.dart b/lib/core/widgets/home/connect_button.dart similarity index 98% rename from lib/core/widgets/connect_button.dart rename to lib/core/widgets/home/connect_button.dart index 962cfa9..48ce801 100644 --- a/lib/core/widgets/connect_button.dart +++ b/lib/core/widgets/home/connect_button.dart @@ -13,8 +13,8 @@ import 'package:netshift/controller/netshift_engine_controller.dart'; import 'package:netshift/controller/stop_watch_controller.dart'; import 'package:netshift/core/resources/app_colors.dart'; import 'package:netshift/core/services/windows_local_notif.dart'; -import 'package:netshift/core/widgets/custom_snack_bar.dart'; -import 'package:netshift/core/widgets/flutter_toast.dart'; +import 'package:netshift/core/widgets/common/custom_snack_bar.dart'; +import 'package:netshift/core/widgets/common/flutter_toast.dart'; class ConnectButton extends StatelessWidget { ConnectButton({super.key}); diff --git a/lib/core/widgets/home/connection_controls_panel.dart b/lib/core/widgets/home/connection_controls_panel.dart new file mode 100644 index 0000000..7817c6f --- /dev/null +++ b/lib/core/widgets/home/connection_controls_panel.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:netshift/controller/splash_controller.dart'; +import 'package:netshift/controller/stop_watch_controller.dart'; +import 'package:netshift/core/resources/app_colors.dart'; +import 'package:netshift/core/widgets/home/connect_button.dart'; +import 'package:netshift/core/widgets/dns/dns_status.dart'; +import 'package:netshift/core/widgets/common/offline_banner.dart'; + +class ConnectionControlsPanel extends StatelessWidget { + ConnectionControlsPanel({super.key}); + + final StopWatchController stopWatchController = Get.find(); + final SplashScreenController splashController = Get.find(); + + @override + Widget build(BuildContext context) { + return Obx( + () => Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (splashController.isOffline.value) + OfflineBanner(splashController: splashController), + const SizedBox(height: 20), + Text( + stopWatchController.formatDuration( + stopWatchController.elapsedTime.value, + ), + style: TextStyle( + fontFamily: 'IRANSansX', + fontSize: 48, + fontWeight: FontWeight.bold, + color: AppColors.timer, + ), + ), + const SizedBox(height: 40), + ConnectButton(), + const SizedBox(height: 40), + const DNSStatus(), + ], + ), + ); + } +} diff --git a/lib/core/widgets/home/home_app_bar.dart b/lib/core/widgets/home/home_app_bar.dart new file mode 100644 index 0000000..0043038 --- /dev/null +++ b/lib/core/widgets/home/home_app_bar.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:netshift/gen/assets.gen.dart'; +import 'package:netshift/core/resources/app_colors.dart'; +import 'package:netshift/core/widgets/common/app_bar.dart'; + +class HomeAppBar extends StatelessWidget implements PreferredSizeWidget { + const HomeAppBar({super.key}); + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); + + @override + Widget build(BuildContext context) { + return CustomAppBar( + title: "NetShift", + fontFamily: 'Calistoga', + fontSize: 24, + fontWeight: FontWeight.w600, + actions: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: OutlinedButton( + onPressed: () {}, + style: ButtonStyle( + side: const WidgetStatePropertyAll(BorderSide.none), + backgroundColor: WidgetStatePropertyAll(AppColors.iconAppBar), + ), + child: SvgPicture.asset(Assets.svg.crown), + ), + ), + ], + ); + } +} diff --git a/lib/core/widgets/home/mobile_actions_panel.dart b/lib/core/widgets/home/mobile_actions_panel.dart new file mode 100644 index 0000000..78766dd --- /dev/null +++ b/lib/core/widgets/home/mobile_actions_panel.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:netshift/controller/netshift_engine_controller.dart'; +import 'package:netshift/controller/random_dns_generator_controller.dart'; +import 'package:netshift/controller/splash_controller.dart'; +import 'package:netshift/gen/assets.gen.dart'; +import 'package:netshift/core/widgets/dns/add_dns.dart'; +import 'package:netshift/core/widgets/common/custom_floating_action_button.dart'; +import 'package:netshift/core/widgets/common/custom_snack_bar.dart'; +import 'package:netshift/core/widgets/dns/dns_generator_dialog.dart'; + +class MobileActionsPanel extends StatelessWidget { + MobileActionsPanel({super.key}); + + final NetshiftEngineController netshiftEngineController = Get.find(); + final SplashScreenController splashScreenController = Get.find(); + + @override + Widget build(BuildContext context) { + Get.lazyPut(() => RandomDnsGeneratorController()); + final RandomDnsGeneratorController randomDnsGeneratorController = + Get.find(); + + return Padding( + padding: const EdgeInsets.only(right: 12, left: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomFloatingActionButton( + path: Assets.svg.generateDns, + onTap: () => + _handleGenerateDns(context, randomDnsGeneratorController), + ), + CustomFloatingActionButton( + path: Assets.svg.addDns, + onTap: () => _handleAddDns(context), + ), + ], + ), + ); + } + + void _handleGenerateDns( + BuildContext context, + RandomDnsGeneratorController controller, + ) { + if (splashScreenController.isOffline.value) { + _showErrorSnackBar( + "Failed to GENERATE DNS, please start the app in online mode", + ); + return; + } + + controller.generateRandomDns(); + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => DnsGeneratorDialog(), + ); + } + + void _handleAddDns(BuildContext context) { + if (netshiftEngineController.isActive.value) { + _showErrorSnackBar("Failed to ADD DNS, please stop the service first"); + return; + } + + showModalBottomSheet( + context: context, + sheetAnimationStyle: AnimationStyle( + duration: const Duration(milliseconds: 400), + curve: Curves.fastOutSlowIn, + reverseDuration: const Duration(milliseconds: 300), + reverseCurve: Curves.fastOutSlowIn, + ), + isScrollControlled: true, + builder: (context) => const AddDnsBottomSheet(), + ); + } + + void _showErrorSnackBar(String message) { + CustomSnackBar( + title: "Operation Failed", + message: message, + backColor: Colors.red.shade700.withValues(alpha: 0.9), + iconColor: Colors.white, + icon: Icons.error_outline, + textColor: Colors.white, + ).customSnackBar(); + } +} diff --git a/lib/core/widgets/home/mobile_dns_field.dart b/lib/core/widgets/home/mobile_dns_field.dart new file mode 100644 index 0000000..81af965 --- /dev/null +++ b/lib/core/widgets/home/mobile_dns_field.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:netshift/core/resources/extention_sized.dart'; +import 'package:netshift/core/resources/app_colors.dart'; +import 'package:netshift/core/widgets/common/custom_textfield.dart'; + +class MobileDnsField extends StatelessWidget { + const MobileDnsField({ + super.key, + required this.label, + required this.dns, + required this.isPinging, + required this.pingResult, + }); + + final String label; + final String dns; + final bool isPinging; + final dynamic pingResult; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "$label :", + style: TextStyle( + fontFamily: 'Poppins', + fontWeight: FontWeight.w600, + fontSize: 16, + color: AppColors.dnsText, + ), + ), + isPinging + ? SpinKitCircle(color: AppColors.spinKitColor, size: 22) + : Text( + "$pingResult ms", + style: TextStyle( + fontFamily: 'Poppins', + fontWeight: FontWeight.w500, + fontSize: 14, + color: AppColors.dnsText, + ), + ), + ], + ), + 8.height, + CustomTextContainer(dns: dns), + ], + ); + } +} diff --git a/lib/core/widgets/home/mobile_status_icons.dart b/lib/core/widgets/home/mobile_status_icons.dart new file mode 100644 index 0000000..1567c5b --- /dev/null +++ b/lib/core/widgets/home/mobile_status_icons.dart @@ -0,0 +1,104 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:netshift/controller/foreground_controller.dart'; +import 'package:netshift/controller/netshift_engine_controller.dart'; +import 'package:netshift/gen/assets.gen.dart'; +import 'package:netshift/core/resources/app_colors.dart'; +import 'package:netshift/core/widgets/common/custom_snack_bar.dart'; +import 'package:netshift/core/widgets/settings/interface_selection_dialog.dart'; +import 'package:netshift/core/widgets/common/status_bar.dart'; + +class MobileStatusIcons extends StatelessWidget { + MobileStatusIcons({super.key}); + + final NetshiftEngineController netshiftEngineController = Get.find(); + final ForegroundController foregroundController = Get.find(); + + @override + Widget build(BuildContext context) { + return Obx( + () => Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Platform.isAndroid + ? StatusIcon( + path: Assets.svg.download, + label: 'Download', + status: foregroundController.download.value, + color: AppColors.download, + iconColor: const Color(0xFF38AC72), + onTap: () {}, + ) + : StatusIcon( + path: Assets.svg.flush, + label: 'Flush DNS', + status: netshiftEngineController.isFlushing.value + ? "Flushed Succesfully" + : "Tap To Flush", + color: AppColors.download, + onTap: _handleFlushDns, + iconColor: const Color(0xFF38AC72), + ), + StatusIcon( + path: Assets.svg.global, + label: 'Address', + status: netshiftEngineController.isIpAddress.value + ? "IP: Getting IP..." + : "IP: ${netshiftEngineController.ipAddressString}", + color: AppColors.ip, + onTap: () => netshiftEngineController.getIpAddress(), + iconColor: const Color(0xFF428BC1), + ), + Platform.isAndroid + ? StatusIcon( + path: Assets.svg.upload, + label: 'Upload', + status: foregroundController.upload.value, + color: AppColors.upload, + iconColor: const Color(0xFFD2222D), + onTap: () {}, + ) + : StatusIcon( + path: Assets.svg.interface, + label: 'Interface', + status: "${netshiftEngineController.interfaceName.value} ⏷", + color: AppColors.upload, + onTap: () => InterfaceSelectionDialog.show(context), + iconColor: const Color(0xFFD2222D), + ), + ], + ), + ); + } + + void _handleFlushDns() { + if (netshiftEngineController.isActive.value) { + CustomSnackBar( + title: "Operation Failed", + message: "Failed to FLUSH DNS, please stop the service first", + backColor: Colors.red.shade700.withValues(alpha: 0.9), + iconColor: Colors.white, + icon: Icons.error_outline, + textColor: Colors.white, + ).customSnackBar(); + return; + } + + if (Platform.isWindows) { + netshiftEngineController.flushDnsForWindows(); + } else if (Platform.isMacOS) { + netshiftEngineController.flushDnsForMacOS(); + } + + CustomSnackBar( + title: "Operation Success", + message: "FLUSHED DNS Successfully", + backColor: const Color.fromARGB(80, 105, 240, 175), + iconColor: Colors.greenAccent, + icon: Icons.check_circle_outline_outlined, + textColor: Colors.white, + ).customSnackBar(); + } +} diff --git a/lib/core/widgets/home/quick_actions_panel.dart b/lib/core/widgets/home/quick_actions_panel.dart new file mode 100644 index 0000000..bb1d000 --- /dev/null +++ b/lib/core/widgets/home/quick_actions_panel.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:netshift/controller/netshift_engine_controller.dart'; +import 'package:netshift/controller/random_dns_generator_controller.dart'; +import 'package:netshift/controller/splash_controller.dart'; +import 'package:netshift/core/resources/app_colors.dart'; +import 'package:netshift/core/widgets/common/action_card.dart'; +import 'package:netshift/core/widgets/dns/add_dns.dart'; +import 'package:netshift/core/widgets/common/custom_snack_bar.dart'; +import 'package:netshift/core/widgets/dns/dns_generator_dialog.dart'; +import 'package:netshift/core/widgets/dns/main_dns_selector.dart'; + +class QuickActionsPanel extends StatelessWidget { + QuickActionsPanel({super.key}); + + final NetshiftEngineController netshiftEngineController = Get.find(); + final SplashScreenController splashScreenController = Get.find(); + + @override + Widget build(BuildContext context) { + Get.lazyPut(() => RandomDnsGeneratorController()); + final RandomDnsGeneratorController randomDnsGeneratorController = + Get.find(); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Quick Actions", + style: TextStyle( + fontFamily: 'Poppins', + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.dnsText, + ), + ), + const SizedBox(height: 20), + ActionCard( + icon: Icons.auto_fix_high, + title: "Generate Random DNS", + subtitle: "Auto-generate optimized DNS settings", + onTap: () => + _handleGenerateDns(context, randomDnsGeneratorController), + ), + const SizedBox(height: 16), + ActionCard( + icon: Icons.add_circle_outline, + title: "Add Custom DNS", + subtitle: "Configure your own DNS servers", + onTap: () => _handleAddDns(context), + ), + const SizedBox(height: 16), + ActionCard( + icon: Icons.dns_rounded, + title: "Browse DNS Providers", + subtitle: "Choose from popular DNS services", + onTap: () => _handleDnsSelection(context), + ), + ], + ); + } + + void _handleDnsSelection(BuildContext context) { + if (netshiftEngineController.isActive.value) { + _showErrorSnackBar("Failed to SELECT DNS, please stop the service first"); + return; + } + + showModalBottomSheet( + sheetAnimationStyle: AnimationStyle( + duration: const Duration(milliseconds: 400), + curve: Curves.easeInOut, + reverseDuration: const Duration(milliseconds: 300), + reverseCurve: Curves.easeInOut, + ), + context: context, + builder: (context) => MainDNSSelector(), + ); + } + + void _handleGenerateDns( + BuildContext context, + RandomDnsGeneratorController controller, + ) { + if (splashScreenController.isOffline.value) { + _showErrorSnackBar( + "Failed to GENERATE DNS, please start the app in online mode", + ); + return; + } + + controller.generateRandomDns(); + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => DnsGeneratorDialog(), + ); + } + + void _handleAddDns(BuildContext context) { + if (netshiftEngineController.isActive.value) { + _showErrorSnackBar("Failed to ADD DNS, please stop the service first"); + return; + } + + showModalBottomSheet( + context: context, + sheetAnimationStyle: AnimationStyle( + duration: const Duration(milliseconds: 400), + curve: Curves.fastOutSlowIn, + reverseDuration: const Duration(milliseconds: 300), + reverseCurve: Curves.fastOutSlowIn, + ), + isScrollControlled: true, + builder: (context) => const AddDnsBottomSheet(), + ); + } + + void _showErrorSnackBar(String message) { + CustomSnackBar( + title: "Operation Failed", + message: message, + backColor: Colors.red.shade700.withValues(alpha: 0.9), + iconColor: Colors.white, + icon: Icons.error_outline, + textColor: Colors.white, + ).customSnackBar(); + } +} diff --git a/lib/core/widgets/home/status_cards_panel.dart b/lib/core/widgets/home/status_cards_panel.dart new file mode 100644 index 0000000..70c449d --- /dev/null +++ b/lib/core/widgets/home/status_cards_panel.dart @@ -0,0 +1,86 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:netshift/controller/netshift_engine_controller.dart'; +import 'package:netshift/gen/assets.gen.dart'; +import 'package:netshift/core/resources/app_colors.dart'; +import 'package:netshift/core/widgets/common/custom_snack_bar.dart'; +import 'package:netshift/core/widgets/desktop/desktop_status_card.dart'; +import 'package:netshift/core/widgets/settings/interface_selection_dialog.dart'; + +class StatusCardsPanel extends StatelessWidget { + StatusCardsPanel({super.key}); + + final NetshiftEngineController netshiftEngineController = Get.find(); + + @override + Widget build(BuildContext context) { + return Obx( + () => Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + DesktopStatusCard( + icon: Assets.svg.flush, + label: 'Flush DNS', + status: netshiftEngineController.isFlushing.value + ? "Flushed Successfully" + : "Click to Flush", + color: AppColors.download, + iconColor: const Color(0xFF38AC72), + onTap: _handleFlushDns, + ), + const SizedBox(height: 16), + DesktopStatusCard( + icon: Assets.svg.global, + label: 'IP Address', + status: netshiftEngineController.isIpAddress.value + ? "Getting IP..." + : netshiftEngineController.ipAddressString.value, + color: AppColors.ip, + iconColor: const Color(0xFF428BC1), + onTap: () => netshiftEngineController.getIpAddress(), + ), + const SizedBox(height: 16), + DesktopStatusCard( + icon: Assets.svg.interface, + label: 'Network Interface', + status: netshiftEngineController.interfaceName.value, + color: AppColors.upload, + iconColor: const Color(0xFFD2222D), + onTap: () => InterfaceSelectionDialog.show(context), + ), + ], + ), + ); + } + + void _handleFlushDns() { + if (netshiftEngineController.isActive.value) { + CustomSnackBar( + title: "Operation Failed", + message: "Failed to FLUSH DNS, please stop the service first", + backColor: Colors.red.shade700.withValues(alpha: 0.9), + iconColor: Colors.white, + icon: Icons.error_outline, + textColor: Colors.white, + ).customSnackBar(); + return; + } + + if (Platform.isWindows) { + netshiftEngineController.flushDnsForWindows(); + } else if (Platform.isMacOS) { + netshiftEngineController.flushDnsForMacOS(); + } + + CustomSnackBar( + title: "Operation Success", + message: "FLUSHED DNS Successfully", + backColor: const Color.fromARGB(80, 105, 240, 175), + iconColor: Colors.greenAccent, + icon: Icons.check_circle_outline_outlined, + textColor: Colors.white, + ).customSnackBar(); + } +} diff --git a/lib/core/widgets/world_map.dart b/lib/core/widgets/home/world_map.dart similarity index 52% rename from lib/core/widgets/world_map.dart rename to lib/core/widgets/home/world_map.dart index 9e6edba..c08d2f4 100644 --- a/lib/core/widgets/world_map.dart +++ b/lib/core/widgets/home/world_map.dart @@ -11,18 +11,17 @@ class WorldMap extends StatelessWidget { @override Widget build(BuildContext context) { return Column( - children: [ - 20.height, - Flexible( - child: SvgPicture.asset( - Assets.svg.world, - colorFilter: ColorFilter.mode( - AppColors.worldMap, BlendMode.srcIn), - fit: BoxFit.cover, - height: ScreenSize.height * 0.5, - ), - ), - ], - ); + children: [ + 20.height, + Flexible( + child: SvgPicture.asset( + Assets.svg.world, + colorFilter: ColorFilter.mode(AppColors.worldMap, BlendMode.srcIn), + fit: BoxFit.cover, + height: ScreenSize.height * 0.5, + ), + ), + ], + ); } -} \ No newline at end of file +} diff --git a/lib/core/widgets/ping/ping_badge.dart b/lib/core/widgets/ping/ping_badge.dart new file mode 100644 index 0000000..fc646d1 --- /dev/null +++ b/lib/core/widgets/ping/ping_badge.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; + +class PingBadge extends StatelessWidget { + const PingBadge({super.key, required this.avgPing, this.isMobile = false}); + + final String avgPing; + final bool isMobile; + + bool get isTimeout => avgPing == '-1'; + + Color get pingColor { + int pingValue = int.tryParse(avgPing) ?? -1; + if (pingValue == -1) return Colors.red; + if (pingValue < 50) return Colors.green; + if (pingValue < 150) return Colors.lightGreen; + if (pingValue < 300) return Colors.orange; + return Colors.deepOrange; + } + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.symmetric( + horizontal: isMobile ? 10 : 12, + vertical: isMobile ? 4 : 6, + ), + decoration: BoxDecoration( + color: pingColor.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(isMobile ? 16 : 20), + ), + child: Text( + isTimeout ? "Timeout" : "$avgPing ms", + style: TextStyle( + color: pingColor, + fontSize: 14, + fontFamily: 'Poppins', + fontWeight: FontWeight.w600, + ), + ), + ); + } +} diff --git a/lib/core/widgets/ping/ping_header.dart b/lib/core/widgets/ping/ping_header.dart new file mode 100644 index 0000000..9828c48 --- /dev/null +++ b/lib/core/widgets/ping/ping_header.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:netshift/controller/sorted_dns_ping_controller.dart'; +import 'package:netshift/core/resources/app_colors.dart'; +import 'package:netshift/core/widgets/ping/ping_progress_indicator.dart'; +import 'package:netshift/core/widgets/ping/ping_refresh_button.dart'; + +class PingHeader extends StatelessWidget { + const PingHeader({ + super.key, + required this.controller, + required this.onRefresh, + }); + + final SortedDnsPingController controller; + final VoidCallback onRefresh; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "DNS Ping Results", + style: TextStyle( + fontFamily: 'Poppins', + fontSize: 28, + fontWeight: FontWeight.bold, + color: AppColors.dnsText, + ), + ), + const SizedBox(height: 8), + Text( + "Test and compare DNS server latencies", + style: TextStyle( + fontFamily: 'Poppins', + fontSize: 14, + color: AppColors.dnsText.withValues(alpha: 0.7), + ), + ), + ], + ), + Row( + children: [ + PingProgressIndicator( + completed: controller.completedCount.value, + total: controller.totalCount.value, + isVisible: controller.isPinging.value, + ), + const SizedBox(width: 16), + PingRefreshButton( + isPinging: controller.isPinging.value, + onRefresh: onRefresh, + ), + ], + ), + ], + ); + } +} diff --git a/lib/core/widgets/ping/ping_loading_state.dart b/lib/core/widgets/ping/ping_loading_state.dart new file mode 100644 index 0000000..66f86fb --- /dev/null +++ b/lib/core/widgets/ping/ping_loading_state.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:netshift/core/resources/app_colors.dart'; + +class PingLoadingState extends StatelessWidget { + const PingLoadingState({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SpinKitCircle(color: AppColors.spinKitColor, size: 70), + const SizedBox(height: 16), + Text( + "Starting ping tests...", + style: TextStyle( + fontFamily: 'Poppins', + fontSize: 16, + color: AppColors.dnsText.withValues(alpha: 0.7), + ), + ), + ], + ), + ); + } +} diff --git a/lib/core/widgets/ping_progress_indicator.dart b/lib/core/widgets/ping/ping_progress_indicator.dart similarity index 90% rename from lib/core/widgets/ping_progress_indicator.dart rename to lib/core/widgets/ping/ping_progress_indicator.dart index 10ea025..1e39000 100644 --- a/lib/core/widgets/ping_progress_indicator.dart +++ b/lib/core/widgets/ping/ping_progress_indicator.dart @@ -34,7 +34,9 @@ class PingProgressIndicator extends StatelessWidget { height: 18, child: CircularProgressIndicator( strokeWidth: 2, - color: AppColors.isDarkMode ? Colors.greenAccent : Colors.indigo, + color: AppColors.isDarkMode + ? Colors.greenAccent + : Colors.indigo, ), ), const SizedBox(width: 12), @@ -43,7 +45,9 @@ class PingProgressIndicator extends StatelessWidget { style: TextStyle( fontFamily: 'Poppins', fontSize: 14, - color: AppColors.isDarkMode ? Colors.greenAccent : Colors.indigo, + color: AppColors.isDarkMode + ? Colors.greenAccent + : Colors.indigo, fontWeight: FontWeight.w500, ), ), diff --git a/lib/core/widgets/ping/ping_refresh_button.dart b/lib/core/widgets/ping/ping_refresh_button.dart new file mode 100644 index 0000000..775b88d --- /dev/null +++ b/lib/core/widgets/ping/ping_refresh_button.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:netshift/core/resources/app_colors.dart'; + +class PingRefreshButton extends StatelessWidget { + const PingRefreshButton({ + super.key, + required this.isPinging, + required this.onRefresh, + }); + + final bool isPinging; + final VoidCallback onRefresh; + + @override + Widget build(BuildContext context) { + return ElevatedButton.icon( + onPressed: isPinging ? null : onRefresh, + icon: isPinging + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : const Icon(Icons.refresh, size: 20), + label: Text(isPinging ? "Pinging..." : "Refresh All"), + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.isDarkMode + ? const Color(0xFF16725C) + : Colors.indigo, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + ); + } +} diff --git a/lib/core/widgets/ping/ping_status_indicator.dart b/lib/core/widgets/ping/ping_status_indicator.dart new file mode 100644 index 0000000..db8778b --- /dev/null +++ b/lib/core/widgets/ping/ping_status_indicator.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:netshift/core/resources/app_colors.dart'; + +class PingStatusIndicator extends StatelessWidget { + const PingStatusIndicator({ + super.key, + required this.isPinging, + required this.pingResult, + }); + + final bool isPinging; + final dynamic pingResult; + + @override + Widget build(BuildContext context) { + if (isPinging) { + return SpinKitCircle(color: AppColors.spinKitColor, size: 24); + } + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: AppColors.isDarkMode + ? Colors.greenAccent.withValues(alpha: 0.15) + : Colors.indigo.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + "$pingResult ms", + style: TextStyle( + fontFamily: 'Poppins', + fontSize: 14, + fontWeight: FontWeight.w600, + color: AppColors.isDarkMode ? Colors.greenAccent : Colors.indigo, + ), + ), + ); + } +} diff --git a/lib/core/widgets/check_for_update_widget.dart b/lib/core/widgets/settings/check_for_update_widget.dart similarity index 81% rename from lib/core/widgets/check_for_update_widget.dart rename to lib/core/widgets/settings/check_for_update_widget.dart index 155c289..7539db8 100644 --- a/lib/core/widgets/check_for_update_widget.dart +++ b/lib/core/widgets/settings/check_for_update_widget.dart @@ -6,7 +6,7 @@ import 'package:get/get.dart'; import 'package:netshift/controller/check_for_update_controller.dart'; import 'package:netshift/core/resources/app_colors.dart'; import 'package:netshift/core/services/windows_title_bar_box.dart'; -import 'package:netshift/core/widgets/flutter_toast.dart'; +import 'package:netshift/core/widgets/common/flutter_toast.dart'; import 'package:window_manager/window_manager.dart'; class CheckForUpdateWidget extends StatelessWidget { @@ -41,23 +41,32 @@ class CheckForUpdateWidget extends StatelessWidget { Text( "Current Version: ${check.currentVersion.value}", style: TextStyle( - color: AppColors.offlineStatusText, fontSize: 14), + color: AppColors.offlineStatusText, + fontSize: 14, + ), ), Text( "New Version: ${check.updateVersion.value}", style: TextStyle( - color: AppColors.offlineStatusText, fontSize: 14), + color: AppColors.offlineStatusText, + fontSize: 14, + ), ), Text( "Update Size: ${check.appSize.value} MB", style: TextStyle( - color: AppColors.offlineStatusText, fontSize: 14), + color: AppColors.offlineStatusText, + fontSize: 14, + ), ), const SizedBox(height: 10), - Text("Would you like to update now?", - style: TextStyle( - fontWeight: FontWeight.bold, - color: AppColors.offlineStatusText)), + Text( + "Would you like to update now?", + style: TextStyle( + fontWeight: FontWeight.bold, + color: AppColors.offlineStatusText, + ), + ), ], ), ); diff --git a/lib/core/widgets/interface_selection_dialog.dart b/lib/core/widgets/settings/interface_selection_dialog.dart similarity index 83% rename from lib/core/widgets/interface_selection_dialog.dart rename to lib/core/widgets/settings/interface_selection_dialog.dart index e22efb8..17ddc8b 100644 --- a/lib/core/widgets/interface_selection_dialog.dart +++ b/lib/core/widgets/settings/interface_selection_dialog.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:netshift/controller/netshift_engine_controller.dart'; import 'package:netshift/core/resources/app_colors.dart'; -import 'package:netshift/core/widgets/custom_snack_bar.dart'; +import 'package:netshift/core/widgets/common/custom_snack_bar.dart'; class InterfaceSelectionDialog { static void show(BuildContext context) { @@ -26,9 +26,7 @@ class InterfaceSelectionDialog { context: context, builder: (context) => AlertDialog( backgroundColor: AppColors.background, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), title: Center( child: Text( "Select an Interface", @@ -46,7 +44,8 @@ class InterfaceSelectionDialog { itemCount: netshiftEngineController.interfaceKeys.length, itemBuilder: (context, index) { final isConnected = - netshiftEngineController.interfaceValues[index] == 'Connected'; + netshiftEngineController.interfaceValues[index] == + 'Connected'; return ListTile( title: Text( netshiftEngineController.interfaceKeys[index], @@ -59,11 +58,15 @@ class InterfaceSelectionDialog { netshiftEngineController.interfaceName.value = netshiftEngineController.interfaceKeys[index]; netshiftEngineController.saveInterfaceName(); - log("Interface changed to ${netshiftEngineController.interfaceName.value}"); + log( + "Interface changed to ${netshiftEngineController.interfaceName.value}", + ); Navigator.pop(context); }, leading: Icon( - isConnected ? Icons.wifi_password_sharp : Icons.wifi_off_outlined, + isConnected + ? Icons.wifi_password_sharp + : Icons.wifi_off_outlined, color: isConnected ? const Color.fromARGB(255, 72, 190, 133) : Colors.redAccent, @@ -79,7 +82,9 @@ class InterfaceSelectionDialog { actions: [ TextButton( style: ButtonStyle( - overlayColor: WidgetStatePropertyAll(AppColors.interfaceCloseHover), + overlayColor: WidgetStatePropertyAll( + AppColors.interfaceCloseHover, + ), ), onPressed: () => Navigator.pop(context), child: Text( diff --git a/lib/core/widgets/languages_option.dart b/lib/core/widgets/settings/languages_option.dart similarity index 88% rename from lib/core/widgets/languages_option.dart rename to lib/core/widgets/settings/languages_option.dart index 0f4f1e1..6f683b6 100644 --- a/lib/core/widgets/languages_option.dart +++ b/lib/core/widgets/settings/languages_option.dart @@ -32,11 +32,9 @@ class _LanguagesOptionWidgetState extends State { groupValue: currentOption, activeColor: AppColors.settingsAppLanguage, onChanged: (value) { - setState( - () { - currentOption = value.toString(); - }, - ); + setState(() { + currentOption = value.toString(); + }); }, ), Divider( @@ -61,11 +59,9 @@ class _LanguagesOptionWidgetState extends State { groupValue: currentOption, activeColor: AppColors.settingsAppLanguage, onChanged: (value) { - setState( - () { - currentOption = value.toString(); - }, - ); + setState(() { + currentOption = value.toString(); + }); }, ), ], diff --git a/lib/core/widgets/other_apps_widgets.dart b/lib/core/widgets/settings/other_apps_widgets.dart similarity index 90% rename from lib/core/widgets/other_apps_widgets.dart rename to lib/core/widgets/settings/other_apps_widgets.dart index 8c7cffd..7b521be 100644 --- a/lib/core/widgets/other_apps_widgets.dart +++ b/lib/core/widgets/settings/other_apps_widgets.dart @@ -42,11 +42,7 @@ class OtherAppsWidget extends StatelessWidget { // colorFilter: ColorFilter.mode( // AppColors.settingsCustomCardSupportIcon, BlendMode.srcIn), // ), - Image.asset( - Assets.png.tray, - width: 60.0, - height: 60.0, - ), + Image.asset(Assets.png.tray, width: 60.0, height: 60.0), Text( Platform.isAndroid ? "NetShift Windows" : "NetShift", style: TextStyle( @@ -80,11 +76,7 @@ class OtherAppsWidget extends StatelessWidget { // colorFilter: ColorFilter.mode( // AppColors.settingsCustomCardSupportIcon, BlendMode.srcIn), // ), - Image.asset( - Assets.svg.autoshut, - width: 60.0, - height: 60.0, - ), + Image.asset(Assets.svg.autoshut, width: 60.0, height: 60.0), Text( 'AutoShut', style: TextStyle( @@ -95,7 +87,7 @@ class OtherAppsWidget extends StatelessWidget { ), ], ), - ) + ), ], ); } diff --git a/lib/core/widgets/settings_custom_card.dart b/lib/core/widgets/settings/settings_custom_card.dart similarity index 90% rename from lib/core/widgets/settings_custom_card.dart rename to lib/core/widgets/settings/settings_custom_card.dart index 886eaea..ff96170 100644 --- a/lib/core/widgets/settings_custom_card.dart +++ b/lib/core/widgets/settings/settings_custom_card.dart @@ -51,7 +51,9 @@ class _SettingsCustomCardState extends State { duration: const Duration(milliseconds: 200), padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: _isPressed ? AppColors.settingsCustomCardPressed: AppColors.settingsCustomCardNotPressed, + color: _isPressed + ? AppColors.settingsCustomCardPressed + : AppColors.settingsCustomCardNotPressed, borderRadius: BorderRadius.circular(12), boxShadow: _isPressed ? [ @@ -71,11 +73,7 @@ class _SettingsCustomCardState extends State { ), child: Row( children: [ - Icon( - widget.icon, - color: AppColors.settingsIconColors, - size: 28, - ), + Icon(widget.icon, color: AppColors.settingsIconColors, size: 28), const SizedBox(width: 16), Expanded( child: Column( @@ -92,7 +90,7 @@ class _SettingsCustomCardState extends State { const SizedBox(height: 4), Text( widget.subtitle, - style: TextStyle( + style: TextStyle( color: AppColors.settingsCustomCardSubTitle, fontSize: 14, ), diff --git a/lib/core/widgets/settings/settings_section.dart b/lib/core/widgets/settings/settings_section.dart new file mode 100644 index 0000000..4faff32 --- /dev/null +++ b/lib/core/widgets/settings/settings_section.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:netshift/core/resources/app_colors.dart'; + +class SettingsSection extends StatelessWidget { + const SettingsSection({ + super.key, + required this.title, + required this.children, + }); + + final String title; + final List children; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: AppColors.isDarkMode + ? const Color(0xFF1E2025) + : Colors.white.withValues(alpha: 0.9), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColors.isDarkMode + ? Colors.black.withValues(alpha: 0.2) + : Colors.indigo.withValues(alpha: 0.1), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 4, + height: 20, + decoration: BoxDecoration( + color: AppColors.isDarkMode + ? Colors.greenAccent + : Colors.indigo, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(width: 12), + Text( + title, + style: TextStyle( + fontFamily: 'Poppins', + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.settingsGeneralCategory, + ), + ), + ], + ), + const SizedBox(height: 24), + ...children, + ], + ), + ); + } +} diff --git a/lib/core/widgets/support_content.dart b/lib/core/widgets/settings/support_content.dart similarity index 91% rename from lib/core/widgets/support_content.dart rename to lib/core/widgets/settings/support_content.dart index b5e9bee..34f2bbe 100644 --- a/lib/core/widgets/support_content.dart +++ b/lib/core/widgets/settings/support_content.dart @@ -41,7 +41,9 @@ class SupportContent extends StatelessWidget { width: 50.0, height: 50.0, colorFilter: ColorFilter.mode( - AppColors.settingsCustomCardSupportIcon, BlendMode.srcIn), + AppColors.settingsCustomCardSupportIcon, + BlendMode.srcIn, + ), ), Text( 'GitHub', @@ -74,7 +76,9 @@ class SupportContent extends StatelessWidget { width: 50.0, height: 50.0, colorFilter: ColorFilter.mode( - AppColors.settingsCustomCardSupportIcon, BlendMode.srcIn), + AppColors.settingsCustomCardSupportIcon, + BlendMode.srcIn, + ), ), Text( 'Telegram', @@ -86,7 +90,7 @@ class SupportContent extends StatelessWidget { ), ], ), - ) + ), ], ); } diff --git a/lib/main.dart b/lib/main.dart index 3a3878e..f3c8e9b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -74,9 +74,7 @@ Future main(List arguments) async { } SystemChromeController().setSystemUIOverlayStyle(); - runApp( - const MyApp(), - ); + runApp(const MyApp()); } class MyApp extends StatelessWidget { @@ -88,13 +86,17 @@ class MyApp extends StatelessWidget { ScreenSize().init(context); return GetMaterialApp( theme: ThemeData( - scrollbarTheme: ScrollbarThemeData( - thumbColor: WidgetStateProperty.all(ananas.isDarkMode - ? Colors.greenAccent.withValues(alpha: 0.6) - : Colors.indigo.withValues(alpha: 0.6)), - trackColor: WidgetStateProperty.all( - ananas.isDarkMode ? Colors.black : Colors.grey), - )), + scrollbarTheme: ScrollbarThemeData( + thumbColor: WidgetStateProperty.all( + ananas.isDarkMode + ? Colors.greenAccent.withValues(alpha: 0.6) + : Colors.indigo.withValues(alpha: 0.6), + ), + trackColor: WidgetStateProperty.all( + ananas.isDarkMode ? Colors.black : Colors.grey, + ), + ), + ), initialBinding: InitialBindings(), debugShowCheckedModeBanner: false, home: SplashScreen(), diff --git a/lib/main_wrapper.dart b/lib/main_wrapper.dart index 9fc7b84..4dddf2c 100644 --- a/lib/main_wrapper.dart +++ b/lib/main_wrapper.dart @@ -8,9 +8,9 @@ import 'package:netshift/core/resources/app_colors.dart'; import 'package:netshift/core/services/platform_service.dart'; import 'package:netshift/core/services/windows_title_bar_box.dart'; import 'package:netshift/core/services/macos_title_bar_box.dart'; -import 'package:netshift/core/widgets/desktop_sidebar.dart'; -import 'package:netshift/core/widgets/double_tap_to_exit.dart'; -import 'package:netshift/core/widgets/nav_bar.dart'; +import 'package:netshift/core/widgets/desktop/desktop_sidebar.dart'; +import 'package:netshift/core/widgets/common/double_tap_to_exit.dart'; +import 'package:netshift/core/widgets/common/nav_bar.dart'; class MainWrapper extends StatelessWidget { MainWrapper({super.key}); @@ -21,8 +21,8 @@ class MainWrapper extends StatelessWidget { @override Widget build(BuildContext context) { // Use desktop layout for macOS and Windows on larger screens - final isDesktopLayout = PlatformService.isDesktop && - MediaQuery.of(context).size.width >= 800; + final isDesktopLayout = + PlatformService.isDesktop && MediaQuery.of(context).size.width >= 800; if (isDesktopLayout) { return _buildDesktopLayout(context); @@ -84,9 +84,7 @@ class MainWrapper extends StatelessWidget { ) : null, backgroundColor: AppColors.mainWrapperBackground, - body: Obx( - () => mainWrapperController.selectedPage, - ), + body: Obx(() => mainWrapperController.selectedPage), bottomNavigationBar: SafeArea( child: Padding( padding: const EdgeInsets.all(14), @@ -104,7 +102,7 @@ class MainWrapper extends StatelessWidget { blurRadius: 15, offset: const Offset(0, 4), spreadRadius: 5, - ) + ), ], ), child: BottomAppBar( diff --git a/lib/models/dns_model.dart b/lib/models/dns_model.dart index eb49c4b..7452a0e 100644 --- a/lib/models/dns_model.dart +++ b/lib/models/dns_model.dart @@ -8,13 +8,14 @@ class DnsModel { required this.primaryDNS, required this.secondaryDNS, }); - Map toJson() { + Map toJson() { return { 'name': name, 'primaryDNS': primaryDNS, 'secondaryDNS': secondaryDNS, }; } + factory DnsModel.fromJson(Map json) { return DnsModel( name: json['name'], diff --git a/lib/screens/blocked_apps_screen.dart b/lib/screens/blocked_apps_screen.dart index 041f5bc..061886c 100644 --- a/lib/screens/blocked_apps_screen.dart +++ b/lib/screens/blocked_apps_screen.dart @@ -47,33 +47,32 @@ class BlockedAppsScreen extends StatelessWidget { }, child: Row( children: [ - Obx( - () { - return Checkbox( - value: blockedAppsController.isSystemApp.value, - onChanged: (value) { - blockedAppsController.isSystemApp.value = - value!; - blockedAppsController.apps.clear(); - blockedAppsController.installedApps(); - blockedAppsController.saveSystemAppState(); - Navigator.pop(context); - }, - ); - }, - ), + Obx(() { + return Checkbox( + value: blockedAppsController.isSystemApp.value, + onChanged: (value) { + blockedAppsController.isSystemApp.value = value!; + blockedAppsController.apps.clear(); + blockedAppsController.installedApps(); + blockedAppsController.saveSystemAppState(); + Navigator.pop(context); + }, + ); + }), Text( 'System Apps', style: TextStyle( - color: AppColors.blockedAppBar, fontSize: 14), + color: AppColors.blockedAppBar, + fontSize: 14, + ), ), ], ), ), - ) + ), ]; }, - ) + ), ], iconTheme: const IconThemeData(color: Colors.white), centerTitle: true, @@ -82,10 +81,7 @@ class BlockedAppsScreen extends StatelessWidget { body: Obx(() { if (blockedAppsController.apps.isEmpty) { return Center( - child: SpinKitFoldingCube( - color: AppColors.spinKitColor, - size: 48, - ), + child: SpinKitFoldingCube(color: AppColors.spinKitColor, size: 48), ); } return ListView.builder( @@ -96,10 +92,12 @@ class BlockedAppsScreen extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: GestureDetector( onTap: () { - if (blockedAppsController.blockedApps - .contains(appList.packageName)) { - blockedAppsController.blockedApps - .remove(appList.packageName); + if (blockedAppsController.blockedApps.contains( + appList.packageName, + )) { + blockedAppsController.blockedApps.remove( + appList.packageName, + ); blockedAppsController.saveBlockedAppsList(); log("App Removed ${appList.packageName}"); } else { @@ -111,19 +109,17 @@ class BlockedAppsScreen extends StatelessWidget { child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: AppColors.blockedAppContainer, - border: Border.all( - color: AppColors.blockedAppContainerBorder, - width: 1, - ), - borderRadius: BorderRadius.circular(12)), + color: AppColors.blockedAppContainer, + border: Border.all( + color: AppColors.blockedAppContainerBorder, + width: 1, + ), + borderRadius: BorderRadius.circular(12), + ), child: Row( children: [ appList.icon != null && appList.icon!.isNotEmpty - ? Image.memory( - appList.icon!, - height: 32, - ) + ? Image.memory(appList.icon!, height: 32) : const Icon( Icons.android, color: Colors.greenAccent, @@ -147,8 +143,9 @@ class BlockedAppsScreen extends StatelessWidget { Text( appList.packageName, style: TextStyle( - color: AppColors.blockedAppAppName - .withValues(alpha: 0.7), + color: AppColors.blockedAppAppName.withValues( + alpha: 0.7, + ), fontSize: 12, fontFamily: 'Poppins', ), @@ -159,32 +156,34 @@ class BlockedAppsScreen extends StatelessWidget { ), ), const Spacer(), - Obx( - () { - return Checkbox( - side: const BorderSide( - color: Colors.black, width: 1.5), - checkColor: Colors.black, - activeColor: - const Color.fromARGB(190, 105, 240, 127), - value: blockedAppsController.blockedApps - .contains(appList.packageName), - onChanged: (value) { - if (value == true) { - blockedAppsController.blockedApps - .add(appList.packageName); - blockedAppsController.saveBlockedAppsList(); - log("App Added ${appList.packageName}"); - } else { - blockedAppsController.blockedApps - .remove(appList.packageName); - blockedAppsController.saveBlockedAppsList(); - log("App Removed ${appList.packageName}"); - } - }, - ); - }, - ) + Obx(() { + return Checkbox( + side: const BorderSide( + color: Colors.black, + width: 1.5, + ), + checkColor: Colors.black, + activeColor: const Color.fromARGB(190, 105, 240, 127), + value: blockedAppsController.blockedApps.contains( + appList.packageName, + ), + onChanged: (value) { + if (value == true) { + blockedAppsController.blockedApps.add( + appList.packageName, + ); + blockedAppsController.saveBlockedAppsList(); + log("App Added ${appList.packageName}"); + } else { + blockedAppsController.blockedApps.remove( + appList.packageName, + ); + blockedAppsController.saveBlockedAppsList(); + log("App Removed ${appList.packageName}"); + } + }, + ); + }), ], ), ), diff --git a/lib/screens/dns_page.dart b/lib/screens/dns_page.dart index bbfb3e9..d46f3b1 100644 --- a/lib/screens/dns_page.dart +++ b/lib/screens/dns_page.dart @@ -1,43 +1,33 @@ import 'package:flutter/material.dart'; -import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:get/get.dart'; -import 'package:netshift/core/resources/extention_sized.dart'; import 'package:netshift/core/resources/media_query_size.dart'; import 'package:netshift/core/services/platform_service.dart'; import 'package:netshift/controller/netshift_engine_controller.dart'; -import 'package:netshift/controller/random_dns_generator_controller.dart'; import 'package:netshift/controller/single_dns_ping_controller.dart'; -import 'package:netshift/controller/splash_controller.dart'; -import 'package:netshift/gen/assets.gen.dart'; import 'package:netshift/core/resources/app_colors.dart'; -import 'package:netshift/core/widgets/action_card.dart'; -import 'package:netshift/core/widgets/add_dns.dart'; -import 'package:netshift/core/widgets/app_bar.dart'; -import 'package:netshift/core/widgets/custom_floating_action_button.dart'; -import 'package:netshift/core/widgets/custom_textfield.dart'; -import 'package:netshift/core/widgets/dns_generator_dialog.dart'; -import 'package:netshift/core/widgets/dns_info_row.dart'; -import 'package:netshift/core/widgets/dns_selection_container.dart'; -import 'package:netshift/core/widgets/custom_button.dart'; -import 'package:netshift/core/widgets/main_dns_selector.dart'; -import 'package:netshift/core/widgets/custom_snack_bar.dart'; -import 'package:netshift/core/widgets/world_map.dart'; +import 'package:netshift/core/widgets/common/app_bar.dart'; +import 'package:netshift/core/widgets/common/custom_button.dart'; +import 'package:netshift/core/widgets/common/custom_snack_bar.dart'; +import 'package:netshift/core/widgets/dns/dns_configuration_card.dart'; +import 'package:netshift/core/widgets/dns/dns_selection_container.dart'; +import 'package:netshift/core/widgets/dns/main_dns_selector.dart'; +import 'package:netshift/core/widgets/home/mobile_actions_panel.dart'; +import 'package:netshift/core/widgets/home/mobile_dns_field.dart'; +import 'package:netshift/core/widgets/home/quick_actions_panel.dart'; +import 'package:netshift/core/widgets/home/world_map.dart'; class DNSPage extends StatelessWidget { DNSPage({super.key}); final NetshiftEngineController netshiftEngineController = Get.find(); - final splashScreenController = Get.find(); @override Widget build(BuildContext context) { Get.lazyPut(() => SingleDnsPingController()); - final dnsPingController = Get.find(); - Get.lazyPut(() => RandomDnsGeneratorController()); - final randomDnsGeneratorController = Get.find(); + final SingleDnsPingController dnsPingController = Get.find(); - final isDesktop = PlatformService.isDesktop && - MediaQuery.of(context).size.width >= 800; + final isDesktop = + PlatformService.isDesktop && MediaQuery.of(context).size.width >= 800; return Scaffold( resizeToAvoidBottomInset: true, @@ -52,186 +42,31 @@ class DNSPage extends StatelessWidget { ), backgroundColor: AppColors.background, body: isDesktop - ? _buildDesktopBody(context, dnsPingController, randomDnsGeneratorController) - : _buildMobileBody(context, dnsPingController, randomDnsGeneratorController), + ? _buildDesktopBody() + : _buildMobileBody(dnsPingController), ); } - Widget _buildDesktopBody( - BuildContext context, - SingleDnsPingController dnsPingController, - RandomDnsGeneratorController randomDnsGeneratorController, - ) { + Widget _buildDesktopBody() { return Stack( children: [ const WorldMap(), - Obx( - () => Padding( - padding: const EdgeInsets.all(32), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - flex: 3, - child: _buildDnsConfiguration(context, dnsPingController), - ), - const SizedBox(width: 32), - Expanded( - flex: 2, - child: _buildQuickActions(context, randomDnsGeneratorController), - ), - ], - ), + Padding( + padding: const EdgeInsets.all(32), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(flex: 3, child: DnsConfigurationCard()), + const SizedBox(width: 32), + Expanded(flex: 2, child: QuickActionsPanel()), + ], ), ), ], ); } - Widget _buildDnsConfiguration(BuildContext context, SingleDnsPingController dnsPingController) { - return SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "DNS Configuration", - style: TextStyle( - fontFamily: 'Poppins', - fontSize: 24, - fontWeight: FontWeight.bold, - color: AppColors.dnsText, - ), - ), - const SizedBox(height: 8), - Text( - "Select and configure your preferred DNS servers", - style: TextStyle( - fontFamily: 'Poppins', - fontSize: 14, - color: AppColors.dnsText.withValues(alpha: 0.7), - ), - ), - const SizedBox(height: 32), - DnsSelectionContainer( - onPressed: () => _handleDnsSelection(context), - color: AppColors.dnsSelectionContainerIcon, - ), - const SizedBox(height: 32), - _buildDnsDetailsCard(dnsPingController), - ], - ), - ); - } - - Widget _buildDnsDetailsCard(SingleDnsPingController dnsPingController) { - return Container( - padding: const EdgeInsets.all(24), - decoration: BoxDecoration( - color: AppColors.isDarkMode - ? const Color(0xFF1E2025) - : Colors.white.withValues(alpha: 0.9), - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: AppColors.isDarkMode - ? Colors.black.withValues(alpha: 0.2) - : Colors.indigo.withValues(alpha: 0.1), - blurRadius: 12, - offset: const Offset(0, 4), - ), - ], - ), - child: Column( - children: [ - DnsInfoRow( - label: "Primary DNS", - dns: netshiftEngineController.selectedDns.value.primaryDNS, - isPinging: dnsPingController.isPingP.value, - pingResult: dnsPingController.resultPingP, - ), - const SizedBox(height: 20), - Divider( - color: AppColors.isDarkMode - ? Colors.white12 - : Colors.indigo.withValues(alpha: 0.1), - ), - const SizedBox(height: 20), - DnsInfoRow( - label: "Secondary DNS", - dns: netshiftEngineController.selectedDns.value.secondaryDNS, - isPinging: dnsPingController.isPingS.value, - pingResult: dnsPingController.resultPingS, - ), - const SizedBox(height: 24), - _buildPingButton(dnsPingController), - ], - ), - ); - } - - Widget _buildPingButton(SingleDnsPingController dnsPingController) { - return SizedBox( - width: double.infinity, - child: ElevatedButton.icon( - onPressed: () { - dnsPingController.pingPrimaryDns(); - dnsPingController.pingSecondaryDns(); - }, - icon: const Icon(Icons.speed, size: 20), - label: const Text("Test DNS Latency"), - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.isDarkMode ? const Color(0xFF16725C) : Colors.indigo, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - ), - ), - ); - } - - Widget _buildQuickActions(BuildContext context, RandomDnsGeneratorController controller) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Quick Actions", - style: TextStyle( - fontFamily: 'Poppins', - fontSize: 18, - fontWeight: FontWeight.bold, - color: AppColors.dnsText, - ), - ), - const SizedBox(height: 20), - ActionCard( - icon: Icons.auto_fix_high, - title: "Generate Random DNS", - subtitle: "Auto-generate optimized DNS settings", - onTap: () => _handleGenerateDns(context, controller), - ), - const SizedBox(height: 16), - ActionCard( - icon: Icons.add_circle_outline, - title: "Add Custom DNS", - subtitle: "Configure your own DNS servers", - onTap: () => _handleAddDns(context), - ), - const SizedBox(height: 16), - ActionCard( - icon: Icons.dns_rounded, - title: "Browse DNS Providers", - subtitle: "Choose from popular DNS services", - onTap: () => _handleDnsSelection(context), - ), - ], - ); - } - - Widget _buildMobileBody( - BuildContext context, - SingleDnsPingController dnsPingController, - RandomDnsGeneratorController randomDnsGeneratorController, - ) { + Widget _buildMobileBody(SingleDnsPingController dnsPingController) { return Stack( children: [ const WorldMap(), @@ -245,20 +80,21 @@ class DNSPage extends StatelessWidget { children: [ SizedBox(height: ScreenSize.height * 0.02), DnsSelectionContainer( - onPressed: () => _handleDnsSelection(context), + onPressed: () => _handleDnsSelection(), color: AppColors.dnsSelectionContainerIcon, ), SizedBox(height: ScreenSize.height * 0.02), - _buildMobileDnsField( + MobileDnsField( label: "Primary", dns: netshiftEngineController.selectedDns.value.primaryDNS, isPinging: dnsPingController.isPingP.value, pingResult: dnsPingController.resultPingP, ), SizedBox(height: ScreenSize.height * 0.02), - _buildMobileDnsField( + MobileDnsField( label: "Secondary", - dns: netshiftEngineController.selectedDns.value.secondaryDNS, + dns: + netshiftEngineController.selectedDns.value.secondaryDNS, isPinging: dnsPingController.isPingS.value, pingResult: dnsPingController.resultPingS, ), @@ -271,7 +107,7 @@ class DNSPage extends StatelessWidget { }, ), const Spacer(), - _buildMobileActions(context, randomDnsGeneratorController), + MobileActionsPanel(), ], ), ), @@ -281,67 +117,16 @@ class DNSPage extends StatelessWidget { ); } - Widget _buildMobileDnsField({ - required String label, - required String dns, - required bool isPinging, - required dynamic pingResult, - }) { - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "$label :", - style: TextStyle( - fontFamily: 'Poppins', - fontWeight: FontWeight.w600, - fontSize: 16, - color: AppColors.dnsText, - ), - ), - isPinging - ? SpinKitCircle(color: AppColors.spinKitColor, size: 22) - : Text( - "$pingResult ms", - style: TextStyle( - fontFamily: 'Poppins', - fontWeight: FontWeight.w500, - fontSize: 14, - color: AppColors.dnsText, - ), - ), - ], - ), - 8.height, - CustomTextContainer(dns: dns), - ], - ); - } - - Widget _buildMobileActions(BuildContext context, RandomDnsGeneratorController controller) { - return Padding( - padding: const EdgeInsets.only(right: 12, left: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CustomFloatingActionButton( - path: Assets.svg.generateDns, - onTap: () => _handleGenerateDns(context, controller), - ), - CustomFloatingActionButton( - path: Assets.svg.addDns, - onTap: () => _handleAddDns(context), - ), - ], - ), - ); - } - - void _handleDnsSelection(BuildContext context) { + void _handleDnsSelection() { if (netshiftEngineController.isActive.value) { - _showErrorSnackBar("Failed to SELECT DNS, please stop the service first"); + CustomSnackBar( + title: "Operation Failed", + message: "Failed to SELECT DNS, please stop the service first", + backColor: Colors.red.shade700.withValues(alpha: 0.9), + iconColor: Colors.white, + icon: Icons.error_outline, + textColor: Colors.white, + ).customSnackBar(); return; } @@ -352,52 +137,8 @@ class DNSPage extends StatelessWidget { reverseDuration: const Duration(milliseconds: 300), reverseCurve: Curves.easeInOut, ), - context: context, + context: Get.context!, builder: (context) => MainDNSSelector(), ); } - - void _handleGenerateDns(BuildContext context, RandomDnsGeneratorController controller) { - if (splashScreenController.isOffline.value) { - _showErrorSnackBar("Failed to GENERATE DNS, please start the app in online mode"); - return; - } - - controller.generateRandomDns(); - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => DnsGeneratorDialog(), - ); - } - - void _handleAddDns(BuildContext context) { - if (netshiftEngineController.isActive.value) { - _showErrorSnackBar("Failed to ADD DNS, please stop the service first"); - return; - } - - showModalBottomSheet( - context: context, - sheetAnimationStyle: AnimationStyle( - duration: const Duration(milliseconds: 400), - curve: Curves.fastOutSlowIn, - reverseDuration: const Duration(milliseconds: 300), - reverseCurve: Curves.fastOutSlowIn, - ), - isScrollControlled: true, - builder: (context) => const AddDnsBottomSheet(), - ); - } - - void _showErrorSnackBar(String message) { - CustomSnackBar( - title: "Operation Failed", - message: message, - backColor: Colors.red.shade700.withValues(alpha: 0.9), - iconColor: Colors.white, - icon: Icons.error_outline, - textColor: Colors.white, - ).customSnackBar(); - } } diff --git a/lib/screens/dns_ping.dart b/lib/screens/dns_ping.dart index 18ce61c..0b737d1 100644 --- a/lib/screens/dns_ping.dart +++ b/lib/screens/dns_ping.dart @@ -1,7 +1,6 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:get/get.dart'; import 'package:netshift/core/services/platform_service.dart'; import 'package:netshift/controller/single_dns_ping_controller.dart'; @@ -9,11 +8,13 @@ import 'package:netshift/controller/sorted_dns_ping_controller.dart'; import 'package:netshift/controller/netshift_engine_controller.dart'; import 'package:netshift/models/dns_model.dart'; import 'package:netshift/core/resources/app_colors.dart'; -import 'package:netshift/core/widgets/app_bar.dart'; -import 'package:netshift/core/widgets/custom_snack_bar.dart'; -import 'package:netshift/core/widgets/dns_ping_card.dart'; -import 'package:netshift/core/widgets/flutter_toast.dart'; -import 'package:netshift/core/widgets/ping_progress_indicator.dart'; +import 'package:netshift/core/widgets/common/app_bar.dart'; +import 'package:netshift/core/widgets/common/custom_snack_bar.dart'; +import 'package:netshift/core/widgets/dns/dns_ping_card.dart'; +import 'package:netshift/core/widgets/common/flutter_toast.dart'; +import 'package:netshift/core/widgets/ping/ping_header.dart'; +import 'package:netshift/core/widgets/ping/ping_loading_state.dart'; +import 'package:netshift/core/widgets/ping/ping_progress_indicator.dart'; class DNSPing extends StatelessWidget { DNSPing({super.key}); @@ -27,8 +28,8 @@ class DNSPing extends StatelessWidget { Get.lazyPut(() => SingleDnsPingController()); final singleDnsPingController = Get.find(); - final isDesktop = PlatformService.isDesktop && - MediaQuery.of(context).size.width >= 800; + final isDesktop = + PlatformService.isDesktop && MediaQuery.of(context).size.width >= 800; return Obx( () => Scaffold( @@ -76,72 +77,18 @@ class DNSPing extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildDesktopHeader(dnsPingController), + PingHeader( + controller: dnsPingController, + onRefresh: () => _handleRefresh(dnsPingController), + ), const SizedBox(height: 24), - Expanded(child: _buildDesktopContent(dnsPingController, singleDnsPingController)), - ], - ), - ); - } - - Widget _buildDesktopHeader(SortedDnsPingController controller) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "DNS Ping Results", - style: TextStyle( - fontFamily: 'Poppins', - fontSize: 28, - fontWeight: FontWeight.bold, - color: AppColors.dnsText, - ), - ), - const SizedBox(height: 8), - Text( - "Test and compare DNS server latencies", - style: TextStyle( - fontFamily: 'Poppins', - fontSize: 14, - color: AppColors.dnsText.withValues(alpha: 0.7), - ), + Expanded( + child: _buildDesktopContent( + dnsPingController, + singleDnsPingController, ), - ], - ), - Row( - children: [ - PingProgressIndicator( - completed: controller.completedCount.value, - total: controller.totalCount.value, - isVisible: controller.isPinging.value, - ), - const SizedBox(width: 16), - _buildRefreshButton(controller), - ], - ), - ], - ); - } - - Widget _buildRefreshButton(SortedDnsPingController controller) { - return ElevatedButton.icon( - onPressed: controller.isPinging.value ? null : () => _handleRefresh(controller), - icon: controller.isPinging.value - ? const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white), - ) - : const Icon(Icons.refresh, size: 20), - label: Text(controller.isPinging.value ? "Pinging..." : "Refresh All"), - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.isDarkMode ? const Color(0xFF16725C) : Colors.indigo, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + ], ), ); } @@ -151,7 +98,7 @@ class DNSPing extends StatelessWidget { SingleDnsPingController singleDnsPingController, ) { if (dnsPingController.pingResultMap.isEmpty) { - return _buildLoadingState(); + return const PingLoadingState(); } return GridView.builder( @@ -161,16 +108,17 @@ class DNSPing extends StatelessWidget { crossAxisSpacing: 16, childAspectRatio: 2.2, ), - itemCount: dnsPingController.ananas.length, + itemCount: dnsPingController.dnsNames.length, itemBuilder: (context, index) => DnsPingCard( - name: dnsPingController.ananas[index], - primaryDns: dnsPingController.ananas1[index], - secondaryDns: dnsPingController.ananas2[index], - avgPing: dnsPingController.ananas3[index], - primaryPing: dnsPingController.ananas4[index], - secondaryPing: dnsPingController.ananas5[index], + name: dnsPingController.dnsNames[index], + primaryDns: dnsPingController.primaryDnsList[index], + secondaryDns: dnsPingController.secondaryDnsList[index], + avgPing: dnsPingController.avgPingList[index], + primaryPing: dnsPingController.primaryPingList[index], + secondaryPing: dnsPingController.secondaryPingList[index], isDesktop: true, - onApply: () => _handleApply(dnsPingController, singleDnsPingController, index), + onApply: () => + _handleApply(dnsPingController, singleDnsPingController, index), ), ); } @@ -180,7 +128,7 @@ class DNSPing extends StatelessWidget { SingleDnsPingController singleDnsPingController, ) { if (dnsPingController.pingResultMap.isEmpty) { - return _buildLoadingState(); + return const PingLoadingState(); } return Column( @@ -195,15 +143,19 @@ class DNSPing extends StatelessWidget { child: Padding( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 14), child: ListView.builder( - itemCount: dnsPingController.ananas.length, + itemCount: dnsPingController.dnsNames.length, itemBuilder: (context, index) => DnsPingCard( - name: dnsPingController.ananas[index], - primaryDns: dnsPingController.ananas1[index], - secondaryDns: dnsPingController.ananas2[index], - avgPing: dnsPingController.ananas3[index], - primaryPing: dnsPingController.ananas4[index], - secondaryPing: dnsPingController.ananas5[index], - onApply: () => _handleApply(dnsPingController, singleDnsPingController, index), + name: dnsPingController.dnsNames[index], + primaryDns: dnsPingController.primaryDnsList[index], + secondaryDns: dnsPingController.secondaryDnsList[index], + avgPing: dnsPingController.avgPingList[index], + primaryPing: dnsPingController.primaryPingList[index], + secondaryPing: dnsPingController.secondaryPingList[index], + onApply: () => _handleApply( + dnsPingController, + singleDnsPingController, + index, + ), ), ), ), @@ -212,26 +164,6 @@ class DNSPing extends StatelessWidget { ); } - Widget _buildLoadingState() { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SpinKitCircle(color: AppColors.spinKitColor, size: 70), - const SizedBox(height: 16), - Text( - "Starting ping tests...", - style: TextStyle( - fontFamily: 'Poppins', - fontSize: 16, - color: AppColors.dnsText.withValues(alpha: 0.7), - ), - ), - ], - ), - ); - } - void _handleRefresh(SortedDnsPingController controller) { if (controller.isPinging.value) { CustomSnackBar( @@ -261,11 +193,14 @@ class DNSPing extends StatelessWidget { } if (Platform.isAndroid && netshiftEngineController.isActive.value) { - _showErrorSnackBar("Failed to START SERVICE, please stop the service first"); + _showErrorSnackBar( + "Failed to START SERVICE, please stop the service first", + ); return; } - if ((Platform.isAndroid && netshiftEngineController.isPermissionGiven.value) || + if ((Platform.isAndroid && + netshiftEngineController.isPermissionGiven.value) || Platform.isWindows || Platform.isMacOS) { if (!netshiftEngineController.isActive.value) { @@ -291,16 +226,17 @@ class DNSPing extends StatelessWidget { int index, ) { netshiftEngineController.selectedDns.value = DnsModel( - name: dnsPingController.ananas[index], - primaryDNS: dnsPingController.ananas1[index], - secondaryDNS: dnsPingController.ananas2[index], + name: dnsPingController.dnsNames[index], + primaryDNS: dnsPingController.primaryDnsList[index], + secondaryDNS: dnsPingController.secondaryDnsList[index], ); netshiftEngineController.saveSelectedDnsValue(); singleDnsPingController.pingPrimaryDns(); singleDnsPingController.pingSecondaryDns(); CustomSnackBar( title: "Operation Success", - message: "${netshiftEngineController.selectedDns.value.name} Has Been Applied Successfully", + message: + "${netshiftEngineController.selectedDns.value.name} Has Been Applied Successfully", backColor: const Color.fromARGB(255, 50, 189, 122).withValues(alpha: 0.9), iconColor: Colors.white, icon: Icons.check_circle_outline, diff --git a/lib/screens/home_page.dart b/lib/screens/home_page.dart index 1e46d16..ac66bb8 100644 --- a/lib/screens/home_page.dart +++ b/lib/screens/home_page.dart @@ -1,150 +1,55 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; import 'package:get/get.dart'; -import 'package:netshift/controller/foreground_controller.dart'; -import 'package:netshift/controller/netshift_engine_controller.dart'; import 'package:netshift/controller/splash_controller.dart'; import 'package:netshift/controller/stop_watch_controller.dart'; import 'package:netshift/core/services/platform_service.dart'; -import 'package:netshift/gen/assets.gen.dart'; import 'package:netshift/core/resources/app_colors.dart'; -import 'package:netshift/core/widgets/app_bar.dart'; -import 'package:netshift/core/widgets/connect_button.dart'; -import 'package:netshift/core/widgets/custom_snack_bar.dart'; -import 'package:netshift/core/widgets/desktop_status_card.dart'; -import 'package:netshift/core/widgets/dns_status.dart'; -import 'package:netshift/core/widgets/interface_selection_dialog.dart'; -import 'package:netshift/core/widgets/offline_banner.dart'; -import 'package:netshift/core/widgets/status_bar.dart'; -import 'package:netshift/core/widgets/world_map.dart'; +import 'package:netshift/core/widgets/home/connect_button.dart'; +import 'package:netshift/core/widgets/home/connection_controls_panel.dart'; +import 'package:netshift/core/widgets/dns/dns_status.dart'; +import 'package:netshift/core/widgets/home/home_app_bar.dart'; +import 'package:netshift/core/widgets/home/mobile_status_icons.dart'; +import 'package:netshift/core/widgets/common/offline_banner.dart'; +import 'package:netshift/core/widgets/home/status_cards_panel.dart'; +import 'package:netshift/core/widgets/home/world_map.dart'; class HomePage extends StatelessWidget { HomePage({super.key}); - final NetshiftEngineController netshiftEngineController = Get.find(); final StopWatchController stopWatchController = Get.find(); - final ForegroundController foregroundController = Get.find(); final SplashScreenController splashController = Get.find(); @override Widget build(BuildContext context) { - final isDesktop = PlatformService.isDesktop && - MediaQuery.of(context).size.width >= 800; + final isDesktop = + PlatformService.isDesktop && MediaQuery.of(context).size.width >= 800; return Scaffold( backgroundColor: AppColors.background, - appBar: isDesktop ? null : _buildMobileAppBar(), - body: isDesktop ? _buildDesktopBody(context) : _buildMobileBody(context), - ); - } - - PreferredSizeWidget _buildMobileAppBar() { - return CustomAppBar( - title: "NetShift", - fontFamily: 'Calistoga', - fontSize: 24, - fontWeight: FontWeight.w600, - actions: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: OutlinedButton( - onPressed: () {}, - style: ButtonStyle( - side: const WidgetStatePropertyAll(BorderSide.none), - backgroundColor: WidgetStatePropertyAll(AppColors.iconAppBar), - ), - child: SvgPicture.asset(Assets.svg.crown), - ), - ), - ], + appBar: isDesktop ? null : const HomeAppBar(), + body: isDesktop ? _buildDesktopBody() : _buildMobileBody(), ); } - Widget _buildDesktopBody(BuildContext context) { + Widget _buildDesktopBody() { return Stack( children: [ const WorldMap(), - Obx( - () => Padding( - padding: const EdgeInsets.all(32), - child: Row( - children: [ - Expanded(flex: 3, child: _buildConnectionControls()), - const SizedBox(width: 32), - Expanded(flex: 2, child: _buildStatusCards(context)), - ], - ), - ), - ), - ], - ); - } - - Widget _buildConnectionControls() { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (splashController.isOffline.value) - OfflineBanner(splashController: splashController), - const SizedBox(height: 20), - Text( - stopWatchController.formatDuration(stopWatchController.elapsedTime.value), - style: TextStyle( - fontFamily: 'IRANSansX', - fontSize: 48, - fontWeight: FontWeight.bold, - color: AppColors.timer, + Padding( + padding: const EdgeInsets.all(32), + child: Row( + children: [ + Expanded(flex: 3, child: ConnectionControlsPanel()), + const SizedBox(width: 32), + Expanded(flex: 2, child: StatusCardsPanel()), + ], ), ), - const SizedBox(height: 40), - ConnectButton(), - const SizedBox(height: 40), - const DNSStatus(), - ], - ); - } - - Widget _buildStatusCards(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - DesktopStatusCard( - icon: Assets.svg.flush, - label: 'Flush DNS', - status: netshiftEngineController.isFlushing.value - ? "Flushed Successfully" - : "Click to Flush", - color: AppColors.download, - iconColor: const Color(0xFF38AC72), - onTap: _handleFlushDns, - ), - const SizedBox(height: 16), - DesktopStatusCard( - icon: Assets.svg.global, - label: 'IP Address', - status: netshiftEngineController.isIpAddress.value - ? "Getting IP..." - : netshiftEngineController.ipAddressString.value, - color: AppColors.ip, - iconColor: const Color(0xFF428BC1), - onTap: () => netshiftEngineController.getIpAddress(), - ), - const SizedBox(height: 16), - DesktopStatusCard( - icon: Assets.svg.interface, - label: 'Network Interface', - status: netshiftEngineController.interfaceName.value, - color: AppColors.upload, - iconColor: const Color(0xFFD2222D), - onTap: () => InterfaceSelectionDialog.show(context), - ), ], ); } - Widget _buildMobileBody(BuildContext context) { + Widget _buildMobileBody() { return Stack( children: [ const WorldMap(), @@ -160,7 +65,9 @@ class HomePage extends StatelessWidget { else const SizedBox(), Text( - stopWatchController.formatDuration(stopWatchController.elapsedTime.value), + stopWatchController.formatDuration( + stopWatchController.elapsedTime.value, + ), style: TextStyle( fontFamily: 'IRANSansX', fontSize: 32, @@ -170,7 +77,7 @@ class HomePage extends StatelessWidget { ), ConnectButton(), const DNSStatus(), - _buildMobileStatusIcons(context), + MobileStatusIcons(), ], ), ), @@ -178,87 +85,4 @@ class HomePage extends StatelessWidget { ], ); } - - Widget _buildMobileStatusIcons(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Platform.isAndroid - ? StatusIcon( - path: Assets.svg.download, - label: 'Download', - status: foregroundController.download.value, - color: AppColors.download, - iconColor: const Color(0xFF38AC72), - onTap: () {}, - ) - : StatusIcon( - path: Assets.svg.flush, - label: 'Flush DNS', - status: netshiftEngineController.isFlushing.value - ? "Flushed Succesfully" - : "Tap To Flush", - color: AppColors.download, - onTap: _handleFlushDns, - iconColor: const Color(0xFF38AC72), - ), - StatusIcon( - path: Assets.svg.global, - label: 'Address', - status: netshiftEngineController.isIpAddress.value - ? "IP: Getting IP..." - : "IP: ${netshiftEngineController.ipAddressString}", - color: AppColors.ip, - onTap: () => netshiftEngineController.getIpAddress(), - iconColor: const Color(0xFF428BC1), - ), - Platform.isAndroid - ? StatusIcon( - path: Assets.svg.upload, - label: 'Upload', - status: foregroundController.upload.value, - color: AppColors.upload, - iconColor: const Color(0xFFD2222D), - onTap: () {}, - ) - : StatusIcon( - path: Assets.svg.interface, - label: 'Interface', - status: "${netshiftEngineController.interfaceName.value} ⏷", - color: AppColors.upload, - onTap: () => InterfaceSelectionDialog.show(context), - iconColor: const Color(0xFFD2222D), - ), - ], - ); - } - - void _handleFlushDns() { - if (netshiftEngineController.isActive.value) { - CustomSnackBar( - title: "Operation Failed", - message: "Failed to FLUSH DNS, please stop the service first", - backColor: Colors.red.shade700.withValues(alpha: 0.9), - iconColor: Colors.white, - icon: Icons.error_outline, - textColor: Colors.white, - ).customSnackBar(); - return; - } - - if (Platform.isWindows) { - netshiftEngineController.flushDnsForWindows(); - } else if (Platform.isMacOS) { - netshiftEngineController.flushDnsForMacOS(); - } - - CustomSnackBar( - title: "Operation Success", - message: "FLUSHED DNS Successfully", - backColor: const Color.fromARGB(80, 105, 240, 175), - iconColor: Colors.greenAccent, - icon: Icons.check_circle_outline_outlined, - textColor: Colors.white, - ).customSnackBar(); - } } diff --git a/lib/screens/settings_page.dart b/lib/screens/settings_page.dart index b2cda9a..c0b473c 100644 --- a/lib/screens/settings_page.dart +++ b/lib/screens/settings_page.dart @@ -9,11 +9,13 @@ import 'package:netshift/controller/netshift_engine_controller.dart'; import 'package:netshift/controller/theme_controller.dart'; import 'package:netshift/core/resources/app_colors.dart'; import 'package:netshift/screens/blocked_apps_screen.dart'; -import 'package:netshift/core/widgets/app_bar.dart'; -import 'package:netshift/core/widgets/custom_snack_bar.dart'; -import 'package:netshift/core/widgets/other_apps_widgets.dart'; -import 'package:netshift/core/widgets/settings_custom_card.dart'; -import 'package:netshift/core/widgets/support_content.dart'; +import 'package:netshift/core/widgets/common/app_bar.dart'; +import 'package:netshift/core/widgets/common/custom_snack_bar.dart'; +import 'package:netshift/core/widgets/desktop/desktop_settings_card.dart'; +import 'package:netshift/core/widgets/settings/other_apps_widgets.dart'; +import 'package:netshift/core/widgets/settings/settings_custom_card.dart'; +import 'package:netshift/core/widgets/settings/settings_section.dart'; +import 'package:netshift/core/widgets/settings/support_content.dart'; class SettingsPage extends StatelessWidget { SettingsPage({super.key}); @@ -23,8 +25,8 @@ class SettingsPage extends StatelessWidget { @override Widget build(BuildContext context) { - final isDesktop = PlatformService.isDesktop && - MediaQuery.of(context).size.width >= 800; + final isDesktop = + PlatformService.isDesktop && MediaQuery.of(context).size.width >= 800; return Scaffold( appBar: isDesktop @@ -48,7 +50,6 @@ class SettingsPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Header Text( "Settings", style: TextStyle( @@ -68,16 +69,14 @@ class SettingsPage extends StatelessWidget { ), ), const SizedBox(height: 32), - // Settings Grid Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Left Column - General Settings Expanded( - child: _buildSettingsSection( + child: SettingsSection( title: "General", children: [ - _buildDesktopSettingsCard( + DesktopSettingsCard( icon: themeController.isDarkMode ? Icons.dark_mode : Icons.light_mode, @@ -95,7 +94,7 @@ class SettingsPage extends StatelessWidget { ), ), const SizedBox(height: 16), - _buildDesktopSettingsCard( + DesktopSettingsCard( icon: Icons.apps_rounded, title: "Other Apps", subtitle: "Explore our other applications", @@ -105,25 +104,24 @@ class SettingsPage extends StatelessWidget { ), ), const SizedBox(width: 32), - // Right Column - About Expanded( - child: _buildSettingsSection( + child: SettingsSection( title: "About", children: [ - _buildDesktopSettingsCard( + DesktopSettingsCard( icon: Icons.info_outline, title: "App Version", subtitle: _getVersionString(), ), const SizedBox(height: 16), - _buildDesktopSettingsCard( + DesktopSettingsCard( icon: Icons.contact_support_outlined, title: "Support", subtitle: "Get help and contact us", onTap: () => _showSupport(context), ), const SizedBox(height: 16), - _buildDesktopSettingsCard( + DesktopSettingsCard( icon: Icons.code, title: "Platform", subtitle: _getPlatformString(), @@ -139,142 +137,6 @@ class SettingsPage extends StatelessWidget { ); } - Widget _buildSettingsSection({ - required String title, - required List children, - }) { - return Container( - padding: const EdgeInsets.all(24), - decoration: BoxDecoration( - color: AppColors.isDarkMode - ? const Color(0xFF1E2025) - : Colors.white.withValues(alpha: 0.9), - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: AppColors.isDarkMode - ? Colors.black.withValues(alpha: 0.2) - : Colors.indigo.withValues(alpha: 0.1), - blurRadius: 12, - offset: const Offset(0, 4), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - width: 4, - height: 20, - decoration: BoxDecoration( - color: - AppColors.isDarkMode ? Colors.greenAccent : Colors.indigo, - borderRadius: BorderRadius.circular(2), - ), - ), - const SizedBox(width: 12), - Text( - title, - style: TextStyle( - fontFamily: 'Poppins', - fontSize: 18, - fontWeight: FontWeight.bold, - color: AppColors.settingsGeneralCategory, - ), - ), - ], - ), - const SizedBox(height: 24), - ...children, - ], - ), - ); - } - - Widget _buildDesktopSettingsCard({ - required IconData icon, - required String title, - required String subtitle, - Widget? trailing, - VoidCallback? onTap, - }) { - return MouseRegion( - cursor: - onTap != null ? SystemMouseCursors.click : SystemMouseCursors.basic, - child: GestureDetector( - onTap: onTap, - child: Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColors.isDarkMode - ? const Color(0xFF262626) - : const Color(0xFFF5F5F5), - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: AppColors.isDarkMode - ? Colors.white.withValues(alpha: 0.05) - : Colors.indigo.withValues(alpha: 0.1), - ), - ), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: AppColors.isDarkMode - ? Colors.greenAccent.withValues(alpha: 0.1) - : Colors.indigo.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Icon( - icon, - color: AppColors.settingsIconColors, - size: 22, - ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: TextStyle( - fontFamily: 'Poppins', - fontSize: 14, - fontWeight: FontWeight.w600, - color: AppColors.settingsCustomCardTitle, - ), - ), - const SizedBox(height: 2), - Text( - subtitle, - style: TextStyle( - fontFamily: 'Poppins', - fontSize: 12, - color: AppColors.settingsCustomCardSubTitle, - ), - ), - ], - ), - ), - if (trailing != null) trailing, - if (onTap != null && trailing == null) - Icon( - Icons.chevron_right, - color: AppColors.isDarkMode - ? Colors.greenAccent.withValues(alpha: 0.5) - : Colors.indigo.withValues(alpha: 0.5), - ), - ], - ), - ), - ), - ); - } - Widget _buildMobileBody(BuildContext context) { return SingleChildScrollView( child: Padding( @@ -331,8 +193,7 @@ class SettingsPage extends StatelessWidget { title: "Operation Failed", message: "Failed to OPEN, please stop the service first", - backColor: - Colors.red.shade700.withValues(alpha: 0.9), + backColor: Colors.red.shade700.withValues(alpha: 0.9), iconColor: Colors.white, icon: Icons.error_outline, textColor: Colors.white, @@ -421,15 +282,10 @@ class SettingsPage extends StatelessWidget { return Container( width: double.infinity, height: ScreenSize.height * 0.2, - padding: const EdgeInsets.symmetric( - horizontal: 12.0, - vertical: 15.0, - ), + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 15.0), decoration: BoxDecoration( color: AppColors.settingsCustomCardBackground, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(24), - ), + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), ), child: const OtherAppsWidget(), ); @@ -451,15 +307,10 @@ class SettingsPage extends StatelessWidget { return Container( width: double.infinity, height: ScreenSize.height * 0.2, - padding: const EdgeInsets.symmetric( - horizontal: 12.0, - vertical: 15.0, - ), + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 15.0), decoration: BoxDecoration( color: AppColors.settingsCustomCardBackground, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(24), - ), + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), ), child: const SupportContent(), ); diff --git a/lib/screens/splash_screen.dart b/lib/screens/splash_screen.dart index a6d7617..f8bd4d7 100644 --- a/lib/screens/splash_screen.dart +++ b/lib/screens/splash_screen.dart @@ -10,9 +10,9 @@ import 'package:netshift/controller/splash_controller.dart'; import 'package:netshift/core/resources/app_colors.dart'; import 'package:netshift/core/services/windows_local_notif.dart'; import 'package:netshift/core/services/windows_title_bar_box.dart'; -import 'package:netshift/core/widgets/check_for_update_widget.dart'; -import 'package:netshift/core/widgets/double_tap_to_exit.dart'; -import 'package:netshift/core/widgets/flutter_toast.dart'; +import 'package:netshift/core/widgets/settings/check_for_update_widget.dart'; +import 'package:netshift/core/widgets/common/double_tap_to_exit.dart'; +import 'package:netshift/core/widgets/common/flutter_toast.dart'; class SplashScreen extends StatelessWidget { SplashScreen({super.key}); @@ -306,8 +306,9 @@ class SplashScreen extends StatelessWidget { color: Color.fromARGB(255, 255, 235, 58), width: 2, ), - backgroundColor: - Colors.black.withValues(alpha: 0.3), + backgroundColor: Colors.black.withValues( + alpha: 0.3, + ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), @@ -315,14 +316,13 @@ class SplashScreen extends StatelessWidget { onPressed: () { if (Platform.isAndroid) { FlutterToast( - message: - "Starting in Offline Mode") - .flutterToast(); + message: "Starting in Offline Mode", + ).flutterToast(); } else if (Platform.isWindows) { WindowsLocalNotif( - body: "Starting in Offline Mode", - title: "NetShift Service") - .showNotification(); + body: "Starting in Offline Mode", + title: "NetShift Service", + ).showNotification(); } splashController.online.remove('online'); splashController.getDnsListOffline(); @@ -336,7 +336,7 @@ class SplashScreen extends StatelessWidget { ), ), ], - ) + ), ], ), ),