Add schedule saving functionality and update schedule events

This commit is contained in:
mohammad
2025-06-19 15:46:40 +03:00
parent ed2a8f6ba2
commit 2267d95795
5 changed files with 186 additions and 62 deletions

View File

@ -37,28 +37,39 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
Timer? _countdownTimer; Timer? _countdownTimer;
Duration countdownRemaining = Duration.zero; Duration countdownRemaining = Duration.zero;
void _onStopScheduleEvent( Future<void> _onStopScheduleEvent(
StopScheduleEvent event, StopScheduleEvent event,
Emitter<ScheduleState> emit, Emitter<ScheduleState> emit,
) { ) async {
if (state is ScheduleLoaded) { if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded; final currentState = state as ScheduleLoaded;
_countdownTimer?.cancel();
if (event.mode == ScheduleModes.countdown) { final success = await RemoteControlDeviceService().controlDevice(
emit(currentState.copyWith( deviceUuid: deviceId,
countdownHours: 0, status: Status(
countdownMinutes: 0, code: 'countdown_1',
isCountdownActive: false, value: 0,
countdownRemaining: Duration.zero, ),
)); );
} else if (event.mode == ScheduleModes.inching) { if (success) {
emit(currentState.copyWith( _countdownTimer?.cancel();
inchingHours: 0, if (event.mode == ScheduleModes.countdown) {
inchingMinutes: 0, emit(currentState.copyWith(
isInchingActive: false, countdownHours: 0,
countdownRemaining: Duration.zero, countdownMinutes: 0,
)); isCountdownActive: false,
countdownRemaining: Duration.zero,
));
} else if (event.mode == ScheduleModes.inching) {
emit(currentState.copyWith(
inchingHours: 0,
inchingMinutes: 0,
isInchingActive: false,
countdownRemaining: Duration.zero,
));
}
} else {
emit(const ScheduleError('Failed to stop schedule'));
} }
} }
} }
@ -241,16 +252,14 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
) async { ) async {
try { try {
if (state is ScheduleLoaded) { if (state is ScheduleLoaded) {
final newSchedule = ScheduleEntry( final dateTime = DateTime.parse(event.time);
final success = await DevicesManagementApi().postSchedule(
category: event.category, category: event.category,
time: event.time, deviceId: deviceId,
function: Status(code: 'switch_1', value: event.functionOn), time: getTimeStampWithoutSeconds(dateTime).toString(),
code: 'switch_1',
value: event.functionOn,
days: event.selectedDays); days: event.selectedDays);
final success = await DevicesManagementApi().addScheduleRecord(
newSchedule,
deviceId,
);
if (success) { if (success) {
add(const ScheduleGetEvent(category: 'switch_1')); add(const ScheduleGetEvent(category: 'switch_1'));
} else { } else {
@ -268,14 +277,14 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
) async { ) async {
try { try {
if (state is ScheduleLoaded) { if (state is ScheduleLoaded) {
final dateTime = DateTime.parse(event.time);
final updatedSchedule = ScheduleEntry( final updatedSchedule = ScheduleEntry(
scheduleId: event.scheduleId, scheduleId: event.scheduleId,
category: event.category, category: event.category,
time: event.time, time: getTimeStampWithoutSeconds(dateTime).toString(),
function: Status(code: 'switch_1', value: event.functionOn), function: Status(code: 'switch_1', value: event.functionOn),
days: event.selectedDays, days: event.selectedDays,
); );
final success = await DevicesManagementApi().editScheduleRecord( final success = await DevicesManagementApi().editScheduleRecord(
deviceId, deviceId,
updatedSchedule, updatedSchedule,
@ -299,10 +308,12 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
try { try {
if (state is ScheduleLoaded) { if (state is ScheduleLoaded) {
final currentState = state as ScheduleLoaded; final currentState = state as ScheduleLoaded;
final updatedSchedules = currentState.schedules.map((schedule) { final updatedSchedules = currentState.schedules.map((schedule) {
if (schedule.scheduleId == event.scheduleId) { if (schedule.scheduleId == event.scheduleId) {
return schedule.copyWith( return schedule.copyWith(
function: Status(code: 'switch_1', value: event.functionOn), function: Status(code: 'switch_1', value: event.functionOn),
enable: event.enable,
); );
} }
return schedule; return schedule;
@ -491,10 +502,16 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
try { try {
final status = final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId); await DevicesManagementApi().getDeviceStatus(event.deviceId);
print(status.status);
final deviceStatus = final deviceStatus =
WaterHeaterStatusModel.fromJson(event.deviceId, status.status); WaterHeaterStatusModel.fromJson(event.deviceId, status.status);
final scheduleMode = deviceStatus.scheduleMode; final scheduleMode =
deviceStatus.countdownHours > 0 || deviceStatus.countdownMinutes > 0
? ScheduleModes.countdown
: deviceStatus.inchingHours > 0 || deviceStatus.inchingMinutes > 0
? ScheduleModes.inching
: ScheduleModes.schedule;
final isCountdown = scheduleMode == ScheduleModes.countdown; final isCountdown = scheduleMode == ScheduleModes.countdown;
final isInching = scheduleMode == ScheduleModes.inching; final isInching = scheduleMode == ScheduleModes.inching;
@ -516,6 +533,10 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
Duration.zero; Duration.zero;
} }
if (state is ScheduleLoaded) { if (state is ScheduleLoaded) {
print('Updating existing state with fetched status');
print('scheduleMode: $scheduleMode');
print('countdownRemaining: $countdownRemaining');
print('isCountdownActive: $isCountdownActive');
final currentState = state as ScheduleLoaded; final currentState = state as ScheduleLoaded;
emit(currentState.copyWith( emit(currentState.copyWith(
scheduleMode: scheduleMode, scheduleMode: scheduleMode,
@ -546,12 +567,54 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
)); ));
} }
if (isCountdownActive && countdownRemaining != null) { // if (isCountdownActive && countdownRemaining != null) {
_startCountdownTimer(emit, countdownRemaining); // _startCountdownTimer(emit, countdownRemaining);
} // }
} catch (e) { } catch (e) {
emit(ScheduleError('Failed to fetch device status: $e')); emit(ScheduleError('Failed to fetch device status: $e'));
} }
} }
String extractTime(String isoDateTime) {
// Example input: "2025-06-19T15:45:00.000"
return isoDateTime.split('T')[1].split('.')[0]; // gives "15:45:00"
}
int? getTimeStampWithoutSeconds(DateTime? dateTime) {
if (dateTime == null) return null;
DateTime dateTimeWithoutSeconds = DateTime(dateTime.year, dateTime.month,
dateTime.day, dateTime.hour, dateTime.minute);
return dateTimeWithoutSeconds.millisecondsSinceEpoch ~/ 1000;
}
// Future <void> _updateScheduleEvent(
// StatusUpdatedScheduleEvent event,
// Emitter<ScheduleState> emit,
// ) async {
// if (state is ScheduleLoaded) {
// final currentState = state as ScheduleLoaded;
// final updatedSchedules = currentState.schedules.map((schedule) {
// if (schedule.scheduleId == event.scheduleId) {
// return schedule.copyWith(
// function: Status(code: 'switch_1', value: event.functionOn),
// enable: event.enable,
// );
// }
// return schedule;
// }).toList();
// bool success = await DevicesManagementApi().updateScheduleRecord(
// enable: event.enable,
// uuid: currentState.status.uuid,
// scheduleId: event.scheduleId,
// );
// if (success) {
// emit(currentState.copyWith(schedules: updatedSchedules));
// } else {
// emit(currentState);
// }
// }
// }
} }

View File

@ -68,7 +68,7 @@ class ScheduleGetEvent extends ScheduleEvent {
class ScheduleAddEvent extends ScheduleEvent { class ScheduleAddEvent extends ScheduleEvent {
final String category; final String category;
final String time; final String time;
final List<String> selectedDays; final List<String> selectedDays;
final bool functionOn; final bool functionOn;
@ -219,3 +219,12 @@ class DeleteScheduleEvent extends ScheduleEvent {
@override @override
List<Object> get props => [scheduleId]; List<Object> get props => [scheduleId];
} }
class StatusUpdatedScheduleEvent extends ScheduleEvent {
final String id;
const StatusUpdatedScheduleEvent(this.id);
@override
List<Object> get props => [id];
}

View File

@ -160,28 +160,26 @@ class _ScheduleTableView extends StatelessWidget {
children: [ children: [
Center( Center(
child: GestureDetector( child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () { onTap: () {
///TODO: Implement toggle functionality context.read<ScheduleBloc>().add(
ScheduleUpdateEntryEvent(
// Toggle enabled state using ScheduleBloc scheduleId: schedule.scheduleId,
// context.read<ScheduleBloc>().add( functionOn: schedule.function.value,
// UpdateScheduleEvent( enable: !schedule.enable,
// scheduleId: schedule.scheduleId,
// functionOn: schedule.function.value,
// enable: !schedule.enable,
// ),
// );
},
child: SizedBox(
width: 24,
height: 24,
child: schedule.enable
? const Icon(Icons.radio_button_checked,
color: ColorsManager.blueColor)
: const Icon(
Icons.radio_button_unchecked,
color: ColorsManager.grayColor,
), ),
);
},
child: Center(
child: SizedBox(
width: 24,
height: 24,
child: schedule.enable
? const Icon(Icons.radio_button_checked,
color: ColorsManager.blueColor)
: const Icon(Icons.radio_button_unchecked,
color: ColorsManager.grayColor),
),
), ),
), ),
), ),
@ -202,7 +200,6 @@ class _ScheduleTableView extends StatelessWidget {
schedule: ScheduleEntry.fromScheduleModel(schedule), schedule: ScheduleEntry.fromScheduleModel(schedule),
isEdit: true, isEdit: true,
).then((updatedSchedule) { ).then((updatedSchedule) {
print('updatedSchedule : $updatedSchedule');
if (updatedSchedule != null) { if (updatedSchedule != null) {
context.read<ScheduleBloc>().add( context.read<ScheduleBloc>().add(
ScheduleEditEvent( ScheduleEditEvent(
@ -225,12 +222,38 @@ class _ScheduleTableView extends StatelessWidget {
), ),
TextButton( TextButton(
style: TextButton.styleFrom(padding: EdgeInsets.zero), style: TextButton.styleFrom(padding: EdgeInsets.zero),
onPressed: () { onPressed: () async {
context.read<ScheduleBloc>().add( final confirmed = await showDialog<bool>(
DeleteScheduleEvent( context: context,
schedule.scheduleId, builder: (BuildContext dialogContext) {
), return AlertDialog(
title: const Text('Confirm Delete'),
content: const Text(
'Are you sure you want to delete this schedule?'),
actions: [
TextButton(
onPressed: () =>
Navigator.of(dialogContext).pop(false),
child: Text('Cancel'),
),
TextButton(
onPressed: () =>
Navigator.of(dialogContext).pop(true),
child: const Text(
'Delete',
style: TextStyle(color: Colors.red),
),
),
],
); );
},
);
if (confirmed == true) {
context.read<ScheduleBloc>().add(
ScheduleDeleteEvent(schedule.scheduleId),
);
}
}, },
child: Text( child: Text(
'Delete', 'Delete',
@ -239,7 +262,7 @@ class _ScheduleTableView extends StatelessWidget {
.bodySmall! .bodySmall!
.copyWith(color: ColorsManager.blueColor), .copyWith(color: ColorsManager.blueColor),
), ),
), )
], ],
), ),
), ),
@ -248,7 +271,6 @@ class _ScheduleTableView extends StatelessWidget {
} }
String _getSelectedDays(List<bool> selectedDays) { String _getSelectedDays(List<bool> selectedDays) {
// Use the same order as in ScheduleDialogHelper
const days = ScheduleDialogHelper.allDays; const days = ScheduleDialogHelper.allDays;
return selectedDays return selectedDays
.asMap() .asMap()
@ -257,6 +279,4 @@ class _ScheduleTableView extends StatelessWidget {
.map((entry) => days[entry.key]) .map((entry) => days[entry.key])
.join(', '); .join(', ');
} }
// Removed allDays from here as it is now in ScheduleDialogHelper
} }

View File

@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:core'; import 'dart:core';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart';
@ -386,4 +387,34 @@ class DevicesManagementApi {
); );
return response; return response;
} }
Future<bool> postSchedule({
required String category,
required String deviceId,
required String time,
required String code,
required bool value,
required List<String> days,
}) async {
final response = await HTTPService().post(
path: ApiEndpoints.saveSchedule.replaceAll('{deviceUuid}', deviceId),
showServerMessage: false,
body: jsonEncode(
{
'category': category,
'time': time,
'function': {
'code': code,
'value': value,
},
'days': days
},
),
expectedResponseModel: (json) {
return json['success'] ?? false;
},
);
return response;
}
} }

View File

@ -136,4 +136,5 @@ abstract class ApiEndpoints {
static const String assignDeviceToRoom = static const String assignDeviceToRoom =
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/subspaces/{subSpaceUuid}/devices/{deviceUuid}'; '/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/subspaces/{subSpaceUuid}/devices/{deviceUuid}';
static const String saveSchedule = '/schedule/{deviceUuid}';
} }