import 'dart:async'; import 'package:dio/dio.dart'; import 'package:firebase_database/firebase_database.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart'; import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; class AcBloc extends Bloc { late AcStatusModel deviceStatus; final String deviceId; Timer? _timer; Timer? _countdownTimer; AcBloc({required this.deviceId}) : super(AcsInitialState()) { on(_onFetchAcStatus); on(_onFetchAcBatchStatus); on(_onAcControl); on(_onAcBatchControl); on(_onFactoryReset); on(_onAcStatusUpdated); on(_onClose); on(_handleIncreaseTime); on(_handleDecreaseTime); on(_handleUpdateTimer); on(_handleToggleTimer); on(_handleApiCountdownValue); } bool timerActive = false; int scheduledHours = 0; int scheduledMinutes = 0; FutureOr _onFetchAcStatus( AcFetchDeviceStatusEvent event, Emitter emit) async { emit(AcsLoadingState()); try { final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status); if (deviceStatus.countdown1 != 0) { // Convert API value to minutes final totalMinutes = deviceStatus.countdown1 * 6; scheduledHours = totalMinutes ~/ 60; scheduledMinutes = totalMinutes % 60; timerActive = true; _startCountdownTimer(emit); } emit(ACStatusLoaded( status: deviceStatus, scheduledHours: scheduledHours, scheduledMinutes: scheduledMinutes, isTimerActive: timerActive, )); _listenToChanges(event.deviceId); } catch (e) { emit(AcsFailedState(error: e.toString())); } } _listenToChanges(deviceId) { try { DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId'); Stream stream = ref.onValue; stream.listen((DatabaseEvent event) async { if (event.snapshot.value == null) return; if (_timer != null) { await Future.delayed(const Duration(seconds: 1)); } Map usersMap = event.snapshot.value as Map; List statusList = []; usersMap['status'].forEach((element) { statusList .add(Status(code: element['code'], value: element['value'])); }); deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList); if (!isClosed) { add(AcStatusUpdated(deviceStatus)); } }); } catch (_) {} } void _onAcStatusUpdated(AcStatusUpdated event, Emitter emit) { deviceStatus = event.deviceStatus; emit(ACStatusLoaded(status: deviceStatus)); } FutureOr _onAcControl( AcControlEvent event, Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value, emit); emit(ACStatusLoaded(status: deviceStatus)); await _runDebounce( isBatch: false, deviceId: event.deviceId, code: event.code, value: event.value, oldValue: oldValue, emit: emit, ); } Future _runDebounce({ required dynamic deviceId, required String code, required dynamic value, required dynamic oldValue, required Emitter emit, required bool isBatch, }) async { late String id; if (deviceId is List) { id = deviceId.first; } else { id = deviceId; } if (_timer != null) { _timer!.cancel(); } _timer = Timer(const Duration(seconds: 1), () async { try { late bool response; if (isBatch) { response = await DevicesManagementApi() .deviceBatchControl(deviceId, code, value); } else { response = await DevicesManagementApi() .deviceControl(deviceId, Status(code: code, value: value)); } if (!response) { _revertValueAndEmit(id, code, oldValue, emit); } } catch (e) { if (e is DioException && e.response != null) { debugPrint('Error response: ${e.response?.data}'); } _revertValueAndEmit(id, code, oldValue, emit); } }); } void _revertValueAndEmit( String deviceId, String code, dynamic oldValue, Emitter emit) { _updateLocalValue(code, oldValue, emit); emit(ACStatusLoaded(status: deviceStatus)); } void _updateLocalValue(String code, dynamic value, Emitter emit) { switch (code) { case 'switch': if (value is bool) { deviceStatus = deviceStatus.copyWith(acSwitch: value); } break; case 'temp_set': if (value is int) { deviceStatus = deviceStatus.copyWith(tempSet: value); } break; case 'mode': if (value is String) { deviceStatus = deviceStatus.copyWith( modeString: value, ); } break; case 'level': if (value is String) { deviceStatus = deviceStatus.copyWith( fanSpeedsString: value, ); } break; case 'child_lock': if (value is bool) { deviceStatus = deviceStatus.copyWith(childLock: value); } case 'countdown_time': if (value is int) { deviceStatus = deviceStatus.copyWith(countdown1: value); } break; default: break; } emit(ACStatusLoaded(status: deviceStatus)); } dynamic _getValueByCode(String code) { switch (code) { case 'switch': return deviceStatus.acSwitch; case 'temp_set': return deviceStatus.tempSet; case 'mode': return deviceStatus.modeString; case 'level': return deviceStatus.fanSpeedsString; case 'child_lock': return deviceStatus.childLock; case 'countdown_time': return deviceStatus.countdown1; default: return null; } } FutureOr _onFetchAcBatchStatus( AcFetchBatchStatusEvent event, Emitter emit) async { emit(AcsLoadingState()); try { final status = await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = AcStatusModel.fromJson(event.devicesIds.first, status.status); emit(ACStatusLoaded(status: deviceStatus)); } catch (e) { emit(AcsFailedState(error: e.toString())); } } FutureOr _onAcBatchControl( AcBatchControlEvent event, Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value, emit); emit(ACStatusLoaded(status: deviceStatus)); await _runDebounce( isBatch: true, deviceId: event.devicesIds, code: event.code, value: event.value, oldValue: oldValue, emit: emit, ); } FutureOr _onFactoryReset( AcFactoryResetEvent event, Emitter emit) async { emit(AcsLoadingState()); try { final response = await DevicesManagementApi().factoryReset( event.factoryResetModel, event.deviceId, ); if (!response) { emit(const AcsFailedState(error: 'Failed')); } else { add(AcFetchDeviceStatusEvent(event.deviceId)); } } catch (e) { emit(AcsFailedState(error: e.toString())); } } void _onClose(OnClose event, Emitter emit) { _countdownTimer?.cancel(); _timer?.cancel(); } void _handleIncreaseTime(IncreaseTimeEvent event, Emitter emit) { if (state is! ACStatusLoaded) return; final currentState = state as ACStatusLoaded; int newHours = scheduledHours; int newMinutes = scheduledMinutes + 30; newHours += newMinutes ~/ 60; newMinutes = newMinutes % 60; if (newHours > 23) { newHours = 23; newMinutes = 59; } scheduledHours = newHours; scheduledMinutes = newMinutes; emit(currentState.copyWith( scheduledHours: scheduledHours, scheduledMinutes: scheduledMinutes, )); } void _handleDecreaseTime(DecreaseTimeEvent event, Emitter emit) { if (state is! ACStatusLoaded) return; final currentState = state as ACStatusLoaded; int totalMinutes = (scheduledHours * 60) + scheduledMinutes; totalMinutes = (totalMinutes - 30).clamp(0, 1440); scheduledHours = totalMinutes ~/ 60; scheduledMinutes = totalMinutes % 60; emit(currentState.copyWith( scheduledHours: scheduledHours, scheduledMinutes: scheduledMinutes, )); } Future _handleToggleTimer( ToggleScheduleEvent event, Emitter emit) async { if (state is! ACStatusLoaded) return; final currentState = state as ACStatusLoaded; timerActive = !timerActive; if (timerActive) { final totalMinutes = scheduledHours * 60 + scheduledMinutes; if (totalMinutes <= 0) { timerActive = false; emit(currentState.copyWith(isTimerActive: timerActive)); return; } try { final scaledValue = totalMinutes ~/ 6; await _runDebounce( isBatch: false, deviceId: deviceId, code: 'countdown_time', value: scaledValue, oldValue: scaledValue, emit: emit, ); _startCountdownTimer(emit); emit(currentState.copyWith(isTimerActive: timerActive)); } catch (e) { timerActive = false; emit(AcsFailedState(error: e.toString())); } } else { await _runDebounce( isBatch: false, deviceId: deviceId, code: 'countdown_time', value: 0, oldValue: 0, emit: emit, ); _countdownTimer?.cancel(); scheduledHours = 0; scheduledMinutes = 0; emit(currentState.copyWith( isTimerActive: timerActive, scheduledHours: 0, scheduledMinutes: 0, )); } } void _startCountdownTimer(Emitter emit) { _countdownTimer?.cancel(); int totalSeconds = (scheduledHours * 3600) + (scheduledMinutes * 60); _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { if (totalSeconds > 0) { totalSeconds--; scheduledHours = totalSeconds ~/ 3600; scheduledMinutes = (totalSeconds % 3600) ~/ 60; add(UpdateTimerEvent()); } else { _countdownTimer?.cancel(); timerActive = false; scheduledHours = 0; scheduledMinutes = 0; add(TimerCompletedEvent()); } }); } void _handleUpdateTimer(UpdateTimerEvent event, Emitter emit) { if (state is ACStatusLoaded) { final currentState = state as ACStatusLoaded; emit(currentState.copyWith( scheduledHours: scheduledHours, scheduledMinutes: scheduledMinutes, isTimerActive: timerActive, )); } } void _handleApiCountdownValue( ApiCountdownValueEvent event, Emitter emit) { if (state is ACStatusLoaded) { final totalMinutes = event.apiValue * 6; final scheduledHours = totalMinutes ~/ 60; scheduledMinutes = totalMinutes % 60; _startCountdownTimer( emit, ); add(UpdateTimerEvent()); } } @override Future close() { add(OnClose()); return super.close(); } }