From f98636a2e5ba51d4fda99e6113439dd45710fdd1 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 2 Jun 2025 10:44:43 +0300 Subject: [PATCH] Migrated `AcBloc` single/batch controls the new services. --- .../device_managment/ac/bloc/ac_bloc.dart | 340 ++++++++---------- .../ac/factories/ac_bloc_factory.dart | 18 + .../ac/view/ac_device_batch_control.dart | 7 +- .../ac/view/ac_device_control.dart | 6 +- 4 files changed, 175 insertions(+), 196 deletions(-) create mode 100644 lib/pages/device_managment/ac/factories/ac_bloc_factory.dart diff --git a/lib/pages/device_managment/ac/bloc/ac_bloc.dart b/lib/pages/device_managment/ac/bloc/ac_bloc.dart index 501d29d8..af5a7b0a 100644 --- a/lib/pages/device_managment/ac/bloc/ac_bloc.dart +++ b/lib/pages/device_managment/ac/bloc/ac_bloc.dart @@ -1,21 +1,27 @@ 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/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/services/batch_control_devices_service.dart'; +import 'package:syncrow_web/services/control_device_service.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; class AcBloc extends Bloc { late AcStatusModel deviceStatus; final String deviceId; - Timer? _timer; + final ControlDeviceService controlDeviceService; + final BatchControlDevicesService batchControlDevicesService; Timer? _countdownTimer; - AcBloc({required this.deviceId}) : super(AcsInitialState()) { + AcBloc({ + required this.deviceId, + required this.controlDeviceService, + required this.batchControlDevicesService, + }) : super(AcsInitialState()) { on(_onFetchAcStatus); on(_onFetchAcBatchStatus); on(_onAcControl); @@ -34,14 +40,14 @@ class AcBloc extends Bloc { int scheduledMinutes = 0; FutureOr _onFetchAcStatus( - AcFetchDeviceStatusEvent event, Emitter emit) async { + AcFetchDeviceStatusEvent event, + Emitter emit, + ) async { emit(AcsLoadingState()); try { - final status = - await DevicesManagementApi().getDeviceStatus(event.deviceId); + 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; @@ -62,30 +68,24 @@ class AcBloc extends Bloc { } } - _listenToChanges(deviceId) { + void _listenToChanges(deviceId) { try { - DatabaseReference ref = - FirebaseDatabase.instance.ref('device-status/$deviceId'); - Stream stream = ref.onValue; + final ref = FirebaseDatabase.instance.ref('device-status/$deviceId'); + final 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'])); + statusList.add(Status(code: element['code'], value: element['value'])); }); - deviceStatus = - AcStatusModel.fromJson(usersMap['productUuid'], statusList); + deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList); if (!isClosed) { add(AcStatusUpdated(deviceStatus)); } @@ -93,146 +93,44 @@ class AcBloc extends Bloc { } catch (_) {} } - void _onAcStatusUpdated(AcStatusUpdated event, Emitter emit) { + 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); - + AcControlEvent event, + Emitter emit, + ) async { + emit(AcsLoadingState()); + _updateDeviceFunctionFromCode(event.code, event.value); emit(ACStatusLoaded(status: deviceStatus)); - await _runDebounce( - isBatch: false, - deviceId: event.deviceId, - code: event.code, - value: event.value, - oldValue: oldValue, - emit: emit, - ); - } + try { + final success = await controlDeviceService.controlDevice( + deviceUuid: event.deviceId, + status: Status(code: event.code, value: event.value), + ); - 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); + if (!success) { + emit(const AcsFailedState(error: 'Failed to control device')); } - }); - } - - 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; + } catch (e) { + emit(AcsFailedState(error: e.toString())); } } FutureOr _onFetchAcBatchStatus( - AcFetchBatchStatusEvent event, Emitter emit) async { + AcFetchBatchStatusEvent event, + Emitter emit, + ) async { emit(AcsLoadingState()); try { - final status = - await DevicesManagementApi().getBatchStatus(event.devicesIds); - deviceStatus = - AcStatusModel.fromJson(event.devicesIds.first, status.status); + 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())); @@ -240,25 +138,32 @@ class AcBloc extends Bloc { } FutureOr _onAcBatchControl( - AcBatchControlEvent event, Emitter emit) async { - final oldValue = _getValueByCode(event.code); - - _updateLocalValue(event.code, event.value, emit); - + AcBatchControlEvent event, + Emitter emit, + ) async { + emit(AcsLoadingState()); + _updateDeviceFunctionFromCode(event.code, event.value); emit(ACStatusLoaded(status: deviceStatus)); - await _runDebounce( - isBatch: true, - deviceId: event.devicesIds, - code: event.code, - value: event.value, - oldValue: oldValue, - emit: emit, - ); + try { + final success = await batchControlDevicesService.batchControlDevices( + uuids: event.devicesIds, + code: event.code, + value: event.value, + ); + + if (!success) { + emit(const AcsFailedState(error: 'Failed to control devices')); + } + } catch (e) { + emit(AcsFailedState(error: e.toString())); + } } - FutureOr _onFactoryReset( - AcFactoryResetEvent event, Emitter emit) async { + Future _onFactoryReset( + AcFactoryResetEvent event, + Emitter emit, + ) async { emit(AcsLoadingState()); try { final response = await DevicesManagementApi().factoryReset( @@ -275,9 +180,11 @@ class AcBloc extends Bloc { } } - void _onClose(OnClose event, Emitter emit) { + void _onClose( + OnClose event, + Emitter emit, + ) { _countdownTimer?.cancel(); - _timer?.cancel(); } void _handleIncreaseTime(IncreaseTimeEvent event, Emitter emit) { @@ -300,7 +207,10 @@ class AcBloc extends Bloc { )); } - void _handleDecreaseTime(DecreaseTimeEvent event, Emitter emit) { + void _handleDecreaseTime( + DecreaseTimeEvent event, + Emitter emit, + ) { if (state is! ACStatusLoaded) return; final currentState = state as ACStatusLoaded; int totalMinutes = (scheduledHours * 60) + scheduledMinutes; @@ -315,7 +225,9 @@ class AcBloc extends Bloc { } Future _handleToggleTimer( - ToggleScheduleEvent event, Emitter emit) async { + ToggleScheduleEvent event, + Emitter emit, + ) async { if (state is! ACStatusLoaded) return; final currentState = state as ACStatusLoaded; @@ -331,37 +243,44 @@ class AcBloc extends Bloc { try { final scaledValue = totalMinutes ~/ 6; - await _runDebounce( - isBatch: false, - deviceId: deviceId, - code: 'countdown_time', - value: scaledValue, - oldValue: scaledValue, - emit: emit, + final success = await controlDeviceService.controlDevice( + deviceUuid: deviceId, + status: Status(code: 'countdown_time', value: scaledValue), ); - _startCountdownTimer(emit); - emit(currentState.copyWith(isTimerActive: timerActive)); + + if (success) { + _startCountdownTimer(emit); + emit(currentState.copyWith(isTimerActive: timerActive)); + } else { + timerActive = false; + emit(const AcsFailedState(error: 'Failed to set timer')); + } } 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, - )); + try { + final success = await controlDeviceService.controlDevice( + deviceUuid: deviceId, + status: Status(code: 'countdown_time', value: 0), + ); + + if (success) { + _countdownTimer?.cancel(); + scheduledHours = 0; + scheduledMinutes = 0; + emit(currentState.copyWith( + isTimerActive: timerActive, + scheduledHours: 0, + scheduledMinutes: 0, + )); + } else { + emit(const AcsFailedState(error: 'Failed to stop timer')); + } + } catch (e) { + emit(AcsFailedState(error: e.toString())); + } } } @@ -385,7 +304,10 @@ class AcBloc extends Bloc { }); } - void _handleUpdateTimer(UpdateTimerEvent event, Emitter emit) { + void _handleUpdateTimer( + UpdateTimerEvent event, + Emitter emit, + ) { if (state is ACStatusLoaded) { final currentState = state as ACStatusLoaded; emit(currentState.copyWith( @@ -400,7 +322,6 @@ class AcBloc extends Bloc { ApiCountdownValueEvent event, Emitter emit) { if (state is ACStatusLoaded) { final totalMinutes = event.apiValue * 6; - final scheduledHours = totalMinutes ~/ 60; scheduledMinutes = totalMinutes % 60; _startCountdownTimer( emit, @@ -409,6 +330,43 @@ class AcBloc extends Bloc { } } + void _updateDeviceFunctionFromCode(String code, dynamic value) { + 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); + } + break; + case 'countdown_time': + if (value is int) { + deviceStatus = deviceStatus.copyWith(countdown1: value); + } + break; + default: + break; + } + } + @override Future close() { add(OnClose()); diff --git a/lib/pages/device_managment/ac/factories/ac_bloc_factory.dart b/lib/pages/device_managment/ac/factories/ac_bloc_factory.dart new file mode 100644 index 00000000..9e5f4c1c --- /dev/null +++ b/lib/pages/device_managment/ac/factories/ac_bloc_factory.dart @@ -0,0 +1,18 @@ +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart'; + +abstract final class AcBlocFactory { + const AcBlocFactory._(); + + static AcBloc create({ + required String deviceId, + }) { + return AcBloc( + deviceId: deviceId, + controlDeviceService: + DeviceBlocDependenciesFactory.createControlDeviceService(), + batchControlDevicesService: + DeviceBlocDependenciesFactory.createBatchControlDevicesService(), + ); + } +} diff --git a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart index 3005c1c5..aad0669b 100644 --- a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart @@ -3,12 +3,12 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.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/factories/ac_bloc_factory.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; -// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -26,8 +26,9 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); return BlocProvider( - create: (context) => - AcBloc(deviceId: devicesIds.first)..add(AcFetchBatchStatusEvent(devicesIds)), + create: (context) => AcBlocFactory.create( + deviceId: devicesIds.first, + )..add(AcFetchBatchStatusEvent(devicesIds)), child: BlocBuilder( builder: (context, state) { if (state is ACStatusLoaded) { diff --git a/lib/pages/device_managment/ac/view/ac_device_control.dart b/lib/pages/device_managment/ac/view/ac_device_control.dart index 8c33c853..a882e6d5 100644 --- a/lib/pages/device_managment/ac/view/ac_device_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_control.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.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/factories/ac_bloc_factory.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/control_list/ac_mode.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/control_list/current_temp.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/control_list/fan_speed.dart'; @@ -24,8 +25,9 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { final isMedium = isMediumScreenSize(context); return BlocProvider( - create: (context) => AcBloc(deviceId: device.uuid!) - ..add(AcFetchDeviceStatusEvent(device.uuid!)), + create: (context) => AcBlocFactory.create( + deviceId: device.uuid!, + )..add(AcFetchDeviceStatusEvent(device.uuid!)), child: BlocBuilder( builder: (context, state) { final acBloc = BlocProvider.of(context);