From 6fd9753600ce93f20ca2b0c71b0dcb69b84d4d83 Mon Sep 17 00:00:00 2001 From: henrik Date: Sun, 20 Aug 2023 17:58:58 +0200 Subject: [PATCH] finally working. dart v3 version and glitches fixed --- lib/screens/home/bottom_sheet_form.dart | 41 ++--- lib/screens/home/discover.dart | 9 +- lib/screens/home/home.dart | 204 ++++++++++++++---------- lib/services/data.dart | 18 +-- lib/services/database.dart | 17 +- lib/services/network.dart | 2 +- lib/widgets/layout_elements.dart | 11 +- pubspec.yaml | 2 +- 8 files changed, 174 insertions(+), 130 deletions(-) diff --git a/lib/screens/home/bottom_sheet_form.dart b/lib/screens/home/bottom_sheet_form.dart index 975174a..e69c9cf 100644 --- a/lib/screens/home/bottom_sheet_form.dart +++ b/lib/screens/home/bottom_sheet_form.dart @@ -15,12 +15,14 @@ import '../../widgets/universal_ui_components.dart'; abstract class ModularBottomFormPage extends StatefulWidget { final String title; final Device device; - final Function(List) onSubmitDeviceCallback; + final List devices; + final Function(List, String?) onSubmitDeviceCallback; final bool deleteButton; ModularBottomFormPage( {Key? key, required this.device, + required this.devices, required this.title, required this.onSubmitDeviceCallback, this.deleteButton = false}) @@ -57,6 +59,7 @@ abstract class ModularBottomFormPage extends StatefulWidget { macAddress: controllerMac.text, modified: DateTime.now(), wolPort: wolPort, + isOnline: storageDevice.isOnline, deviceType: deviceType); } else { return NetworkDevice( @@ -74,14 +77,13 @@ abstract class ModularBottomFormPage extends StatefulWidget { /// dataOperationOnSave() is an abstract method that is implemented in the child classes and is called when the submitButton is pressed /// it saves the device to the json file and returns the updated [StorageDevice] list - Future> dataOperationOnSave(); + Future<(List, StorageDevice)> dataOperationOnSave(); /// dataOperationOnDelete() is triggered when the delete button is pressed and delete a device from the json file and returns the updated [StorageDevice] list Future> dataOperationOnDelete() async { StorageDevice device = getDevice as StorageDevice; - List devices = await deviceStorage.deleteDevice( - device.id, - ); + List devices = + await deviceStorage.deleteDevice(device.id, this.devices); return devices; } @@ -213,10 +215,11 @@ class _ModularBottomFormPageState extends State { onPressed: () => { validateFormFields(onSubmitDeviceCallback: () async { Navigator.popUntil(context, (route) => route.isFirst); - List device = + (List, StorageDevice) updatedDevices = await widget.dataOperationOnSave(); // sent device to callback function in order to update the UI - widget.onSubmitDeviceCallback(device); + widget.onSubmitDeviceCallback( + updatedDevices.$1, updatedDevices.$2.id); }) }, text: AppLocalizations.of(context)!.formApplyButtonText, @@ -529,9 +532,9 @@ class _ModularBottomFormPageState extends State { rightColor: Theme.of(context).colorScheme.error, rightOnPressed: () async { Navigator.popUntil(context, (route) => route.isFirst); - List device = await widget.dataOperationOnDelete(); + List devices = await widget.dataOperationOnDelete(); // sent device to callback function in order to update the UI - widget.onSubmitDeviceCallback(device); + widget.onSubmitDeviceCallback(devices, null); }, ); }, @@ -558,15 +561,15 @@ class NetworkDeviceFormPage extends ModularBottomFormPage { NetworkDeviceFormPage( {super.key, required super.device, + required super.devices, required super.title, required super.onSubmitDeviceCallback}); @override - Future> dataOperationOnSave() async { - List devices = await deviceStorage.addDevice( - getDevice as NetworkDevice, - ); - return devices; + Future<(List, StorageDevice)> dataOperationOnSave() async { + (List, StorageDevice) updatedDevices = + await deviceStorage.addDevice(getDevice as NetworkDevice, devices); + return updatedDevices; } } @@ -576,15 +579,15 @@ class EditDeviceFormPage extends ModularBottomFormPage { {super.key, required super.device, required super.title, + required super.devices, required super.onSubmitDeviceCallback}) : super(deleteButton: true); @override - Future> dataOperationOnSave() async { - List devices = await deviceStorage.updateDevice( - getDevice as StorageDevice, - ); - return devices; + Future<(List, StorageDevice)> dataOperationOnSave() async { + (List, StorageDevice) updatedDevices = + await deviceStorage.updateDevice(getDevice as StorageDevice, devices); + return updatedDevices; } } diff --git a/lib/screens/home/discover.dart b/lib/screens/home/discover.dart index f0c7a34..7341039 100644 --- a/lib/screens/home/discover.dart +++ b/lib/screens/home/discover.dart @@ -10,9 +10,11 @@ import '../../widgets/layout_elements.dart'; import '../../widgets/universal_ui_components.dart'; class DiscoverPage extends StatefulWidget { - final Function(List) updateDevicesList; + final Function(List, String?) updateDevicesList; + final List devices; - const DiscoverPage({Key? key, required this.updateDevicesList}) + const DiscoverPage( + {Key? key, required this.updateDevicesList, required this.devices}) : super(key: key); @override @@ -105,6 +107,7 @@ class _DiscoverPageState extends State { title: AppLocalizations.of(context)!.discoverAddDeviceAlertTitle, device: NetworkDevice(), + devices: widget.devices, onSubmitDeviceCallback: widget.updateDevicesList)), text: AppLocalizations.of(context)!.discoverAddCustomDeviceButton, icon: const Icon(Icons.add)), @@ -167,6 +170,7 @@ class _DiscoverPageState extends State { .discoverAddDeviceAlertTitle, device: _devices[index] .copyWith(wolPort: 9), + devices: widget.devices, onSubmitDeviceCallback: widget.updateDevicesList)), ); @@ -221,6 +225,7 @@ class _DiscoverPageState extends State { builder: (context) => NetworkDeviceFormPage( title: title, device: device.copyWith(wolPort: port), + devices: widget.devices, onSubmitDeviceCallback: widget.updateDevicesList), ); } diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index cac8dc8..c694d0d 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -67,6 +67,26 @@ class _HomePageState extends State { super.dispose(); } + /// loads a list of devices from the device storage + Future _loadDevices() async { + setState(() { + _isLoading = true; + }); + + try { + final devices = await _deviceStorage.loadDevices(); + setState(() { + _devicesRaw = devices; + }); + } on PlatformException catch (e) { + debugPrint('Failed to load devices: $e'); + } finally { + setState(() { + _isLoading = false; + }); + } + } + /// sort Devices by chipsDeviceTypes selection void filterDevicesByType() { List sortedDevices = []; @@ -75,7 +95,8 @@ class _HomePageState extends State { sortedDevices.add(device); } else { for (int i = 0; i < widget.chipsDeviceTypes.length; i++) { - if (deviceTypesValues[i] && device.deviceType == widget.chipsDeviceTypes[i].value) { + if (deviceTypesValues[i] && + device.deviceType == widget.chipsDeviceTypes[i].value) { sortedDevices.add(device); break; } @@ -87,44 +108,13 @@ class _HomePageState extends State { }); } - /// ping devices periodically in the background to get the current status - /// of the devices and update the ui accordingly - void _pingDevices() { - print('---------timer started----------'); - _timer = Timer.periodic(const Duration(seconds: 10), (timer) { - for (StorageDevice device in _devices) { - checkStatus(device); - } - }); - } - - /// ping a device and update the ui accordingly - /// [device] is the device to ping - /// if the widget is not mounted anymore, the function will stop - Future checkStatus(StorageDevice device) async { - print('pinging device: ${device.hostName}'); - bool isOnline = await pingDevice(ipAddress: device.ipAddress); - setState(() { - device.isOnline = isOnline; - }); - // while (mounted) { - // print('pinging device: ${device.hostName}'); - // bool isOnline = await pingDevice(ipAddress: device.ipAddress); - // if (mounted) { - // setState(() { - // device.isOnline = isOnline; - // }); - // } - // await Future.delayed(const Duration(seconds: 10)); - // } - } - /// sort devices by selectedMenu value. [alphabetical], [recently] and [type] are possible. void sortDevices() { switch (selectedMenu) { case SortingOrder.alphabetical: setState(() { - _devices.sort((a, b) => a.hostName.toLowerCase().compareTo(b.hostName.toLowerCase())); + _devices.sort((a, b) => + a.hostName.toLowerCase().compareTo(b.hostName.toLowerCase())); }); break; case SortingOrder.recently: @@ -134,28 +124,40 @@ class _HomePageState extends State { break; case SortingOrder.type: setState(() { - _devices.sort((a, b) => a.deviceType == null ? -1 : a.deviceType!.compareTo(b.deviceType ?? '')); + _devices.sort((a, b) => a.deviceType == null + ? -1 + : a.deviceType!.compareTo(b.deviceType ?? '')); }); break; } } - /// loads a list of devices from the device storage - Future _loadDevices() async { - setState(() { - _isLoading = true; + /// ping devices periodically in the background to get the current status + /// of the devices and update the ui accordingly + void _pingDevices() { + checkAllDevicesStatus(); + print('---------timer started----------'); + _timer = Timer.periodic(const Duration(seconds: 10), (timer) { + checkAllDevicesStatus(); }); + } - try { - final devices = await _deviceStorage.loadDevices(); - setState(() { - _devicesRaw = devices; - }); - } on PlatformException catch (e) { - debugPrint('Failed to load devices: $e'); - } finally { + /// updates the status of all devices in [_devices] + Future checkAllDevicesStatus() async { + for (StorageDevice device in _devices) { + checkDeviceStatus(device); + } + } + + /// ping a device and update the ui accordingly + /// [device] is the device to ping + /// if the widget is not mounted anymore, the function will stop + Future checkDeviceStatus(StorageDevice device) async { + print('pinging device: ${device.hostName}'); + bool isOnline = await pingDevice(ipAddress: device.ipAddress); + if (mounted) { setState(() { - _isLoading = false; + device.isOnline = isOnline; }); } } @@ -174,7 +176,8 @@ class _HomePageState extends State { ), // The menu should appear below the button. The offset is dependent of the selected menu item so the offset is calculated dependent of // the current selected menu item. - offset: Offset(0, 00 + 50.0 * (SortingOrder.values[selectedMenu.index].index + 1)), + offset: Offset(0, + 00 + 50.0 * (SortingOrder.values[selectedMenu.index].index + 1)), initialValue: selectedMenu, // Callback that sets the selected popup menu item. onSelected: (SortingOrder item) { @@ -207,6 +210,7 @@ class _HomePageState extends State { MaterialPageRoute( builder: (context) => DiscoverPage( updateDevicesList: updateDevicesList, + devices: _devices, )), ); if (newDevice != null) { @@ -221,39 +225,53 @@ class _HomePageState extends State { ); } - updateDevicesList(devices) { + /// callback function for updating the list of devices + /// [devices] is the list of devices + /// [deviceId] is the changed device id. This devices gets pinged additionally to the background timer to get the current status. + /// If it is set to null, no device gets pinged (e.g. if device gets deleted, this devices doesn't need to get pinged) + updateDevicesList(List devices, String? deviceId) { setState(() { - //_devices.add(device); _devicesRaw = devices; filterDevicesByType(); sortDevices(); + if (deviceId != null) { + StorageDevice device = + devices.firstWhere((element) => element.id == deviceId); + // set online state to null because online state is not known yet + device.isOnline = null; + checkDeviceStatus(device); + } }); } Widget buildListview() { - return ListView( - padding: AppConstants.screenPaddingScrollView, - children: [ - TextTitle( - title: AppLocalizations.of(context)!.homeFilterDevicesTitle, - children: [ - SizedBox( - height: 50, - child: filterDevicesChipsV2(), - ), - ], - ), - TextTitle( - title: AppLocalizations.of(context)!.homeDeviceListTitle, - children: [buildDeviceList()], - ), - ], + return RefreshIndicator( + onRefresh: () async {}, + child: ListView( + padding: AppConstants.screenPaddingScrollView, + children: [ + TextTitle( + title: AppLocalizations.of(context)!.homeFilterDevicesTitle, + children: [ + SizedBox( + height: 50, + child: filterDevicesChipsV2(), + ), + ], + ), + TextTitle( + title: AppLocalizations.of(context)!.homeDeviceListTitle, + children: [buildDeviceList()], + ), + ], + ), ); } /// returns a List of Chips for filtering devices ListView filterDevicesChipsV2() { - List> chipsDeviceTypes = AppConstants().getChipsDeviceTypes(context: context); + List> chipsDeviceTypes = + AppConstants().getChipsDeviceTypes(context: context); return ListView( primary: true, shrinkWrap: true, @@ -271,9 +289,13 @@ class _HomePageState extends State { if (label != null) Text(label), ], ), - backgroundColor: deviceTypesValues[index] ? Theme.of(context).colorScheme.secondaryContainer : Theme.of(context).colorScheme.surface, + backgroundColor: deviceTypesValues[index] + ? Theme.of(context).colorScheme.secondaryContainer + : Theme.of(context).colorScheme.surface, side: BorderSide( - color: deviceTypesValues[index] ? Theme.of(context).colorScheme.secondaryContainer : Theme.of(context).colorScheme.secondary, + color: deviceTypesValues[index] + ? Theme.of(context).colorScheme.secondaryContainer + : Theme.of(context).colorScheme.secondary, width: 1.0, ), // selected: mode == chipsTheme[index].value, @@ -291,7 +313,8 @@ class _HomePageState extends State { /// returns a List of Chips for filtering devices ListView filterDevicesChipsV1() { - List> chipsDeviceTypes = AppConstants().getChipsDeviceTypes(context: context); + List> chipsDeviceTypes = + AppConstants().getChipsDeviceTypes(context: context); return ListView( primary: true, shrinkWrap: true, @@ -332,7 +355,8 @@ class _HomePageState extends State { child: CircularProgressIndicator(), ) : _devices.isEmpty - ? Text(AppLocalizations.of(context)!.homeNoDevices, style: Theme.of(context).textTheme.bodyMedium) + ? Text(AppLocalizations.of(context)!.homeNoDevices, + style: Theme.of(context).textTheme.bodyMedium) : ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), @@ -379,12 +403,20 @@ class _HomePageState extends State { showDialog( context: context, builder: (BuildContext context) { - return deviceInfoDialog(device: device, title: title, subtitle1: subtitle1, subtitle2: subtitle2); + return deviceInfoDialog( + device: device, + title: title, + subtitle1: subtitle1, + subtitle2: subtitle2); }); } /// returns the actual alert dialog for waking and editing the device - Widget deviceInfoDialog({required StorageDevice device, required String title, required String subtitle1, required String subtitle2}) { + Widget deviceInfoDialog( + {required StorageDevice device, + required String title, + required String subtitle1, + required String subtitle2}) { return customDualChoiceAlertdialog( title: title != "" ? title : null, child: (subtitle1 != "" || subtitle2 != "") @@ -404,7 +436,12 @@ class _HomePageState extends State { rightOnPressed: () => { Navigator.of(context).pop(), showCustomBottomSheet( - context: context, formPage: EditDeviceFormPage(title: "Edit Device", device: device, onSubmitDeviceCallback: updateDevicesList)) + context: context, + formPage: EditDeviceFormPage( + title: "Edit Device", + device: device, + devices: _devices, + onSubmitDeviceCallback: updateDevicesList)) }); } @@ -416,18 +453,21 @@ class _HomePageState extends State { builder: (context) { return StreamBuilder>( stream: sendWolAndGetMessages(device: device.toNetworkDevice()), - builder: (BuildContext context, AsyncSnapshot> snapshot) { + builder: (BuildContext context, + AsyncSnapshot> snapshot) { // set color, text and icon of dialog box according to the arrived messages Color? color; String rightText = AppLocalizations.of(context)!.cancel; IconData? rightIcon = AppConstants.denyIcon; - if (snapshot.hasData && snapshot.data!.last.type == MsgType.online) { + if (snapshot.hasData && + snapshot.data!.last.type == MsgType.online) { color = AppConstants.successMessageColor; rightText = AppLocalizations.of(context)!.done; rightIcon = AppConstants.checkIcon; } - if (snapshot.hasData && snapshot.data!.last.type == MsgType.error) { + if (snapshot.hasData && + snapshot.data!.last.type == MsgType.error) { color = Theme.of(context).colorScheme.error; rightText = AppLocalizations.of(context)!.ok; rightIcon = null; @@ -439,7 +479,8 @@ class _HomePageState extends State { ? SizedBox( width: 200, child: ListView.separated( - separatorBuilder: (context, index) => const Divider(), + separatorBuilder: (context, index) => + const Divider(), shrinkWrap: true, itemCount: snapshot.data!.length, itemBuilder: (context, index) { @@ -449,7 +490,8 @@ class _HomePageState extends State { style: TextStyle( color: (message.type == MsgType.error) ? Theme.of(context).colorScheme.error - : (message.type == MsgType.check || message.type == MsgType.online) + : (message.type == MsgType.check || + message.type == MsgType.online) ? AppConstants.successMessageColor : null, ), diff --git a/lib/services/data.dart b/lib/services/data.dart index 434de7a..da93e01 100644 --- a/lib/services/data.dart +++ b/lib/services/data.dart @@ -1,9 +1,5 @@ -import 'dart:io'; - import 'package:simple_wake_on_lan/services/utilities.dart'; -import 'network.dart'; - abstract class Device implements Comparable { final String hostName; final String ipAddress; @@ -34,7 +30,7 @@ abstract class Device implements Comparable { class StorageDevice extends Device { final String id; final DateTime modified; - bool isOnline = false; + bool? isOnline; StorageDevice( {required this.id, @@ -42,7 +38,7 @@ class StorageDevice extends Device { required ipAddress, required macAddress, wolPort, - isOnline, + this.isOnline, required this.modified, deviceType}) : super( @@ -66,6 +62,7 @@ class StorageDevice extends Device { int? wolPort, DateTime? modified, String? deviceType, + bool? isOnline, }) { return StorageDevice( id: id ?? this.id, @@ -75,6 +72,7 @@ class StorageDevice extends Device { wolPort: wolPort ?? this.wolPort, modified: modified ?? this.modified, deviceType: deviceType ?? this.deviceType, + isOnline: isOnline ?? this.isOnline, ); } @@ -112,14 +110,6 @@ class StorageDevice extends Device { deviceType: deviceType, ); } - - /// pings the device with this ip address and returns true if the device is online - Future checkStatus() async { - while (true) { - isOnline = await pingDevice(ipAddress: ipAddress); - await Future.delayed(const Duration(seconds: 10)); - } - } } class NetworkDevice extends Device { diff --git a/lib/services/database.dart b/lib/services/database.dart index e2b5b5d..2640720 100644 --- a/lib/services/database.dart +++ b/lib/services/database.dart @@ -39,19 +39,20 @@ class DeviceStorage { /// Adds a new device to the list of devices /// [device] the device to add - Future> addDevice(NetworkDevice device) async { - final devices = await loadDevices(); + Future<(List, StorageDevice)> addDevice( + NetworkDevice device, List devices) async { final storageDevice = device.toStorageDevice(id: const Uuid().v1(), modified: DateTime.now()); final updatedDevices = [...devices, storageDevice]; await saveDevices(updatedDevices); - return updatedDevices; + return (updatedDevices, storageDevice); } /// Updates a device in the list of devices /// [updatedDevice] the device to update - Future> updateDevice(StorageDevice updatedDevice) async { - final devices = await loadDevices(); + /// [devices] the list of all devices + Future<(List, StorageDevice)> updateDevice( + StorageDevice updatedDevice, List devices) async { final updatedDevices = devices.map((device) { if (device.id == updatedDevice.id) { return updatedDevice.copyWith(modified: DateTime.now()); @@ -59,13 +60,13 @@ class DeviceStorage { return device; }).toList(); await saveDevices(updatedDevices); - return updatedDevices; + return (updatedDevices, updatedDevice); } /// Deletes a device from the list of devices /// [deviceId] the id of the device to delete - Future> deleteDevice(String deviceId) async { - final devices = await loadDevices(); + Future> deleteDevice( + String deviceId, List devices) async { final updatedDevices = devices.where((device) => device.id != deviceId).toList(); await saveDevices(updatedDevices); diff --git a/lib/services/network.dart b/lib/services/network.dart index 143f7b9..0e6b379 100644 --- a/lib/services/network.dart +++ b/lib/services/network.dart @@ -159,7 +159,7 @@ Stream> sendWolAndGetMessages( /// ping a list of devices and return their status Future pingDevice({required String ipAddress}) async { - final ping = Ping(ipAddress, count: 1, timeout: 5); + final ping = Ping(ipAddress, count: 1, timeout: 2); // Wait for the current ping to complete await for (final response in ping.stream) { diff --git a/lib/widgets/layout_elements.dart b/lib/widgets/layout_elements.dart index b17cdd5..0b70ed9 100644 --- a/lib/widgets/layout_elements.dart +++ b/lib/widgets/layout_elements.dart @@ -144,7 +144,7 @@ class DeviceCard extends StatelessWidget { final VoidCallback? onTap; final String? title, subtitle, deviceType; final Widget? trailing; - final bool isOnline; + final bool? isOnline; const DeviceCard( {super.key, @@ -153,7 +153,7 @@ class DeviceCard extends StatelessWidget { this.subtitle, this.deviceType, this.trailing, - this.isOnline = false}); + this.isOnline}); @override Widget build(BuildContext context) { @@ -184,8 +184,11 @@ class DeviceCard extends StatelessWidget { right: 0.0, child: Icon(Icons.brightness_1, size: 10.0, - color: - isOnline ? Colors.green : Colors.redAccent), + color: isOnline == null + ? Colors.grey + : isOnline! + ? Colors.green + : Colors.redAccent), ) ], )) diff --git a/pubspec.yaml b/pubspec.yaml index 4513185..1cc1020 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.1.0+27 environment: - sdk: '>=2.19.2 <3.0.0' + sdk: '>=3.0.0' # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions