Skip to content

Commit

Permalink
Feat/ping devices from dashboard v1.2.0 (#3)
Browse files Browse the repository at this point in the history
online state of all devices is periodically checked in the background. Defaults for ping timeout and interval are defined in `lib/constants.dart`. Dart SDK is upgraded to `v3.0.0`. Interacting with `lib/services/database.dart` was reworked
  • Loading branch information
herzhenr committed Aug 20, 2023
1 parent 0c9a7c4 commit df3df6e
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 70 deletions.
4 changes: 4 additions & 0 deletions lib/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ class AppConstants {
static const add = Icon(Icons.add);
static const sort = Icon(Icons.sort);

// Home Ping Timeouts and Intervals for scanning
static const homePingTimeout = 1;
static const homePingInterval = 12;

// Wake Up Dialog Elements
static const errorMessageColor = Colors.red;
static const successMessageColor = Colors.green;
Expand Down
2 changes: 2 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
"homeDeviceListTitle": "Devices",
"homeDeviceCardWakeButton": "Wake Up",
"homeDeviceCardEditButton": "Edit",
"homeDeviceCardOnline": "Device is online",
"homeDeviceCardOffline": "Device is offline",
"homeWolCardTitle": "Waking up...",
"@DISCOVER": {},
"discoverTitle": "Discover Devices",
Expand Down
41 changes: 22 additions & 19 deletions lib/screens/home/bottom_sheet_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import '../../widgets/universal_ui_components.dart';
abstract class ModularBottomFormPage extends StatefulWidget {
final String title;
final Device device;
final Function(List<StorageDevice>) onSubmitDeviceCallback;
final List<StorageDevice> devices;
final Function(List<StorageDevice>, String?) onSubmitDeviceCallback;
final bool deleteButton;

ModularBottomFormPage(
{Key? key,
required this.device,
required this.devices,
required this.title,
required this.onSubmitDeviceCallback,
this.deleteButton = false})
Expand Down Expand Up @@ -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(
Expand All @@ -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<List<StorageDevice>> dataOperationOnSave();
Future<(List<StorageDevice>, 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<List<StorageDevice>> dataOperationOnDelete() async {
StorageDevice device = getDevice as StorageDevice;
List<StorageDevice> devices = await deviceStorage.deleteDevice(
device.id,
);
List<StorageDevice> devices =
await deviceStorage.deleteDevice(device.id, this.devices);
return devices;
}

Expand Down Expand Up @@ -213,10 +215,11 @@ class _ModularBottomFormPageState extends State<ModularBottomFormPage> {
onPressed: () => {
validateFormFields(onSubmitDeviceCallback: () async {
Navigator.popUntil(context, (route) => route.isFirst);
List<StorageDevice> device =
(List<StorageDevice>, 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,
Expand Down Expand Up @@ -529,9 +532,9 @@ class _ModularBottomFormPageState extends State<ModularBottomFormPage> {
rightColor: Theme.of(context).colorScheme.error,
rightOnPressed: () async {
Navigator.popUntil(context, (route) => route.isFirst);
List<StorageDevice> device = await widget.dataOperationOnDelete();
List<StorageDevice> devices = await widget.dataOperationOnDelete();
// sent device to callback function in order to update the UI
widget.onSubmitDeviceCallback(device);
widget.onSubmitDeviceCallback(devices, null);
},
);
},
Expand All @@ -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<List<StorageDevice>> dataOperationOnSave() async {
List<StorageDevice> devices = await deviceStorage.addDevice(
getDevice as NetworkDevice,
);
return devices;
Future<(List<StorageDevice>, StorageDevice)> dataOperationOnSave() async {
(List<StorageDevice>, StorageDevice) updatedDevices =
await deviceStorage.addDevice(getDevice as NetworkDevice, devices);
return updatedDevices;
}
}

Expand All @@ -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<List<StorageDevice>> dataOperationOnSave() async {
List<StorageDevice> devices = await deviceStorage.updateDevice(
getDevice as StorageDevice,
);
return devices;
Future<(List<StorageDevice>, StorageDevice)> dataOperationOnSave() async {
(List<StorageDevice>, StorageDevice) updatedDevices =
await deviceStorage.updateDevice(getDevice as StorageDevice, devices);
return updatedDevices;
}
}

Expand Down
9 changes: 7 additions & 2 deletions lib/screens/home/discover.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import '../../widgets/layout_elements.dart';
import '../../widgets/universal_ui_components.dart';

class DiscoverPage extends StatefulWidget {
final Function(List<StorageDevice>) updateDevicesList;
final Function(List<StorageDevice>, String?) updateDevicesList;
final List<StorageDevice> devices;

const DiscoverPage({Key? key, required this.updateDevicesList})
const DiscoverPage(
{Key? key, required this.updateDevicesList, required this.devices})
: super(key: key);

@override
Expand Down Expand Up @@ -105,6 +107,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
title:
AppLocalizations.of(context)!.discoverAddDeviceAlertTitle,
device: NetworkDevice(),
devices: widget.devices,
onSubmitDeviceCallback: widget.updateDevicesList)),
text: AppLocalizations.of(context)!.discoverAddCustomDeviceButton,
icon: const Icon(Icons.add)),
Expand Down Expand Up @@ -167,6 +170,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
.discoverAddDeviceAlertTitle,
device: _devices[index]
.copyWith(wolPort: 9),
devices: widget.devices,
onSubmitDeviceCallback:
widget.updateDevicesList)),
);
Expand Down Expand Up @@ -221,6 +225,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
builder: (context) => NetworkDeviceFormPage(
title: title,
device: device.copyWith(wolPort: port),
devices: widget.devices,
onSubmitDeviceCallback: widget.updateDevicesList),
);
}
Expand Down
142 changes: 109 additions & 33 deletions lib/screens/home/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,44 @@ class _HomePageState extends State<HomePage> {

late SortingOrder selectedMenu = widget.selectedMenu;

Timer? _pingDevicesTimer;

@override
void initState() {
super.initState();
_loadDevices().then((value) => {
filterDevicesByType(),
sortDevices(),
_pingDevices(),
});
}

@override
void dispose() {
_pingDevicesTimer?.cancel();
super.dispose();
}

/// loads a list of devices from the device storage
Future<void> _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<StorageDevice> sortedDevices = [];
Expand Down Expand Up @@ -102,22 +131,31 @@ class _HomePageState extends State<HomePage> {
}
}

/// loads a list of devices from the device storage
Future<void> _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();
_pingDevicesTimer = Timer.periodic(
const Duration(seconds: AppConstants.homePingInterval), (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<void> 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<void> checkDeviceStatus(StorageDevice device) async {
bool isOnline = await pingDevice(ipAddress: device.ipAddress);
if (mounted) {
setState(() {
_isLoading = false;
device.isOnline = isOnline;
});
}
}
Expand Down Expand Up @@ -170,6 +208,7 @@ class _HomePageState extends State<HomePage> {
MaterialPageRoute(
builder: (context) => DiscoverPage(
updateDevicesList: updateDevicesList,
devices: _devices,
)),
);
if (newDevice != null) {
Expand All @@ -184,33 +223,53 @@ class _HomePageState extends State<HomePage> {
);
}

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<StorageDevice> 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 {
_pingDevicesTimer?.cancel();
// set online state for all devices to null because online state is not known yet
for (StorageDevice device in _devices) {
device.isOnline = null;
}
_pingDevices();
},
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()],
),
],
),
);
}

Expand Down Expand Up @@ -325,6 +384,7 @@ class _HomePageState extends State<HomePage> {
title = device.ipAddress;
}
return DeviceCard(
isOnline: device.isOnline,
title: title,
subtitle: subtitle,
deviceType: device.deviceType,
Expand Down Expand Up @@ -364,15 +424,30 @@ class _HomePageState extends State<HomePage> {
required String subtitle2}) {
return customDualChoiceAlertdialog(
title: title != "" ? title : null,
child: (subtitle1 != "" || subtitle2 != "")
child: (subtitle1 != "" || subtitle2 != "" || device.isOnline != null)
? Column(
children: [
if (device.isOnline != null)
Text(
device.isOnline!
? AppLocalizations.of(context)!.homeDeviceCardOnline
: AppLocalizations.of(context)!
.homeDeviceCardOffline,
style: TextStyle(
color: device.isOnline!
? AppConstants.successMessageColor
: Theme.of(context).colorScheme.error)),
if (subtitle1 != "") Text(subtitle1),
if (subtitle2 != "") Text(subtitle2),
],
)
: null,
icon: getIcon(device.deviceType),
iconColor: device.isOnline != null
? device.isOnline!
? AppConstants.successMessageColor
: Theme.of(context).colorScheme.error
: null,
leftText: AppLocalizations.of(context)!.homeDeviceCardWakeButton,
rightText: AppLocalizations.of(context)!.homeDeviceCardEditButton,
leftIcon: AppConstants.wakeUp,
Expand All @@ -385,6 +460,7 @@ class _HomePageState extends State<HomePage> {
formPage: EditDeviceFormPage(
title: "Edit Device",
device: device,
devices: _devices,
onSubmitDeviceCallback: updateDevicesList))
});
}
Expand Down
Loading

0 comments on commit df3df6e

Please sign in to comment.