diff --git a/lib/helper/dark_theme_preference.dart b/lib/helper/dark_theme_preference.dart index 99f8ddce..fd894fe4 100644 --- a/lib/helper/dark_theme_preference.dart +++ b/lib/helper/dark_theme_preference.dart @@ -1,15 +1,19 @@ import 'package:shared_preferences/shared_preferences.dart'; +import 'package:vernet/models/dark_theme_provider.dart'; class DarkThemePreference { - static const themeStatus = 'THEMESTATUS'; + static const themeStatus = 'THEMESTATUS_NEW'; - Future setDarkTheme(bool value) async { + Future setDarkTheme(ThemePreference value) async { final SharedPreferences prefs = await SharedPreferences.getInstance(); - prefs.setBool(themeStatus, value); + prefs.setString(themeStatus, value.name); } - Future getTheme() async { + Future getTheme() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); - return prefs.getBool(themeStatus) ?? false; + return ThemePreference.values.firstWhere( + (element) => element.name == prefs.getString(themeStatus), + orElse: () => ThemePreference.light, + ); } } diff --git a/lib/main.dart b/lib/main.dart index 91eacbc4..970451dd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -42,7 +42,7 @@ class _MyAppState extends State { } Future getCurrentAppTheme() async { - themeChangeProvider.darkTheme = + themeChangeProvider.themePref = await themeChangeProvider.darkThemePreference.getTheme(); } diff --git a/lib/models/dark_theme_provider.dart b/lib/models/dark_theme_provider.dart index 1c17bd2c..339dff16 100644 --- a/lib/models/dark_theme_provider.dart +++ b/lib/models/dark_theme_provider.dart @@ -1,15 +1,26 @@ import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:vernet/helper/dark_theme_preference.dart'; class DarkThemeProvider with ChangeNotifier { DarkThemePreference darkThemePreference = DarkThemePreference(); - bool _darkTheme = false; + ThemePreference _darkTheme = ThemePreference.system; - bool get darkTheme => _darkTheme; + ThemePreference get themePref => _darkTheme; - set darkTheme(bool value) { + set themePref(ThemePreference value) { _darkTheme = value; darkThemePreference.setDarkTheme(value); notifyListeners(); } + + bool get darkTheme { + if (themePref == ThemePreference.system) { + return SchedulerBinding.instance.platformDispatcher.platformBrightness == + Brightness.dark; + } + return ThemePreference.dark == themePref; + } } + +enum ThemePreference { system, dark, light } diff --git a/lib/pages/network_troubleshoot/port_scan_page.dart b/lib/pages/network_troubleshoot/port_scan_page.dart index c0a7ef2e..e567f11d 100644 --- a/lib/pages/network_troubleshoot/port_scan_page.dart +++ b/lib/pages/network_troubleshoot/port_scan_page.dart @@ -23,7 +23,7 @@ enum ScanType { single, top, range } class _PortScanPageState extends State with SingleTickerProviderStateMixin { final Set _openPorts = {}; - final Map _allPorts = {}; + double _progress = 0; final TextEditingController _targetIPEditingController = @@ -91,7 +91,6 @@ class _PortScanPageState extends State progressCallback: _handleProgress, ).listen(_handleEvent, onDone: _handleOnDone); } else { - //TODO: uncomment _streamSubscription = PortScannerFlutter.scanPortsForSingleDevice( _targetIPEditingController.text, startPort: int.parse(_startPortEditingController.text), @@ -107,11 +106,6 @@ class _PortScanPageState extends State super.initState(); _tabController = TabController(length: _tabs.length, vsync: this); _targetIPEditingController.text = widget.target; - PortDescLoader('assets/ports_lists.json').load().then((value) { - setState(() { - _allPorts.addAll(value); - }); - }); } ScanType? _type = ScanType.top; @@ -235,242 +229,273 @@ class _PortScanPageState extends State ), ], ), - body: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Card( - child: Container( - padding: const EdgeInsets.all(5.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Form( - key: _formKey, - child: Row( + body: FutureBuilder>( + future: PortDescLoader('assets/ports_lists.json').load(), + builder: ( + BuildContext context, + AsyncSnapshot> snapshot, + ) { + if (snapshot.hasData) { + final Map allPorts = + snapshot.data ?? {}; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Card( + child: Container( + padding: const EdgeInsets.all(5.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - child: TextFormField( - validator: validateIP, - controller: _targetIPEditingController, - decoration: const InputDecoration( - filled: true, - hintText: 'Enter a domain or IP', - ), - ), - ), - const SizedBox(width: 3), - if (_type != ScanType.top) - Expanded(child: _getFields()) - else - const SizedBox(), - ], - ), - ), - Row( - children: [ - Expanded( - child: CustomTile( - leading: Radio( - value: ScanType.top, - groupValue: _type, - onChanged: (ScanType? value) { - _tabController.index = 0; - setState(() { - _type = value; - }); - }, - ), - child: const Text('Top'), - ), - ), - Expanded( - child: CustomTile( - leading: Radio( - value: ScanType.range, - groupValue: _type, - onChanged: (ScanType? value) { - _tabController.index = 1; - setState(() { - _type = value; - }); - }, - ), - child: const Text( - 'Range', - overflow: TextOverflow.ellipsis, - ), - ), - ), - Expanded( - child: CustomTile( - leading: Radio( - value: ScanType.single, - groupValue: _type, - onChanged: (ScanType? value) { - _tabController.index = 2; - setState(() { - _type = value; - }); - }, + Form( + key: _formKey, + child: Row( + children: [ + Expanded( + child: TextFormField( + validator: validateIP, + controller: _targetIPEditingController, + decoration: const InputDecoration( + filled: true, + hintText: 'Enter a domain or IP', + ), + ), + ), + const SizedBox(width: 3), + if (_type != ScanType.top) + Expanded(child: _getFields()) + else + const SizedBox(), + ], ), - child: const Text('Single'), - ), - ), - Padding( - padding: const EdgeInsets.all(3.0), - child: ElevatedButton( - onPressed: _completed - ? () { - if (_formKey.currentState!.validate()) { - _startScanning(); - } - } - : null, - child: Text(_completed ? 'Scan' : 'Scanning'), ), - ), - ], - ), - ], - ), - ), - ), - Expanded( - child: Card( - child: Container( - padding: const EdgeInsets.all(5.0), - child: DefaultTabController( - length: _tabs.length, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - TabBar( - controller: _tabController, - tabs: _tabs, - labelColor: Theme.of(context).colorScheme.secondary, - ), - Flexible( - child: TabBarView( - controller: _tabController, + Row( children: [ - Wrap( - children: [ - _getDomainChip('192.168.1.1'), - _getDomainChip('google.com'), - _getDomainChip('youtube.com'), - _getDomainChip('apple.com'), - _getDomainChip('amazon.com'), - _getDomainChip('cloudflare.com'), - ], - ), - Wrap( - children: [ - _getCustomRangeChip( - '0-1024 (known)', - '0', - '1024', + Expanded( + child: CustomTile( + leading: Radio( + value: ScanType.top, + groupValue: _type, + onChanged: (ScanType? value) { + _tabController.index = 0; + setState(() { + _type = value; + }); + }, ), - _getCustomRangeChip( - '0-100 (short)', - '0', - '100', + child: const Text('Top'), + ), + ), + Expanded( + child: CustomTile( + leading: Radio( + value: ScanType.range, + groupValue: _type, + onChanged: (ScanType? value) { + _tabController.index = 1; + setState(() { + _type = value; + }); + }, ), - _getCustomRangeChip( - '0-10 (very short)', - '0', - '10', + child: const Text( + 'Range', + overflow: TextOverflow.ellipsis, ), - _getCustomRangeChip( - '0-65535 (Full)', - '0', - '65535', + ), + ), + Expanded( + child: CustomTile( + leading: Radio( + value: ScanType.single, + groupValue: _type, + onChanged: (ScanType? value) { + _tabController.index = 2; + setState(() { + _type = value; + }); + }, ), - ], + child: const Text('Single'), + ), ), - Wrap( - children: [ - _getSinglePortChip('20 (FTP Data)', '20'), - _getSinglePortChip('21 (FTP Control)', '21'), - _getSinglePortChip('22 (SSH)', '22'), - _getSinglePortChip('80 (HTTP)', '80'), - _getSinglePortChip('443 (HTTPS)', '443'), - ], + Padding( + padding: const EdgeInsets.all(3.0), + child: ElevatedButton( + onPressed: _completed + ? () { + if (_formKey.currentState!.validate()) { + _startScanning(); + } + } + : null, + child: Text(_completed ? 'Scan' : 'Scanning'), + ), ), ], ), - ), - ], + ], + ), ), ), - ), - ), - ), - Expanded( - flex: 2, - child: _openPorts.isEmpty - ? const Center( - child: Text( - 'No open ports found yet.\nOpen ports will appear here.', - textAlign: TextAlign.center, - ), - ) - : ListView.builder( - itemCount: _openPorts.length, - itemBuilder: (context, index) { - final OpenPort openPort = _openPorts.toList()[index]; - return Column( - children: [ - ListTile( - dense: true, - contentPadding: - const EdgeInsets.only(left: 10.0, right: 10.0), - leading: Text( - '${index + 1}', - style: Theme.of(context).textTheme.titleMedium, + Expanded( + child: Card( + child: Container( + padding: const EdgeInsets.all(5.0), + child: DefaultTabController( + length: _tabs.length, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TabBar( + controller: _tabController, + tabs: _tabs, + labelColor: + Theme.of(context).colorScheme.secondary, ), - trailing: Text( - '${openPort.port}', - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith( - color: - Theme.of(context).colorScheme.secondary, + Flexible( + child: TabBarView( + controller: _tabController, + children: [ + Wrap( + children: [ + _getDomainChip('192.168.1.1'), + _getDomainChip('google.com'), + _getDomainChip('youtube.com'), + _getDomainChip('apple.com'), + _getDomainChip('amazon.com'), + _getDomainChip('cloudflare.com'), + ], ), - ), - title: _allPorts.isEmpty - ? const SizedBox() - : Text( - _allPorts[openPort.port.toString()]!.desc, + Wrap( + children: [ + _getCustomRangeChip( + '0-1024 (known)', + '0', + '1024', + ), + _getCustomRangeChip( + '0-100 (short)', + '0', + '100', + ), + _getCustomRangeChip( + '0-10 (very short)', + '0', + '10', + ), + _getCustomRangeChip( + '0-65535 (Full)', + '0', + '65535', + ), + ], ), - subtitle: _allPorts.isEmpty - ? const SizedBox() - : Row( + Wrap( children: [ - if (_allPorts[openPort.port.toString()]! - .isTCP) - const Text('TCP ') - else - const SizedBox(), - if (_allPorts[openPort.port.toString()]! - .isUDP) - const Text('UDP ') - else - const SizedBox(), - Text( - _allPorts[openPort.port.toString()]! - .status, + _getSinglePortChip('20 (FTP Data)', '20'), + _getSinglePortChip( + '21 (FTP Control)', + '21', ), + _getSinglePortChip('22 (SSH)', '22'), + _getSinglePortChip('80 (HTTP)', '80'), + _getSinglePortChip('443 (HTTPS)', '443'), ], ), - ), - const Divider(height: 4), - ], - ); - }, + ], + ), + ), + ], + ), + ), + ), ), - ), - ], + ), + Expanded( + flex: 2, + child: _openPorts.isEmpty + ? const Center( + child: Text( + 'No open ports found yet.\nOpen ports will appear here.', + textAlign: TextAlign.center, + ), + ) + : ListView.builder( + itemCount: _openPorts.length, + itemBuilder: (context, index) { + final OpenPort openPort = + _openPorts.toList()[index]; + final port = allPorts[openPort.port.toString()]; + + return Column( + children: [ + ListTile( + dense: true, + contentPadding: const EdgeInsets.only( + left: 10.0, + right: 10.0, + ), + leading: Text( + '${index + 1}', + style: + Theme.of(context).textTheme.titleMedium, + ), + trailing: Text( + '${openPort.port}', + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith( + color: Theme.of(context) + .colorScheme + .secondary, + ), + ), + title: port == null + ? const SizedBox() + : Text( + port.desc, + ), + subtitle: port == null + ? const SizedBox() + : Row( + children: [ + if (port.isTCP) + const Text('TCP ') + else + const SizedBox(), + if (port.isUDP) + const Text('UDP ') + else + const SizedBox(), + Text( + port.status, + ), + ], + ), + ), + const Divider(height: 4), + ], + ); + }, + ), + ), + ], + ); + } else if (snapshot.hasError) { + return const Center( + child: Text( + 'There is an error while loading..\nPlease try again after sometime.', + textAlign: TextAlign.center, + ), + ); + } else { + return const Center( + child: CircularProgressIndicator(), + ); + } + }, ), ); } diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart index d7242208..0668eee7 100644 --- a/lib/pages/settings_page.dart +++ b/lib/pages/settings_page.dart @@ -9,6 +9,7 @@ import 'package:vernet/ui/settings_dialog/first_subnet_dialog.dart'; import 'package:vernet/ui/settings_dialog/last_subnet_dialog.dart'; import 'package:vernet/ui/settings_dialog/ping_count_dialog.dart'; import 'package:vernet/ui/settings_dialog/socket_timeout_dialog.dart'; +import 'package:vernet/ui/settings_dialog/theme_dialog.dart'; import 'package:vernet/values/strings.dart'; class SettingsPage extends StatefulWidget { @@ -27,13 +28,16 @@ class _SettingsPageState extends State { children: [ Card( child: ListTile( - title: const Text('Dark Theme'), - trailing: Switch( - value: themeChange.darkTheme, - onChanged: (bool? value) { - themeChange.darkTheme = value ?? false; - }, - ), + title: const Text('Theme'), + subtitle: Text(themeChange.themePref.name), + onTap: () async { + await showDialog( + context: context, + builder: (context) => const ThemeDialog(), + ); + await appSettings.load(); + setState(() {}); + }, ), ), Card( diff --git a/lib/ui/settings_dialog/theme_dialog.dart b/lib/ui/settings_dialog/theme_dialog.dart new file mode 100644 index 00000000..7b19b7b0 --- /dev/null +++ b/lib/ui/settings_dialog/theme_dialog.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:vernet/models/dark_theme_provider.dart'; + +class ThemeDialog extends StatefulWidget { + const ThemeDialog({super.key}); + + @override + State createState() => _ThemeDialogState(); +} + +class _ThemeDialogState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + final themeChange = Provider.of(context); + return AlertDialog( + title: const Text("Choose theme"), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: const Text('Follow system'), + leading: Radio( + value: ThemePreference.system, + groupValue: themeChange.themePref, + onChanged: (value) { + themeChange.themePref = ThemePreference.system; + }, + ), + ), + ListTile( + title: const Text('Dark'), + leading: Radio( + value: ThemePreference.dark, + groupValue: themeChange.themePref, + onChanged: (value) { + themeChange.themePref = ThemePreference.dark; + }, + ), + ), + ListTile( + title: const Text('Light'), + leading: Radio( + value: ThemePreference.light, + groupValue: themeChange.themePref, + onChanged: (value) { + themeChange.themePref = ThemePreference.light; + }, + ), + ), + ], + ), + ); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 8d91c82a..3ef206f5 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -13,7 +13,7 @@ import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { NetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "NetworkInfoPlusPlugin")) - FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 54066a3e..08980a31 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -39,8 +39,8 @@ SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 network_info_plus: f4fbc7877ab7b3294500d9441dfa53cd54972d05 package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce - path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 - shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 + path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c + shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 PODFILE CHECKSUM: 4d1ddd58dcd1dc92dd2b397bbacb622f345603ab