diff --git a/lib/BlindMasterResources/blind_control_widget.dart b/lib/BlindMasterResources/blind_control_widget.dart new file mode 100644 index 0000000..143397b --- /dev/null +++ b/lib/BlindMasterResources/blind_control_widget.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; + +class BlindControlWidget extends StatelessWidget { + final String imagePath; + final double blindPosition; + final Function(double) onPositionChanged; + + const BlindControlWidget({ + super.key, + required this.imagePath, + required this.blindPosition, + required this.onPositionChanged, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: MediaQuery.of(context).size.height * 0.5, + child: Container( + padding: const EdgeInsets.fromLTRB(0, 20, 0, 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width * 0.15, + ), + Stack( + children: [ + // Background image + Align( + alignment: Alignment.center, + child: Image.asset( + imagePath, + width: MediaQuery.of(context).size.width * 0.7, + ), + ), + // Blind slats overlay + Align( + alignment: Alignment.center, + child: LayoutBuilder( + builder: (context, constraints) { + final containerHeight = MediaQuery.of(context).size.width * 0.68; + final maxSlatHeight = containerHeight / 10; + final slatHeight = blindPosition < 5 + ? maxSlatHeight * (5 - blindPosition) / 5 + : maxSlatHeight * (blindPosition - 5) / 5; + + return Container( + margin: EdgeInsets.only(top: MediaQuery.of(context).size.width * 0.05), + height: containerHeight, + width: MediaQuery.of(context).size.width * 0.7, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate(10, (index) { + return AnimatedContainer( + duration: const Duration(milliseconds: 300), + height: slatHeight, + width: MediaQuery.of(context).size.width * 0.65, + color: const Color.fromARGB(255, 121, 85, 72), + ); + }), + ), + ); + } + ) + ) + ], + ), + // Slider on the side + Expanded( + child: Center( + child: RotatedBox( + quarterTurns: -1, + child: Slider( + value: blindPosition, + activeColor: Theme.of(context).primaryColorDark, + thumbColor: Theme.of(context).primaryColorLight, + inactiveColor: Theme.of(context).primaryColorDark, + min: 0, + max: 10, + divisions: 10, + onChanged: onPositionChanged, + ), + ), + ) + ) + ], + ), + ), + ); + } +} diff --git a/lib/BlindMasterScreens/day_time_picker.dart b/lib/BlindMasterScreens/day_time_picker.dart index 34fbfad..6010b5d 100644 --- a/lib/BlindMasterScreens/day_time_picker.dart +++ b/lib/BlindMasterScreens/day_time_picker.dart @@ -9,22 +9,25 @@ class DayTimePicker extends StatefulWidget { super.key, required this.defaultTime, required this.sendSchedule, - required this.peripheralId, - required this.peripheralNum, - required this.deviceId, + this.peripheralId, + this.peripheralNum, + this.deviceId, + this.groupId, this.existingSchedule, this.scheduleId, }); final TimeOfDay defaultTime; final void Function(TimeOfDay) sendSchedule; - final int peripheralId; - final int peripheralNum; - final int deviceId; + final int? peripheralId; + final int? peripheralNum; + final int? deviceId; + final int? groupId; final Map? existingSchedule; final String? scheduleId; bool get isEditing => existingSchedule != null && scheduleId != null; + bool get isGroupMode => groupId != null; @override State createState() => _DayTimePickerState(); @@ -266,24 +269,45 @@ class _DayTimePickerState extends State { final timeToUse = scheduleTime ?? widget.defaultTime; - final payload = { - 'periphId': widget.peripheralId, - 'periphNum': widget.peripheralNum, - 'deviceId': widget.deviceId, - 'newPos': _blindPosition.toInt(), - 'time': { - 'hour': timeToUse.hour, - 'minute': timeToUse.minute, - }, - 'daysOfWeek': daysOfWeek, - }; - // Add jobId if editing - if (widget.isEditing) { - payload['jobId'] = widget.scheduleId!; + Map payload; + String endpoint; + + if (widget.isGroupMode) { + payload = { + 'groupId': widget.groupId, + 'newPos': _blindPosition.toInt(), + 'time': { + 'hour': timeToUse.hour, + 'minute': timeToUse.minute, + }, + 'daysOfWeek': daysOfWeek, + }; + + if (widget.isEditing) { + payload['jobId'] = widget.scheduleId!; + } + + endpoint = widget.isEditing ? 'update_group_schedule' : 'add_group_schedule'; + } else { + payload = { + 'periphId': widget.peripheralId, + 'periphNum': widget.peripheralNum, + 'deviceId': widget.deviceId, + 'newPos': _blindPosition.toInt(), + 'time': { + 'hour': timeToUse.hour, + 'minute': timeToUse.minute, + }, + 'daysOfWeek': daysOfWeek, + }; + + if (widget.isEditing) { + payload['jobId'] = widget.scheduleId!; + } + + endpoint = widget.isEditing ? 'update_schedule' : 'add_schedule'; } - - final endpoint = widget.isEditing ? 'update_schedule' : 'add_schedule'; final response = await securePost(payload, endpoint); if (response == null) throw Exception("Auth Error"); diff --git a/lib/BlindMasterScreens/groupControl/edit_group_dialog.dart b/lib/BlindMasterScreens/groupControl/edit_group_dialog.dart new file mode 100644 index 0000000..026258a --- /dev/null +++ b/lib/BlindMasterScreens/groupControl/edit_group_dialog.dart @@ -0,0 +1,277 @@ +import 'dart:convert'; + +import 'package:blind_master/BlindMasterResources/secure_transmissions.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class EditGroupDialog extends StatefulWidget { + const EditGroupDialog({ + super.key, + required this.groupId, + required this.groupName, + required this.currentPeripheralIds, + }); + + final int groupId; + final String groupName; + final List currentPeripheralIds; + + @override + State createState() => _EditGroupDialogState(); +} + +class _EditGroupDialogState extends State { + List> devices = []; + Map>> devicePeripherals = {}; + Set selectedPeripheralIds = {}; + bool isLoading = true; + String? errorMessage; + + final bool dev = true; + + @override + void initState() { + super.initState(); + selectedPeripheralIds = widget.currentPeripheralIds.toSet(); + _loadDevicesAndPeripherals(); + } + + Future _loadDevicesAndPeripherals() async { + setState(() { + isLoading = true; + errorMessage = null; + }); + + try { + // Fetch devices + final devicesResponse = await secureGet('device_list'); + if (devicesResponse == null || devicesResponse.statusCode != 200) { + throw Exception('Failed to load devices'); + } + + final devicesBody = jsonDecode(devicesResponse.body); + final deviceIds = List.from(devicesBody['device_ids']); + final deviceNames = List.from(devicesBody['devices']); + + devices = List.generate(deviceIds.length, (i) => { + 'id': deviceIds[i], + 'name': deviceNames[i], + }); + + // Fetch peripherals for each device + for (var device in devices) { + final periphResponse = await secureGet( + 'peripheral_list', + queryParameters: {'deviceId': device['id'].toString()} + ); + + if (periphResponse != null && periphResponse.statusCode == 200) { + final periphBody = jsonDecode(periphResponse.body); + final periphIds = List.from(periphBody['peripheral_ids']); + final periphNames = List.from(periphBody['peripheral_names']); + + devicePeripherals[device['id']] = List.generate(periphIds.length, (i) => { + 'id': periphIds[i], + 'name': periphNames[i], + }); + } else { + devicePeripherals[device['id']] = []; + } + } + + setState(() { + isLoading = false; + }); + } catch (e) { + setState(() { + isLoading = false; + errorMessage = 'Error loading devices: ${e.toString()}'; + }); + } + } + + Future _updateGroup() async { + if (selectedPeripheralIds.length < 2 && !dev) { + setState(() { + errorMessage = 'Please select at least 2 blinds'; + }); + return; + } + + try { + final response = await securePost( + { + 'groupId': widget.groupId, + 'peripheral_ids': selectedPeripheralIds.toList(), + }, + 'update_group' + ); + + if (response != null && response.statusCode == 200) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Group updated successfully'), + backgroundColor: Colors.green, + ), + ); + Navigator.of(context).pop(); + } + } else if (response != null && response.statusCode == 409) { + final errorBody = jsonDecode(response.body); + setState(() { + errorMessage = errorBody['error'] ?? 'This combination of blinds already exists'; + }); + } else { + setState(() { + errorMessage = 'Failed to update group'; + }); + } + } catch (e) { + setState(() { + errorMessage = 'Error updating group: ${e.toString()}'; + }); + } + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text( + 'Edit "${widget.groupName}"', + style: GoogleFonts.aBeeZee(), + ), + content: SizedBox( + width: double.maxFinite, + child: isLoading + ? const Center(child: CircularProgressIndicator()) + : Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: selectedPeripheralIds.length >= 2 || dev + ? Theme.of(context).primaryColorLight.withValues(alpha: 0.5) + : Colors.orange.withValues(alpha: 0.5), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + '${selectedPeripheralIds.length} blind${selectedPeripheralIds.length != 1 ? 's' : ''} selected', + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 16), + if (errorMessage != null) + Container( + padding: const EdgeInsets.all(8), + margin: const EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: Colors.red.withValues(alpha: 0.5), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + errorMessage!, + ), + ), + Flexible( + child: devices.isEmpty + ? const Text('No devices found') + : ListView.builder( + shrinkWrap: true, + itemCount: devices.length, + itemBuilder: (context, i) { + final device = devices[i]; + final peripherals = devicePeripherals[device['id']] ?? []; + + if (peripherals.isEmpty) { + return const SizedBox.shrink(); + } + + return ExpansionTile( + title: Text( + device['name'], + style: const TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Text('${peripherals.length} blind${peripherals.length != 1 ? 's' : ''}'), + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Wrap( + spacing: 8.0, + runSpacing: 8.0, + children: peripherals.map((peripheral) { + final isSelected = selectedPeripheralIds.contains(peripheral['id']); + return FilterChip( + showCheckmark: false, + label: Text(peripheral['name']), + selected: isSelected, + selectedColor: Theme.of(context).primaryColorDark, + labelStyle: TextStyle( + color: isSelected + ? Theme.of(context).highlightColor + : null, + ), + onSelected: (bool selected) { + setState(() { + if (selected) { + selectedPeripheralIds.add(peripheral['id']); + } else { + selectedPeripheralIds.remove(peripheral['id']); + } + if (errorMessage != null) { + errorMessage = null; + } + }); + }, + ); + }).toList(), + ), + ), + ], + ); + }, + ), + ), + ], + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ElevatedButton( + onPressed: () { + Navigator.of(context).pop(); + }, + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10) + ), + ), + child: const Text( + "Cancel", + style: TextStyle( + color: Colors.red + ), + ) + ), + ElevatedButton( + onPressed: isLoading ? null : _updateGroup, + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10) + ), + backgroundColor: Theme.of(context).primaryColorDark, + foregroundColor: Theme.of(context).highlightColor, + ), + child: const Text("Update") + ) + ] + ) + ], + ); + } +} diff --git a/lib/BlindMasterScreens/groupControl/group_screen.dart b/lib/BlindMasterScreens/groupControl/group_screen.dart index ebd0c27..90b5a76 100644 --- a/lib/BlindMasterScreens/groupControl/group_screen.dart +++ b/lib/BlindMasterScreens/groupControl/group_screen.dart @@ -1,8 +1,12 @@ import 'dart:convert'; +import 'package:blind_master/BlindMasterResources/blind_control_widget.dart'; import 'package:blind_master/BlindMasterResources/blindmaster_progress_indicator.dart'; import 'package:blind_master/BlindMasterResources/error_snackbar.dart'; import 'package:blind_master/BlindMasterResources/secure_transmissions.dart'; +import 'package:blind_master/BlindMasterResources/text_inputs.dart'; +import 'package:blind_master/BlindMasterScreens/groupControl/edit_group_dialog.dart'; +import 'package:blind_master/BlindMasterScreens/schedules_screen.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -21,6 +25,15 @@ class _GroupScreenState extends State { double _blindPosition = 5.0; List> peripherals = []; bool allCalibrated = false; + String currentGroupName = ""; + + final _groupRenameController = TextEditingController(); + + @override + void dispose() { + _groupRenameController.dispose(); + super.dispose(); + } void getImage() { final hour = DateTime.now().hour; @@ -39,6 +52,7 @@ class _GroupScreenState extends State { @override void initState() { super.initState(); + currentGroupName = widget.groupName; initAll(); } @@ -74,6 +88,95 @@ class _GroupScreenState extends State { await getGroupDetails(); } + void rename() { + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return AlertDialog( + title: Text( + "Rename Group", + style: GoogleFonts.aBeeZee(), + ), + content: BlindMasterMainInput("New Group Name", controller: _groupRenameController), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ElevatedButton( + onPressed: () { + Navigator.of(dialogContext).pop(); + }, + child: const Text( + "Cancel", + style: TextStyle( + color: Colors.red + ), + ) + ), + ElevatedButton( + onPressed: () { + updateGroupName(_groupRenameController.text, widget.groupId); + Navigator.of(dialogContext).pop(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).primaryColorDark, + foregroundColor: Theme.of(context).highlightColor, + ), + child: const Text("Rename") + ) + ] + ) + ], + ); + } + ); + } + + Future updateGroupName(String name, int id) async { + try { + if (name.isEmpty) throw Exception("New name cannot be empty!"); + final payload = { + 'groupId': id, + 'newName': name, + }; + final response = await securePost(payload, 'rename_group'); + if (response == null) throw Exception("Auth Error"); + if (response.statusCode != 204) { + if (response.statusCode == 409) throw Exception("Choose a unique name!"); + throw Exception("Server Error"); + } + if (!mounted) return; + setState(() { + currentGroupName = name; + }); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Group renamed successfully'), + backgroundColor: Colors.green, + ), + ); + } catch (e) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e)); + } + } + + void editMembers() { + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return EditGroupDialog( + groupId: widget.groupId, + groupName: currentGroupName, + currentPeripheralIds: peripherals.map((p) => p['peripheral_id'] as int).toList(), + ); + } + ).then((_) { + // Refresh group details after editing + getGroupDetails(); + }); + } + Future updateGroupPosition() async { try { final payload = { @@ -97,7 +200,7 @@ class _GroupScreenState extends State { return Scaffold( appBar: AppBar( title: Text( - widget.groupName, + currentGroupName, style: GoogleFonts.aBeeZee(), ), backgroundColor: Theme.of(context).primaryColorLight, @@ -107,84 +210,15 @@ class _GroupScreenState extends State { ? (allCalibrated ? Column( children: [ - SizedBox( - height: MediaQuery.of(context).size.height * 0.5, - child: Container( - padding: EdgeInsets.fromLTRB(0, 20, 0, 20), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: MediaQuery.of(context).size.width * 0.15, - ), - Stack( - children: [ - // Background image - Align( - alignment: Alignment.center, - child: Image.asset( - imagePath, - width: MediaQuery.of(context).size.width * 0.7, - ), - ), - - Align( - alignment: Alignment.center, - child: LayoutBuilder( - builder: (context, constraints) { - final containerHeight = MediaQuery.of(context).size.width * 0.68; - final maxSlatHeight = containerHeight / 10; - final slatHeight = _blindPosition < 5 - ? maxSlatHeight * (5 - _blindPosition) / 5 - : maxSlatHeight * (_blindPosition - 5) / 5; - - return Container( - margin: EdgeInsets.only(top: MediaQuery.of(context).size.width * 0.05), - height: containerHeight, - width: MediaQuery.of(context).size.width * 0.7, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: List.generate(10, (index) { - return AnimatedContainer( - duration: const Duration(milliseconds: 300), - height: slatHeight, - width: MediaQuery.of(context).size.width * 0.65, - color: const Color.fromARGB(255, 121, 85, 72), - ); - }), - ), - ); - } - ) - ) - ], - ), - // Slider on the side - Expanded( - child: Center( - child: RotatedBox( - quarterTurns: -1, - child: Slider( - value: _blindPosition, - activeColor: Theme.of(context).primaryColorDark, - thumbColor: Theme.of(context).primaryColorLight, - inactiveColor: Theme.of(context).primaryColorDark, - min: 0, - max: 10, - divisions: 10, - onChanged: (value) { - setState(() { - _blindPosition = value; - updateGroupPosition(); - }); - }, - ), - ), - ) - ) - ], - ), - ) + BlindControlWidget( + imagePath: imagePath, + blindPosition: _blindPosition, + onPositionChanged: (value) { + setState(() { + _blindPosition = value; + updateGroupPosition(); + }); + }, ), Container( padding: EdgeInsets.all(25), @@ -196,9 +230,14 @@ class _GroupScreenState extends State { padding: EdgeInsets.all(10), child: ElevatedButton( onPressed: () { - // TODO: Navigate to group schedules screen - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Group schedules coming soon!')) + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SchedulesScreen( + groupId: widget.groupId, + groupName: currentGroupName, + ) + ) ); }, child: Text( @@ -245,28 +284,20 @@ class _GroupScreenState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ FloatingActionButton( - heroTag: "placeholder1", - tooltip: "Placeholder", - onPressed: () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Feature coming soon!')) - ); - }, + heroTag: "rename", + tooltip: "Rename Group", + onPressed: rename, foregroundColor: Theme.of(context).highlightColor, backgroundColor: Theme.of(context).primaryColorDark, - child: Icon(Icons.settings), + child: Icon(Icons.drive_file_rename_outline_sharp), ), FloatingActionButton( - heroTag: "placeholder2", - tooltip: "Placeholder", - onPressed: () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Feature coming soon!')) - ); - }, + heroTag: "editMembers", + tooltip: "Edit Group Members", + onPressed: editMembers, foregroundColor: Theme.of(context).highlightColor, backgroundColor: Theme.of(context).primaryColorDark, - child: Icon(Icons.tune), + child: Icon(Icons.format_list_bulleted_sharp), ), ], ), diff --git a/lib/BlindMasterScreens/groupControl/groups_menu.dart b/lib/BlindMasterScreens/groupControl/groups_menu.dart index 1fd9fda..364d507 100644 --- a/lib/BlindMasterScreens/groupControl/groups_menu.dart +++ b/lib/BlindMasterScreens/groupControl/groups_menu.dart @@ -153,7 +153,10 @@ class _GroupsMenuState extends State { groupName: group['name'], ), ), - ); + ).then((_) { + // Refresh groups list when returning from group screen + getGroups(); + }); }, ), ), diff --git a/lib/BlindMasterScreens/individualControl/peripheral_screen.dart b/lib/BlindMasterScreens/individualControl/peripheral_screen.dart index 969f93c..27b65c7 100644 --- a/lib/BlindMasterScreens/individualControl/peripheral_screen.dart +++ b/lib/BlindMasterScreens/individualControl/peripheral_screen.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:blind_master/BlindMasterResources/blind_control_widget.dart'; import 'package:blind_master/BlindMasterResources/blindmaster_progress_indicator.dart'; import 'package:blind_master/BlindMasterResources/error_snackbar.dart'; import 'package:blind_master/BlindMasterResources/secure_transmissions.dart'; @@ -592,85 +593,15 @@ class _PeripheralScreenState extends State { : (calibrated ? Column( children: [ - SizedBox( - height: MediaQuery.of(context).size.height * 0.5, - child: Container( - padding: EdgeInsets.fromLTRB(0, 20, 0, 20), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: MediaQuery.of(context).size.width * 0.15, - ), - Stack( - children: [ - // Background image - Align( - alignment: Alignment.center, - child: Image.asset( - imagePath, - // fit: BoxFit.cover, - width: MediaQuery.of(context).size.width * 0.7, - ), - ), - - Align( - alignment: Alignment.center, - child: LayoutBuilder( - builder: (context, constraints) { - final containerHeight = MediaQuery.of(context).size.width * 0.68; - final maxSlatHeight = containerHeight / 10; - final slatHeight = _blindPosition < 5 - ? maxSlatHeight * (5 - _blindPosition) / 5 - : maxSlatHeight * (_blindPosition - 5) / 5; - - return Container( - margin: EdgeInsets.only(top: MediaQuery.of(context).size.width * 0.05), - height: containerHeight, - width: MediaQuery.of(context).size.width * 0.7, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: List.generate(10, (index) { - return AnimatedContainer( - duration: const Duration(milliseconds: 300), - height: slatHeight, - width: MediaQuery.of(context).size.width * 0.65, - color: const Color.fromARGB(255, 121, 85, 72), - ); - }), - ), - ); - } - ) - ) - ], - ), - // Slider on the side - Expanded( - child: Center( - child: RotatedBox( - quarterTurns: -1, - child: Slider( - value: _blindPosition, - activeColor: Theme.of(context).primaryColorDark, - thumbColor: Theme.of(context).primaryColorLight, - inactiveColor: Theme.of(context).primaryColorDark, - min: 0, - max: 10, - divisions: 10, - onChanged: (value) { - setState(() { - _blindPosition = value; - updateBlindPosition(); - }); - }, - ), - ), - ) - ) - ], - ), - ) + BlindControlWidget( + imagePath: imagePath, + blindPosition: _blindPosition, + onPositionChanged: (value) { + setState(() { + _blindPosition = value; + updateBlindPosition(); + }); + }, ), Container( padding: EdgeInsets.all(25), diff --git a/lib/BlindMasterScreens/schedules_screen.dart b/lib/BlindMasterScreens/schedules_screen.dart index cdc1fcd..67256b1 100644 --- a/lib/BlindMasterScreens/schedules_screen.dart +++ b/lib/BlindMasterScreens/schedules_screen.dart @@ -8,13 +8,26 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; class SchedulesScreen extends StatefulWidget { - const SchedulesScreen({super.key, required this.peripheralId, required this.deviceId, - required this.peripheralNum, required this.deviceName, required this.periphName}); - final int peripheralId; - final int peripheralNum; - final int deviceId; - final String deviceName; - final String periphName; + const SchedulesScreen({ + super.key, + this.peripheralId, + this.deviceId, + this.peripheralNum, + this.deviceName, + this.periphName, + this.groupId, + this.groupName, + }); + final int? peripheralId; + final int? peripheralNum; + final int? deviceId; + final String? deviceName; + final String? periphName; + final int? groupId; + final String? groupName; + + bool get isGroupMode => groupId != null; + @override State createState() => _SchedulesScreenState(); } @@ -48,10 +61,12 @@ class _SchedulesScreenState extends State { Future getSchedules() async { try{ - final payload = { - "periphId": widget.peripheralId - }; - final response = await securePost(payload, 'periph_schedule_list'); + final payload = widget.isGroupMode + ? {"groupId": widget.groupId} + : {"periphId": widget.peripheralId}; + + final endpoint = widget.isGroupMode ? 'group_schedule_list' : 'periph_schedule_list'; + final response = await securePost(payload, endpoint); if (response == null) throw Exception("no response!"); if (response.statusCode == 200) { @@ -122,7 +137,8 @@ class _SchedulesScreenState extends State { final scheduleId = schedule['id'].toString(); try { final payload = {'jobId': scheduleId}; - final response = await securePost(payload, 'delete_schedule'); + final endpoint = widget.isGroupMode ? 'delete_group_schedule' : 'delete_schedule'; + final response = await securePost(payload, endpoint); if (response == null) throw Exception("Auth Error"); if (response.statusCode != 200) { @@ -174,6 +190,7 @@ class _SchedulesScreenState extends State { peripheralId: widget.peripheralId, peripheralNum: widget.peripheralNum, deviceId: widget.deviceId, + groupId: widget.groupId, existingSchedule: schedule, scheduleId: schedule['id'].toString(), ); @@ -199,13 +216,14 @@ class _SchedulesScreenState extends State { void addSchedule() { showDialog( context: context, - builder: (BuildContext dialogContext) { // Use dialogContext for navigation within the dialog + builder: (BuildContext dialogContext) { return DayTimePicker( defaultTime: TimeOfDay(hour: 12, minute: 0), sendSchedule: sendSchedule, peripheralId: widget.peripheralId, peripheralNum: widget.peripheralNum, deviceId: widget.deviceId, + groupId: widget.groupId, ); } ).then((_) { getSchedules(); }); @@ -216,7 +234,9 @@ class _SchedulesScreenState extends State { return Scaffold( appBar: AppBar( title: Text( - "Schedules: ${widget.deviceName} - ${widget.periphName}", + widget.isGroupMode + ? "Group Schedules: ${widget.groupName}" + : "Schedules: ${widget.deviceName} - ${widget.periphName}", style: GoogleFonts.aBeeZee(), ), backgroundColor: Theme.of(context).primaryColorLight,