mirror of
https://github.com/SyncrowIOT/syncrow-app.git
synced 2025-07-16 01:56:19 +00:00
475 lines
15 KiB
Dart
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();
|
|
// }
|
|
}
|