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_app/features/app_layout/bloc/home_cubit.dart'; import 'package:syncrow_app/features/app_layout/model/community_model.dart'; import 'package:syncrow_app/features/app_layout/model/space_model.dart'; import 'package:syncrow_app/features/auth/model/project_model.dart'; import 'package:syncrow_app/features/devices/bloc/sos_bloc/sos_event.dart'; import 'package:syncrow_app/features/devices/bloc/sos_bloc/sos_state.dart'; import 'package:syncrow_app/features/devices/model/device_control_model.dart'; import 'package:syncrow_app/features/devices/model/device_info_model.dart'; import 'package:syncrow_app/features/devices/model/device_model.dart'; import 'package:syncrow_app/features/devices/model/device_report_model.dart'; import 'package:syncrow_app/features/devices/model/question_model.dart'; import 'package:syncrow_app/features/devices/model/sos_model.dart'; import 'package:syncrow_app/features/devices/model/status_model.dart'; import 'package:syncrow_app/features/devices/model/subspace_model.dart'; import 'package:syncrow_app/navigation/routing_constants.dart'; import 'package:syncrow_app/services/api/devices_api.dart'; import 'package:syncrow_app/services/api/home_management_api.dart'; import 'package:syncrow_app/services/api/spaces_api.dart'; import 'package:syncrow_app/utils/constants/temp_const.dart'; import 'package:syncrow_app/utils/helpers/snack_bar.dart'; class SosBloc extends Bloc { final String sosId; SosBloc({ required this.sosId, }) : super(const SosState()) { on(_fetchStatus); on(fetchLogsForLastMonth); on(_toggleLowBattery); on(_toggleClosingReminder); on(_changeName); on(_onSearchFaq); on(_onSosInitial); on(_fetchRoomsAndDevices); on(_onOptionSelected); on(_onSaveSelection); on(_assignDevice); on(fetchDeviceInfo); on(saveName); on(_toggleUpdate); on(_toggleHelpful); } final TextEditingController nameController = TextEditingController(text: deviceName); bool isSaving = false; bool editName = false; final FocusNode focusNode = FocusNode(); bool enableAlarm = false; bool closingReminder = false; SosModel deviceStatus = SosModel(sosContactState: 'sos', batteryPercentage: 0); DeviceInfoModel deviceInfo = DeviceInfoModel( activeTime: 0, category: "", categoryName: "", createTime: 0, gatewayId: "", icon: "", ip: "", lat: "", localKey: "", lon: "", model: "", name: "", nodeId: "", online: false, ownerId: "", productName: "", sub: false, timeZone: "", updateTime: 0, uuid: "", productUuid: "", productType: "", permissionType: "", macAddress: "", subspace: Subspace( uuid: "", createdAt: "", updatedAt: "", subspaceName: "", ), ); void _toggleLowBattery( ToggleEnableAlarmEvent event, Emitter emit) async { emit(LoadingNewSate(sosSensor: deviceStatus)); try { enableAlarm = event.isLowBatteryEnabled; emit(UpdateState(sensor: deviceStatus)); await DevicesAPI.controlDevice( DeviceControlModel( deviceId: sosId, code: 'low_battery', value: enableAlarm, ), sosId, ); } catch (e) { emit(SosFailedState(errorMessage: e.toString())); } } void _toggleClosingReminder( ToggleClosingReminderEvent event, Emitter emit) async { emit(LoadingNewSate(sosSensor: deviceStatus)); try { closingReminder = event.isClosingReminderEnabled; emit(UpdateState(sensor: deviceStatus)); } catch (e) { emit(SosFailedState(errorMessage: e.toString())); } } DeviceReport recordGroups = DeviceReport(startTime: '0', endTime: '0', data: []); Future fetchLogsForLastMonth( ReportLogsInitial event, Emitter emit) async { DateTime now = DateTime.now(); DateTime lastMonth = DateTime(now.year, now.month - 1, now.day); int startTime = lastMonth.millisecondsSinceEpoch; int endTime = now.millisecondsSinceEpoch; try { emit(SosLoadingState()); var response = await DevicesAPI.getReportLogs( startTime: startTime.toString(), endTime: endTime.toString(), deviceUuid: sosId, code: 'sos', ); recordGroups = response; emit(UpdateState(sensor: deviceStatus)); } on DioException catch (e) { final errorData = e.response!.data; String errorMessage = errorData['message']; emit(SosFailedState(errorMessage: e.toString())); } } //========================= Device Info & Status ============================= static String deviceName = ''; fetchDeviceInfo(SosInitialDeviseInfo event, Emitter emit) async { try { emit(SosLoadingState()); var response = await DevicesAPI.getDeviceInfo(sosId); deviceInfo = DeviceInfoModel.fromJson(response); deviceName = deviceInfo.name; emit(LoadingSosDeviceInfo(deviceInfo: deviceInfo)); } catch (e) { emit(SosFailedState(errorMessage: e.toString())); } } void _fetchStatus(SosInitial event, Emitter emit) async { emit(SosLoadingState()); try { var response = await DevicesAPI.getDeviceStatus(sosId); List statusModelList = []; for (var status in response['status']) { statusModelList.add(StatusModel.fromJson(status)); } deviceStatus = SosModel.fromJson( statusModelList, ); emit(UpdateState(sensor: deviceStatus)); Future.delayed(const Duration(milliseconds: 500)); // _listenToChanges(); } catch (e) { emit(SosFailedState(errorMessage: e.toString())); return; } } //========================= assign & unassign devise to room ============================= List allDevices = []; List roomsList = []; void _fetchRoomsAndDevices( FetchRoomsEvent event, Emitter emit) async { try { emit(SosLoadingState()); Project? project = HomeCubit.getInstance().project; roomsList = await SpacesAPI.getSubSpaceBySpaceId( event.unit.community.uuid, event.unit.id, project?.uuid ?? TempConst.projectIdDev); emit(FetchRoomsState(devicesList: allDevices, roomsList: roomsList)); } catch (e) { emit(const SosFailedState(errorMessage: 'Something went wrong')); return; } } String roomId = ''; SpaceModel unit = SpaceModel(id: '', name: '', community: Community(uuid: '', name: '')); String _selectedOption = ''; bool _hasSelectionChanged = false; void _onOptionSelected(SelectOptionEvent event, Emitter emit) { emit(SosLoadingState()); _selectedOption = event.selectedOption; _hasSelectionChanged = true; emit(OptionSelectedState( selectedOption: _selectedOption, hasSelectionChanged: _hasSelectionChanged)); } void _onSaveSelection(SaveSelectionEvent event, Emitter emit) { if (_hasSelectionChanged) { _hasSelectionChanged = false; add(AssignRoomEvent(roomId: roomId, unit: unit, context: event.context)); emit(SaveSelectionSuccessState()); var cubit = HomeCubit.getInstance(); cubit.updatePageIndex(1); Navigator.pushReplacementNamed(event.context, Routes.homeRoute); } } void _assignDevice(AssignRoomEvent event, Emitter emit) async { try { emit(SosLoadingState()); Project? project = HomeCubit.getInstance().project; await HomeManagementAPI.assignDeviceToRoom( event.unit.community.uuid, event.unit.id, event.roomId, sosId, project?.uuid ?? TempConst.projectIdDev); final devicesList = await DevicesAPI.getDevicesByRoomId( communityUuid: event.unit.community.uuid, spaceUuid: event.unit.id, roomId: event.roomId, projectId: project?.uuid ?? TempConst.projectIdDev); List allDevicesIds = []; allDevices.forEach((element) { allDevicesIds.add(element.uuid!); }); await HomeCubit.getInstance().fetchUnitsByUserId(); CustomSnackBar.displaySnackBar('Save Successfully'); emit(SaveSelectionSuccessState()); } catch (e) { emit(const SosFailedState(errorMessage: 'Something went wrong')); return; } } void _unassignDevice(UnassignRoomEvent event, Emitter emit) async { try { Map roomDevicesId = {}; Project? project = HomeCubit.getInstance().project; emit(SosLoadingState()); await HomeManagementAPI.unAssignDeviceToRoom( event.unit.community.uuid, event.unit.id, event.roomId, sosId, project?.uuid ?? TempConst.projectIdDev); final devicesList = await DevicesAPI.getDevicesByRoomId( communityUuid: event.unit.community.uuid, spaceUuid: event.unit.id, roomId: event.roomId, projectId: project?.uuid ?? TempConst.projectIdDev); List allDevicesIds = []; allDevices.forEach((element) { allDevicesIds.add(element.uuid!); }); devicesList.forEach((e) { if (allDevicesIds.contains(e.uuid!)) { roomDevicesId[e.uuid!] = true; } else { roomDevicesId[e.uuid!] = false; } }); } catch (e) { emit(const SosFailedState(errorMessage: 'Something went wrong')); return; } } Map> devicesByRoom = {}; //======================= setting name ====================================== Future saveName(SaveNameEvent event, Emitter emit) async { if (_validateInputs()) return; try { add(const ChangeNameEvent(value: false)); isSaving = true; emit(SosLoadingState()); var response = await DevicesAPI.putDeviceName( deviceId: sosId, deviceName: nameController.text); add(SosInitialDeviseInfo()); CustomSnackBar.displaySnackBar('Save Successfully'); emit(SaveState()); } catch (e) { emit(SosFailedState(errorMessage: e.toString())); } finally { isSaving = false; } } bool _validateInputs() { final nameError = fullNameValidator(nameController.text); if (nameError != null) { CustomSnackBar.displaySnackBar(nameError); return true; } return false; } String? fullNameValidator(String? value) { if (value == null) return 'name is required'; final withoutExtraSpaces = value.replaceAll(RegExp(r"\s+"), ' ').trim(); if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) { return 'name must be between 2 and 30 characters long'; } if (RegExp(r"/[^ a-zA-Z0-9-\']/").hasMatch(withoutExtraSpaces)) { return 'Only alphanumeric characters, space, dash and single quote are allowed'; } return null; } void _changeName(ChangeNameEvent event, Emitter emit) { emit(SosLoadingState()); editName = event.value!; if (editName) { Future.delayed(const Duration(milliseconds: 500), () { focusNode.requestFocus(); }); } else { focusNode.unfocus(); } emit(NameEditingState(editName: editName)); } //============================== update setting =============================== bool enableUpdate = false; void _toggleUpdate(ToggleUpdateEvent event, Emitter emit) async { try { emit(SosLoadingState()); enableUpdate = event.isUpdateEnabled!; emit(SaveState()); } catch (e) { emit(SosFailedState(errorMessage: e.toString())); } } //============================ Question and faq ============================ final List faqQuestions = [ QuestionModel( id: 1, question: 'How does an SOS emergency button work?', answer: 'The SOS emergency button sends an alert to your contacts when pressed.', ), QuestionModel( id: 2, question: 'How long will an SOS alarm persist?', answer: 'The SOS alarm will persist until it is manually turned off or after a set time.', ), QuestionModel( id: 3, question: 'What should I do if the SOS button is unresponsive?', answer: 'Try restarting the device. If it persists, contact support.', ), QuestionModel( id: 4, question: 'Can I use the SOS feature without a network connection?', answer: 'No, a network connection is required to send the alert to your contacts.', ), QuestionModel( id: 5, question: 'How often should I check the SOS battery?', answer: 'Check the SOS battery at least once a month to ensure it is operational.', ), ]; Future _onSosInitial( SosInitialQuestion event, Emitter emit) async { emit(SosLoadingState()); emit(FaqLoadedState(filteredFaqQuestions: faqQuestions)); } void _onSearchFaq(SearchFaqEvent event, Emitter emit) { emit(SosLoadingState()); List _faqQuestions = faqQuestions.where((question) { return question.question .toLowerCase() .contains(event.query.toLowerCase()); }).toList(); emit(FaqSearchState(filteredFaqQuestions: _faqQuestions)); } bool isHelpful = false; void _toggleHelpful(ToggleHelpfulEvent event, Emitter emit) async { try { emit(SosLoadingState()); isHelpful = event.isHelpful!; emit(SaveState()); } catch (e) { emit(SosFailedState(errorMessage: e.toString())); } } //===================== delete Device =================================== deleteDevice(DeleteDeviceEvent event, Emitter emit) async { try { emit(SosLoadingState()); var response = await DevicesAPI.resetDevise(devicesUuid: sosId); add(SosInitialDeviseInfo()); add(const SosInitial()); CustomSnackBar.displaySnackBar('Reset Successfully'); } catch (e) { emit(SosFailedState(errorMessage: e.toString())); } } //real-time db // StreamSubscription? _streamSubscription; // Timer? _timer; // void _listenToChanges() { // try { // _streamSubscription?.cancel(); // DatabaseReference ref = // FirebaseDatabase.instance.ref('device-status/$sosId'); // Stream stream = ref.onValue; // _streamSubscription = stream.listen((DatabaseEvent event) async { // if (_timer != null) { // await Future.delayed(const Duration(seconds: 2)); // } // Map usersMap = // event.snapshot.value as Map; // List statusList = []; // usersMap['status'].forEach((element) { // statusList // .add(StatusModel(code: element['code'], value: element['value'])); // }); // deviceStatus = SosModel.fromJson(statusList); // }); // } catch (_) {} // } // @override // Future close() async { // _streamSubscription?.cancel(); // _streamSubscription = null; // return super.close(); // } }