Migrated AcBloc single/batch controls the new services.

This commit is contained in:
Faris Armoush
2025-06-02 10:44:43 +03:00
parent 19548e99ab
commit f98636a2e5
4 changed files with 175 additions and 196 deletions

View File

@ -1,21 +1,27 @@
import 'dart:async'; import 'dart:async';
import 'package:dio/dio.dart';
import 'package:firebase_database/firebase_database.dart'; import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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_event.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.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/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'; import 'package:syncrow_web/services/devices_mang_api.dart';
class AcBloc extends Bloc<AcsEvent, AcsState> { class AcBloc extends Bloc<AcsEvent, AcsState> {
late AcStatusModel deviceStatus; late AcStatusModel deviceStatus;
final String deviceId; final String deviceId;
Timer? _timer; final ControlDeviceService controlDeviceService;
final BatchControlDevicesService batchControlDevicesService;
Timer? _countdownTimer; Timer? _countdownTimer;
AcBloc({required this.deviceId}) : super(AcsInitialState()) { AcBloc({
required this.deviceId,
required this.controlDeviceService,
required this.batchControlDevicesService,
}) : super(AcsInitialState()) {
on<AcFetchDeviceStatusEvent>(_onFetchAcStatus); on<AcFetchDeviceStatusEvent>(_onFetchAcStatus);
on<AcFetchBatchStatusEvent>(_onFetchAcBatchStatus); on<AcFetchBatchStatusEvent>(_onFetchAcBatchStatus);
on<AcControlEvent>(_onAcControl); on<AcControlEvent>(_onAcControl);
@ -34,14 +40,14 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
int scheduledMinutes = 0; int scheduledMinutes = 0;
FutureOr<void> _onFetchAcStatus( FutureOr<void> _onFetchAcStatus(
AcFetchDeviceStatusEvent event, Emitter<AcsState> emit) async { AcFetchDeviceStatusEvent event,
Emitter<AcsState> emit,
) async {
emit(AcsLoadingState()); emit(AcsLoadingState());
try { try {
final status = final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status); deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status);
if (deviceStatus.countdown1 != 0) { if (deviceStatus.countdown1 != 0) {
// Convert API value to minutes
final totalMinutes = deviceStatus.countdown1 * 6; final totalMinutes = deviceStatus.countdown1 * 6;
scheduledHours = totalMinutes ~/ 60; scheduledHours = totalMinutes ~/ 60;
scheduledMinutes = totalMinutes % 60; scheduledMinutes = totalMinutes % 60;
@ -62,30 +68,24 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
} }
} }
_listenToChanges(deviceId) { void _listenToChanges(deviceId) {
try { try {
DatabaseReference ref = final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
FirebaseDatabase.instance.ref('device-status/$deviceId'); final stream = ref.onValue;
Stream<DatabaseEvent> stream = ref.onValue;
stream.listen((DatabaseEvent event) async { stream.listen((DatabaseEvent event) async {
if (event.snapshot.value == null) return; if (event.snapshot.value == null) return;
if (_timer != null) {
await Future.delayed(const Duration(seconds: 1));
}
Map<dynamic, dynamic> usersMap = Map<dynamic, dynamic> usersMap =
event.snapshot.value as Map<dynamic, dynamic>; event.snapshot.value as Map<dynamic, dynamic>;
List<Status> statusList = []; List<Status> statusList = [];
usersMap['status'].forEach((element) { usersMap['status'].forEach((element) {
statusList statusList.add(Status(code: element['code'], value: element['value']));
.add(Status(code: element['code'], value: element['value']));
}); });
deviceStatus = deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList);
AcStatusModel.fromJson(usersMap['productUuid'], statusList);
if (!isClosed) { if (!isClosed) {
add(AcStatusUpdated(deviceStatus)); add(AcStatusUpdated(deviceStatus));
} }
@ -93,146 +93,44 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
} catch (_) {} } catch (_) {}
} }
void _onAcStatusUpdated(AcStatusUpdated event, Emitter<AcsState> emit) { void _onAcStatusUpdated(
AcStatusUpdated event,
Emitter<AcsState> emit,
) {
deviceStatus = event.deviceStatus; deviceStatus = event.deviceStatus;
emit(ACStatusLoaded(status: deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
} }
FutureOr<void> _onAcControl( FutureOr<void> _onAcControl(
AcControlEvent event, Emitter<AcsState> emit) async { AcControlEvent event,
final oldValue = _getValueByCode(event.code); Emitter<AcsState> emit,
) async {
_updateLocalValue(event.code, event.value, emit); emit(AcsLoadingState());
_updateDeviceFunctionFromCode(event.code, event.value);
emit(ACStatusLoaded(status: deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
await _runDebounce(
isBatch: false,
deviceId: event.deviceId,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
);
}
Future<void> _runDebounce({
required dynamic deviceId,
required String code,
required dynamic value,
required dynamic oldValue,
required Emitter<AcsState> 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 { try {
late bool response; final success = await controlDeviceService.controlDevice(
if (isBatch) { deviceUuid: event.deviceId,
response = await DevicesManagementApi() status: Status(code: event.code, value: event.value),
.deviceBatchControl(deviceId, code, value); );
} else {
response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value));
}
if (!response) { if (!success) {
_revertValueAndEmit(id, code, oldValue, emit); emit(const AcsFailedState(error: 'Failed to control device'));
} }
} catch (e) { } catch (e) {
if (e is DioException && e.response != null) { emit(AcsFailedState(error: e.toString()));
debugPrint('Error response: ${e.response?.data}');
}
_revertValueAndEmit(id, code, oldValue, emit);
}
});
}
void _revertValueAndEmit(
String deviceId, String code, dynamic oldValue, Emitter<AcsState> emit) {
_updateLocalValue(code, oldValue, emit);
emit(ACStatusLoaded(status: deviceStatus));
}
void _updateLocalValue(String code, dynamic value, Emitter<AcsState> 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<void> _onFetchAcBatchStatus( FutureOr<void> _onFetchAcBatchStatus(
AcFetchBatchStatusEvent event, Emitter<AcsState> emit) async { AcFetchBatchStatusEvent event,
Emitter<AcsState> emit,
) async {
emit(AcsLoadingState()); emit(AcsLoadingState());
try { try {
final status = final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = AcStatusModel.fromJson(event.devicesIds.first, status.status);
deviceStatus =
AcStatusModel.fromJson(event.devicesIds.first, status.status);
emit(ACStatusLoaded(status: deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
} catch (e) { } catch (e) {
emit(AcsFailedState(error: e.toString())); emit(AcsFailedState(error: e.toString()));
@ -240,25 +138,32 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
} }
FutureOr<void> _onAcBatchControl( FutureOr<void> _onAcBatchControl(
AcBatchControlEvent event, Emitter<AcsState> emit) async { AcBatchControlEvent event,
final oldValue = _getValueByCode(event.code); Emitter<AcsState> emit,
) async {
_updateLocalValue(event.code, event.value, emit); emit(AcsLoadingState());
_updateDeviceFunctionFromCode(event.code, event.value);
emit(ACStatusLoaded(status: deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
await _runDebounce( try {
isBatch: true, final success = await batchControlDevicesService.batchControlDevices(
deviceId: event.devicesIds, uuids: event.devicesIds,
code: event.code, code: event.code,
value: event.value, value: event.value,
oldValue: oldValue,
emit: emit,
); );
if (!success) {
emit(const AcsFailedState(error: 'Failed to control devices'));
}
} catch (e) {
emit(AcsFailedState(error: e.toString()));
}
} }
FutureOr<void> _onFactoryReset( Future<void> _onFactoryReset(
AcFactoryResetEvent event, Emitter<AcsState> emit) async { AcFactoryResetEvent event,
Emitter<AcsState> emit,
) async {
emit(AcsLoadingState()); emit(AcsLoadingState());
try { try {
final response = await DevicesManagementApi().factoryReset( final response = await DevicesManagementApi().factoryReset(
@ -275,9 +180,11 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
} }
} }
void _onClose(OnClose event, Emitter<AcsState> emit) { void _onClose(
OnClose event,
Emitter<AcsState> emit,
) {
_countdownTimer?.cancel(); _countdownTimer?.cancel();
_timer?.cancel();
} }
void _handleIncreaseTime(IncreaseTimeEvent event, Emitter<AcsState> emit) { void _handleIncreaseTime(IncreaseTimeEvent event, Emitter<AcsState> emit) {
@ -300,7 +207,10 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
)); ));
} }
void _handleDecreaseTime(DecreaseTimeEvent event, Emitter<AcsState> emit) { void _handleDecreaseTime(
DecreaseTimeEvent event,
Emitter<AcsState> emit,
) {
if (state is! ACStatusLoaded) return; if (state is! ACStatusLoaded) return;
final currentState = state as ACStatusLoaded; final currentState = state as ACStatusLoaded;
int totalMinutes = (scheduledHours * 60) + scheduledMinutes; int totalMinutes = (scheduledHours * 60) + scheduledMinutes;
@ -315,7 +225,9 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
} }
Future<void> _handleToggleTimer( Future<void> _handleToggleTimer(
ToggleScheduleEvent event, Emitter<AcsState> emit) async { ToggleScheduleEvent event,
Emitter<AcsState> emit,
) async {
if (state is! ACStatusLoaded) return; if (state is! ACStatusLoaded) return;
final currentState = state as ACStatusLoaded; final currentState = state as ACStatusLoaded;
@ -331,29 +243,30 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
try { try {
final scaledValue = totalMinutes ~/ 6; final scaledValue = totalMinutes ~/ 6;
await _runDebounce( final success = await controlDeviceService.controlDevice(
isBatch: false, deviceUuid: deviceId,
deviceId: deviceId, status: Status(code: 'countdown_time', value: scaledValue),
code: 'countdown_time',
value: scaledValue,
oldValue: scaledValue,
emit: emit,
); );
if (success) {
_startCountdownTimer(emit); _startCountdownTimer(emit);
emit(currentState.copyWith(isTimerActive: timerActive)); emit(currentState.copyWith(isTimerActive: timerActive));
} else {
timerActive = false;
emit(const AcsFailedState(error: 'Failed to set timer'));
}
} catch (e) { } catch (e) {
timerActive = false; timerActive = false;
emit(AcsFailedState(error: e.toString())); emit(AcsFailedState(error: e.toString()));
} }
} else { } else {
await _runDebounce( try {
isBatch: false, final success = await controlDeviceService.controlDevice(
deviceId: deviceId, deviceUuid: deviceId,
code: 'countdown_time', status: Status(code: 'countdown_time', value: 0),
value: 0,
oldValue: 0,
emit: emit,
); );
if (success) {
_countdownTimer?.cancel(); _countdownTimer?.cancel();
scheduledHours = 0; scheduledHours = 0;
scheduledMinutes = 0; scheduledMinutes = 0;
@ -362,6 +275,12 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
scheduledHours: 0, scheduledHours: 0,
scheduledMinutes: 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<AcsEvent, AcsState> {
}); });
} }
void _handleUpdateTimer(UpdateTimerEvent event, Emitter<AcsState> emit) { void _handleUpdateTimer(
UpdateTimerEvent event,
Emitter<AcsState> emit,
) {
if (state is ACStatusLoaded) { if (state is ACStatusLoaded) {
final currentState = state as ACStatusLoaded; final currentState = state as ACStatusLoaded;
emit(currentState.copyWith( emit(currentState.copyWith(
@ -400,7 +322,6 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
ApiCountdownValueEvent event, Emitter<AcsState> emit) { ApiCountdownValueEvent event, Emitter<AcsState> emit) {
if (state is ACStatusLoaded) { if (state is ACStatusLoaded) {
final totalMinutes = event.apiValue * 6; final totalMinutes = event.apiValue * 6;
final scheduledHours = totalMinutes ~/ 60;
scheduledMinutes = totalMinutes % 60; scheduledMinutes = totalMinutes % 60;
_startCountdownTimer( _startCountdownTimer(
emit, emit,
@ -409,6 +330,43 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
} }
} }
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 @override
Future<void> close() { Future<void> close() {
add(OnClose()); add(OnClose());

View File

@ -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(),
);
}
}

View File

@ -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_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.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/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_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_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/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/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/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/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
@ -26,8 +26,9 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
final isLarge = isLargeScreenSize(context); final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context); final isMedium = isMediumScreenSize(context);
return BlocProvider( return BlocProvider(
create: (context) => create: (context) => AcBlocFactory.create(
AcBloc(deviceId: devicesIds.first)..add(AcFetchBatchStatusEvent(devicesIds)), deviceId: devicesIds.first,
)..add(AcFetchBatchStatusEvent(devicesIds)),
child: BlocBuilder<AcBloc, AcsState>( child: BlocBuilder<AcBloc, AcsState>(
builder: (context, state) { builder: (context, state) {
if (state is ACStatusLoaded) { if (state is ACStatusLoaded) {

View File

@ -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_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.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/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/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/current_temp.dart';
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/fan_speed.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); final isMedium = isMediumScreenSize(context);
return BlocProvider( return BlocProvider(
create: (context) => AcBloc(deviceId: device.uuid!) create: (context) => AcBlocFactory.create(
..add(AcFetchDeviceStatusEvent(device.uuid!)), deviceId: device.uuid!,
)..add(AcFetchDeviceStatusEvent(device.uuid!)),
child: BlocBuilder<AcBloc, AcsState>( child: BlocBuilder<AcBloc, AcsState>(
builder: (context, state) { builder: (context, state) {
final acBloc = BlocProvider.of<AcBloc>(context); final acBloc = BlocProvider.of<AcBloc>(context);