diff --git a/.github/workflows/flutter_release.yml b/.github/workflows/flutter_release.yml
index 34707e41..0d56c30b 100644
--- a/.github/workflows/flutter_release.yml
+++ b/.github/workflows/flutter_release.yml
@@ -16,9 +16,9 @@ jobs:
runs-on: ubuntu-latest
env:
LINUX_ZIP: Vernet-${{github.ref_name}}-linux.zip
- ANDROID_APK_ARM_V7A: app-armeabi-v7a-dev-debug.apk
- ANDROID_APK_ARM_V8A: app-arm64-v8a-dev-debug.apk
- ANDROID_APK_x86_64: app-x86_64-dev-debug.apk
+ ANDROID_APK_ARM_V7A: app-armeabi-v7a-dev-release.apk
+ ANDROID_APK_ARM_V8A: app-arm64-v8a-dev-release.apk
+ ANDROID_APK_x86_64: app-x86_64-dev-release.apk
steps:
- name: Checkout
@@ -50,15 +50,11 @@ jobs:
encodedString: '${{ secrets.ANDROID_KEYSTORE_BASE64 }}'
- name: Create key.properties
- run: >
- echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" >
- android/key.properties
- echo "storePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >>
- android/key.properties
- echo "keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}" >>
- android/key.properties
- echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" >>
- android/key.properties
+ run: |
+ echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" >> android/key.properties
+ echo "storePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >> android/key.properties
+ echo "keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}" >> android/key.properties
+ echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" >> android/key.properties
- name: Create artifacts directory
run: mkdir -p artifacts
@@ -69,7 +65,7 @@ jobs:
- name: Build Android App and Linux Bundle
# Use signing keys for release instead of debug
run: |
- flutter build apk --debug --split-per-abi --flavor dev
+ flutter build apk --split-per-abi --flavor dev
flutter build linux --release
- name: Rename ANDROID APKs
diff --git a/README.md b/README.md
index 32bdc5fd..9aaf43f6 100644
--- a/README.md
+++ b/README.md
@@ -42,9 +42,10 @@ Note: macOS build hasn't been notarized yet.
### Instructions for Linux
1. Star this repository.
-2. Download vernet-linux.zip from [releases](https://github.com/git-elliot/vernet/releases/latest)
-3. Extract downloaded zip file.
-4. Go to bundle folder and double click vernet file.
+2. Install `net-tools` package for `arp` command, otherwise app will not run.
+3. Download vernet-linux.zip from [releases](https://github.com/git-elliot/vernet/releases/latest)
+4. Extract downloaded zip file.
+5. Go to bundle folder and double click vernet file.
## Contributors Required
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 6f8019ea..1e42c596 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -31,7 +31,7 @@ if (keystorePropertiesFile.exists()) {
}
android {
- compileSdkVersion 33
+ compileSdkVersion 34
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
@@ -41,7 +41,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "org.fsociety.vernet"
minSdkVersion 19
- targetSdkVersion 33
+ targetSdkVersion 34
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
@@ -67,7 +67,7 @@ android {
productFlavors {
dev {
dimension "deploy"
- signingConfig signingConfigs.debug
+ signingConfig signingConfigs.release
}
fdroid {
dimension "deploy"
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index ea67ebbf..24bf09b8 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -10,6 +10,7 @@
+
+
+
+
diff --git a/fastlane/metadata/android/en-US/changelogs/18.txt b/fastlane/metadata/android/en-US/changelogs/18.txt
index 4b0202d7..db77bb61 100644
--- a/fastlane/metadata/android/en-US/changelogs/18.txt
+++ b/fastlane/metadata/android/en-US/changelogs/18.txt
@@ -1 +1 @@
-Follow system theme added and bug fixes
\ No newline at end of file
+Follow system theme added and bug fixes
diff --git a/fastlane/metadata/android/en-US/changelogs/19.txt b/fastlane/metadata/android/en-US/changelogs/19.txt
new file mode 100644
index 00000000..bcc900b1
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/19.txt
@@ -0,0 +1 @@
+Many improvements and bug fixes
\ No newline at end of file
diff --git a/lib/api/update_checker.dart b/lib/api/update_checker.dart
index ce4d4fa5..b963a2e2 100644
--- a/lib/api/update_checker.dart
+++ b/lib/api/update_checker.dart
@@ -1,12 +1,15 @@
import 'dart:convert';
import 'dart:io';
+import 'package:external_app_launcher/external_app_launcher.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:package_info_plus/package_info_plus.dart';
import 'package:vernet/helper/utils_helper.dart';
+import 'package:vernet/main.dart';
+
Future _checkUpdates(String v) async {
final Uri url = Uri.parse(
'https://api.github.com/repos/git-elliot/vernet/tags?per_page=1',
@@ -37,7 +40,10 @@ Future checkForUpdates(
try {
final info = await PackageInfo.fromPlatform();
final String v = '${info.version}+${info.buildNumber}';
- final bool available = await compute(_checkUpdates, v);
+ bool available = false;
+ if (appSettings.inAppInternet) {
+ available = await compute(_checkUpdates, v);
+ }
ScaffoldMessenger.of(context).clearSnackBars();
Widget? content;
SnackBarAction? action;
@@ -46,12 +52,16 @@ Future checkForUpdates(
action = SnackBarAction(
label: 'Update',
onPressed: () {
- _navigateToStore();
+ _navigateToStore(context);
},
);
} else {
if (showIfNoUpdate) {
content = const Text('No updates found');
+ if (!appSettings.inAppInternet) {
+ content =
+ const Text('Please turn on In-App Internet to check updates.');
+ }
}
}
if (ScaffoldMessenger.of(context).mounted && content != null) {
@@ -67,14 +77,28 @@ Future checkForUpdates(
}
}
-Future _navigateToStore() async {
+Future _navigateToStore(BuildContext context) async {
String url = 'https://github.com/git-elliot/vernet/releases/latest';
+
if (Platform.isAndroid) {
+ final isFdroidInstalled = await LaunchApp.isAppInstalled(
+ androidPackageName: 'org.fdroid.fdroid',
+ iosUrlScheme: 'fdroid://',
+ );
+
if ((await PackageInfo.fromPlatform()).version.contains('store')) {
//Goto playstore
url =
'https://play.google.com/store/apps/details?id=org.fsociety.vernet.store';
+ } else if (isFdroidInstalled == true) {
+ await LaunchApp.openApp(
+ androidPackageName: 'org.fdroid.fdroid',
+ iosUrlScheme: 'fdroid://',
+ appStoreLink: 'itms-apps://itunes.apple.com/',
+ openStore: false,
+ );
+ return;
}
}
- launchURL(url);
+ launchURLWithWarning(context, url);
}
diff --git a/lib/helper/app_settings.dart b/lib/helper/app_settings.dart
index eb364a0a..caa717e8 100644
--- a/lib/helper/app_settings.dart
+++ b/lib/helper/app_settings.dart
@@ -8,10 +8,14 @@ class AppSettings {
static const String _firstSubnetKey = 'AppSettings-FIRST_SUBNET';
static const String _socketTimeoutKey = 'AppSettings-SOCKET_TIMEOUT';
static const String _pingCountKey = 'AppSettings-PING_COUNT';
+ static const String _inAppInternetKey = 'AppSettings-IN-APP-INTERNET';
+ static const String _customSubnetKey = 'AppSettings-CUSTOM-SUBNET';
int _firstSubnet = 1;
int _lastSubnet = 254;
int _socketTimeout = 500;
int _pingCount = 5;
+ bool _inAppInternet = false;
+ String _customSubnet = '';
static final AppSettings _instance = AppSettings._();
@@ -20,6 +24,11 @@ class AppSettings {
int get lastSubnet => _lastSubnet;
int get socketTimeout => _socketTimeout;
int get pingCount => _pingCount;
+ bool get inAppInternet => _inAppInternet;
+ String get customSubnet => _customSubnet;
+ String get gatewayIP => _customSubnet.isNotEmpty
+ ? _customSubnet.substring(0, _customSubnet.lastIndexOf('.'))
+ : _customSubnet;
Future setFirstSubnet(int firstSubnet) async {
_firstSubnet = firstSubnet;
@@ -45,12 +54,25 @@ class AppSettings {
.setInt(_pingCountKey, _pingCount);
}
+ Future setInAppInternet(bool inAppInternet) async {
+ _inAppInternet = inAppInternet;
+ return (await SharedPreferences.getInstance())
+ .setBool(_inAppInternetKey, _inAppInternet);
+ }
+
+ Future setCustomSubnet(String customSubnet) async {
+ _customSubnet = customSubnet;
+ return (await SharedPreferences.getInstance())
+ .setString(_customSubnetKey, _customSubnet);
+ }
+
Future load() async {
debugPrint("Fetching all app settings");
_firstSubnet =
(await SharedPreferences.getInstance()).getInt(_firstSubnetKey) ??
_firstSubnet;
debugPrint("First subnet : $_firstSubnet");
+
_lastSubnet =
(await SharedPreferences.getInstance()).getInt(_lastSubnetKey) ??
_lastSubnet;
@@ -60,10 +82,20 @@ class AppSettings {
(await SharedPreferences.getInstance()).getInt(_socketTimeoutKey) ??
_socketTimeout;
debugPrint("Socket timeout : $_socketTimeout");
+
_pingCount =
(await SharedPreferences.getInstance()).getInt(_pingCountKey) ??
_pingCount;
+ debugPrint("Ping count : $_pingCount");
+
+ _inAppInternet =
+ (await SharedPreferences.getInstance()).getBool(_inAppInternetKey) ??
+ _inAppInternet;
+ debugPrint("In-App Internet : $_inAppInternet");
- debugPrint("Ping count : $_socketTimeout");
+ _customSubnet =
+ (await SharedPreferences.getInstance()).getString(_customSubnetKey) ??
+ _customSubnet;
+ debugPrint("Custom Subnet : $_customSubnet");
}
}
diff --git a/lib/helper/utils_helper.dart b/lib/helper/utils_helper.dart
index 262cf8c9..b6500cd2 100644
--- a/lib/helper/utils_helper.dart
+++ b/lib/helper/utils_helper.dart
@@ -1,5 +1,16 @@
+import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher_string.dart';
+import 'package:vernet/ui/external_link_dialog.dart';
Future launchURL(String url) async => await canLaunchUrlString(url)
? await launchUrlString(url)
: throw 'Could not launch $url';
+
+Future launchURLWithWarning(BuildContext context, String url) {
+ return showDialog(
+ context: context,
+ builder: (context) => ExternalLinkWarningDialog(
+ link: url,
+ ),
+ );
+}
diff --git a/lib/main.dart b/lib/main.dart
index 687cac74..90ee73aa 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -16,7 +16,7 @@ Future main() async {
configureDependencies(Env.prod);
WidgetsFlutterBinding.ensureInitialized();
final appDocDirectory = await getApplicationDocumentsDirectory();
- await configureNetworkTools(appDocDirectory.path);
+ await configureNetworkToolsFlutter(appDocDirectory.path);
final bool allowed = await ConsentLoader.isConsentPageShown();
// load app settings
diff --git a/lib/models/wifi_info.dart b/lib/models/wifi_info.dart
index d51620c7..966504d3 100644
--- a/lib/models/wifi_info.dart
+++ b/lib/models/wifi_info.dart
@@ -7,6 +7,20 @@ class WifiInfo {
final String? _name;
bool unknown;
String get ip => _ip ?? 'x.x.x.x';
- String get name => _name ?? 'Wi-Fi';
+
+ static const String noWifiName = 'Wi-Fi';
+
+ String get name {
+ if (_name == null || _name!.isEmpty) return noWifiName;
+ if (_name!.startsWith('"') && _name!.endsWith('"')) {
+ final array = _name!.split('"');
+ if (array.length > 1) {
+ final wifiName = array[1];
+ return wifiName.isEmpty ? noWifiName : wifiName;
+ }
+ }
+ return _name!;
+ }
+
String get bssid => _bssid ?? defaultBSSID.first;
}
diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart
index 4ffac460..32ed1032 100644
--- a/lib/pages/home_page.dart
+++ b/lib/pages/home_page.dart
@@ -5,6 +5,7 @@ import 'package:network_info_plus/network_info_plus.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:vernet/api/isp_loader.dart';
import 'package:vernet/helper/utils_helper.dart';
+import 'package:vernet/main.dart';
import 'package:vernet/models/internet_provider.dart';
import 'package:vernet/models/wifi_info.dart';
import 'package:vernet/pages/dns/dns_page.dart';
@@ -194,52 +195,58 @@ class _WifiDetailState extends State {
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- FutureBuilder(
- future: ISPLoader().load(),
- builder: (
- BuildContext context,
- AsyncSnapshot snapshot,
- ) {
- if (snapshot.hasData && snapshot.data != null) {
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- CustomTile(
- leading: Icon(
- Icons.public,
- color: Theme.of(context).colorScheme.secondary,
+ if (appSettings.inAppInternet)
+ FutureBuilder(
+ future: ISPLoader().load(),
+ builder: (
+ BuildContext context,
+ AsyncSnapshot snapshot,
+ ) {
+ if (snapshot.hasData && snapshot.data != null) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ CustomTile(
+ leading: Icon(
+ Icons.public,
+ color:
+ Theme.of(context).colorScheme.secondary,
+ ),
+ child: Text(snapshot.data!.ip),
),
- child: Text(snapshot.data!.ip),
- ),
- CustomTile(
- leading: Icon(
- Icons.dns,
- color: Theme.of(context).colorScheme.secondary,
+ CustomTile(
+ leading: Icon(
+ Icons.dns,
+ color:
+ Theme.of(context).colorScheme.secondary,
+ ),
+ child: Text(snapshot.data!.isp),
),
- child: Text(snapshot.data!.isp),
- ),
- CustomTile(
- leading: Icon(
- Icons.location_on,
- color: Theme.of(context).colorScheme.secondary,
+ CustomTile(
+ leading: Icon(
+ Icons.location_on,
+ color:
+ Theme.of(context).colorScheme.secondary,
+ ),
+ child: Text(snapshot.data!.location.address),
),
- child: Text(snapshot.data!.location.address),
- ),
- const SizedBox(height: 5),
- const Divider(height: 3),
- ],
- );
- }
- if (snapshot.hasError) {
- return const Text('Unable to fetch ISP details');
- }
- return const Text('Loading ISP details..');
- },
- ),
+ const SizedBox(height: 5),
+ const Divider(height: 3),
+ ],
+ );
+ }
+ if (snapshot.hasError) {
+ return const Text('Unable to fetch ISP details');
+ }
+ return const Text('Loading ISP details..');
+ },
+ )
+ else
+ const Text("In-App Internet is off"),
const SizedBox(height: 10),
ElevatedButton.icon(
onPressed: () {
- launchURL('https://fast.com');
+ launchURLWithWarning(context, 'https://fast.com');
},
icon: const Icon(Icons.speed),
label: const Text('Speed Test'),
diff --git a/lib/pages/host_scan_page/host_scna_bloc/host_scan_bloc.dart b/lib/pages/host_scan_page/host_scan_bloc/host_scan_bloc.dart
similarity index 72%
rename from lib/pages/host_scan_page/host_scna_bloc/host_scan_bloc.dart
rename to lib/pages/host_scan_page/host_scan_bloc/host_scan_bloc.dart
index c410bcb7..638f8e9d 100644
--- a/lib/pages/host_scan_page/host_scna_bloc/host_scan_bloc.dart
+++ b/lib/pages/host_scan_page/host_scan_bloc/host_scan_bloc.dart
@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
+import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart';
import 'package:network_info_plus/network_info_plus.dart';
@@ -39,9 +40,10 @@ class HostScanBloc extends Bloc {
) async {
emit(const HostScanState.loadInProgress());
ip = await NetworkInfo().getWifiIP();
- subnet = ip!.substring(0, ip!.lastIndexOf('.'));
- gatewayIp = await NetworkInfo().getWifiGatewayIP();
-
+ gatewayIp = appSettings.customSubnet.isNotEmpty
+ ? appSettings.customSubnet
+ : await NetworkInfo().getWifiGatewayIP();
+ subnet = gatewayIp!.substring(0, gatewayIp!.lastIndexOf('.'));
add(const HostScanEvent.startNewScan());
}
@@ -49,38 +51,7 @@ class HostScanBloc extends Bloc {
StartNewScan event,
Emitter emit,
) async {
- MdnsScanner.searchMdnsDevices()
- .then((List activeHostList) async {
- for (final ActiveHost activeHost in activeHostList) {
- final int index = indexOfActiveHost(activeHost.address);
- final MdnsInfo? mDns = await activeHost.mdnsInfo;
- if (mDns == null) {
- continue;
- }
-
- if (index == -1) {
- deviceInTheNetworkList.add(
- DeviceInTheNetwork.createFromActiveHost(
- activeHost: activeHost,
- currentDeviceIp: ip!,
- gatewayIp: gatewayIp!,
- mdns: mDns,
- mac: (await activeHost.arpData)?.macAddress,
- ),
- );
- } else {
- deviceInTheNetworkList[index] = deviceInTheNetworkList[index]
- ..mdns = mDns;
- }
-
- deviceInTheNetworkList.sort(sort);
-
- emit(const HostScanState.loadInProgress());
- emit(HostScanState.foundNewDevice(deviceInTheNetworkList));
- }
- });
-
- final streamController = HostScannerFlutter.getAllPingableDevices(
+ final streamController = HostScannerService.instance.getAllPingableDevices(
subnet!,
firstHostId: appSettings.firstSubnet,
lastHostId: appSettings.lastSubnet,
@@ -108,10 +79,40 @@ class HostScanBloc extends Bloc {
}
deviceInTheNetworkList.sort(sort);
+ emit(const HostScanState.loadInProgress());
+ emit(HostScanState.foundNewDevice(deviceInTheNetworkList));
+ }
+ final activeMdnsHostList =
+ await MdnsScannerService.instance.searchMdnsDevices();
+
+ for (final ActiveHost activeHost in activeMdnsHostList) {
+ final int index = indexOfActiveHost(activeHost.address);
+ final MdnsInfo? mDns = await activeHost.mdnsInfo;
+ if (mDns == null) {
+ continue;
+ }
+
+ if (index == -1) {
+ deviceInTheNetworkList.add(
+ DeviceInTheNetwork.createFromActiveHost(
+ activeHost: activeHost,
+ currentDeviceIp: ip!,
+ gatewayIp: gatewayIp!,
+ mdns: mDns,
+ mac: (await activeHost.arpData)?.macAddress,
+ ),
+ );
+ } else {
+ deviceInTheNetworkList[index] = deviceInTheNetworkList[index]
+ ..mdns = mDns;
+ }
+
+ deviceInTheNetworkList.sort(sort);
emit(const HostScanState.loadInProgress());
emit(HostScanState.foundNewDevice(deviceInTheNetworkList));
}
+ emit(HostScanState.loadSuccess(deviceInTheNetworkList));
}
/// Getting active host IP and finds it's index inside of activeHostList
diff --git a/lib/pages/host_scan_page/host_scna_bloc/host_scan_event.dart b/lib/pages/host_scan_page/host_scan_bloc/host_scan_event.dart
similarity index 100%
rename from lib/pages/host_scan_page/host_scna_bloc/host_scan_event.dart
rename to lib/pages/host_scan_page/host_scan_bloc/host_scan_event.dart
diff --git a/lib/pages/host_scan_page/host_scna_bloc/host_scan_state.dart b/lib/pages/host_scan_page/host_scan_bloc/host_scan_state.dart
similarity index 100%
rename from lib/pages/host_scan_page/host_scna_bloc/host_scan_state.dart
rename to lib/pages/host_scan_page/host_scan_bloc/host_scan_state.dart
diff --git a/lib/pages/host_scan_page/host_scan_page.dart b/lib/pages/host_scan_page/host_scan_page.dart
index 2d246894..e40b6c58 100644
--- a/lib/pages/host_scan_page/host_scan_page.dart
+++ b/lib/pages/host_scan_page/host_scan_page.dart
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:vernet/injection.dart';
-import 'package:vernet/pages/host_scan_page/host_scna_bloc/host_scan_bloc.dart';
+import 'package:vernet/pages/host_scan_page/host_scan_bloc/host_scan_bloc.dart';
import 'package:vernet/pages/host_scan_page/widgets/host_scan_widget.dart';
class HostScanPage extends StatelessWidget {
@@ -10,24 +10,6 @@ class HostScanPage extends StatelessWidget {
return Scaffold(
appBar: AppBar(
title: const Text('Scan for Devices'),
- // actions: [
- // if (_isScanning)
- // Container(
- // margin: const EdgeInsets.only(right: 20.0),
- // child: CircularPercentIndicator(
- // radius: 10.0,
- // lineWidth: 2.5,
- // percent: _progress / 100,
- // backgroundColor: Colors.grey,
- // progressColor: Colors.white,
- // ),
- // )
- // else
- // IconButton(
- // onPressed: _getDevices,
- // icon: const Icon(Icons.refresh),
- // ),
- // ],
),
body: BlocProvider(
create: (context) =>
diff --git a/lib/pages/host_scan_page/widgets/host_scan_widget.dart b/lib/pages/host_scan_page/widgets/host_scan_widget.dart
index 49ed3d30..d0cc8d3a 100644
--- a/lib/pages/host_scan_page/widgets/host_scan_widget.dart
+++ b/lib/pages/host_scan_page/widgets/host_scan_widget.dart
@@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:vernet/main.dart';
import 'package:vernet/pages/host_scan_page/device_in_the_network.dart';
-import 'package:vernet/pages/host_scan_page/host_scna_bloc/host_scan_bloc.dart';
+import 'package:vernet/pages/host_scan_page/host_scan_bloc/host_scan_bloc.dart';
import 'package:vernet/pages/network_troubleshoot/port_scan_page.dart';
-// import 'package:vernet/pages/port_scan_page/port_scan_page.dart';
class HostScanWidget extends StatelessWidget {
@override
@@ -17,16 +17,19 @@ class HostScanWidget extends StatelessWidget {
return Center(
child: Container(
margin: const EdgeInsets.all(30),
- child: const Column(
+ child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
- CircularProgressIndicator(),
- SizedBox(
+ const CircularProgressIndicator(),
+ const SizedBox(
height: 30,
),
Text(
- 'Searching for devices in your local network',
- style: TextStyle(
+ appSettings.gatewayIP.isNotEmpty
+ ? 'Searching for devices in ${appSettings.gatewayIP} network'
+ : 'Searching for devices in your local network',
+ textAlign: TextAlign.center,
+ style: const TextStyle(
fontSize: 18,
color: Colors.blue,
),
@@ -37,71 +40,13 @@ class HostScanWidget extends StatelessWidget {
);
},
foundNewDevice: (FoundNewDevice value) {
- final List activeHostList =
- value.activeHostList;
-
- return Flex(
- direction: Axis.vertical,
- children: [
- Padding(
- padding: const EdgeInsets.all(4.0),
- child: Text("Found ${activeHostList.length} devices"),
- ),
- Expanded(
- child: ListView.builder(
- itemCount: activeHostList.length,
- itemBuilder: (context, index) {
- final DeviceInTheNetwork host = activeHostList[index];
- return ListTile(
- leading: Icon(host.iconData),
- title: FutureBuilder(
- future: host.make,
- builder: (context, AsyncSnapshot snapshot) {
- return Text(snapshot.data ?? '');
- },
- initialData: 'Generic Device',
- ),
- subtitle: Text(
- '${host.internetAddress.address} ${host.mac}',
- ),
- trailing: IconButton(
- tooltip: 'Scan open ports for this target',
- icon: const Icon(Icons.radar),
- onPressed: () {
- Navigator.push(
- context,
- MaterialPageRoute(
- builder: (context) => PortScanPage(
- target: host.internetAddress.address,
- ),
- ),
- );
- },
- ),
- onLongPress: () {
- Clipboard.setData(
- ClipboardData(
- text: host.internetAddress.address,
- ),
- );
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(
- content: Text('IP copied to clipboard'),
- ),
- );
- },
- );
- },
- ),
- ),
- ],
- );
+ return _devicesWidget(value.activeHostList);
},
loadFailure: (value) {
return const Text('Failure');
},
loadSuccess: (value) {
- return const Text('Done');
+ return _devicesWidget(value.activeHostList);
},
error: (Error value) {
return const Text('Error');
@@ -110,4 +55,63 @@ class HostScanWidget extends StatelessWidget {
},
);
}
+
+ Widget _devicesWidget(List activeHostList) {
+ return Flex(
+ direction: Axis.vertical,
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(4.0),
+ child: Text("Found ${activeHostList.length} devices"),
+ ),
+ Expanded(
+ child: ListView.builder(
+ itemCount: activeHostList.length,
+ itemBuilder: (context, index) {
+ final DeviceInTheNetwork host = activeHostList[index];
+ return ListTile(
+ leading: Icon(host.iconData),
+ title: FutureBuilder(
+ future: host.make,
+ builder: (context, AsyncSnapshot snapshot) {
+ return Text(snapshot.data ?? '');
+ },
+ initialData: 'Generic Device',
+ ),
+ subtitle: Text(
+ '${host.internetAddress.address} ${host.mac}',
+ ),
+ trailing: IconButton(
+ tooltip: 'Scan open ports for this target',
+ icon: const Icon(Icons.radar),
+ onPressed: () {
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => PortScanPage(
+ target: host.internetAddress.address,
+ ),
+ ),
+ );
+ },
+ ),
+ onLongPress: () {
+ Clipboard.setData(
+ ClipboardData(
+ text: host.internetAddress.address,
+ ),
+ );
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('IP copied to clipboard'),
+ ),
+ );
+ },
+ );
+ },
+ ),
+ ),
+ ],
+ );
+ }
}
diff --git a/lib/pages/network_troubleshoot/port_scan_page.dart b/lib/pages/network_troubleshoot/port_scan_page.dart
index 6bd33565..a3534fe1 100644
--- a/lib/pages/network_troubleshoot/port_scan_page.dart
+++ b/lib/pages/network_troubleshoot/port_scan_page.dart
@@ -10,9 +10,10 @@ import 'package:vernet/ui/custom_tile.dart';
import 'package:vernet/ui/popular_chip.dart';
class PortScanPage extends StatefulWidget {
- const PortScanPage({this.target = ''});
+ const PortScanPage({this.target = '', this.runDefaultScan = false});
final String target;
+ final bool runDefaultScan;
@override
_PortScanPageState createState() => _PortScanPageState();
@@ -77,28 +78,35 @@ class _PortScanPageState extends State
_openPorts.clear();
});
if (_type == ScanType.single) {
- PortScannerFlutter.isOpen(
+ PortScannerService.instance
+ .isOpen(
_targetIPEditingController.text,
int.parse(_singlePortEditingController.text),
- ).then((value) {
+ )
+ .then((value) {
_handleEvent(value);
_handleOnDone();
});
} else if (_type == ScanType.top) {
- _streamSubscription = PortScannerFlutter.customDiscover(
- _targetIPEditingController.text,
- timeout: Duration(milliseconds: appSettings.socketTimeout),
- progressCallback: _handleProgress,
- ).listen(_handleEvent, onDone: _handleOnDone);
+ _streamSubscription = PortScannerService.instance
+ .customDiscover(
+ _targetIPEditingController.text,
+ timeout: Duration(milliseconds: appSettings.socketTimeout),
+ progressCallback: _handleProgress,
+ async: true,
+ )
+ .listen(_handleEvent, onDone: _handleOnDone);
} else {
- _streamSubscription = PortScannerFlutter.scanPortsForSingleDevice(
- _targetIPEditingController.text,
- startPort: int.parse(_startPortEditingController.text),
- endPort: int.parse(_endPortEditingController.text),
- timeout: Duration(milliseconds: appSettings.socketTimeout),
- progressCallback: _handleProgress,
- async: true,
- ).listen(_handleEvent, onDone: _handleOnDone);
+ _streamSubscription = PortScannerService.instance
+ .scanPortsForSingleDevice(
+ _targetIPEditingController.text,
+ startPort: int.parse(_startPortEditingController.text),
+ endPort: int.parse(_endPortEditingController.text),
+ timeout: Duration(milliseconds: appSettings.socketTimeout),
+ progressCallback: _handleProgress,
+ async: true,
+ )
+ .listen(_handleEvent, onDone: _handleOnDone);
}
}
@@ -107,6 +115,9 @@ class _PortScanPageState extends State
super.initState();
_tabController = TabController(length: _tabs.length, vsync: this);
_targetIPEditingController.text = widget.target;
+ if (widget.runDefaultScan) {
+ Future.delayed(Durations.short2, _startScanning);
+ }
}
ScanType? _type = ScanType.top;
diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart
index 0668eee7..fe55062d 100644
--- a/lib/pages/settings_page.dart
+++ b/lib/pages/settings_page.dart
@@ -5,6 +5,7 @@ import 'package:vernet/api/update_checker.dart';
import 'package:vernet/helper/utils_helper.dart';
import 'package:vernet/main.dart';
import 'package:vernet/models/dark_theme_provider.dart';
+import 'package:vernet/ui/settings_dialog/custom_subnet_dialog.dart';
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';
@@ -40,6 +41,19 @@ class _SettingsPageState extends State {
},
),
),
+ Card(
+ child: ListTile(
+ title: const Text('In-App Internet'),
+ trailing: Switch(
+ value: appSettings.inAppInternet,
+ onChanged: (bool? value) async {
+ appSettings.setInAppInternet(value ?? false);
+ await appSettings.load();
+ setState(() {});
+ },
+ ),
+ ),
+ ),
Card(
child: ListTile(
title: const Text(StringValue.firstSubnet),
@@ -124,6 +138,27 @@ class _SettingsPageState extends State {
},
),
),
+ Card(
+ child: ListTile(
+ title: const Text(StringValue.customSubnet),
+ subtitle: const Text(StringValue.customSubnetDesc),
+ trailing: Text(
+ appSettings.customSubnet,
+ style: Theme.of(context)
+ .textTheme
+ .titleSmall
+ ?.copyWith(color: Theme.of(context).colorScheme.secondary),
+ ),
+ onTap: () async {
+ await showDialog(
+ context: context,
+ builder: (context) => const CustomSubnetDialog(),
+ );
+ await appSettings.load();
+ setState(() {});
+ },
+ ),
+ ),
Card(
child: ListTile(
title: const Text('Check for Updates'),
@@ -149,45 +184,23 @@ class _SettingsPageState extends State {
ListTile(
leading: const Icon(Icons.bug_report),
title: const Text('Report Issues'),
- // subtitle: Text(_issueUrl),
- // trailing: IconButton(
- // icon: Icon(Icons.open_in_new),
- // onPressed: () {
- // _launchURL(_issueUrl);
- // },
- // ),
onTap: () {
- launchURL(_issueUrl);
+ launchURLWithWarning(context, _issueUrl);
},
),
ListTile(
leading: const Icon(Icons.favorite),
title: const Text('Donate'),
- // subtitle: Text(_donateUrl),
- // trailing: IconButton(
- // icon: Icon(Icons.open_in_new),
- // onPressed: () {
- // _launchURL(_donateUrl);
- // },
- // ),
onTap: () {
- launchURL(_donateUrl);
+ launchURLWithWarning(context, _donateUrl);
},
),
ListTile(
leading: const Icon(Icons.code),
title: const Text('Source Code'),
- // subtitle: Text(_srcUrl),
onTap: () {
- launchURL(_srcUrl);
+ launchURLWithWarning(context, _srcUrl);
},
-
- // trailing: IconButton(
- // icon: Icon(Icons.open_in_new),
- // onPressed: () {
- // _launchURL(_srcUrl);
- // },
- // ),
),
const ListTile(
title: Text(
diff --git a/lib/ui/external_link_dialog.dart b/lib/ui/external_link_dialog.dart
new file mode 100644
index 00000000..aea870eb
--- /dev/null
+++ b/lib/ui/external_link_dialog.dart
@@ -0,0 +1,31 @@
+import 'package:flutter/material.dart';
+import 'package:vernet/helper/utils_helper.dart';
+
+class ExternalLinkWarningDialog extends StatelessWidget {
+ const ExternalLinkWarningDialog({super.key, required this.link});
+
+ final String link;
+
+ @override
+ Widget build(BuildContext context) {
+ return AlertDialog(
+ title: const Text("Confirm to open external link"),
+ content: Text(link),
+ actions: [
+ TextButton(
+ onPressed: () {
+ Navigator.pop(context);
+ },
+ child: const Text('Cancel'),
+ ),
+ TextButton.icon(
+ onPressed: () {
+ launchURL(link);
+ },
+ icon: const Icon(Icons.link),
+ label: const Text('Open Link'),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/ui/settings_dialog/custom_subnet_dialog.dart b/lib/ui/settings_dialog/custom_subnet_dialog.dart
new file mode 100644
index 00000000..ff9b45a1
--- /dev/null
+++ b/lib/ui/settings_dialog/custom_subnet_dialog.dart
@@ -0,0 +1,45 @@
+import 'package:flutter/material.dart';
+import 'package:vernet/main.dart';
+import 'package:vernet/ui/base_settings_dialog.dart';
+import 'package:vernet/values/strings.dart';
+
+class CustomSubnetDialog extends StatefulWidget {
+ const CustomSubnetDialog({super.key});
+
+ @override
+ State createState() => _CustomSubnetDialogState();
+}
+
+class _CustomSubnetDialogState extends BaseSettingsDialog {
+ @override
+ String getDialogTitle() {
+ return StringValue.customSubnet;
+ }
+
+ @override
+ String getHintText() {
+ return StringValue.customSubnetHint;
+ }
+
+ @override
+ String getInitialValue() {
+ return appSettings.customSubnet;
+ }
+
+ @override
+ TextInputType getKeyBoardType() {
+ return TextInputType.number;
+ }
+
+ @override
+ void onSubmit(String value) {
+ if (value != appSettings.customSubnet) {
+ appSettings.setCustomSubnet(value);
+ }
+ }
+
+ @override
+ String? validate(String? value) {
+ return null;
+ }
+}
diff --git a/lib/ui/settings_dialog/internet_dialog.dart b/lib/ui/settings_dialog/internet_dialog.dart
new file mode 100644
index 00000000..e69de29b
diff --git a/lib/values/strings.dart b/lib/values/strings.dart
index 11d41756..d8607c3c 100644
--- a/lib/values/strings.dart
+++ b/lib/values/strings.dart
@@ -14,4 +14,9 @@ class StringValue {
static const String pingCount = 'Ping Count';
static const String pingCountDesc =
'Number of times ping request should be sent';
+
+ static const String customSubnet = 'Custom Subnet';
+ static const String customSubnetDesc =
+ 'Scan a custom subnet instead of local one.';
+ static const String customSubnetHint = 'Enter Gateway IP e.g., 10.102.200.1';
}
diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj
index 36107ed8..2be7bb43 100644
--- a/macos/Runner.xcodeproj/project.pbxproj
+++ b/macos/Runner.xcodeproj/project.pbxproj
@@ -203,7 +203,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
- LastUpgradeCheck = 1430;
+ LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
33CC10EC2044A3C60003C045 = {
diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index 3bbcb37d..198e90cf 100644
--- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -1,6 +1,6 @@
=2.17.0 <3.0.0"
@@ -17,6 +17,7 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.5
dart_ping: ^9.0.0
+ external_app_launcher: ^3.1.0
flutter:
sdk: flutter
# Bloc for state management, replace StatefulWidget
@@ -34,7 +35,7 @@ dependencies:
# Discover network info and configure themselves accordingly
network_info_plus: ^4.0.2
# Helps you discover open ports, devices on subnet and more.
- network_tools_flutter: ^1.0.5
+ network_tools_flutter: ^2.0.0
# Querying information about the application package, such as CFBundleVersion
package_info_plus: ^4.1.0
path_provider: ^2.1.1