diff --git a/assets/icons/edit_name_sos_icon.svg b/assets/icons/edit_name_sos_icon.svg new file mode 100644 index 0000000..c9e6a9e --- /dev/null +++ b/assets/icons/edit_name_sos_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/features/devices/bloc/6_scene_switch_bloc/6_scene_bloc.dart b/lib/features/devices/bloc/6_scene_switch_bloc/6_scene_bloc.dart index 6fbfb69..68c33ad 100644 --- a/lib/features/devices/bloc/6_scene_switch_bloc/6_scene_bloc.dart +++ b/lib/features/devices/bloc/6_scene_switch_bloc/6_scene_bloc.dart @@ -8,7 +8,7 @@ import 'package:syncrow_app/features/devices/model/device_control_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/group_devices_model.dart'; -import 'package:syncrow_app/features/devices/model/scene_switch_model.dart'; +import 'package:syncrow_app/features/devices/model/device_info_model.dart'; import 'package:syncrow_app/features/devices/model/sex_scene_question_model.dart'; import 'package:syncrow_app/features/devices/model/six_scene_model.dart'; import 'package:syncrow_app/features/devices/model/status_model.dart'; @@ -68,7 +68,7 @@ class SixSceneBloc extends Bloc { scene_id_group_id: '', switch_backlight: false); - SceneSwitch deviceInfo = SceneSwitch( + DeviceInfoModel deviceInfo = DeviceInfoModel( activeTime: 0, category: "", categoryName: "", @@ -175,7 +175,7 @@ class SixSceneBloc extends Bloc { try { emit(SixSceneLoadingState()); var response = await DevicesAPI.getDeviceInfo(sixSceneId); - deviceInfo = SceneSwitch.fromJson(response); + deviceInfo = DeviceInfoModel.fromJson(response); deviceName = deviceInfo.name; emit(LoadingDeviceInfo(deviceInfo: deviceInfo)); } catch (e) { diff --git a/lib/features/devices/bloc/6_scene_switch_bloc/6_scene_state.dart b/lib/features/devices/bloc/6_scene_switch_bloc/6_scene_state.dart index d5480f5..318d37a 100644 --- a/lib/features/devices/bloc/6_scene_switch_bloc/6_scene_state.dart +++ b/lib/features/devices/bloc/6_scene_switch_bloc/6_scene_state.dart @@ -1,7 +1,7 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_app/features/devices/model/device_model.dart'; import 'package:syncrow_app/features/devices/model/group_devices_model.dart'; -import 'package:syncrow_app/features/devices/model/scene_switch_model.dart'; +import 'package:syncrow_app/features/devices/model/device_info_model.dart'; import 'package:syncrow_app/features/devices/model/sex_scene_question_model.dart'; import 'package:syncrow_app/features/devices/model/six_scene_model.dart'; import 'package:syncrow_app/features/devices/model/subspace_model.dart'; @@ -121,7 +121,7 @@ class SceneSelectionUpdatedState extends SixSceneState { } class LoadingDeviceInfo extends SixSceneState { - final SceneSwitch deviceInfo; + final DeviceInfoModel deviceInfo; const LoadingDeviceInfo({required this.deviceInfo}); @override diff --git a/lib/features/devices/bloc/four_scene_bloc/four_scene_bloc.dart b/lib/features/devices/bloc/four_scene_bloc/four_scene_bloc.dart index 469515f..4987e7a 100644 --- a/lib/features/devices/bloc/four_scene_bloc/four_scene_bloc.dart +++ b/lib/features/devices/bloc/four_scene_bloc/four_scene_bloc.dart @@ -8,10 +8,10 @@ import 'package:syncrow_app/features/devices/model/device_control_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/four_scene_model.dart'; -import 'package:syncrow_app/features/devices/model/four_scene_question_model.dart'; +import 'package:syncrow_app/features/devices/model/question_model.dart'; import 'package:syncrow_app/features/devices/model/four_scene_switch_model.dart'; import 'package:syncrow_app/features/devices/model/group_devices_model.dart'; -import 'package:syncrow_app/features/devices/model/scene_switch_model.dart'; +import 'package:syncrow_app/features/devices/model/device_info_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/features/scene/model/scenes_model.dart'; @@ -68,7 +68,7 @@ class FourSceneBloc extends Bloc { scene_id_group_id: '', switch_backlight: false); - SceneSwitch deviceInfo = SceneSwitch( + DeviceInfoModel deviceInfo = DeviceInfoModel( activeTime: 0, category: "", categoryName: "", @@ -285,7 +285,7 @@ class FourSceneBloc extends Bloc { try { emit(FourSceneLoadingState()); var response = await DevicesAPI.getDeviceInfo(fourSceneId); - deviceInfo = SceneSwitch.fromJson(response); + deviceInfo = DeviceInfoModel.fromJson(response); deviceName = deviceInfo.name; emit(LoadingDeviceInfo(deviceInfo: deviceInfo)); } catch (e) { @@ -295,7 +295,7 @@ class FourSceneBloc extends Bloc { void _onSearchFaq(SearchFaqEvent event, Emitter emit) { emit(FourSceneLoadingState()); - List _faqQuestions = faqQuestions.where((question) { + List _faqQuestions = faqQuestions.where((question) { return question.question .toLowerCase() .contains(event.query.toLowerCase()); @@ -339,31 +339,31 @@ class FourSceneBloc extends Bloc { DeviceReport recordGroups = DeviceReport(startTime: '0', endTime: '0', data: []); - final List faqQuestions = [ - FourSceneQuestionModel( + 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.', ), - FourSceneQuestionModel( + 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.', ), - FourSceneQuestionModel( + QuestionModel( id: 3, question: 'What should I do if the SOS button is unresponsive?', answer: 'Try restarting the device. If it persists, contact support.', ), - FourSceneQuestionModel( + 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.', ), - FourSceneQuestionModel( + QuestionModel( id: 5, question: 'How often should I check the SOS battery?', answer: diff --git a/lib/features/devices/bloc/four_scene_bloc/four_scene_state.dart b/lib/features/devices/bloc/four_scene_bloc/four_scene_state.dart index 66c888d..08c849c 100644 --- a/lib/features/devices/bloc/four_scene_bloc/four_scene_state.dart +++ b/lib/features/devices/bloc/four_scene_bloc/four_scene_state.dart @@ -1,9 +1,9 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_app/features/devices/model/device_model.dart'; import 'package:syncrow_app/features/devices/model/four_scene_model.dart'; -import 'package:syncrow_app/features/devices/model/four_scene_question_model.dart'; +import 'package:syncrow_app/features/devices/model/question_model.dart'; import 'package:syncrow_app/features/devices/model/group_devices_model.dart'; -import 'package:syncrow_app/features/devices/model/scene_switch_model.dart'; +import 'package:syncrow_app/features/devices/model/device_info_model.dart'; import 'package:syncrow_app/features/devices/model/subspace_model.dart'; import 'package:syncrow_app/features/scene/model/scenes_model.dart'; @@ -58,13 +58,13 @@ class NameEditingState extends FourSceneState { } class FaqLoadedState extends FourSceneState { - final List filteredFaqQuestions; + final List filteredFaqQuestions; FaqLoadedState({this.filteredFaqQuestions = const []}); } class FaqSearchState extends FourSceneState { - final List filteredFaqQuestions; + final List filteredFaqQuestions; const FaqSearchState({this.filteredFaqQuestions = const []}); } @@ -116,7 +116,7 @@ class OptionSelectedState extends FourSceneState { } class LoadingDeviceInfo extends FourSceneState { - final SceneSwitch deviceInfo; + final DeviceInfoModel deviceInfo; const LoadingDeviceInfo({required this.deviceInfo}); @override diff --git a/lib/features/devices/bloc/sos_bloc/sos_bloc.dart b/lib/features/devices/bloc/sos_bloc/sos_bloc.dart new file mode 100644 index 0000000..02d385e --- /dev/null +++ b/lib/features/devices/bloc/sos_bloc/sos_bloc.dart @@ -0,0 +1,452 @@ +import 'dart:async'; +import 'package:dio/dio.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/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/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(_unassignDevice); + // on(_toggleWaterLeakAlarm); + } + + final TextEditingController nameController = + TextEditingController(text: '${'firstName'}'); + bool isSaving = false; + bool editName = false; + final FocusNode focusNode = FocusNode(); + Timer? _timer; + bool enableAlarm = false; + bool closingReminder = false; + bool waterAlarm = false; + SosModel deviceStatus = + SosModel(sosContactState: 'normal', batteryPercentage: 0); + + 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; + } + } + + 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 _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)); + } + + 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)); + } + + 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)); + + // API call to update the state, if necessary + // await DevicesAPI.controlDevice( + // DeviceControlModel( + // deviceId: sosId, + // code: 'closing_reminder', + // value: closingReminder, + // ), + // sosId, + // ); + } 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())); + } + } + + 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())); + } + } + + // Demo list of FAQ questions using the QuestionModel class + 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()); + // SosModel sosModel = await fetchSosData(sosId); // Define this function as needed + emit(FaqLoadedState(filteredFaqQuestions: faqQuestions)); + } + + List allDevices = []; + List roomsList = []; + + void _fetchRoomsAndDevices( + FetchRoomsEvent event, Emitter emit) async { + try { + emit(SosLoadingState()); + roomsList = await SpacesAPI.getSubSpaceBySpaceId( + event.unit.community.uuid, event.unit.id); + 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()); + + // Navigator.pushNamedAndRemoveUntil( + // event.context, Routes.homeRoute, (route) => false); + var cubit = HomeCubit.getInstance(); + cubit.updatePageIndex(1); + Navigator.pushReplacementNamed(event.context, Routes.homeRoute); + } + } + + void _assignDevice(AssignRoomEvent event, Emitter emit) async { + try { + // Map roomDevicesId = {}; + emit(SosLoadingState()); + + await HomeManagementAPI.assignDeviceToRoom( + event.unit.community.uuid, event.unit.id, event.roomId, sosId); + final devicesList = await DevicesAPI.getDevicesByRoomId( + communityUuid: event.unit.community.uuid, + spaceUuid: event.unit.id, + roomId: event.roomId); + + List allDevicesIds = []; + + allDevices.forEach((element) { + allDevicesIds.add(element.uuid!); + }); + emit(SaveSelectionSuccessState()); + + // devicesList.forEach((e) { + // if (allDevicesIds.contains(e.uuid!)) { + // roomDevicesId[e.uuid!] = true; + // } else { + // roomDevicesId[e.uuid!] = false; + // } + // }); + // emit(FetchDeviceByRoomIdState( + // roomDevices: devicesList, + // allDevices: allDevices, + // roomDevicesId: roomDevicesId, + // roomId: event.roomId)); + } catch (e) { + emit(const SosFailedState(errorMessage: 'Something went wrong')); + return; + } + } + + void _unassignDevice(UnassignRoomEvent event, Emitter emit) async { + try { + Map roomDevicesId = {}; + emit(SosLoadingState()); + await HomeManagementAPI.unAssignDeviceToRoom( + event.unit.community.uuid, event.unit.id, event.roomId, sosId); + final devicesList = await DevicesAPI.getDevicesByRoomId( + communityUuid: event.unit.community.uuid, + spaceUuid: event.unit.id, + roomId: event.roomId); + + 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; + } + }); + // emit(FetchDeviceByRoomIdState( + // roomDevices: devicesList, + // allDevices: allDevices, + // roomDevicesId: roomDevicesId, + // roomId: event.roomId)); + } catch (e) { + emit(const SosFailedState(errorMessage: 'Something went wrong')); + return; + } + } + + Map> devicesByRoom = {}; + +// Your existing function + void _fetchDevicesByRoomId( + FetchDevicesByRoomIdEvent event, Emitter emit) async { + try { + Map roomDevicesId = {}; + emit(SosLoadingState()); + + // Fetch devices for the specified room + final devicesList = await DevicesAPI.getDevicesByRoomId( + communityUuid: event.unit.community.uuid, + spaceUuid: event.unit.id, + roomId: event.roomId); + + // Fetch all devices accessible by the user + allDevices = await HomeManagementAPI.fetchDevicesByUserId(); + + // Map all accessible device IDs + List allDevicesIds = []; + allDevices.forEach((element) { + allDevicesIds.add(element.uuid!); + }); + + // Mark devices as accessible/inaccessible for the room and add to devicesByRoom map + devicesList.forEach((e) { + roomDevicesId[e.uuid!] = allDevicesIds.contains(e.uuid!); + }); + + // Update the devicesByRoom map with the devices in the current room + devicesByRoom[event.roomId] = devicesList; + + // emit(FetchDeviceByRoomIdState( + // roomDevices: devicesList, + // allDevices: allDevices, + // roomDevicesId: roomDevicesId, + // roomId: event.roomId)); + } catch (e) { + emit(const SosFailedState(errorMessage: 'Something went wrong')); + return; + } + } + + 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; + } +} diff --git a/lib/features/devices/bloc/sos_bloc/sos_event.dart b/lib/features/devices/bloc/sos_bloc/sos_event.dart new file mode 100644 index 0000000..8a882a9 --- /dev/null +++ b/lib/features/devices/bloc/sos_bloc/sos_event.dart @@ -0,0 +1,196 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/app_layout/model/space_model.dart'; +import 'package:syncrow_app/features/devices/model/device_info_model.dart'; + +abstract class SosEvent extends Equatable { + const SosEvent(); + + @override + List get props => []; +} + +class SosLoading extends SosEvent {} + +class SosSwitch extends SosEvent { + final String switchD; + final String deviceId; + final String productId; + const SosSwitch( + {required this.switchD, this.deviceId = '', this.productId = ''}); + + @override + List get props => [switchD, deviceId, productId]; +} + +class SosUpdated extends SosEvent {} +class SosInitialDeviseInfo extends SosEvent {} + +class SosInitial extends SosEvent { + const SosInitial(); +} + +class ReportLogsInitial extends SosEvent { + const ReportLogsInitial(); +} + +class SosChangeStatus extends SosEvent {} + +class GetCounterEvent extends SosEvent { + final String deviceCode; + const GetCounterEvent({required this.deviceCode}); + @override + List get props => [deviceCode]; +} + +class ToggleEnableAlarmEvent extends SosEvent { + final bool isLowBatteryEnabled; + + const ToggleEnableAlarmEvent(this.isLowBatteryEnabled); + + @override + List get props => [isLowBatteryEnabled]; +} + +class ToggleClosingReminderEvent extends SosEvent { + final bool isClosingReminderEnabled; + + const ToggleClosingReminderEvent(this.isClosingReminderEnabled); + + @override + List get props => [isClosingReminderEnabled]; +} + +class ToggleSosAlarmEvent extends SosEvent { + final bool isSosAlarmEnabled; + + const ToggleSosAlarmEvent(this.isSosAlarmEnabled); + + @override + List get props => [isSosAlarmEnabled]; +} + +class SetCounterValue extends SosEvent { + final Duration duration; + final String deviceCode; + const SetCounterValue({required this.duration, required this.deviceCode}); + @override + List get props => [duration, deviceCode]; +} + +class StartTimer extends SosEvent { + final int duration; + + const StartTimer(this.duration); + + @override + List get props => [duration]; +} + +class TickTimer extends SosEvent { + final int remainingTime; + + const TickTimer(this.remainingTime); + + @override + List get props => [remainingTime]; +} + +class StopTimer extends SosEvent {} + +class OnClose extends SosEvent {} +class SaveNameEvent extends SosEvent {} + +class ChangeNameEvent extends SosEvent { + final bool? value; + const ChangeNameEvent({this.value}); +} + +class SearchFaqEvent extends SosEvent { + final String query; + + const SearchFaqEvent(this.query); +} + +class SosInitialQuestion extends SosEvent { + const SosInitialQuestion(); +} + +class FetchRoomsEvent extends SosEvent { + final SpaceModel unit; + + const FetchRoomsEvent({required this.unit}); + + @override + List get props => [unit]; +} + + +class SelectOptionEvent extends SosEvent { + dynamic selectedOption; + SelectOptionEvent({ + this.selectedOption, + }); +} + +class SaveSelectionEvent extends SosEvent { + BuildContext context; + SaveSelectionEvent({ + required this.context, + }); +} + +class AssignRoomEvent extends SosEvent { + final String roomId; + final SpaceModel unit; + final BuildContext context; + + const AssignRoomEvent({ + required this.roomId, + required this.unit, + required this.context, + }); + + @override + List get props => [ + roomId, + unit, + context, + ]; +} +class UnassignRoomEvent extends SosEvent { + final String roomId; + final String deviceId; + final SpaceModel unit; + + const UnassignRoomEvent( + {required this.roomId, required this.deviceId, required this.unit}); + + @override + List get props => [roomId, unit]; +} + +class FetchDevicesByRoomIdEvent extends SosEvent { + final SpaceModel unit; // Represents the unit (e.g., room or space) context + final String roomId; // Unique identifier for the room + + // Constructor + FetchDevicesByRoomIdEvent({ + required this.unit, + required this.roomId, + }); + + // Adding properties for Equatable to compare + @override + List get props => [unit, roomId]; +} + + +class LoadingDeviceInfo extends SosEvent { + DeviceInfoModel deviceInfo; + LoadingDeviceInfo({required this.deviceInfo}); + + @override + List get props => [deviceInfo]; +} \ No newline at end of file diff --git a/lib/features/devices/bloc/sos_bloc/sos_state.dart b/lib/features/devices/bloc/sos_bloc/sos_state.dart new file mode 100644 index 0000000..1ffee2d --- /dev/null +++ b/lib/features/devices/bloc/sos_bloc/sos_state.dart @@ -0,0 +1,94 @@ +import 'package:equatable/equatable.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/question_model.dart'; +import 'package:syncrow_app/features/devices/model/sos_model.dart'; +import 'package:syncrow_app/features/devices/model/subspace_model.dart'; + +class SosState extends Equatable { + const SosState(); + + @override + List get props => []; +} + +class SosInitialState extends SosState {} + +class SosLoadingState extends SosState {} +class SaveState extends SosState {} + +class LoadingSosDeviceInfo extends SosState { + final DeviceInfoModel? deviceInfo; + const LoadingSosDeviceInfo({this.deviceInfo}); +} + +class SosFailedState extends SosState { + final String errorMessage; + + const SosFailedState({required this.errorMessage}); + + @override + List get props => [errorMessage]; +} + +class UpdateState extends SosState { + final SosModel sensor; + const UpdateState({required this.sensor}); + + @override + List get props => [sensor]; +} + +class LoadingNewSate extends SosState { + final SosModel sosSensor; + const LoadingNewSate({required this.sosSensor}); + + @override + List get props => [sosSensor]; +} + +class NameEditingState extends SosState { + final bool editName; + + NameEditingState({required this.editName}); +} + +class FaqLoadedState extends SosState { + final List filteredFaqQuestions; + + FaqLoadedState({this.filteredFaqQuestions = const []}); +} + +class FaqSearchState extends SosState { + final List filteredFaqQuestions; + + const FaqSearchState({this.filteredFaqQuestions = const []}); +} + +class FetchRoomsState extends SosState { + final List roomsList; + final List devicesList; + + const FetchRoomsState({required this.devicesList, required this.roomsList}); + + @override + List get props => [devicesList]; +} + +class OptionSelectedState extends SosState { + final String selectedOption; + final bool hasSelectionChanged; + + OptionSelectedState({ + required this.selectedOption, + required this.hasSelectionChanged, + }); + + @override + List get props => [selectedOption, hasSelectionChanged]; +} + +class SaveSelectionSuccessState extends SosState { + @override + List get props => []; +} diff --git a/lib/features/devices/model/scene_switch_model.dart b/lib/features/devices/model/device_info_model.dart similarity index 96% rename from lib/features/devices/model/scene_switch_model.dart rename to lib/features/devices/model/device_info_model.dart index f21c1ad..9c01d70 100644 --- a/lib/features/devices/model/scene_switch_model.dart +++ b/lib/features/devices/model/device_info_model.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -class SceneSwitch { +class DeviceInfoModel { final int activeTime; final String category; final String categoryName; @@ -27,7 +27,7 @@ class SceneSwitch { final String macAddress; final Subspace subspace; - SceneSwitch({ + DeviceInfoModel({ required this.activeTime, required this.category, required this.categoryName, @@ -55,8 +55,8 @@ class SceneSwitch { required this.subspace, }); - factory SceneSwitch.fromJson(Map json) { - return SceneSwitch( + factory DeviceInfoModel.fromJson(Map json) { + return DeviceInfoModel( activeTime: json['activeTime'], category: json['category'], categoryName: json['categoryName'], diff --git a/lib/features/devices/model/four_scene_question_model.dart b/lib/features/devices/model/question_model.dart similarity index 71% rename from lib/features/devices/model/four_scene_question_model.dart rename to lib/features/devices/model/question_model.dart index ed4eacc..2965620 100644 --- a/lib/features/devices/model/four_scene_question_model.dart +++ b/lib/features/devices/model/question_model.dart @@ -1,9 +1,9 @@ -class FourSceneQuestionModel { +class QuestionModel { final int id; final String question; final String answer; - FourSceneQuestionModel({ + QuestionModel({ required this.id, required this.question, required this.answer, diff --git a/lib/features/devices/model/sos_model.dart b/lib/features/devices/model/sos_model.dart new file mode 100644 index 0000000..7534b60 --- /dev/null +++ b/lib/features/devices/model/sos_model.dart @@ -0,0 +1,28 @@ +import 'package:syncrow_app/features/devices/model/status_model.dart'; + +class SosModel { + String sosContactState; + int batteryPercentage; + + SosModel({ + required this.sosContactState, + required this.batteryPercentage, + }); + + factory SosModel.fromJson(List jsonList) { + late String _sosContactState; + late int _batteryPercentage; + + for (int i = 0; i < jsonList.length; i++) { + if (jsonList[i].code == 'sos') { + _sosContactState = jsonList[i].value ?? ''; + } else if (jsonList[i].code == 'battery_percentage') { + _batteryPercentage = jsonList[i].value ?? 0; + } + } + return SosModel( + sosContactState: _sosContactState, + batteryPercentage: _batteryPercentage, + ); + } +} diff --git a/lib/features/devices/view/widgets/four_scene_switch/four_scene_setting/faq_four_scene_page.dart b/lib/features/devices/view/widgets/four_scene_switch/four_scene_setting/faq_four_scene_page.dart index d43945a..30e30e2 100644 --- a/lib/features/devices/view/widgets/four_scene_switch/four_scene_setting/faq_four_scene_page.dart +++ b/lib/features/devices/view/widgets/four_scene_switch/four_scene_setting/faq_four_scene_page.dart @@ -5,7 +5,7 @@ import 'package:syncrow_app/features/devices/bloc/four_scene_bloc/four_scene_blo import 'package:syncrow_app/features/devices/bloc/four_scene_bloc/four_scene_event.dart'; import 'package:syncrow_app/features/devices/bloc/four_scene_bloc/four_scene_state.dart'; import 'package:syncrow_app/features/devices/model/device_model.dart'; -import 'package:syncrow_app/features/devices/model/four_scene_question_model.dart'; +import 'package:syncrow_app/features/devices/model/question_model.dart'; import 'package:syncrow_app/features/devices/view/widgets/four_scene_switch/four_scene_setting/question_page_four_scene.dart'; import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; @@ -30,7 +30,7 @@ class FaqFourScenePage extends StatelessWidget { builder: (context, state) { final sensor = BlocProvider.of(context); - List displayedQuestions = []; + List displayedQuestions = []; if (state is FaqSearchState) { displayedQuestions = state.filteredFaqQuestions; } else if (state is FaqLoadedState) { diff --git a/lib/features/devices/view/widgets/four_scene_switch/four_scene_setting/question_page_four_scene.dart b/lib/features/devices/view/widgets/four_scene_switch/four_scene_setting/question_page_four_scene.dart index 3d1b495..5ebbc8b 100644 --- a/lib/features/devices/view/widgets/four_scene_switch/four_scene_setting/question_page_four_scene.dart +++ b/lib/features/devices/view/widgets/four_scene_switch/four_scene_setting/question_page_four_scene.dart @@ -5,7 +5,7 @@ import 'package:syncrow_app/features/devices/bloc/four_scene_bloc/four_scene_blo import 'package:syncrow_app/features/devices/bloc/four_scene_bloc/four_scene_event.dart'; import 'package:syncrow_app/features/devices/bloc/four_scene_bloc/four_scene_state.dart'; import 'package:syncrow_app/features/devices/model/four_scene_model.dart'; -import 'package:syncrow_app/features/devices/model/four_scene_question_model.dart'; +import 'package:syncrow_app/features/devices/model/question_model.dart'; import 'package:syncrow_app/features/shared_widgets/default_button.dart'; import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; @@ -15,7 +15,7 @@ import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; class QuestionPageFourScene extends StatelessWidget { - final FourSceneQuestionModel? questionModel; + final QuestionModel? questionModel; const QuestionPageFourScene({super.key, this.questionModel}); diff --git a/lib/features/devices/view/widgets/room_page_switch.dart b/lib/features/devices/view/widgets/room_page_switch.dart index bb61799..89e6c22 100644 --- a/lib/features/devices/view/widgets/room_page_switch.dart +++ b/lib/features/devices/view/widgets/room_page_switch.dart @@ -18,6 +18,7 @@ import 'package:syncrow_app/features/devices/view/widgets/lights/light_interface import 'package:syncrow_app/features/devices/view/widgets/one_gang/one_gang_Interface.dart'; import 'package:syncrow_app/features/devices/view/widgets/one_touch/one_touch_screen.dart'; import 'package:syncrow_app/features/devices/view/widgets/power_clamp/power_clamp_page.dart'; +import 'package:syncrow_app/features/devices/view/widgets/sos/sos_screen.dart'; import 'package:syncrow_app/features/devices/view/widgets/three_touch/three_touch_interface.dart'; import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_gang_Interface.dart'; import 'package:syncrow_app/features/devices/view/widgets/two_touch/two_touch_Interface.dart'; @@ -228,6 +229,13 @@ void showDeviceInterface(DeviceModel device, BuildContext context) { pageBuilder: (context, animation1, animation2) => FourSceneScreen(device: device))); + case DeviceType.SOS: + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + SosScreen(device: device))); + break; default: } diff --git a/lib/features/devices/view/widgets/sos/sos_alarm_management_page.dart b/lib/features/devices/view/widgets/sos/sos_alarm_management_page.dart new file mode 100644 index 0000000..c750368 --- /dev/null +++ b/lib/features/devices/view/widgets/sos/sos_alarm_management_page.dart @@ -0,0 +1,112 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/sos_bloc/sos_bloc.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/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class AlarmManagementPage extends StatelessWidget { + const AlarmManagementPage({super.key}); + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Alarm Settings', + child: BlocProvider( + create: (context) => SosBloc(sosId: ''), + child: BlocBuilder( + builder: (context, state) { + final _bloc = BlocProvider.of(context); + + return state is LoadingNewSate + ? const Center( + child: DefaultContainer( + width: 50, + height: 50, + child: CircularProgressIndicator()), + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 10, + ), + const BodyMedium( + text: 'The Alarm Management', + fontWeight: FontWeight.w700, + fontSize: 12, + fontColor: ColorsManager.grayColor, + ), + const SizedBox( + height: 5, + ), + DefaultContainer( + child: Padding( + padding: const EdgeInsets.only(left: 10, right: 10), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 50, + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: const BodyMedium( + text: 'SOS Alarm', + fontWeight: FontWeight.w400, + fontSize: 15, + ), + trailing: Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: _bloc.enableAlarm, + onChanged: (value) { + context.read().add( + ToggleEnableAlarmEvent(value)); + }, + applyTheme: true, + )), + ), + ), + const Divider( + color: ColorsManager.graysColor, + ), + SizedBox( + height: 50, + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: const BodyLarge( + text: 'Low Battery Reminder', + fontWeight: FontWeight.w400, + fontSize: 15, + ), + trailing: Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: _bloc.closingReminder, + onChanged: (value) { + context.read().add( + ToggleClosingReminderEvent( + value)); + }, + applyTheme: true, + )), + ), + ), + ], + ), + ), + ), + ], + ); + }, + ), + )); + } +} diff --git a/lib/features/devices/view/widgets/sos/sos_records_screen.dart b/lib/features/devices/view/widgets/sos/sos_records_screen.dart new file mode 100644 index 0000000..e459226 --- /dev/null +++ b/lib/features/devices/view/widgets/sos/sos_records_screen.dart @@ -0,0 +1,231 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:intl/intl.dart'; +import 'package:syncrow_app/features/devices/bloc/sos_bloc/sos_bloc.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_report_model.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class SosRecordsScreen extends StatefulWidget { + final String sosId; + const SosRecordsScreen({super.key, required this.sosId}); + + @override + State createState() => _SosRecordsScreenState(); +} + +class _SosRecordsScreenState extends State { + int _selectedIndex = 0; + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Records', + child: BlocProvider( + create: (context) => + SosBloc(sosId: widget.sosId)..add(const ReportLogsInitial()), + child: BlocBuilder( + builder: (context, state) { + final waterSensorBloc = BlocProvider.of(context); + final Map> groupedRecords = {}; + + if (state is LoadingNewSate) { + return const Center( + child: DefaultContainer( + width: 50, height: 50, child: CircularProgressIndicator()), + ); + } else if (state is UpdateState) { + for (var record in waterSensorBloc.recordGroups.data!) { + final DateTime eventDateTime = + DateTime.fromMillisecondsSinceEpoch(record.eventTime!); + final String formattedDate = + DateFormat('EEEE, dd/MM/yyyy').format(eventDateTime); + if (groupedRecords.containsKey(formattedDate)) { + groupedRecords[formattedDate]!.add(record); + } else { + groupedRecords[formattedDate] = [record]; + } + } + } + + return DefaultTabController( + length: 2, + child: Column( + children: [ + Container( + width: MediaQuery.of(context).size.width, + decoration: const ShapeDecoration( + color: ColorsManager.onPrimaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(30)), + ), + ), + child: TabBar( + onTap: (value) { + setState(() { + _selectedIndex = value; + }); + }, + indicatorColor: Colors.white, + dividerHeight: 0, + indicatorSize: TabBarIndicatorSize.tab, + indicator: const ShapeDecoration( + color: ColorsManager.slidingBlueColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + ), + tabs: [ + Tab( + child: Container( + padding: const EdgeInsets.symmetric(vertical: 10), + child: BodySmall( + text: 'Record', + style: context.bodySmall.copyWith( + color: _selectedIndex == 0 + ? Colors.white + : ColorsManager.blackColor, + fontSize: 12, + fontWeight: FontWeight.w400, + ), + ), + ), + ), + Tab( + child: Container( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Text( + 'Automation Log', + style: context.bodySmall.copyWith( + color: _selectedIndex == 1 + ? Colors.white + : ColorsManager.blackColor, + fontSize: 12, + fontWeight: FontWeight.w400, + ), + ), + ), + ), + ], + ), + ), + Expanded( + child: TabBarView( + physics: const NeverScrollableScrollPhysics(), + children: [ + groupedRecords.isEmpty + ? Center( + child: SvgPicture.asset( + Assets.emptyLog, + fit: BoxFit.fill, + ), + ) + : ListView.builder( + itemCount: groupedRecords.length, + itemBuilder: (context, index) { + final String date = + groupedRecords.keys.elementAt(index); + final List recordsForDate = + groupedRecords[date]!; + + return Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + date, + style: const TextStyle( + color: ColorsManager.grayColor, + fontSize: 13, + fontWeight: FontWeight.w700, + ), + ), + ), + DefaultContainer( + child: Column( + children: [ + ...recordsForDate + .asMap() + .entries + .map((entry) { + final int idx = entry.key; + final DeviceEvent record = + entry.value; + final DateTime eventDateTime = + DateTime + .fromMillisecondsSinceEpoch( + record.eventTime!); + final String formattedTime = + DateFormat('HH:mm:ss') + .format(eventDateTime); + + return Column( + children: [ + ListTile( + leading: SvgPicture.asset( + record.value == 'true' + ? Assets + .leakDetectedIcon + : Assets + .leakNormalIcon, + height: 25, + width: 25, + ), + title: const Text( + "SOS Alert Triggered", + style: const TextStyle( + fontWeight: + FontWeight.bold, + fontSize: 18, + ), + ), + subtitle: + Text(formattedTime), + ), + if (idx != + recordsForDate.length - 1) + const Divider( + color: ColorsManager + .graysColor, + ), + ], + ); + }).toList(), + ], + ), + ), + ], + ); + }, + ), + Container( + height: 10, + width: 20, + child: Center( + child: SvgPicture.asset( + Assets.emptyLog, + fit: BoxFit.fill, + ), + ), + ), + ], + ), + ), + ], + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/sos/sos_screen.dart b/lib/features/devices/view/widgets/sos/sos_screen.dart new file mode 100644 index 0000000..e55ee41 --- /dev/null +++ b/lib/features/devices/view/widgets/sos/sos_screen.dart @@ -0,0 +1,217 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/sos_bloc/sos_bloc.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_model.dart'; +import 'package:syncrow_app/features/devices/model/sos_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/sos/sos_alarm_management_page.dart'; +import 'package:syncrow_app/features/devices/view/widgets/sos/sos_records_screen.dart'; +import 'package:syncrow_app/features/devices/view/widgets/sos/sos_settings.dart'; +import 'package:syncrow_app/features/shared_widgets/battery_bar.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/generated/assets.dart'; + +class SosScreen extends StatelessWidget { + final DeviceModel? device; + + + const SosScreen({super.key, this.device}); + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'SOS', + actions: [ + InkWell( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => SosSettings(device: device!)), + ); + }, + child: SvgPicture.asset(Assets.assetsIconsSettings)), + const SizedBox( + width: 10, + ) + ], + child: BlocProvider( + create: (context) => + SosBloc(sosId: device?.uuid ?? '')..add(const SosInitial()), + child: BlocBuilder( + builder: (context, state) { + final sensor = BlocProvider.of(context); + SosModel model = + SosModel(batteryPercentage: 0, sosContactState: 'normal'); + if (state is LoadingNewSate) { + model = state.sosSensor; + } else if (state is UpdateState) { + model = state.sensor; + } + return state is SosLoadingState + ? const Center( + child: DefaultContainer( + width: 50, + height: 50, + child: CircularProgressIndicator()), + ) + : RefreshIndicator( + onRefresh: () async { + sensor.add(const SosInitial()); + }, + child: ListView( + children: [ + SizedBox( + height: MediaQuery.sizeOf(context).height * 0.8, + child: Column( + children: [ + BatteryBar( + batteryPercentage: model.batteryPercentage, + ), + Expanded( + flex: 4, + child: InkWell( + overlayColor: WidgetStateProperty.all( + Colors.transparent), + onTap: () {}, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + Container( + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(890), + boxShadow: [ + BoxShadow( + color: + Colors.white.withOpacity(0.1), + blurRadius: 24, + offset: const Offset(-5, -5), + blurStyle: BlurStyle.outer, + ), + BoxShadow( + color: Colors.black + .withOpacity(0.11), + blurRadius: 25, + offset: const Offset(5, 5), + blurStyle: BlurStyle.outer, + ), + BoxShadow( + color: Colors.black + .withOpacity(0.13), + blurRadius: 30, + offset: const Offset(5, 5), + blurStyle: BlurStyle.inner, + ), + ], + ), + child: SvgPicture.asset( + model.sosContactState == 'normal' + ? Assets.redSos + : Assets.greenSos, + fit: BoxFit.fill, + ), + ), + ], + ), + ), + ), + Flexible( + child: Row( + children: [ + Expanded( + child: DefaultContainer( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + SosRecordsScreen( + sosId: device!.uuid!)), + ); + }, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + ConstrainedBox( + constraints: const BoxConstraints( + maxHeight: 46, maxWidth: 50), + child: SvgPicture.asset( + Assets.doorRecordsIcon), + ), + const SizedBox( + height: 15, + ), + const Flexible( + child: FittedBox( + child: BodySmall( + text: 'Records', + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: DefaultContainer( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + const AlarmManagementPage()), + ); + }, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + ConstrainedBox( + constraints: const BoxConstraints( + maxHeight: 46, maxWidth: 50), + child: SvgPicture.asset(Assets + .doorNotificationSetting), + ), + const SizedBox( + height: 15, + ), + const Flexible( + child: FittedBox( + child: BodySmall( + text: 'Alarm Settings', + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ) + ], + ), + ), + ], + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/sos/sos_setting/delete_dialog.dart b/lib/features/devices/view/widgets/sos/sos_setting/delete_dialog.dart new file mode 100644 index 0000000..67c62e2 --- /dev/null +++ b/lib/features/devices/view/widgets/sos/sos_setting/delete_dialog.dart @@ -0,0 +1,227 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class DisconnectDeviceDialog extends StatelessWidget { + final Function()? cancelTab; + final Function()? confirmTab; + + const DisconnectDeviceDialog({ + super.key, + required this.cancelTab, + required this.confirmTab, + }); + + @override + Widget build(BuildContext context) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 10, + ), + const BodyLarge( + text: 'Disconnect Device', + fontWeight: FontWeight.w700, + fontColor: ColorsManager.red, + fontSize: 16, + ), + const Padding( + padding: EdgeInsets.only(left: 15, right: 15), + child: Divider( + color: ColorsManager.textGray, + ), + ), + const Padding( + padding: EdgeInsets.only(left: 15, right: 20, top: 15, bottom: 20), + child: Column( + children: [ + Center( + child: Text( + 'This will disconnect your device from this Application')), + ], + ), + ), + Row( + children: [ + Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.textGray, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.textGray, + width: 1.0, + ), + )), + child: SizedBox( + child: InkWell( + onTap: cancelTab, + child: const Padding( + padding: EdgeInsets.all(15), + child: Center( + child: Text( + 'Cancel', + style: TextStyle( + color: ColorsManager.textGray, + fontSize: 14, + fontWeight: FontWeight.w400), + ), + ), + ), + ), + ), + ), + ), + Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border( + left: BorderSide( + color: ColorsManager.textGray, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.textGray, + width: 1.0, + ), + )), + child: InkWell( + onTap: confirmTab, + child: const Padding( + padding: EdgeInsets.all(15), + child: Center( + child: Text( + 'Disconnect', + style: TextStyle( + color: ColorsManager.red, + fontSize: 14, + fontWeight: FontWeight.w400), + ), + ), + )), + )) + ], + ) + ], + ), + ); + } +} + +class DisconnectWipeData extends StatelessWidget { + final Function()? cancelTab; + final Function()? confirmTab; + + const DisconnectWipeData({ + super.key, + required this.cancelTab, + required this.confirmTab, + }); + + @override + Widget build(BuildContext context) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 10, + ), + const BodyLarge( + text: 'Disconnect and Wipe Data', + fontWeight: FontWeight.w700, + fontColor: ColorsManager.red, + fontSize: 16, + ), + const Padding( + padding: EdgeInsets.only(left: 15, right: 15), + child: Divider( + color: ColorsManager.textGray, + ), + ), + const Padding( + padding: EdgeInsets.only(left: 15, right: 20, top: 15, bottom: 20), + child: Column( + children: [ + Center( + child: Text( + 'This will disconnect your device from this Application and wipe all the data')), + ], + ), + ), + Row( + children: [ + Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.textGray, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.textGray, + width: 1.0, + ), + )), + child: SizedBox( + child: InkWell( + onTap: cancelTab, + child: const Padding( + padding: EdgeInsets.all(15), + child: Center( + child: Text( + 'Cancel', + style: TextStyle( + color: ColorsManager.textGray, + fontSize: 14, + fontWeight: FontWeight.w400), + ), + ), + ), + ), + ), + ), + ), + Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border( + left: BorderSide( + color: ColorsManager.textGray, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.textGray, + width: 1.0, + ), + )), + child: InkWell( + onTap: confirmTab, + child: const Padding( + padding: EdgeInsets.all(15), + child: Center( + child: Text( + 'Disconnect', + style: TextStyle( + color: ColorsManager.red, + fontSize: 14, + fontWeight: FontWeight.w400), + ), + ), + )), + )) + ], + ) + ], + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/sos/sos_setting/faq_sos_page.dart b/lib/features/devices/view/widgets/sos/sos_setting/faq_sos_page.dart new file mode 100644 index 0000000..bbb75f7 --- /dev/null +++ b/lib/features/devices/view/widgets/sos/sos_setting/faq_sos_page.dart @@ -0,0 +1,151 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/sos_bloc/sos_bloc.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_model.dart'; +import 'package:syncrow_app/features/devices/model/question_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/sos/sos_setting/question_page.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class FaqSosPage extends StatelessWidget { + final DeviceModel? device; + + const FaqSosPage({super.key, this.device}); + + @override + Widget build(BuildContext context) { + TextEditingController _searchController = TextEditingController(); + return DefaultScaffold( + title: 'FAQ', + child: BlocProvider( + create: (context) => + SosBloc(sosId: device?.uuid ?? '')..add(const SosInitialQuestion()), + child: BlocBuilder( + builder: (context, state) { + final sensor = BlocProvider.of(context); + + List displayedQuestions = []; + if (state is FaqSearchState) { + displayedQuestions = state.filteredFaqQuestions; + } else if (state is FaqLoadedState) { + displayedQuestions = state.filteredFaqQuestions; + } + return state is SosLoadingState + ? const Center( + child: DefaultContainer( + width: 50, + height: 50, + child: CircularProgressIndicator()), + ) + : RefreshIndicator( + onRefresh: () async { + sensor.add(const SosInitial()); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + DefaultContainer( + padding: const EdgeInsets.all(5), + child: TextFormField( + controller: _searchController, + onChanged: (value) { + sensor.add(SearchFaqEvent(value)); + }, + decoration: InputDecoration( + hintText: 'Enter your questions', + hintStyle: const TextStyle( + color: ColorsManager.textGray, + fontSize: 16, + fontWeight: FontWeight.w400), + suffixIcon: Container( + padding: const EdgeInsets.all(5.0), + margin: const EdgeInsets.all(10.0), + child: SvgPicture.asset( + Assets.searchIcon, + fit: BoxFit.contain, + ), + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), + ), + ), + SizedBox( + height: MediaQuery.of(context).size.height * 0.04, + ), + BodyMedium( + text: _searchController.text.isEmpty + ? 'Device Related FAQs' + : '${displayedQuestions.length} Help Topics', + fontWeight: FontWeight.w700, + fontSize: 12, + fontColor: ColorsManager.grayColor, + ), + Expanded( + child: DefaultContainer( + child: ListView.builder( + shrinkWrap: true, + itemCount: displayedQuestions.length, + itemBuilder: (context, index) { + final faq = displayedQuestions[index]; + return InkWell( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => QuestionPage( + questionModel: faq, + )), + ); + }, + child: SizedBox( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Expanded( + child: BodyMedium( + fontSize: 14, + fontWeight: FontWeight.w400, + text: faq.question, + ), + ), + const Icon( + Icons.keyboard_arrow_right, + color: ColorsManager.textGray, + ), + ], + ), + ), + const Divider( + color: ColorsManager.dividerColor, + ), + ], + ), + ), + ); + }, + )), + ), + ], + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/sos/sos_setting/location_setting.dart b/lib/features/devices/view/widgets/sos/sos_setting/location_setting.dart new file mode 100644 index 0000000..88312db --- /dev/null +++ b/lib/features/devices/view/widgets/sos/sos_setting/location_setting.dart @@ -0,0 +1,219 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/model/space_model.dart'; +import 'package:syncrow_app/features/devices/bloc/sos_bloc/sos_bloc.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/sos_model.dart'; +import 'package:syncrow_app/features/devices/model/subspace_model.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/navigation/routing_constants.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class LocationSosPage extends StatelessWidget { + final SpaceModel? space; + final String? deviceId; + + const LocationSosPage({ + super.key, + this.space, + this.deviceId, + }); + + @override + Widget build(BuildContext context) { + String roomIdSelected = ''; + + return Scaffold( + body: BlocProvider( + create: (context) => SosBloc(sosId: deviceId ?? '') + ..add(const SosInitial()) + ..add(SosInitialDeviseInfo()) + ..add(FetchRoomsEvent(unit: space!)), + child: BlocBuilder( + builder: (context, state) { + final _bloc = BlocProvider.of(context); + SosModel model = + SosModel(batteryPercentage: 0, sosContactState: ''); + if (state is SaveSelectionSuccessState) { + new Future.delayed(const Duration(microseconds: 500), () { + _bloc.add(SosInitial()); + Navigator.of(context).pop(true); + }); + } + return state is SosLoadingState + ? const Center( + child: DefaultContainer( + width: 50, + height: 50, + child: CircularProgressIndicator(), + ), + ) + : DefaultScaffold( + actions: [ + BlocBuilder( + builder: (context, state) { + final bool canSave = state is OptionSelectedState && + state.hasSelectionChanged; + return InkWell( + onTap: canSave + ? () { + context.read().add(AssignRoomEvent( + context: context, + roomId: roomIdSelected, + unit: space!)); + } + : null, + child: BodyMedium( + text: 'Save', + fontWeight: FontWeight.w700, + fontSize: 16, + fontColor: canSave + ? ColorsManager.slidingBlueColor + : ColorsManager.primaryTextColor, + ), + ); + }, + ), + const SizedBox(width: 20), + ], + child: RefreshIndicator( + onRefresh: () async { + // sensor.add(const SosInitial()); + }, + child: ListView( + shrinkWrap: true, + padding: const EdgeInsets.symmetric(vertical: 20), + children: [ + const BodyMedium( + text: 'Smart Device Location', + fontWeight: FontWeight.w700, + fontSize: 12, + fontColor: ColorsManager.grayColor, + ), + const SizedBox(height: 5), + DefaultContainer( + padding: const EdgeInsets.all(20), + child: ListView.builder( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: _bloc.roomsList.length, + itemBuilder: (context, index) { + final fromRoom = _bloc.roomsList[index]; + final isSelected = (state + is OptionSelectedState && + state.selectedOption == fromRoom.id) || + (state is! OptionSelectedState && + fromRoom.id == + _bloc.deviceInfo.subspace.uuid); + + return Column( + children: [ + _buildCheckboxOption( + label: fromRoom.name!, + isSelected: isSelected, + onTap: (label) { + context.read().add( + SelectOptionEvent( + selectedOption: fromRoom.id!, + ), + ); + roomIdSelected = fromRoom.id!; + }, + ), + if (index < _bloc.roomsList.length - 1) ...[ + const SizedBox(height: 10), + const Divider( + color: ColorsManager.dividerColor, + ), + const SizedBox(height: 10), + ], + ], + ); + }, + ), + ), + ], + ), + ), + ); + }, + ), + ), + ); + } +} + +class CircularCheckbox extends StatefulWidget { + final bool value; + final ValueChanged onChanged; + + CircularCheckbox({required this.value, required this.onChanged}); + + @override + _CircularCheckboxState createState() => _CircularCheckboxState(); +} + +class _CircularCheckboxState extends State { + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + widget.onChanged(!widget.value); + }, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: widget.value + ? ColorsManager.primaryColorWithOpacity.withOpacity(0.01) + : Colors.grey, + width: 2.0, + ), + color: widget.value + ? ColorsManager.primaryColorWithOpacity + : Colors.transparent, + ), + width: 24.0, + height: 24.0, + child: widget.value + ? const Icon( + Icons.check, + color: Colors.white, + size: 16.0, + ) + : null, + ), + ); + } +} + +Widget _buildCheckboxOption({ + required String label, + required bool isSelected, + required Function(String) onTap, +}) { + return Padding( + padding: const EdgeInsets.only(bottom: 10, top: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyMedium( + text: label, + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w400), + ), + CircularCheckbox( + value: isSelected, + onChanged: (bool? value) { + if (value == true) { + onTap(label); + } + }, + ), + ], + ), + ); +} diff --git a/lib/features/devices/view/widgets/sos/sos_setting/question_page.dart b/lib/features/devices/view/widgets/sos/sos_setting/question_page.dart new file mode 100644 index 0000000..0ae5bd8 --- /dev/null +++ b/lib/features/devices/view/widgets/sos/sos_setting/question_page.dart @@ -0,0 +1,143 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/sos_bloc/sos_bloc.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/question_model.dart'; +import 'package:syncrow_app/features/devices/model/sos_model.dart'; +import 'package:syncrow_app/features/shared_widgets/default_button.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class QuestionPage extends StatelessWidget { + final QuestionModel? questionModel; + + const QuestionPage({super.key, this.questionModel}); + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'FAQ', + child: BlocProvider( + create: (context) => SosBloc(sosId: '')..add(const SosInitial()), + child: BlocBuilder( + builder: (context, state) { + final sensor = BlocProvider.of(context); + SosModel model = + SosModel(batteryPercentage: 0, sosContactState: 'normal'); + if (state is LoadingNewSate) { + model = state.sosSensor; + } else if (state is UpdateState) { + model = state.sensor; + } + return state is SosLoadingState + ? const Center( + child: DefaultContainer( + width: 50, + height: 50, + child: CircularProgressIndicator()), + ) + : RefreshIndicator( + onRefresh: () async { + sensor.add(const SosInitial()); + }, + child: Column( + children: [ + DefaultContainer( + padding: EdgeInsets.all(15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + BodyLarge( + text: questionModel!.question, + fontSize: 22, + fontWeight: FontWeight.w400, + fontColor: ColorsManager.blackColor, + ), + SizedBox( + height: 15, + ), + BodyMedium( + text: questionModel!.answer, + fontSize: 14, + fontWeight: FontWeight.w400, + fontColor: ColorsManager.secondaryTextColor, + ), + ], + ), + ), + SizedBox( + height: MediaQuery.of(context).size.height * 0.15, + ), + Center( + child: SizedBox( + width: 180, + child: DefaultButton( + backgroundColor: ColorsManager.grayButtonColors, + borderRadius: 50, + onPressed: () {}, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + Assets.thumbUp, + fit: BoxFit.fill, + ), + SizedBox( + width: 10, + ), + BodyMedium( + text: 'Helpful', + fontSize: 12, + fontWeight: FontWeight.w400, + fontColor: ColorsManager.blackColor, + ), + ], + )), + ), + ), + SizedBox( + height: 15, + ), + Center( + child: SizedBox( + width: 180, + child: DefaultButton( + backgroundColor: ColorsManager.grayButtonColors, + borderRadius: 50, + onPressed: () {}, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + Assets.thumbDown, + fit: BoxFit.fill, + ), + SizedBox( + width: 10, + ), + BodyMedium( + text: 'Not Helpful', + fontSize: 12, + fontWeight: FontWeight.w400, + fontColor: ColorsManager.blackColor, + ), + ], + )), + ), + ), + ], + )); + }, + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/sos/sos_setting/share_sos_page.dart b/lib/features/devices/view/widgets/sos/sos_setting/share_sos_page.dart new file mode 100644 index 0000000..fdbf4d9 --- /dev/null +++ b/lib/features/devices/view/widgets/sos/sos_setting/share_sos_page.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/sos_bloc/sos_bloc.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_model.dart'; +import 'package:syncrow_app/features/devices/model/sos_model.dart'; +import 'package:syncrow_app/features/shared_widgets/default_button.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class ShareSosPage extends StatelessWidget { + final DeviceModel? device; + + const ShareSosPage({super.key, this.device}); + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Share Device', + child: BlocProvider( + create: (context) => + SosBloc(sosId: device?.uuid ?? '')..add(const SosInitial()), + child: BlocBuilder( + builder: (context, state) { + final sensor = BlocProvider.of(context); + SosModel model = + SosModel(batteryPercentage: 0, sosContactState: 'normal'); + if (state is LoadingNewSate) { + model = state.sosSensor; + } else if (state is UpdateState) { + model = state.sensor; + } + return state is SosLoadingState + ? const Center( + child: DefaultContainer( + width: 50, + height: 50, + child: CircularProgressIndicator()), + ) + : RefreshIndicator( + onRefresh: () async { + sensor.add(const SosInitial()); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const BodyLarge( + text: 'Sharing Method Not Supported', + fontSize: 15, + fontWeight: FontWeight.w400, + fontColor: ColorsManager.blackColor, + ), + const BodyMedium( + text: + 'Currently, you cannot use the specified method to share Bluetooth mesh devices Zigbee devices, infrared devices, Bluetooth Beacon Devices, and certain Bluetooth LE devices with other users.', + fontSize: 14, + fontWeight: FontWeight.w400, + fontColor: ColorsManager.secondaryTextColor, + ), + const SizedBox( + height: 10, + ), + const BodyLarge( + text: 'Recommended Sharing Method', + fontSize: 15, + fontWeight: FontWeight.w400, + fontColor: ColorsManager.blackColor, + ), + const BodyMedium( + text: + 'If the recipient is a home member or a reliable user, tap Me > Home Management > Add Member and add the recipient to your home. Then, devices in the home can be shared with the recipient in bulk.', + fontSize: 14, + fontWeight: FontWeight.w400, + fontColor: ColorsManager.secondaryTextColor, + ), + SizedBox( + height: MediaQuery.of(context).size.height * 0.15, + ), + Center( + child: SizedBox( + width: 250, + child: DefaultButton( + backgroundColor: ColorsManager.blueColor1, + borderRadius: 50, + onPressed: () { + + }, + child: Text('Add Home Member')), + ), + ) + ], + )); + }, + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/sos/sos_setting/sos_info_page.dart b/lib/features/devices/view/widgets/sos/sos_setting/sos_info_page.dart new file mode 100644 index 0000000..1055cb1 --- /dev/null +++ b/lib/features/devices/view/widgets/sos/sos_setting/sos_info_page.dart @@ -0,0 +1,145 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/sos_bloc/sos_bloc.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_model.dart'; +import 'package:syncrow_app/features/devices/model/sos_model.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class SosInfoPage extends StatelessWidget { + final DeviceModel? device; + + const SosInfoPage({super.key, this.device}); + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Device Information', + child: BlocProvider( + create: (context) => SosBloc(sosId: device?.uuid ?? '') + ..add(const SosInitial()) + ..add(SosInitialDeviseInfo()), + child: BlocBuilder( + builder: (context, state) { + final _bloc = BlocProvider.of(context); + SosModel model = + SosModel(batteryPercentage: 0, sosContactState: ''); + if (state is LoadingNewSate) { + model = state.sosSensor; + } else if (state is UpdateState) { + model = state.sensor; + } + return state is SosLoadingState + ? const Center( + child: DefaultContainer( + width: 50, + height: 50, + child: CircularProgressIndicator()), + ) + : RefreshIndicator( + onRefresh: () async { + _bloc.add(const SosInitial()); + }, + child: DefaultContainer( + child: Padding( + padding: const EdgeInsets.only(left: 5, right: 5), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const BodyLarge( + text: 'Virtual ID', + fontSize: 15, + fontWeight: FontWeight.w400, + fontColor: ColorsManager.blackColor, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodySmall( + text: _bloc.deviceInfo.productUuid, + fontColor: ColorsManager.primaryTextColor, + ), + InkWell( + onTap: () { + Clipboard.setData( + ClipboardData( + text: _bloc.deviceInfo.productUuid, + ), + ); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("Copied to Clipboard"), + ), + ); + }, + child: const Row( + children: [ + Icon( + Icons.copy, + color: ColorsManager.blueColor, + ), + BodyMedium( + text: 'Copy', + fontColor: ColorsManager.blueColor, + ), + ], + ), + ) + ], + ), + const Divider( + color: ColorsManager.dividerColor, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodyLarge( + text: 'MAC', + fontSize: 15, + fontWeight: FontWeight.w400, + fontColor: ColorsManager.blackColor, + ), + BodySmall( + text: _bloc.deviceInfo.macAddress, + fontColor: ColorsManager.primaryTextColor, + ), + ], + ), + const Divider( + color: ColorsManager.dividerColor, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const BodyLarge( + text: 'Time Zone', + fontSize: 15, + fontWeight: FontWeight.w400, + fontColor: ColorsManager.blackColor, + ), + BodySmall( + text: _bloc.deviceInfo.timeZone, + fontColor: ColorsManager.primaryTextColor, + ), + ], + ), + ]), + ))); + }, + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/sos/sos_setting/sos_profile_page.dart b/lib/features/devices/view/widgets/sos/sos_setting/sos_profile_page.dart new file mode 100644 index 0000000..bf1d0f9 --- /dev/null +++ b/lib/features/devices/view/widgets/sos/sos_setting/sos_profile_page.dart @@ -0,0 +1,191 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/devices/bloc/sos_bloc/sos_bloc.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_model.dart'; +import 'package:syncrow_app/features/devices/model/sos_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/sos/sos_setting/location_setting.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class SosProfilePage extends StatelessWidget { + final DeviceModel? device; + + const SosProfilePage({super.key, this.device}); + + @override + Widget build(BuildContext context) { + var spaces = HomeCubit.getInstance().spaces; + + return DefaultScaffold( + title: 'Device Settings', + leading: IconButton( + onPressed: () { + Navigator.of(context).pop(true); + }, + icon: const Icon(Icons.arrow_back_ios)), + child: BlocProvider( + create: (context) => SosBloc(sosId: device?.uuid ?? '') + ..add(const SosInitial()) + ..add(SosInitialDeviseInfo()), + child: BlocBuilder( + builder: (context, state) { + final _bloc = BlocProvider.of(context); + SosModel model = + SosModel(batteryPercentage: 0, sosContactState: ''); + if (state is LoadingNewSate) { + model = state.sosSensor; + } else if (state is UpdateState) { + model = state.sensor; + } + return state is SosLoadingState + ? const Center( + child: DefaultContainer( + width: 50, + height: 50, + child: CircularProgressIndicator()), + ) + : RefreshIndicator( + onRefresh: () async { + _bloc.add(const SosInitial()); + }, + child: ListView( + children: [ + CircleAvatar( + radius: 60, + backgroundColor: Colors.white, + child: CircleAvatar( + radius: 55, + backgroundColor: ColorsManager.graysColor, + child: ClipOval( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox( + height: 10, + ), + Center( + child: SvgPicture.asset( + Assets.fourSceneIcon, + fit: BoxFit.contain, + ), + ), + ], + ), + ), + ), + ), + const SizedBox( + height: 10, + ), + SizedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IntrinsicWidth( + child: ConstrainedBox( + constraints: + const BoxConstraints(maxWidth: 200), + child: TextFormField( + maxLength: 30, + style: const TextStyle( + color: Colors.black, + ), + textAlign: TextAlign.center, + focusNode: _bloc.focusNode, + controller: _bloc.nameController, + enabled: _bloc.editName, + onEditingComplete: () { + _bloc.add(SaveNameEvent()); + }, + decoration: const InputDecoration( + hintText: "Your Name", + border: InputBorder.none, + fillColor: Colors.white10, + counterText: '', + ), + ), + ), + ), + const SizedBox(width: 5), + InkWell( + onTap: () { + _bloc.add(const ChangeNameEvent(value: true)); + }, + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 10), + child: Icon( + Icons.edit_outlined, + size: 20, + color: ColorsManager.textPrimaryColor, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 20), + const BodyMedium( + text: 'Smart Device Information', + fontWeight: FontWeight.w700, + fontSize: 12, + fontColor: ColorsManager.grayColor, + ), + DefaultContainer( + padding: const EdgeInsets.all(20), + child: InkWell( + onTap: () async { + bool val = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => LocationSosPage( + space: spaces!.first, + deviceId: device?.uuid ?? '', + )), + ); + if (val == true) { + _bloc.add(SosInitialDeviseInfo()); + } + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox( + child: Text('Location'), + ), + Row( + children: [ + SizedBox( + child: BodyMedium( + text: _bloc + .deviceInfo.subspace.subspaceName, + fontColor: ColorsManager.textGray, + ), + ), + const Icon( + Icons.arrow_forward_ios, + size: 15, + color: ColorsManager.textGray, + ), + ], + ) + ], + ), + ), + ) + ], + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/sos/sos_setting/sos_update_note.dart b/lib/features/devices/view/widgets/sos/sos_setting/sos_update_note.dart new file mode 100644 index 0000000..03e5f85 --- /dev/null +++ b/lib/features/devices/view/widgets/sos/sos_setting/sos_update_note.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class SosUpdateNote extends StatelessWidget { + final Function()? cancelTab; + final Function()? confirmTab; + + const SosUpdateNote({ + super.key, + required this.cancelTab, + required this.confirmTab, + }); + + @override + Widget build(BuildContext context) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 10, + ), + BodyLarge( + text: 'Update Note', + fontWeight: FontWeight.w700, + fontColor: ColorsManager.switchButton.withOpacity(0.6), + fontSize: 16, + ), + const Padding( + padding: EdgeInsets.only(left: 15, right: 15), + child: Divider( + color: ColorsManager.textGray, + ), + ), + const Padding( + padding: EdgeInsets.only(left: 15, right: 20, top: 15, bottom: 20), + child: Column( + children: [ + Center( + child: Text( + 'This update may take a long time. Make sure that the device is fully charged. The device will be unavailable during the update.', + textAlign: TextAlign.center, + )), + ], + ), + ), + Row( + children: [ + Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.textGray, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.textGray, + width: 1.0, + ), + )), + child: SizedBox( + child: InkWell( + onTap: cancelTab, + child: const Padding( + padding: const EdgeInsets.only(top: 15, bottom: 15), + child: Center( + child: Text( + 'Cancel', + style: TextStyle( + color: ColorsManager.textGray, + fontSize: 14, + fontWeight: FontWeight.w400), + ), + ), + ), + ), + ), + ), + ), + Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border( + left: BorderSide( + color: ColorsManager.textGray, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.textGray, + width: 1.0, + ), + )), + child: InkWell( + onTap: confirmTab, + child: Padding( + padding: const EdgeInsets.only(top: 15, bottom: 15), + child: Center( + child: Text( + 'Start Update', + style: TextStyle( + color: + ColorsManager.switchButton.withOpacity(0.6), + fontSize: 14, + fontWeight: FontWeight.w400), + ), + ), + )), + )) + ], + ) + ], + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/sos/sos_setting/sos_update_page.dart b/lib/features/devices/view/widgets/sos/sos_setting/sos_update_page.dart new file mode 100644 index 0000000..fabd7b2 --- /dev/null +++ b/lib/features/devices/view/widgets/sos/sos_setting/sos_update_page.dart @@ -0,0 +1,344 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:percent_indicator/linear_percent_indicator.dart'; +import 'package:syncrow_app/features/devices/bloc/sos_bloc/sos_bloc.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/view/widgets/sos/sos_setting/sos_update_note.dart'; +import 'package:syncrow_app/features/shared_widgets/default_button.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class SosUpdatePage extends StatelessWidget { + const SosUpdatePage({super.key}); + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Device Update', + child: BlocProvider( + create: (context) => SosBloc(sosId: '')..add(const SosInitial()), + child: BlocBuilder( + builder: (context, state) { + return state is SosLoadingState + ? const Center( + child: DefaultContainer( + width: 50, + height: 50, + child: CircularProgressIndicator()), + ) + : RefreshIndicator( + onRefresh: () async {}, + child: Column( + children: [ + // SizedBox( + // height: MediaQuery.of(context).size.height * 0.15, + // ), + DefaultContainer( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 50, + child: ListTile( + contentPadding: EdgeInsets.zero, + leading: SizedBox( + width: 200, + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: const BoxDecoration( + color: + ColorsManager.primaryColor, + borderRadius: BorderRadius.all( + Radius.circular(50))), + child: SvgPicture.asset( + Assets.checkUpdateIcon, + fit: BoxFit.fill, + height: 25, + ), + ), + const SizedBox( + width: 10, + ), + InkWell( + onTap: () {}, + child: const BodyMedium( + text: 'Automatic Update', + fontWeight: FontWeight.normal, + ), + ), + ], + ), + ), + trailing: Container( + width: 100, + child: Row( + mainAxisAlignment: + MainAxisAlignment.end, + children: [ + const BodyMedium( + text: 'Off', + fontColor: ColorsManager.textGray, + ), + Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: true, + onChanged: (value) {}, + applyTheme: true, + ), + ), + ], + ), + )), + ), + ], + ), + ), + const SizedBox( + height: 10, + ), + UpdateSosContainerWithProgressBar( + sosDescription: + 'Connectivity Issue Resolved Fixed a bug that caused the SOS button to disconnect from the app intermittently.', + sosVersion: 'SOS v2.0.5', + ), + // const UpdatedContainer( + // sosVersion: 'SOS v1.0.13', + // sosDescription: 'SOS is up to date', + // ), + + // const NewUpdateContainer( + // sosVersion: 'SOS v2.0.5', + // sosDescription: + // 'Connectivity Issue Resolved Fixed a bug that caused the SOS button to disconnect from the app intermittently.', + // ), + const SizedBox( + height: 15, + ), + ], + )); + }, + ), + ), + ); + } +} + +class UpdatedContainer extends StatelessWidget { + final String? sosVersion; + final String? sosDescription; + const UpdatedContainer({ + this.sosVersion, + this.sosDescription, + super.key, + }); + + @override + Widget build(BuildContext context) { + return DefaultContainer( + height: MediaQuery.of(context).size.height * 0.35, + child: Padding( + padding: const EdgeInsets.all(25), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SvgPicture.asset( + Assets.emptyUpdateIcon, + fit: BoxFit.fill, + ), + BodyMedium( + text: sosVersion!, + fontColor: ColorsManager.primaryTextColor, + ), + BodyMedium( + text: sosDescription!, + fontColor: ColorsManager.blackColor, + ), + ], + ), + ], + ), + ), + ); + } +} + +class NewUpdateContainer extends StatelessWidget { + final String? sosVersion; + final String? sosDescription; + const NewUpdateContainer({ + this.sosVersion, + this.sosDescription, + super.key, + }); + + @override + Widget build(BuildContext context) { + return DefaultContainer( + height: MediaQuery.of(context).size.height * 0.50, + child: Padding( + padding: const EdgeInsets.all(25), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SvgPicture.asset( + Assets.emptyUpdateIcon, + fit: BoxFit.fill, + ), + const BodyMedium( + text: 'New Update Available Now!', + fontColor: ColorsManager.blueColor, + ), + BodyMedium( + text: sosVersion!, + fontColor: ColorsManager.primaryTextColor, + ), + Container( + width: MediaQuery.of(context).size.width * 0.7, + child: BodyMedium( + text: sosDescription!, + fontColor: ColorsManager.textPrimaryColor, + textAlign: TextAlign.center, + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width * 0.6, + child: DefaultButton( + borderRadius: 25, + backgroundColor: ColorsManager.blueColor1, + height: 150, + onPressed: () { + showDialog( + context: context, + builder: (context) { + return SosUpdateNote( + cancelTab: () { + Navigator.of(context).pop(); + }, + confirmTab: () { + Navigator.of(context).pop(); + }, + ); + }, + ); + }, + child: const BodyMedium( + text: 'Update Now', + fontColor: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w700, + textAlign: TextAlign.center, + ), + ), + ) + ], + ), + ], + ), + ), + ); + } +} + +class UpdateSosContainerWithProgressBar extends StatelessWidget { + final String? sosVersion; + final String? sosDescription; + const UpdateSosContainerWithProgressBar({ + this.sosVersion, + this.sosDescription, + super.key, + }); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + DefaultContainer( + height: MediaQuery.of(context).size.height * 0.50, + child: Padding( + padding: const EdgeInsets.all(25), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SvgPicture.asset( + Assets.emptyUpdateIcon, + fit: BoxFit.fill, + ), + const BodyMedium( + text: 'New Update Available Now!', + fontColor: ColorsManager.blueColor, + ), + BodyMedium( + text: sosVersion!, + fontColor: ColorsManager.primaryTextColor, + ), + SizedBox( + width: MediaQuery.of(context).size.width * 0.7, + child: BodyMedium( + text: sosDescription!, + fontColor: ColorsManager.textPrimaryColor, + textAlign: TextAlign.center, + ), + ), + LinearPercentIndicator( + barRadius: Radius.circular(10), + width: 170.0, + animation: true, + animationDuration: 1000, + lineHeight: 5.0, + percent: 0.2, + linearStrokeCap: LinearStrokeCap.butt, + progressColor: ColorsManager.blueColor1, + ), + SizedBox( + width: MediaQuery.of(context).size.width * 0.7, + child: const BodyMedium( + text: 'Downloading Update please be patient', + fontColor: ColorsManager.textPrimaryColor, + textAlign: TextAlign.center, + ), + ), + ], + ), + ], + ), + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width * 0.7, + child: const BodyMedium( + text: + 'Please keep the power of the device connected during the upgrade process.', + fontColor: ColorsManager.textPrimaryColor, + textAlign: TextAlign.center, + ), + ), + ], + ); + } +} diff --git a/lib/features/devices/view/widgets/sos/sos_setting/update_dialog_sos.dart b/lib/features/devices/view/widgets/sos/sos_setting/update_dialog_sos.dart new file mode 100644 index 0000000..0529161 --- /dev/null +++ b/lib/features/devices/view/widgets/sos/sos_setting/update_dialog_sos.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class UpdateInfoDialog extends StatelessWidget { + final Function()? cancelTab; + final Function()? confirmTab; + + const UpdateInfoDialog({ + super.key, + required this.cancelTab, + required this.confirmTab, + }); + + @override + Widget build(BuildContext context) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 10, + ), + BodyLarge( + text: 'Update Available', + fontWeight: FontWeight.w700, + fontColor: ColorsManager.switchButton.withOpacity(0.6), + fontSize: 16, + ), + const Padding( + padding: EdgeInsets.only(left: 15, right: 15), + child: Divider( + color: ColorsManager.textGray, + ), + ), + const Padding( + padding: EdgeInsets.only(left: 15, right: 20, top: 15, bottom: 20), + child: Column( + children: [ + Center( + child: Text( + 'An update is available for your device. Version 2.1.0 includes new features and important fixes to enhance performance and security.', + textAlign: TextAlign.center, + )), + ], + ), + ), + Row( + children: [ + Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.textGray, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.textGray, + width: 1.0, + ), + )), + child: SizedBox( + child: InkWell( + onTap: cancelTab, + child: const Padding( + padding: const EdgeInsets.only(top: 15, bottom: 15), + child: Center( + child: Text( + 'Remind me later', + style: TextStyle( + color: ColorsManager.textGray, + fontSize: 14, + fontWeight: FontWeight.w400), + ), + ), + ), + ), + ), + ), + ), + Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border( + left: BorderSide( + color: ColorsManager.textGray, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.textGray, + width: 1.0, + ), + )), + child: InkWell( + onTap: confirmTab, + child: Padding( + padding: const EdgeInsets.only(top: 15, bottom: 15), + child: Center( + child: Text( + 'Update Now', + style: TextStyle( + color: + ColorsManager.switchButton.withOpacity(0.6), + fontSize: 14, + fontWeight: FontWeight.w400), + ), + ), + )), + )) + ], + ) + ], + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/sos/sos_settings.dart b/lib/features/devices/view/widgets/sos/sos_settings.dart new file mode 100644 index 0000000..8971452 --- /dev/null +++ b/lib/features/devices/view/widgets/sos/sos_settings.dart @@ -0,0 +1,484 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/sos_bloc/sos_bloc.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_model.dart'; +import 'package:syncrow_app/features/devices/model/sos_model.dart'; +import 'package:syncrow_app/features/devices/model/subspace_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/sos/sos_setting/delete_dialog.dart'; +import 'package:syncrow_app/features/devices/view/widgets/sos/sos_setting/sos_profile_page.dart'; +import 'package:syncrow_app/features/devices/view/widgets/sos/sos_setting/faq_sos_page.dart'; +import 'package:syncrow_app/features/devices/view/widgets/sos/sos_setting/share_sos_page.dart'; +import 'package:syncrow_app/features/devices/view/widgets/sos/sos_setting/sos_info_page.dart'; +import 'package:syncrow_app/features/devices/view/widgets/sos/sos_setting/sos_update_page.dart'; +import 'package:syncrow_app/features/devices/view/widgets/sos/sos_setting/update_dialog_sos.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class SosSettings extends StatelessWidget { + final DeviceModel? device; + const SosSettings({ + super.key, + this.device, + }); + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Device Settings', + child: BlocProvider( + create: (context) => SosBloc(sosId: device?.uuid ?? '') + ..add(const SosInitial()) + ..add(SosInitialDeviseInfo()), + child: BlocBuilder( + builder: (context, state) { + final _bloc = BlocProvider.of(context); + + SosModel model = + SosModel(batteryPercentage: 0, sosContactState: 'normal'); + if (state is LoadingNewSate) { + model = state.sosSensor; + } else if (state is UpdateState) { + model = state.sensor; + } + return state is SosLoadingState + ? const Center( + child: DefaultContainer( + width: 50, + height: 50, + child: CircularProgressIndicator()), + ) + : ListView( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + vertical: 10, + ), + child: InkWell( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => SosProfilePage( + device: device, + ), + ), + ); + }, + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 20), + DefaultContainer( + borderRadius: const BorderRadius.all( + Radius.circular(30)), + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Padding( + padding: + const EdgeInsets.only(left: 90), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + const BodyMedium( + text: 'SOS', + fontWeight: FontWeight.bold, + ), + const SizedBox( + height: 5, + ), + BodySmall( + text: _bloc.deviceInfo + .subspace.subspaceName), + ], + ), + const Icon( + Icons.edit_outlined, + color: ColorsManager.grayColor, + ), + ], + ), + ), + ), + ), + ], + ), + Positioned( + top: 0, + left: 20, + child: CircleAvatar( + radius: 40, + backgroundColor: Colors.white, + child: CircleAvatar( + radius: 40, + backgroundColor: Colors.grey, + child: ClipOval( + child: SvgPicture.asset( + Assets.sosProfileIcon, + fit: BoxFit.fill, + ), + ), + ), + ), + ), + ], + ), + ), + ), + const SizedBox(height: 20), + const BodyMedium( + text: 'Device Management', + fontWeight: FontWeight.w700, + fontSize: 12, + fontColor: ColorsManager.grayColor, + ), + DefaultContainer( + child: SettingWidget( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + SosInfoPage(device: device!)), + ); + }, + text: 'Device Information', + icon: Assets.infoIcon, + ), + ), + const SizedBox(height: 20), + const BodyMedium( + text: 'Device Offline Notification', + fontWeight: FontWeight.w700, + fontSize: 12, + fontColor: ColorsManager.grayColor, + ), + DefaultContainer( + child: Column( + children: [ + SettingWidget( + onChanged: (p0) {}, + isNotification: true, + onTap: () {}, + text: 'Offline Notification', + icon: Assets.notificationIcon, + ), + ], + ), + ), + const SizedBox(height: 20), + const BodyMedium( + text: 'Others', + fontWeight: FontWeight.w700, + fontSize: 12, + fontColor: ColorsManager.grayColor, + ), + const SizedBox(height: 5), + DefaultContainer( + child: Column( + children: [ + SettingWidget( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + ShareSosPage(device: device!)), + ); + }, + text: 'Share Device', + icon: Assets.shareIcon, + ), + const Divider( + color: ColorsManager.dividerColor, + ), + SettingWidget( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + FaqSosPage(device: device!)), + ); + }, + text: 'Device FAQ', + icon: Assets.faqIcon, + ), + const Divider( + color: ColorsManager.dividerColor, + ), + SettingWidget( + onTapUpdate: () { + showDialog( + context: context, + builder: (context) { + return UpdateInfoDialog( + cancelTab: () { + Navigator.of(context).pop(); + }, + confirmTab: () { + // context + // .read< + // CreateSceneBloc>() + // .add(DeleteSceneEvent( + // sceneId: sceneId, + // unitUuid: HomeCubit + // .getInstance() + // .selectedSpace! + // .id!, + // )); + Navigator.of(context).pop(); + }, + ); + }, + ); + }, + isUpdate: true, + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + const SosUpdatePage()), + ); + }, + text: 'Device Update', + icon: Assets.updateIcon, + ), + ], + ), + ), + const SizedBox(height: 20), + InkWell( + onTap: () { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return Container( + height: 200, + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const BodyMedium( + text: 'Remove Device', + fontWeight: FontWeight.w700, + fontSize: 16, + fontColor: ColorsManager.red, + ), + const SizedBox(height: 10), + const SizedBox( + width: 250, + child: Divider( + color: ColorsManager.dividerColor, + ), + ), + const SizedBox(height: 10), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + InkWell( + onTap: () { + showDialog( + context: context, + builder: (context) { + return DisconnectDeviceDialog( + cancelTab: () { + Navigator.of(context).pop(); + }, + confirmTab: () { + // context + // .read< + // CreateSceneBloc>() + // .add(DeleteSceneEvent( + // sceneId: sceneId, + // unitUuid: HomeCubit + // .getInstance() + // .selectedSpace! + // .id!, + // )); + Navigator.of(context).pop(); + }, + ); + }, + ); + }, + child: const BodyMedium( + text: 'Disconnect Device', + fontWeight: FontWeight.w400, + fontSize: 15, + fontColor: + ColorsManager.textPrimaryColor, + ), + ), + const Icon( + Icons.keyboard_arrow_right, + color: ColorsManager.textGray, + ) + ], + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + InkWell( + onTap: () { + showDialog( + context: context, + builder: (context) { + return DisconnectWipeData( + cancelTab: () { + Navigator.of(context).pop(); + }, + confirmTab: () { + // context + // .read< + // CreateSceneBloc>() + // .add(DeleteSceneEvent( + // sceneId: sceneId, + // unitUuid: HomeCubit + // .getInstance() + // .selectedSpace! + // .id!, + // )); + Navigator.of(context).pop(); + }, + ); + }, + ); + }, + child: const BodyMedium( + text: + 'Disconnect Device and Wipe Data', + fontWeight: FontWeight.w400, + fontSize: 15, + fontColor: + ColorsManager.textPrimaryColor, + ), + ), + const Icon( + Icons.keyboard_arrow_right, + color: ColorsManager.textGray, + ) + ], + ), + ], + ), + ); + }, + ); + }, + child: const Center( + child: BodyMedium( + text: 'Remove Device', + fontWeight: FontWeight.w400, + fontSize: 15, + fontColor: ColorsManager.red, + ), + ), + ), + ], + ); + }, + ), + ), + ); + } +} + +class SettingWidget extends StatelessWidget { + final String? text; + final bool? isUpdate; + final bool? isNotification; + final String? icon; + final Function()? onTap; + final Function()? onTapUpdate; + final Function(bool)? onChanged; + const SettingWidget( + {super.key, + this.text, + this.icon, + this.onTap, + this.isUpdate, + this.onChanged, + this.isNotification = false, + this.onTapUpdate}); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + children: [ + Expanded( + flex: 2, + child: Container( + padding: EdgeInsets.all(8), + decoration: const BoxDecoration( + color: ColorsManager.primaryColor, + borderRadius: BorderRadius.all(Radius.circular(20))), + child: SvgPicture.asset( + icon!, + fit: BoxFit.none, + height: 30, + ), + ), + ), + const SizedBox( + width: 8, + ), + Expanded( + flex: isUpdate == true ? 5 : 10, + child: BodyMedium( + text: text!, + fontSize: 15, + fontWeight: FontWeight.w400, + )), + ], + ), + ), + isUpdate == true + ? InkWell( + onTap: onTapUpdate, + child: const BodyMedium( + text: '1 Update Available', + fontSize: 13, + fontWeight: FontWeight.w400, + fontColor: ColorsManager.blueColor, + ), + ) + : SizedBox(), + isNotification == false + ? const Icon( + Icons.arrow_forward_ios, + color: ColorsManager.graysColor, + size: 20, + ) + : Transform.scale( + scale: .8, + child: CupertinoSwitch( + value: true, + onChanged: onChanged, + applyTheme: true, + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/sos/sos_status_bar.dart b/lib/features/devices/view/widgets/sos/sos_status_bar.dart new file mode 100644 index 0000000..e1be227 --- /dev/null +++ b/lib/features/devices/view/widgets/sos/sos_status_bar.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_app/features/devices/model/smart_door_model.dart'; +import 'package:syncrow_app/generated/assets.dart'; + +class SosStatusBar extends StatelessWidget { + const SosStatusBar({ + required this.smartDoorModel, + super.key, + }); + + final SmartDoorModel smartDoorModel; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SvgPicture.asset(Assets.assetsIconsWifi), + Transform.rotate( + angle: 1.5708, // 90 degrees in radians (π/2 or 1.5708) + child: Icon( + _getBatteryIcon(smartDoorModel.residualElectricity), + color: _getBatteryColor(smartDoorModel.residualElectricity), + size: 30, + ), + ), + ], + ); + } + + IconData _getBatteryIcon(int batteryLevel) { + // if (batteryState == BatteryState.charging) { + // return Icons.battery_charging_full; + // } else + if (batteryLevel >= 80) { + return Icons.battery_full; + } else if (batteryLevel >= 60) { + return Icons.battery_4_bar; + } else if (batteryLevel >= 40) { + return Icons.battery_3_bar; + } else if (batteryLevel >= 20) { + return Icons.battery_2_bar; + } else { + return Icons.battery_alert; + } + } + + Color _getBatteryColor(int batteryLevel) { + if (batteryLevel >= 80) { + return Colors.green; + } else if (batteryLevel >= 40) { + return Colors.yellowAccent; + } else { + return Colors.red; + } + } +} diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart index 7e7ed11..b08142a 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -1118,6 +1118,9 @@ class Assets { static const String minusIcon = "assets/icons/minus_icon.svg"; static const String addDevicesIcon = "assets/icons/add_devices_icon.svg"; static const String fourSceneIcon = "assets/icons/four_scene_icon.svg"; - static const String fourSceneHomeIcon = "assets/icons/four_scene_home_icon.svg"; + static const String fourSceneHomeIcon = + "assets/icons/four_scene_home_icon.svg"; static const String sixSceneHomeIcon = "assets/icons/six_scene_home_icon.svg"; + static const String editDeviceNameIcon = + "assets/icons/edit_device_name_icon.svg"; } diff --git a/lib/utils/resource_manager/constants.dart b/lib/utils/resource_manager/constants.dart index e995a16..8ffaa7b 100644 --- a/lib/utils/resource_manager/constants.dart +++ b/lib/utils/resource_manager/constants.dart @@ -58,6 +58,7 @@ enum DeviceType { PC, FourScene, SixScene, + SOS, Other, } @@ -92,7 +93,9 @@ Map devicesTypesMap = { "PC": DeviceType.PC, "4S": DeviceType.FourScene, "6S": DeviceType.SixScene, + "SOS": DeviceType.SOS, }; + Map> devicesFunctionsMap = { DeviceType.AC: [ FunctionModel( @@ -592,7 +595,17 @@ Map> devicesFunctionsMap = { code: 'switch_backlight', type: functionTypesMap['Enum'], values: ValueModel.fromJson({})), - ] + ], + DeviceType.SOS: [ + FunctionModel( + code: 'contact_state', + type: functionTypesMap['Raw'], + values: ValueModel.fromJson({})), + FunctionModel( + code: 'battery_percentage', + type: functionTypesMap['Integer'], + values: ValueModel.fromJson({})), + ], }; enum TempModes { hot, cold, wind }