import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'constants.dart'; class Category { Category(this.name) : devices = []; final String name; List devices; static Future> fetch() async { final response = await http.get(Uri.parse("${Constants.BASE_URL}/devices")); if (response.statusCode != 200) { throw Exception("Failed to fetch devices"); } final List categories = []; final Map> json = Map.castFrom(jsonDecode(jsonDecode(response.body))); for (MapEntry> category_entry in json.entries) { final Category category = Category(category_entry.key); for (dynamic device_info_dyn in category_entry.value) { final Map device_info = device_info_dyn; final String device_id = device_info['id']; final String? device_descriptor = device_info['desc']; final bool power_control = device_info['toggle']; final response = await http .get(Uri.parse("${Constants.BASE_URL}/plug_state/$device_id")); if (response.statusCode != 200) { throw Exception("Failed to fetch plug_state for $device_id"); } final Map device_state = Map.castFrom(jsonDecode(jsonDecode(response.body))); final Device device = Device( device_id, device_descriptor, device_state["led"], device_state["power"], device_state["power_draw"], power_control && device_state["power_draw"] < 15, ); category.devices.add(device); } categories.add(category); } return categories; } } class CategoryWidget extends StatelessWidget { Category category; CategoryWidget({super.key, required this.category}); String capitalize(String s) { return "${s[0].toUpperCase()}${s.substring(1)}"; } @override Widget build(BuildContext context) { var list = category.devices .map((device) => TableRow(children: [ Text( textAlign: TextAlign.center, device.device_descriptor ?? device.device_id), DeviceLed( device_id: device.device_id, led_state: device.led_state), DevicePower( device_id: device.device_id, power_state: device.power_state, power_control: device.power_control), Text(textAlign: TextAlign.center, "${device.power_draw} W") ])) .toList(); list.insert( 0, const TableRow( decoration: BoxDecoration( color: Colors.blueGrey, ), children: [ Text( style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center, "Name"), Text( style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center, "LED"), Text( style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center, "Power"), Text( style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center, "Power Draw"), ])); return Column(children: [ Text( style: const TextStyle(fontWeight: FontWeight.bold), textScaleFactor: 2.0, capitalize(category.name)), Table( border: TableBorder.all(), defaultVerticalAlignment: TableCellVerticalAlignment.middle, children: list) ]); } } class Device { final String device_id; String? device_descriptor; bool led_state; bool power_state; double power_draw; bool power_control; Device(this.device_id, this.device_descriptor, this.led_state, this.power_state, this.power_draw, this.power_control); } class DeviceLed extends StatefulWidget { final String device_id; final bool led_state; DeviceLed({super.key, required this.device_id, required this.led_state}); @override State createState() => DeviceLedState(device_id, led_state); } class DeviceLedState extends State { final String device_id; bool led_state; String _led_state_info; DeviceLedState(this.device_id, this.led_state) : this._led_state_info = led_state ? "On" : "Off"; void _toggle_led_state() { String target_state; if (led_state) { target_state = "off"; } else { target_state = "on"; } change_plug_state(device_id, "led", target_state).then((device_state) { led_state = device_state["led"]; setState(() { _led_state_info = led_state ? "On" : "Off"; }); }); } @override Widget build(BuildContext context) { return TextButton( onPressed: _toggle_led_state, child: Text(textAlign: TextAlign.center, _led_state_info)); } } class DevicePower extends StatefulWidget { final String device_id; final bool power_state; final bool power_control; DevicePower( {super.key, required this.device_id, required this.power_state, required this.power_control}); @override State createState() => DevicePowerState(device_id, power_state, power_control); } class DevicePowerState extends State { final String device_id; bool power_state; String _power_state_info; bool power_control; DevicePowerState(this.device_id, this.power_state, this.power_control) : this._power_state_info = power_state ? "On" : "Off"; void _toggle_power_state() { String target_state; if (power_state) { target_state = "off"; } else { target_state = "on"; } change_plug_state(device_id, "power", target_state).then((device_state) { power_state = device_state["power"]; power_control = device_state["power_draw"] < 15; setState(() { _power_state_info = power_state ? "On" : "Off"; }); }); } @override Widget build(BuildContext context) { if (power_control) { return TextButton( onPressed: _toggle_power_state, child: Text(textAlign: TextAlign.center, _power_state_info)); } else { return Text(_power_state_info, textAlign: TextAlign.center); } } } Future> change_plug_state( String device_id, String module, String state) async { var response = await http.post( Uri.parse("${Constants.BASE_URL}/plug/$device_id/${module}_$state")); if (response.statusCode != 200) { throw Exception("Failed to post new state"); } response = await http.get(Uri.parse("${Constants.BASE_URL}/plug_state/$device_id")); if (response.statusCode != 200) { throw Exception("Failed to fetch plug_state for $device_id"); } return Map.castFrom(jsonDecode(jsonDecode(response.body))); }