Files
syncrow-app/lib/features/devices/bloc/sos_bloc/sos_bloc.dart
2025-02-26 12:17:57 +03:00

475 lines
15 KiB
Dart

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<SosEvent, SosState> {
final String sosId;
SosBloc({
required this.sosId,
}) : super(const SosState()) {
on<SosInitial>(_fetchStatus);
on<ReportLogsInitial>(fetchLogsForLastMonth);
on<ToggleEnableAlarmEvent>(_toggleLowBattery);
on<ToggleClosingReminderEvent>(_toggleClosingReminder);
on<ChangeNameEvent>(_changeName);
on<SearchFaqEvent>(_onSearchFaq);
on<SosInitialQuestion>(_onSosInitial);
on<FetchRoomsEvent>(_fetchRoomsAndDevices);
on<SelectOptionEvent>(_onOptionSelected);
on<SaveSelectionEvent>(_onSaveSelection);
on<AssignRoomEvent>(_assignDevice);
on<SosInitialDeviseInfo>(fetchDeviceInfo);
on<SaveNameEvent>(saveName);
on<ToggleUpdateEvent>(_toggleUpdate);
on<ToggleHelpfulEvent>(_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<SosState> 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<SosState> 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<void> fetchLogsForLastMonth(
ReportLogsInitial event, Emitter<SosState> 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<SosState> 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<SosState> emit) async {
emit(SosLoadingState());
try {
var response = await DevicesAPI.getDeviceStatus(sosId);
List<StatusModel> 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<DeviceModel> allDevices = [];
List<SubSpaceModel> roomsList = [];
void _fetchRoomsAndDevices(
FetchRoomsEvent event, Emitter<SosState> 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<SosState> emit) {
emit(SosLoadingState());
_selectedOption = event.selectedOption;
_hasSelectionChanged = true;
emit(OptionSelectedState(
selectedOption: _selectedOption,
hasSelectionChanged: _hasSelectionChanged));
}
void _onSaveSelection(SaveSelectionEvent event, Emitter<SosState> 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<SosState> 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<String> 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<SosState> emit) async {
try {
Map<String, bool> 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<String> 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<String, List<DeviceModel>> devicesByRoom = {};
//======================= setting name ======================================
Future<void> saveName(SaveNameEvent event, Emitter<SosState> 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<SosState> 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<SosState> emit) async {
try {
emit(SosLoadingState());
enableUpdate = event.isUpdateEnabled!;
emit(SaveState());
} catch (e) {
emit(SosFailedState(errorMessage: e.toString()));
}
}
//============================ Question and faq ============================
final List<QuestionModel> 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<void> _onSosInitial(
SosInitialQuestion event, Emitter<SosState> emit) async {
emit(SosLoadingState());
emit(FaqLoadedState(filteredFaqQuestions: faqQuestions));
}
void _onSearchFaq(SearchFaqEvent event, Emitter<SosState> emit) {
emit(SosLoadingState());
List<QuestionModel> _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<SosState> emit) async {
try {
emit(SosLoadingState());
isHelpful = event.isHelpful!;
emit(SaveState());
} catch (e) {
emit(SosFailedState(errorMessage: e.toString()));
}
}
//===================== delete Device ===================================
deleteDevice(DeleteDeviceEvent event, Emitter<SosState> 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<DatabaseEvent>? _streamSubscription;
// Timer? _timer;
// void _listenToChanges() {
// try {
// _streamSubscription?.cancel();
// DatabaseReference ref =
// FirebaseDatabase.instance.ref('device-status/$sosId');
// Stream<DatabaseEvent> stream = ref.onValue;
// _streamSubscription = stream.listen((DatabaseEvent event) async {
// if (_timer != null) {
// await Future.delayed(const Duration(seconds: 2));
// }
// Map<dynamic, dynamic> usersMap =
// event.snapshot.value as Map<dynamic, dynamic>;
// List<StatusModel> statusList = [];
// usersMap['status'].forEach((element) {
// statusList
// .add(StatusModel(code: element['code'], value: element['value']));
// });
// deviceStatus = SosModel.fromJson(statusList);
// });
// } catch (_) {}
// }
// @override
// Future<void> close() async {
// _streamSubscription?.cancel();
// _streamSubscription = null;
// return super.close();
// }
}