diff --git a/analysis_options.yaml b/analysis_options.yaml
index b148779e..2395de07 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -26,9 +26,5 @@ linter:
sort_constructors_first: true
- prefer_single_quotes: true
-
# Good packages document everything
public_member_api_docs: true
-
- lines_longer_than_80_chars: true
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
index c6c432c0..65ff0849 100644
--- a/android/app/src/debug/AndroidManifest.xml
+++ b/android/app/src/debug/AndroidManifest.xml
@@ -3,5 +3,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml
index c6c432c0..65ff0849 100644
--- a/android/app/src/profile/AndroidManifest.xml
+++ b/android/app/src/profile/AndroidManifest.xml
@@ -3,5 +3,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/injection.dart b/lib/injection.dart
new file mode 100644
index 00000000..2795ab62
--- /dev/null
+++ b/lib/injection.dart
@@ -0,0 +1,23 @@
+import 'package:get_it/get_it.dart';
+import 'package:injectable/injectable.dart';
+import 'package:vernet/injection.config.dart';
+
+final getIt = GetIt.instance;
+
+/// Saves the current environment for manual use
+late String currentEnv;
+
+@injectableInit
+void configureDependencies(String env) {
+ currentEnv = env;
+ $initGetIt(getIt, environment: env);
+}
+
+abstract class Env {
+ static const String test = 'test';
+ static const String dev = 'dev';
+ static const String prod = 'prod';
+
+ /// Demo of the app with fake data
+ static const String demo = 'demo';
+}
diff --git a/lib/main.dart b/lib/main.dart
index cba14e19..00777287 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
import 'package:vernet/api/update_checker.dart';
import 'package:vernet/helper/app_settings.dart';
import 'package:vernet/helper/consent_loader.dart';
+import 'package:vernet/injection.dart';
import 'package:vernet/models/dark_theme_provider.dart';
import 'package:vernet/pages/home_page.dart';
import 'package:vernet/pages/location_consent_page.dart';
@@ -10,6 +11,7 @@ import 'package:vernet/pages/settings_page.dart';
late AppSettings appSettings;
Future main() async {
+ configureDependencies(Env.prod);
WidgetsFlutterBinding.ensureInitialized();
final bool allowed = await ConsentLoader.isConsentPageShown();
diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart
index dbfb0f58..12ac8b00 100644
--- a/lib/pages/home_page.dart
+++ b/lib/pages/home_page.dart
@@ -9,7 +9,7 @@ import 'package:vernet/models/internet_provider.dart';
import 'package:vernet/models/wifi_info.dart';
import 'package:vernet/pages/dns/dns_page.dart';
import 'package:vernet/pages/dns/reverse_dns_page.dart';
-import 'package:vernet/pages/host_scan_page.dart';
+import 'package:vernet/pages/host_scan_page/host_scan_page.dart';
import 'package:vernet/pages/network_troubleshoot/ping_page.dart';
import 'package:vernet/pages/network_troubleshoot/port_scan_page.dart';
import 'package:vernet/ui/custom_tile.dart';
@@ -89,7 +89,7 @@ class _WifiDetailState extends State {
Navigator.push(
context,
MaterialPageRoute(
- builder: (context) => const HostScanPage(),
+ builder: (context) => HostScanPage(),
),
);
},
diff --git a/lib/pages/host_scan_page.dart b/lib/pages/host_scan_page.dart
deleted file mode 100644
index 6c97c051..00000000
--- a/lib/pages/host_scan_page.dart
+++ /dev/null
@@ -1,189 +0,0 @@
-import 'dart:async';
-import 'dart:collection';
-import 'dart:io';
-
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:network_info_plus/network_info_plus.dart';
-import 'package:network_tools/network_tools.dart';
-import 'package:percent_indicator/percent_indicator.dart';
-import 'package:vernet/main.dart';
-import 'package:vernet/pages/network_troubleshoot/port_scan_page.dart';
-
-class HostScanPage extends StatefulWidget {
- const HostScanPage({Key? key}) : super(key: key);
-
- @override
- _HostScanPageState createState() => _HostScanPageState();
-}
-
-class _HostScanPageState extends State
- with TickerProviderStateMixin {
- final Set _hosts = {};
- double _progress = 0;
- bool _isScanning = false;
- StreamSubscription? _streamSubscription;
- late String? _ip;
- late String? _gatewayIP;
-
- Future _getDevices() async {
- _hosts.clear();
- _ip = await NetworkInfo().getWifiIP();
- _gatewayIP = await NetworkInfo().getWifiGatewayIP();
-
- if (_ip != null && _ip!.isNotEmpty) {
- final String subnet = _ip!.substring(0, _ip!.lastIndexOf('.'));
- setState(() {
- _isScanning = true;
- });
-
- final stream = HostScanner.discover(
- subnet,
- firstSubnet: appSettings.firstSubnet,
- lastSubnet: appSettings.lastSubnet,
- progressCallback: (progress) {
- if (mounted) {
- setState(() {
- _progress = progress;
- });
- }
- },
- );
-
- _streamSubscription = stream.listen(
- (ActiveHost host) {
- setState(() {
- _hosts.add(host);
- });
- },
- onDone: () {
- if (mounted) {
- setState(() {
- _isScanning = false;
- });
- }
- },
- onError: (error) {
- if (mounted) {
- setState(() {
- _isScanning = false;
- });
- }
- },
- );
- }
- }
-
- @override
- void initState() {
- super.initState();
- _getDevices();
- }
-
- @override
- void dispose() {
- super.dispose();
- _streamSubscription?.cancel();
- }
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(
- title: const Text('Scan for Devices'),
- actions: [
- if (_isScanning)
- Container(
- margin: const EdgeInsets.only(right: 20.0),
- child: CircularPercentIndicator(
- radius: 20.0,
- lineWidth: 2.5,
- percent: _progress / 100,
- backgroundColor: Colors.grey,
- progressColor: Colors.white,
- ),
- )
- else
- IconButton(
- onPressed: _getDevices,
- icon: const Icon(Icons.refresh),
- ),
- ],
- ),
- body: Center(
- child: buildListView(context),
- ),
- );
- }
-
- Widget buildListView(BuildContext context) {
- if (_progress >= 100 && _hosts.isEmpty) {
- return const Text(
- 'No host found.\nTry changing first and last subnet in settings',
- textAlign: TextAlign.center,
- );
- } else if (_isScanning && _hosts.isEmpty) {
- return const CircularProgressIndicator.adaptive();
- }
-
- return Column(
- children: [
- Expanded(
- child: ListView.builder(
- itemCount: _hosts.length,
- itemBuilder: (context, index) {
- final ActiveHost host =
- SplayTreeSet.from(_hosts).toList()[index] as ActiveHost;
- return ListTile(
- leading: _getHostIcon(host.ip),
- title: Text(_getDeviceMake(host)),
- subtitle: Text(host.ip),
- trailing: IconButton(
- tooltip: 'Scan open ports for this target',
- icon: const Icon(Icons.radar),
- onPressed: () {
- Navigator.push(
- context,
- MaterialPageRoute(
- builder: (context) => PortScanPage(target: host.ip),
- ),
- );
- },
- ),
- onLongPress: () {
- Clipboard.setData(ClipboardData(text: host.ip));
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(
- content: Text('IP copied to clipboard'),
- ),
- );
- },
- );
- },
- ),
- )
- ],
- );
- }
-
- String _getDeviceMake(ActiveHost host) {
- if (_ip == host.ip) {
- return 'This device';
- } else if (_gatewayIP == host.ip) {
- return 'Router/Gateway';
- }
- return host.make;
- }
-
- Icon _getHostIcon(String hostIp) {
- if (hostIp == _ip) {
- if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
- return Icon(Icons.computer);
- }
- return Icon(Icons.smartphone);
- } else if (hostIp == _gatewayIP) {
- return Icon(Icons.router);
- }
- return Icon(Icons.devices);
- }
-}
diff --git a/lib/pages/host_scan_page/device_in_the_network.dart b/lib/pages/host_scan_page/device_in_the_network.dart
new file mode 100644
index 00000000..bb20aa10
--- /dev/null
+++ b/lib/pages/host_scan_page/device_in_the_network.dart
@@ -0,0 +1,102 @@
+import 'dart:io';
+
+import 'package:dart_ping/dart_ping.dart';
+import 'package:flutter/material.dart';
+import 'package:network_tools/network_tools.dart';
+
+/// Contains all the information of a device in the network including
+/// icon, open ports and in the future host name and mDNS name
+class DeviceInTheNetwork {
+ /// Create basic device with default (not the correct) icon
+ DeviceInTheNetwork({
+ required this.ip,
+ required this.make,
+ required this.pingData,
+ this.iconData = Icons.devices,
+ this.hostId,
+ });
+
+ /// Create the object from active host with the correct field and icon
+ factory DeviceInTheNetwork.createFromActiveHost({
+ required ActiveHost activeHost,
+ required String currentDeviceIp,
+ required String gatewayIp,
+ }) {
+ return DeviceInTheNetwork.createWithAllNecessaryFields(
+ ip: activeHost.ip,
+ hostId: activeHost.hostId,
+ make: activeHost.make,
+ pingData: activeHost.pingData,
+ currentDeviceIp: currentDeviceIp,
+ gatewayIp: gatewayIp,
+ );
+ }
+
+ /// Create the object with the correct field and icon
+ factory DeviceInTheNetwork.createWithAllNecessaryFields({
+ required String ip,
+ required int hostId,
+ required String make,
+ required PingData pingData,
+ required String currentDeviceIp,
+ required String gatewayIp,
+ }) {
+ final IconData iconData = getHostIcon(
+ currentDeviceIp: currentDeviceIp,
+ hostIp: ip,
+ gatewayIp: gatewayIp,
+ );
+
+ final String deviceMake = getDeviceMake(
+ currentDeviceIp: currentDeviceIp,
+ hostIp: ip,
+ gatewayIp: gatewayIp,
+ hostMake: make,
+ );
+
+ return DeviceInTheNetwork(
+ ip: ip,
+ make: deviceMake,
+ pingData: pingData,
+ hostId: hostId,
+ iconData: iconData,
+ );
+ }
+
+ /// Ip of the device
+ final String ip;
+ final String make;
+ final PingData pingData;
+ final IconData iconData;
+ int? hostId;
+
+ static String getDeviceMake({
+ required String currentDeviceIp,
+ required String hostIp,
+ required String gatewayIp,
+ required String hostMake,
+ }) {
+ if (currentDeviceIp == hostIp) {
+ return 'This device';
+ } else if (gatewayIp == hostIp) {
+ return 'Router/Gateway';
+ }
+ return hostMake;
+ }
+
+ static IconData getHostIcon({
+ required String currentDeviceIp,
+ required String hostIp,
+ required String gatewayIp,
+ }) {
+ if (hostIp == currentDeviceIp) {
+ if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
+ return Icons.computer;
+ }
+ return Icons.smartphone;
+ } else if (hostIp == gatewayIp) {
+ return Icons.router;
+ }
+ return Icons.devices;
+ }
+}
diff --git a/lib/pages/host_scan_page/host_scan_page.dart b/lib/pages/host_scan_page/host_scan_page.dart
new file mode 100644
index 00000000..d281e103
--- /dev/null
+++ b/lib/pages/host_scan_page/host_scan_page.dart
@@ -0,0 +1,41 @@
+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/widgets/host_scan_widget.dart';
+
+class HostScanPage extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ 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: Center(
+ child: BlocProvider(
+ create: (context) =>
+ getIt()..add(const HostScanEvent.initialized()),
+ child: HostScanWidget(),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/host_scan_page/host_scna_bloc/host_scan_bloc.dart b/lib/pages/host_scan_page/host_scna_bloc/host_scan_bloc.dart
new file mode 100644
index 00000000..6414c722
--- /dev/null
+++ b/lib/pages/host_scan_page/host_scna_bloc/host_scan_bloc.dart
@@ -0,0 +1,129 @@
+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:isolate_contactor/isolate_contactor.dart';
+import 'package:network_info_plus/network_info_plus.dart';
+import 'package:network_tools/network_tools.dart';
+import 'package:vernet/main.dart';
+import 'package:vernet/pages/host_scan_page/device_in_the_network.dart';
+
+part 'host_scan_bloc.freezed.dart';
+part 'host_scan_event.dart';
+part 'host_scan_state.dart';
+
+@injectable
+class HostScanBloc extends Bloc {
+ HostScanBloc() : super(HostScanState.initial()) {
+ on(_initialized);
+ on(_startNewScan);
+ }
+
+ /// IP of the device in the local network.
+ String? ip;
+
+ /// Gateway IP of the current network
+ late String? gatewayIp;
+
+ String? subnet;
+
+ /// List of all ActiveHost devices that got found in the current scan
+ List activeHostList = [];
+
+ Future _initialized(
+ Initialized event,
+ Emitter emit,
+ ) async {
+ emit(const HostScanState.loadInProgress());
+ ip = await NetworkInfo().getWifiIP();
+ subnet = ip!.substring(0, ip!.lastIndexOf('.'));
+ gatewayIp = await NetworkInfo().getWifiGatewayIP();
+
+ add(const HostScanEvent.startNewScan());
+ }
+
+ Future _startNewScan(
+ StartNewScan event,
+ Emitter emit,
+ ) async {
+ const int scanRangeForIsolate = 51;
+ for (int i = appSettings.firstSubnet;
+ i <= appSettings.lastSubnet;
+ i += scanRangeForIsolate + 1) {
+ final IsolateContactor isolateContactor =
+ await IsolateContactor.createOwnIsolate(startSearchingDevices);
+ int limit = i + scanRangeForIsolate;
+ if (limit >= appSettings.lastSubnet) {
+ limit = appSettings.lastSubnet;
+ }
+ isolateContactor.sendMessage([
+ subnet!,
+ i.toString(),
+ limit.toString(),
+ ]);
+ await for (final dynamic message in isolateContactor.onMessage) {
+ try {
+ if (message is ActiveHost) {
+ final DeviceInTheNetwork tempDeviceInTheNetwork =
+ DeviceInTheNetwork.createFromActiveHost(
+ activeHost: message,
+ currentDeviceIp: ip!,
+ gatewayIp: gatewayIp!,
+ );
+
+ activeHostList.add(tempDeviceInTheNetwork);
+ activeHostList.sort((a, b) {
+ final int aIp =
+ int.parse(a.ip.substring(a.ip.lastIndexOf('.') + 1));
+ final int bIp =
+ int.parse(b.ip.substring(b.ip.lastIndexOf('.') + 1));
+ return aIp.compareTo(bIp);
+ });
+ emit(const HostScanState.loadInProgress());
+ emit(HostScanState.foundNewDevice(activeHostList));
+ } else if (message is String && message == 'Done') {
+ isolateContactor.dispose();
+ }
+ } catch (e) {
+ emit(const HostScanState.error());
+ }
+ }
+ }
+ print('The end of the scan');
+
+ // emit(HostScanState.loadSuccess(activeHostList));
+ }
+
+ /// Will search devices in the network inside new isolate
+ static Future startSearchingDevices(dynamic params) async {
+ final channel = IsolateContactorController(params);
+ channel.onIsolateMessage.listen((message) async {
+ List paramsListString = [];
+ if (message is List) {
+ paramsListString = message;
+ } else {
+ return;
+ }
+
+ final String subnetIsolate = paramsListString[0];
+ final int firstSubnetIsolate = int.parse(paramsListString[1]);
+ final int lastSubnetIsolate = int.parse(paramsListString[2]);
+ print('scanning from $firstSubnetIsolate to $lastSubnetIsolate');
+
+ /// Will contain all the hosts that got discovered in the network, will
+ /// be use inorder to cancel on dispose of the page.
+ final Stream hostsDiscoveredInNetwork = HostScanner.discover(
+ subnetIsolate,
+ firstSubnet: firstSubnetIsolate,
+ lastSubnet: lastSubnetIsolate,
+ );
+
+ await for (final ActiveHost activeHostFound in hostsDiscoveredInNetwork) {
+ channel.sendResult(activeHostFound);
+ }
+ channel.sendResult('Done');
+ });
+ }
+}
diff --git a/lib/pages/host_scan_page/host_scna_bloc/host_scan_event.dart b/lib/pages/host_scan_page/host_scna_bloc/host_scan_event.dart
new file mode 100644
index 00000000..88e500f7
--- /dev/null
+++ b/lib/pages/host_scan_page/host_scna_bloc/host_scan_event.dart
@@ -0,0 +1,8 @@
+part of 'host_scan_bloc.dart';
+
+@freezed
+class HostScanEvent with _$HostScanEvent {
+ const factory HostScanEvent.initialized() = Initialized;
+
+ const factory HostScanEvent.startNewScan() = StartNewScan;
+}
diff --git a/lib/pages/host_scan_page/host_scna_bloc/host_scan_state.dart b/lib/pages/host_scan_page/host_scna_bloc/host_scan_state.dart
new file mode 100644
index 00000000..4b35f502
--- /dev/null
+++ b/lib/pages/host_scan_page/host_scna_bloc/host_scan_state.dart
@@ -0,0 +1,20 @@
+part of 'host_scan_bloc.dart';
+
+@freezed
+class HostScanState with _$HostScanState {
+ factory HostScanState.initial() = _Initial;
+
+ const factory HostScanState.loadInProgress() = _LoadInProgress;
+
+ const factory HostScanState.foundNewDevice(
+ List activeHostList,
+ ) = FoundNewDevice;
+
+ const factory HostScanState.loadSuccess(
+ List activeHostList,
+ ) = LoadSuccess;
+
+ const factory HostScanState.loadFailure() = _loadFailure;
+
+ const factory HostScanState.error() = Error;
+}
diff --git a/lib/pages/host_scan_page/widgets/host_scan_widget.dart b/lib/pages/host_scan_page/widgets/host_scan_widget.dart
new file mode 100644
index 00000000..ec2fc6bd
--- /dev/null
+++ b/lib/pages/host_scan_page/widgets/host_scan_widget.dart
@@ -0,0 +1,94 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_bloc/flutter_bloc.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/network_troubleshoot/port_scan_page.dart';
+
+class HostScanWidget extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ children: [
+ BlocBuilder(
+ builder: (context, state) {
+ return state.map(
+ initial: (_) => Container(),
+ loadInProgress: (value) {
+ return Expanded(
+ child: Container(
+ margin: const EdgeInsets.all(30),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: const [
+ CircularProgressIndicator(),
+ SizedBox(
+ height: 30,
+ ),
+ Text(
+ 'Searching for devices in your local network',
+ style: TextStyle(
+ fontSize: 18,
+ color: Colors.blue,
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ foundNewDevice: (FoundNewDevice value) {
+ final List activeHostList =
+ value.activeHostList;
+
+ return Expanded(
+ child: ListView.builder(
+ itemCount: activeHostList.length,
+ itemBuilder: (context, index) {
+ final DeviceInTheNetwork host = activeHostList[index];
+ return ListTile(
+ leading: Icon(host.iconData),
+ title: Text(host.make),
+ subtitle: Text(host.ip),
+ trailing: IconButton(
+ tooltip: 'Scan open ports for this target',
+ icon: const Icon(Icons.radar),
+ onPressed: () {
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) =>
+ PortScanPage(target: host.ip),
+ ),
+ );
+ },
+ ),
+ onLongPress: () {
+ Clipboard.setData(ClipboardData(text: host.ip));
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('IP copied to clipboard'),
+ ),
+ );
+ },
+ );
+ },
+ ),
+ );
+ },
+ loadFailure: (value) {
+ return const Text('Failure');
+ },
+ loadSuccess: (value) {
+ return const Text('Done');
+ },
+ error: (Error value) {
+ return const Text('Error');
+ },
+ );
+ },
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/pages/network_troubleshoot/port_scan_page.dart b/lib/pages/network_troubleshoot/port_scan_page.dart
index f6e730d6..b763392d 100644
--- a/lib/pages/network_troubleshoot/port_scan_page.dart
+++ b/lib/pages/network_troubleshoot/port_scan_page.dart
@@ -226,7 +226,7 @@ class _PortScanPageState extends State
Container(
margin: const EdgeInsets.only(right: 20.0),
child: CircularPercentIndicator(
- radius: 20.0,
+ radius: 10.0,
lineWidth: 2.5,
percent: _progress / 100,
backgroundColor: Colors.grey,
@@ -464,7 +464,7 @@ class _PortScanPageState extends State
],
),
),
- Divider(height: 4),
+ const Divider(height: 4),
],
);
},
diff --git a/pubspec.yaml b/pubspec.yaml
index f03fec06..8ce2d829 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -21,27 +21,57 @@ environment:
sdk: ">=2.12.0 <3.0.0"
dependencies:
+ # Automatically resizes text to fit perfectly within its bounds.
+ auto_size_text: ^3.0.0
+ # Helps implement the BLoC pattern.
+ bloc: ^8.0.2
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.4
dart_ping: ^6.1.1
flutter:
sdk: flutter
+ # Bloc for state management, replace StatefulWidget
+ flutter_bloc: ^8.0.1
+ # Annotations for freezed
+ freezed_annotation: ^1.1.0
+ # Service locator
+ get_it: ^7.2.0
+ # A composable, multi-platform, Future-based API for HTTP requests.
http: ^0.13.4
+ # Convenient code generator for get_it
+ injectable: ^1.5.3
+ # An easy way to create a new isolate, keep it running and communicate with it.
+ isolate_contactor: ^1.2.0+1
+ # Discover network info and configure themselves accordingly
network_info_plus: ^2.1.2
- network_tools: ^1.0.7
+ # Helps you discover open ports, devices on subnet and more.
+ network_tools: ^1.0.8
+ # Querying information about the application package, such as CFBundleVersion
package_info_plus: ^1.3.0
- percent_indicator: ^3.4.0
+ # Allows you to display progress widgets based on percentage.
+ percent_indicator: ^4.0.0
+ # Popup that ask for the requested permission
permission_handler: ^8.3.0
+ # A wrapper around InheritedWidget to make them easier to use and more reusable.
provider: ^6.0.2
- shared_preferences: ^2.0.12
- url_launcher: ^6.0.18
+ # Reading and writing simple key-value pairs
+ shared_preferences: ^2.0.13
+ # Plugin for launching a URL
+ url_launcher: ^6.0.20
dev_dependencies:
+ # A build system for Dart code generation and modular compilation.
+ build_runner:
+
flutter_test:
sdk: flutter
+ # Code generator for unions/pattern-matching/copy.
+ freezed: ^1.1.1
+ # Convenient code generator for get_it.
+ injectable_generator: ^1.5.3
# Collection of lint rules for Dart and Flutter projects.
- lint: ^1.8.1
+ lint: ^1.8.2
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec