import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; import 'package:syncrow_web/services/control_device_service.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; part 'schedule_event.dart'; part 'schedule_state.dart'; class ScheduleBloc extends Bloc { final String deviceId; ScheduleBloc({ required this.deviceId, }) : super(ScheduleInitial()) { on(_initializeAddSchedule); on(_updateSelectedTime); on(_updateSelectedDay); on(_updateFunctionOn); on(_getSchedule); on(_onAddSchedule); on(_onEditSchedule); on(_onUpdateSchedule); on(_onUpdateScheduleMode); on(_onUpdateCountdownTime); on(_onUpdateInchingTime); on(_onStartScheduleEvent); on(_onStopScheduleEvent); on(_onDecrementCountdown); on(_fetchStatus); on(_onDeleteSchedule); } Timer? _countdownTimer; Duration countdownRemaining = Duration.zero; Future _onStopScheduleEvent( StopScheduleEvent event, Emitter emit, ) async { if (state is ScheduleLoaded) { final currentState = state as ScheduleLoaded; final success = await RemoteControlDeviceService().controlDevice( deviceUuid: deviceId, status: Status( code: 'countdown_1', value: 0, ), ); if (success) { _countdownTimer?.cancel(); if (event.mode == ScheduleModes.countdown) { emit(currentState.copyWith( countdownHours: 0, 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')); } } } void _onUpdateScheduleMode( UpdateScheduleModeEvent event, Emitter emit, ) { if (state is ScheduleLoaded) { final currentState = state as ScheduleLoaded; emit(currentState.copyWith( scheduleMode: event.scheduleMode, countdownRemaining: Duration.zero, countdownHours: 0, countdownMinutes: 0, inchingHours: 0, inchingMinutes: 0, isCountdownActive: false, isInchingActive: false, )); } } void _onUpdateCountdownTime( UpdateCountdownTimeEvent event, Emitter emit, ) { if (state is ScheduleLoaded) { final currentState = state as ScheduleLoaded; emit(currentState.copyWith( countdownSeconds: event.seconds, countdownHours: event.hours, countdownMinutes: event.minutes, inchingHours: 0, inchingMinutes: 0, countdownRemaining: Duration.zero, )); } } void _onUpdateInchingTime( UpdateInchingTimeEvent event, Emitter emit, ) { if (state is ScheduleLoaded) { final currentState = state as ScheduleLoaded; emit(currentState.copyWith( inchingHours: event.hours, inchingMinutes: event.minutes, countdownRemaining: Duration.zero, inchingSeconds: 0, // Add this )); } } void _initializeAddSchedule( ScheduleInitializeAddEvent event, Emitter emit, ) { if (state is ScheduleLoaded) { final currentState = state as ScheduleLoaded; emit(currentState.copyWith( selectedTime: event.selectedTime, selectedDays: event.selectedDays ?? List.filled(7, false), functionOn: event.functionOn ?? false, isEditing: event.isEditing, scheduleMode: event.scheduleMode, countdownRemaining: Duration.zero, )); } else { emit(ScheduleLoaded( schedules: const [], selectedTime: event.selectedTime, selectedDays: event.selectedDays ?? List.filled(7, false), functionOn: event.functionOn ?? false, isEditing: event.isEditing, deviceId: deviceId, scheduleMode: event.scheduleMode, countdownHours: 0, countdownMinutes: 0, inchingHours: 0, inchingMinutes: 0, isCountdownActive: false, isInchingActive: false, )); } } void _updateSelectedTime( ScheduleUpdateSelectedTimeEvent event, Emitter emit, ) { if (state is ScheduleLoaded) { final currentState = state as ScheduleLoaded; emit(currentState.copyWith( selectedTime: event.selectedTime, countdownHours: 0, countdownMinutes: 0, inchingHours: 0, inchingMinutes: 0, countdownRemaining: Duration.zero, )); } } void _updateSelectedDay( ScheduleUpdateSelectedDayEvent event, Emitter emit, ) { if (state is ScheduleLoaded) { final currentState = state as ScheduleLoaded; final updatedDays = List.from(currentState.selectedDays); updatedDays[event.index] = event.value; emit(currentState.copyWith( selectedDays: updatedDays, countdownHours: 0, countdownMinutes: 0, inchingHours: 0, inchingMinutes: 0, countdownRemaining: Duration.zero, )); } } void _updateFunctionOn( ScheduleUpdateFunctionOnEvent event, Emitter emit, ) { if (state is ScheduleLoaded) { final currentState = state as ScheduleLoaded; emit(currentState.copyWith( functionOn: event.isOn, countdownHours: 0, countdownMinutes: 0, inchingHours: 0, inchingMinutes: 0, countdownRemaining: Duration.zero, )); } } Future _getSchedule( ScheduleGetEvent event, Emitter emit, ) async { try { emit(ScheduleLoading()); final schedules = await DevicesManagementApi().getDeviceSchedules( deviceId, event.category, ); if (state is ScheduleLoaded) { final currentState = state as ScheduleLoaded; emit(currentState.copyWith( schedules: schedules, selectedTime: null, selectedDays: List.filled(7, false), functionOn: false, isEditing: false, countdownRemaining: Duration.zero, )); } else { emit(ScheduleLoaded( schedules: schedules, selectedTime: null, selectedDays: List.filled(7, false), functionOn: false, isEditing: false, deviceId: deviceId, scheduleMode: ScheduleModes.schedule, countdownHours: 0, countdownMinutes: 0, inchingHours: 0, inchingMinutes: 0, isCountdownActive: false, isInchingActive: false, )); } } catch (e) { emit(ScheduleError('Failed to load schedules: $e')); } } Future _onAddSchedule( ScheduleAddEvent event, Emitter emit, ) async { try { if (state is ScheduleLoaded) { final dateTime = DateTime.parse(event.time); final success = await DevicesManagementApi().postSchedule( category: event.category, deviceId: deviceId, time: getTimeStampWithoutSeconds(dateTime).toString(), code: event.code ?? event.category, value: event.functionOn, days: event.selectedDays); if (success) { add(ScheduleGetEvent(category: event.category)); } else { emit(const ScheduleError('Failed to add schedule')); } } } catch (e) { emit(ScheduleError('Failed to add schedule: $e')); } } Future _onEditSchedule( ScheduleEditEvent event, Emitter emit, ) async { try { if (state is ScheduleLoaded) { final dateTime = DateTime.parse(event.time); Status status = Status(code: '', value: ''); if (event.category == 'CUR_2') { status = status.copyWith( code: 'control', value: event.functionOn == true ? 'open' : 'close'); } else { status = status.copyWith(code: event.category, value: event.functionOn); } final updatedSchedule = ScheduleEntry( scheduleId: event.scheduleId, category: event.category, time: getTimeStampWithoutSeconds(dateTime).toString(), function: status, days: event.selectedDays, ); final success = await DevicesManagementApi().editScheduleRecord( deviceId, updatedSchedule, ); if (success) { add(ScheduleGetEvent( category: event.category, )); } else { emit(const ScheduleError('Failed to update schedule')); } } } catch (e) { emit(ScheduleError('Failed to update schedule: $e')); } } Future _onUpdateSchedule( ScheduleUpdateEntryEvent event, Emitter emit, ) async { try { 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: event.category, value: event.functionOn), enable: event.enable, ); } return schedule; }).toList(); final success = await DevicesManagementApi().updateScheduleRecord( enable: event.enable, uuid: deviceId, scheduleId: event.scheduleId, ); if (success) { emit(currentState.copyWith( schedules: updatedSchedules, countdownHours: 0, countdownMinutes: 0, inchingHours: 0, inchingMinutes: 0, countdownRemaining: Duration.zero, )); } else { emit(const ScheduleError('Failed to update schedule status')); } } } catch (e) { emit(ScheduleError('Failed to update schedule: $e')); } } Future _onDeleteSchedule( ScheduleDeleteEvent event, Emitter emit, ) async { try { if (state is ScheduleLoaded) { final currentState = state as ScheduleLoaded; final success = await DevicesManagementApi().deleteScheduleRecord( deviceId, event.scheduleId, ); if (success) { final updatedSchedules = currentState.schedules .where((s) => s.scheduleId != event.scheduleId) .toList(); emit(currentState.copyWith( schedules: updatedSchedules, countdownHours: 0, countdownMinutes: 0, inchingHours: 0, inchingMinutes: 0, countdownRemaining: Duration.zero, )); } else { emit(const ScheduleError('Failed to delete schedule')); } } } catch (e) { emit(ScheduleError('Failed to delete schedule: $e')); } } Duration? _currentCountdown; Future _onStartScheduleEvent( StartScheduleEvent event, Emitter emit, ) async { if (state is ScheduleLoaded) { final totalSeconds = Duration(hours: event.hours, minutes: event.minutes).inSeconds; final code = event.mode == ScheduleModes.countdown ? 'countdown_1' : 'switch_inching'; final currentState = state as ScheduleLoaded; final duration = Duration(seconds: totalSeconds); _currentCountdown = duration; emit(currentState.copyWith( countdownRemaining: duration, schedules: currentState.schedules.map((schedule) { if (schedule.function.code == code) { return schedule.copyWith( function: Status(code: code, value: totalSeconds), ); } return schedule; }).toList(), countdownHours: event.mode == ScheduleModes.countdown ? event.hours : 0, )); final success = await RemoteControlDeviceService().controlDevice( deviceUuid: deviceId, status: Status( code: code, value: totalSeconds, ), ); if (success) { if (code == 'countdown_1') { final countdownDuration = Duration(seconds: totalSeconds); emit( currentState.copyWith( countdownHours: countdownDuration.inHours, countdownMinutes: countdownDuration.inMinutes % 60, countdownRemaining: countdownDuration, isCountdownActive: true, countdownSeconds: countdownDuration.inSeconds, ), ); if (countdownDuration.inSeconds > 0) { _startCountdownTimer(emit, countdownDuration); } else { _countdownTimer?.cancel(); emit( currentState.copyWith( countdownHours: 0, countdownMinutes: 0, countdownRemaining: Duration.zero, isCountdownActive: false, countdownSeconds: 0, ), ); } } else if (code == 'switch_inching') { final inchingDuration = Duration(seconds: totalSeconds); emit( currentState.copyWith( inchingHours: inchingDuration.inHours, inchingMinutes: inchingDuration.inMinutes % 60, isInchingActive: true, countdownRemaining: inchingDuration, countdownSeconds: inchingDuration.inSeconds, ), ); } } } } void _startCountdownTimer( Emitter emit, Duration duration, ) { _countdownTimer?.cancel(); _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { if (_currentCountdown != null && _currentCountdown! > Duration.zero) { _currentCountdown = _currentCountdown! - const Duration(seconds: 1); countdownRemaining = _currentCountdown!; add(const ScheduleDecrementCountdownEvent()); } else { timer.cancel(); add(StopScheduleEvent( mode: _currentCountdown == null ? ScheduleModes.countdown : ScheduleModes.inching, deviceId: deviceId, )); } }); } void _onDecrementCountdown( ScheduleDecrementCountdownEvent event, Emitter emit, ) { if (state is ScheduleLoaded) { final currentState = state as ScheduleLoaded; emit(currentState.copyWith( countdownRemaining: countdownRemaining, )); } } @override Future close() { _countdownTimer?.cancel(); return super.close(); } Future _fetchStatus( ScheduleFetchStatusEvent event, Emitter emit, ) async { emit(ScheduleLoading()); try { final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); print(status.status); final deviceStatus = WaterHeaterStatusModel.fromJson(event.deviceId, status.status); 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 isInching = scheduleMode == ScheduleModes.inching; Duration? countdownRemaining; var isCountdownActive = false; var isInchingActive = false; if (isCountdown) { countdownRemaining = Duration( hours: deviceStatus.countdownHours, minutes: deviceStatus.countdownMinutes, ); isCountdownActive = countdownRemaining > Duration.zero; } else if (isInching) { isInchingActive = Duration( hours: deviceStatus.inchingHours, minutes: deviceStatus.inchingMinutes, ) > Duration.zero; } if (state is ScheduleLoaded) { final currentState = state as ScheduleLoaded; emit(currentState.copyWith( scheduleMode: scheduleMode, countdownHours: deviceStatus.countdownHours, countdownMinutes: deviceStatus.countdownMinutes, inchingHours: deviceStatus.inchingHours, inchingMinutes: deviceStatus.inchingMinutes, isCountdownActive: isCountdownActive, isInchingActive: isInchingActive, countdownRemaining: countdownRemaining ?? Duration.zero, )); } else { emit(ScheduleLoaded( schedules: const [], selectedTime: null, selectedDays: List.filled(7, false), functionOn: false, isEditing: false, deviceId: deviceId, scheduleMode: scheduleMode, countdownHours: deviceStatus.countdownHours, countdownMinutes: deviceStatus.countdownMinutes, inchingHours: deviceStatus.inchingHours, inchingMinutes: deviceStatus.inchingMinutes, isCountdownActive: isCountdownActive, isInchingActive: isInchingActive, countdownRemaining: countdownRemaining ?? Duration.zero, )); } // if (isCountdownActive && countdownRemaining != null) { // _startCountdownTimer(emit, countdownRemaining); // } } catch (e) { emit(ScheduleError('Failed to fetch device status: $e')); } } String extractTime(String isoDateTime) { return isoDateTime.split('T')[1].split('.')[0]; } 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; } }