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 { final Category category; CategoryWidget({super.key, required this.category}); String Name() { var s = category.name; return "${s[0].toUpperCase()}${s.substring(1)}"; } @override Widget build(BuildContext context) { final double header_height = 40; final double info_height = 30; return Wrap( spacing: 25, runSpacing: 25, children: category.devices.map((device) { return Table( border: TableBorder.all(), defaultVerticalAlignment: TableCellVerticalAlignment.middle, columnWidths: const { 0: FixedColumnWidth(200.0), 1: FixedColumnWidth(200.0), }, children: [ TableRow( decoration: const BoxDecoration( color: Colors.blueGrey, ), children: [ SizedBox( height: header_height, child: Stack( children: [ Align( child: Text( textAlign: TextAlign.center, device.device_descriptor ?? device.device_id)), Align( alignment: Alignment.centerRight, child: IconButton( onPressed: () {}, icon: const Icon(Icons.settings)), ), ], )) ]), TableRow(children: [ SizedBox( height: info_height, child: Stack(children: [ const Align( alignment: Alignment(-0.9, 0.0), child: Text( style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center, "LED")), Align( alignment: const Alignment(0.5, 0.0), child: DeviceLed( device_id: device.device_id, led_state: device.led_state)), ])) ]), TableRow(children: [ SizedBox( height: info_height, child: Stack(children: [ const Align( alignment: Alignment(-0.9, 0.0), child: Text( style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center, "Power")), Align( alignment: const Alignment(0.4, 0.0), child: DevicePower( device_id: device.device_id, power_state: device.power_state, power_control: device.power_control)), ])) ]), TableRow(children: [ SizedBox( height: info_height, child: Stack(children: [ const Align( alignment: Alignment(-0.9, 0.0), child: Text( style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center, "Power Draw")), Align( alignment: const Alignment(0.5, 0.0), child: Text( textAlign: TextAlign.center, "${device.power_draw} W")) ])) ]) ]); }).toList(), ); } } 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))); }