finished schedules screens

This commit is contained in:
2026-01-01 12:53:20 -06:00
parent 00c54159d4
commit 331d926c2b
5 changed files with 282 additions and 68 deletions

View File

@@ -1,12 +1,31 @@
import 'package:blind_master/BlindMasterResources/error_snackbar.dart';
import 'package:blind_master/BlindMasterResources/secure_transmissions.dart';
import 'package:blind_master/main.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
class DayTimePicker extends StatefulWidget {
const DayTimePicker({super.key, required this.defaultTime, required this.sendSchedule});
const DayTimePicker({
super.key,
required this.defaultTime,
required this.sendSchedule,
required this.peripheralId,
required this.peripheralNum,
required this.deviceId,
this.existingSchedule,
this.scheduleId,
});
final TimeOfDay defaultTime;
final void Function(TimeOfDay) sendSchedule;
final int peripheralId;
final int peripheralNum;
final int deviceId;
final Map<String, dynamic>? existingSchedule;
final String? scheduleId;
bool get isEditing => existingSchedule != null && scheduleId != null;
@override
State<DayTimePicker> createState() => _DayTimePickerState();
}
@@ -16,10 +35,25 @@ class _DayTimePickerState extends State<DayTimePicker> {
double _blindPosition = 0;
String imagePath = "";
Set<DaysOfWeek> days = <DaysOfWeek>{};
bool showError = false;
@override
void initState() {
super.initState();
// If editing, pre-populate with existing schedule data
if (widget.isEditing && widget.existingSchedule != null) {
final schedule = widget.existingSchedule!;
final hour = schedule['schedule']['hours'][0] as int;
final minute = schedule['schedule']['minutes'][0] as int;
scheduleTime = TimeOfDay(hour: hour, minute: minute);
_blindPosition = (schedule['pos'] as int).toDouble();
// Pre-populate days
final daysOfWeek = schedule['schedule']['daysOfWeek'] as List;
days = daysOfWeek.map((d) => DaysOfWeek.values[d as int]).toSet();
}
updateBackground();
}
@@ -84,30 +118,38 @@ class _DayTimePickerState extends State<DayTimePicker> {
Align(
alignment: Alignment.center,
child: Container(
margin: EdgeInsets.only(top: MediaQuery.of(context).size.width * 0.05),
height: MediaQuery.of(context).size.width * 0.43,
width: MediaQuery.of(context).size.width * 0.45,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.generate(10, (index) {
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
height: _blindPosition < 5 ?
3.65 * (5 - _blindPosition)
: 3.65 * (_blindPosition - 5),
width: MediaQuery.of(context).size.width * 0.40, // example
color: const Color.fromARGB(255, 121, 85, 72),
);
}),
),
child: Builder(
builder: (context) {
final containerHeight = MediaQuery.of(context).size.width * 0.43;
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.45,
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.40,
color: const Color.fromARGB(255, 121, 85, 72),
);
}),
),
);
}
)
),
],
),
// Slider on the side
Align(
alignment: Alignment.centerRight,
SizedBox(
width: MediaQuery.of(context).size.width * 0.10,
child: RotatedBox(
quarterTurns: -1,
child: Slider(
@@ -171,6 +213,7 @@ class _DayTimePickerState extends State<DayTimePicker> {
setState(() {
if (selected) {
days.add(day);
showError = false;
} else {
days.remove(day);
}
@@ -179,6 +222,18 @@ class _DayTimePickerState extends State<DayTimePicker> {
);
}).toList(),
),
if (showError)
Container(
padding: EdgeInsets.only(top: 10),
child: Text(
'Please select at least one day',
style: TextStyle(
color: Colors.red,
fontSize: 14,
),
textAlign: TextAlign.center,
),
),
],
),
actions: [
@@ -196,6 +251,78 @@ class _DayTimePickerState extends State<DayTimePicker> {
),
)
),
ElevatedButton(
onPressed: () async {
if (days.isEmpty) {
setState(() {
showError = true;
});
return;
}
try {
// Convert DaysOfWeek enum to day numbers (0=Sunday, 1=Monday, etc.)
final daysOfWeek = days.map((day) => day.index).toList();
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!;
}
final endpoint = widget.isEditing ? 'update_schedule' : 'add_schedule';
final response = await securePost(payload, endpoint);
if (response == null) throw Exception("Auth Error");
// Handle duplicate schedule (409 Conflict)
if (response.statusCode == 409) {
if (!context.mounted) return;
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('A schedule already exists at this time for this blind'),
backgroundColor: Colors.orange,
duration: Duration(seconds: 4),
)
);
return;
}
if (response.statusCode != 201 && response.statusCode != 200) {
throw Exception("Server Error");
}
if (!context.mounted) return;
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(widget.isEditing ? 'Schedule updated successfully' : 'Schedule added successfully'),
backgroundColor: Colors.green,
)
);
} catch (e) {
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e));
}
},
child: Text(widget.isEditing ? "Update" : "Add")
)
]
)
],