From a44d4231f1bd391dc2c6568541cda162f50a2569 Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 29 May 2025 14:26:24 +0300 Subject: [PATCH 1/6] Add new grey color constant and new icons for settings in assets Update CreateNewRoutineView to use const constructor Add SubSpaceModel class for device settings Add DefaultContainer widget for web layout Add events and states for device settings bloc Update API endpoints for device settings --- assets/icons/close_settings_icon.svg | 3 + assets/icons/edit_name_icon_settings.svg | 3 + lib/pages/common/custom_table.dart | 4 + .../view/device_managment_page.dart | 2 +- .../widgets/device_managment_body.dart | 90 ++++-- .../bloc/device_info_model.dart | 182 ++++++++++++ .../bloc/setting_bloc_bloc.dart | 149 ++++++++++ .../bloc/setting_bloc_event.dart | 50 ++++ .../bloc/setting_bloc_state.dart | 69 +++++ .../device_setting/bloc/sub_space_model.dart | 35 +++ .../device_setting/device_settings_panel.dart | 267 ++++++++++++++++++ lib/services/devices_mang_api.dart | 65 ++++- lib/services/space_mana_api.dart | 88 ++++-- lib/utils/color_manager.dart | 3 + lib/utils/constants/api_const.dart | 15 +- lib/utils/constants/assets.dart | 5 + lib/web_layout/default_container.dart | 45 +++ 17 files changed, 1031 insertions(+), 44 deletions(-) create mode 100644 assets/icons/close_settings_icon.svg create mode 100644 assets/icons/edit_name_icon_settings.svg create mode 100644 lib/pages/device_managment/device_setting/bloc/device_info_model.dart create mode 100644 lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart create mode 100644 lib/pages/device_managment/device_setting/bloc/setting_bloc_event.dart create mode 100644 lib/pages/device_managment/device_setting/bloc/setting_bloc_state.dart create mode 100644 lib/pages/device_managment/device_setting/bloc/sub_space_model.dart create mode 100644 lib/pages/device_managment/device_setting/device_settings_panel.dart create mode 100644 lib/web_layout/default_container.dart diff --git a/assets/icons/close_settings_icon.svg b/assets/icons/close_settings_icon.svg new file mode 100644 index 00000000..93e615d8 --- /dev/null +++ b/assets/icons/close_settings_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/edit_name_icon_settings.svg b/assets/icons/edit_name_icon_settings.svg new file mode 100644 index 00000000..54bee0af --- /dev/null +++ b/assets/icons/edit_name_icon_settings.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart index 62760a16..0abe075b 100644 --- a/lib/pages/common/custom_table.dart +++ b/lib/pages/common/custom_table.dart @@ -211,6 +211,7 @@ class _DynamicTableState extends State { onChanged: widget.withSelectAll && widget.data.isNotEmpty ? _toggleSelectAll : null, + ), ); } @@ -281,6 +282,7 @@ class _DynamicTableState extends State { padding: EdgeInsets.symmetric( horizontal: index == widget.headers.length - 1 ? 12 : 8.0, vertical: 4), + child: Text( title, style: context.textTheme.titleSmall!.copyWith( @@ -301,6 +303,7 @@ class _DynamicTableState extends State { required int rowIndex, required int columnIndex, }) { + bool isBatteryLevel = content.endsWith('%'); double? batteryLevel; @@ -312,6 +315,7 @@ class _DynamicTableState extends State { if (isSettingsColumn) { return _buildSettingsIcon(rowIndex, size); } + Color? statusColor; switch (content) { diff --git a/lib/pages/device_managment/all_devices/view/device_managment_page.dart b/lib/pages/device_managment/all_devices/view/device_managment_page.dart index fd3a2574..755bc8b7 100644 --- a/lib/pages/device_managment/all_devices/view/device_managment_page.dart +++ b/lib/pages/device_managment/all_devices/view/device_managment_page.dart @@ -95,7 +95,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { return const RoutinesView(); } if (state.createRoutineView) { - return CreateNewRoutineView(); + return const CreateNewRoutineView(); } return BlocBuilder( diff --git a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart index a3c975c1..f4baad0c 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart @@ -6,9 +6,11 @@ import 'package:syncrow_web/pages/common/filter/filter_widget.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/device_settings_panel.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart'; import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/format_date_time.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -58,7 +60,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { 'Low Battery ($lowBatteryCount)', ]; - final buttonLabel = (selectedDevices.length > 1) ? 'Batch Control' : 'Control'; + final buttonLabel = + (selectedDevices.length > 1) ? 'Batch Control' : 'Control'; return Row( children: [ @@ -105,18 +108,23 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { if (selectedDevices.length == 1) { showDialog( context: context, - builder: (context) => DeviceControlDialog( + builder: (context) => + DeviceControlDialog( device: selectedDevices.first, ), ); - } else if (selectedDevices.length > 1) { - final productTypes = selectedDevices - .map((device) => device.productType) - .toSet(); + } else if (selectedDevices.length > + 1) { + final productTypes = + selectedDevices + .map((device) => + device.productType) + .toSet(); if (productTypes.length == 1) { showDialog( context: context, - builder: (context) => DeviceBatchControlDialog( + builder: (context) => + DeviceBatchControlDialog( devices: selectedDevices, ), ); @@ -130,7 +138,9 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { textAlign: TextAlign.center, style: TextStyle( fontSize: 12, - color: isControlButtonEnabled ? Colors.white : Colors.grey, + color: isControlButtonEnabled + ? Colors.white + : Colors.grey, ), ), ), @@ -166,29 +176,40 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { 'Installation Date and Time', 'Status', 'Last Offline Date and Time', + 'Settings' ], data: devicesToShow.map((device) { final combinedSpaceNames = device.spaces != null - ? device.spaces!.map((space) => space.spaceName).join(' > ') + + ? device.spaces! + .map((space) => space.spaceName) + .join(' > ') + (device.community != null ? ' > ${device.community!.name}' : '') - : (device.community != null ? device.community!.name : ''); + : (device.community != null + ? device.community!.name + : ''); return [ device.name ?? '', device.productName ?? '', device.uuid ?? '', - (device.spaces != null && device.spaces!.isNotEmpty) + (device.spaces != null && + device.spaces!.isNotEmpty) ? device.spaces![0].spaceName : '', combinedSpaceNames, - device.batteryLevel != null ? '${device.batteryLevel}%' : '-', - formatDateTime(DateTime.fromMillisecondsSinceEpoch( - (device.createTime ?? 0) * 1000)), + device.batteryLevel != null + ? '${device.batteryLevel}%' + : '-', + formatDateTime( + DateTime.fromMillisecondsSinceEpoch( + (device.createTime ?? 0) * 1000)), device.online == true ? 'Online' : 'Offline', - formatDateTime(DateTime.fromMillisecondsSinceEpoch( - (device.updateTime ?? 0) * 1000)), + formatDateTime( + DateTime.fromMillisecondsSinceEpoch( + (device.updateTime ?? 0) * 1000)), + 'Settings', ]; }).toList(), onSelectionChanged: (selectedRows) { @@ -202,6 +223,10 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { .map((device) => device.uuid!) .toList(), isEmpty: devicesToShow.isEmpty, + onSettingsPressed: (rowIndex) { + final device = devicesToShow[rowIndex]; + showDeviceSettingsSidebar(context, device); + }, ), ), ) @@ -213,4 +238,37 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { }, ); } + + void showDeviceSettingsSidebar(BuildContext context, AllDevicesModel device) { + showGeneralDialog( + context: context, + barrierDismissible: true, + barrierLabel: "Device Settings", + transitionDuration: const Duration(milliseconds: 300), + pageBuilder: (context, anim1, anim2) { + return Align( + alignment: Alignment.centerRight, + child: Material( + child: Container( + width: MediaQuery.of(context).size.width * 0.3, + color: ColorsManager.whiteColors, + child: DeviceSettingsPanel( + device: device, + onClose: () => Navigator.of(context).pop(), + ), + ), + ), + ); + }, + transitionBuilder: (context, anim1, anim2, child) { + return SlideTransition( + position: Tween( + begin: const Offset(1, 0), + end: Offset.zero, + ).animate(anim1), + child: child, + ); + }, + ); + } } diff --git a/lib/pages/device_managment/device_setting/bloc/device_info_model.dart b/lib/pages/device_managment/device_setting/bloc/device_info_model.dart new file mode 100644 index 00000000..65a48508 --- /dev/null +++ b/lib/pages/device_managment/device_setting/bloc/device_info_model.dart @@ -0,0 +1,182 @@ +class DeviceInfoModel { + final int activeTime; + final String category; + final String categoryName; + final int createTime; + final String gatewayId; + final String icon; + final String ip; + final String lat; + final String localKey; + final String lon; + final String model; + final String name; + final String nodeId; + final bool online; + final String ownerId; + final String productName; + final bool sub; + final String timeZone; + final int updateTime; + final String uuid; + final String productUuid; + final String productType; + final String permissionType; + final String macAddress; + final Subspace subspace; + + DeviceInfoModel({ + required this.activeTime, + required this.category, + required this.categoryName, + required this.createTime, + required this.gatewayId, + required this.icon, + required this.ip, + required this.lat, + required this.localKey, + required this.lon, + required this.model, + required this.name, + required this.nodeId, + required this.online, + required this.ownerId, + required this.productName, + required this.sub, + required this.timeZone, + required this.updateTime, + required this.uuid, + required this.productUuid, + required this.productType, + required this.permissionType, + required this.macAddress, + required this.subspace, + }); + + factory DeviceInfoModel.fromJson(Map json) { + return DeviceInfoModel( + activeTime: json['activeTime'], + category: json['category'], + categoryName: json['categoryName'], + createTime: json['createTime'], + gatewayId: json['gatewayId'], + icon: json['icon'], + ip: json['ip'] ?? "", + lat: json['lat'], + localKey: json['localKey'], + lon: json['lon'], + model: json['model'], + name: json['name'], + nodeId: json['nodeId'], + online: json['online'], + ownerId: json['ownerId'], + productName: json['productName'], + sub: json['sub'], + timeZone: json['timeZone'], + updateTime: json['updateTime'], + uuid: json['uuid'], + productUuid: json['productUuid'], + productType: json['productType'], + permissionType: json['permissionType'] ?? '', + macAddress: json['macAddress'], + subspace: Subspace.fromJson(json['subspace']), + ); + } + + Map toJson() { + return { + 'activeTime': activeTime, + 'category': category, + 'categoryName': categoryName, + 'createTime': createTime, + 'gatewayId': gatewayId, + 'icon': icon, + 'ip': ip, + 'lat': lat, + 'localKey': localKey, + 'lon': lon, + 'model': model, + 'name': name, + 'nodeId': nodeId, + 'online': online, + 'ownerId': ownerId, + 'productName': productName, + 'sub': sub, + 'timeZone': timeZone, + 'updateTime': updateTime, + 'uuid': uuid, + 'productUuid': productUuid, + 'productType': productType, + 'permissionType': permissionType, + 'macAddress': macAddress, + 'subspace': subspace.toJson(), + }; + } + + static DeviceInfoModel empty() { + return 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: '', + ), + ); + } +} + +class Subspace { + final String uuid; + final String createdAt; + final String updatedAt; + final String subspaceName; + + Subspace({ + required this.uuid, + required this.createdAt, + required this.updatedAt, + required this.subspaceName, + }); + + factory Subspace.fromJson(Map json) { + return Subspace( + uuid: json['uuid'], + createdAt: json['createdAt'], + updatedAt: json['updatedAt'], + subspaceName: json['subspaceName'], + ); + } + + Map toJson() { + return { + 'uuid': uuid, + 'createdAt': createdAt, + 'updatedAt': updatedAt, + 'subspaceName': subspaceName, + }; + } +} diff --git a/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart b/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart new file mode 100644 index 00000000..55e5e74e --- /dev/null +++ b/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart @@ -0,0 +1,149 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/bloc/device_info_model.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_state.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/bloc/sub_space_model.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; +import 'package:syncrow_web/utils/snack_bar.dart'; +part 'setting_bloc_event.dart'; + +class SettingBlocBloc extends Bloc { + final String deviceId; + SettingBlocBloc({ + required this.deviceId, + }) : super(const SettingBlocInitial()) { + on(fetchDeviceInfo); + on(saveName); + on(_changeName); + on(deleteDevice); + //on(_fetchRoomsAndDevices); + } + static String deviceName = ''; + final TextEditingController nameController = + TextEditingController(text: deviceName); + List roomsList = []; + bool isEditingName = 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; + } + + Future saveName( + SaveNameEvent event, Emitter emit) async { + if (_validateInputs()) return; + try { + emit(SettingLoadingState()); + var response = await DevicesManagementApi.putDeviceName( + deviceId: deviceId, deviceName: nameController.text); + add(DeviceSettingInitialInfo()); + CustomSnackBar.displaySnackBar('Save Successfully'); + emit(UpdateSettingState(deviceName: nameController.text)); + } catch (e) { + emit(ErrorState(message: e.toString())); + } finally { + // isSaving = false; + } + } + + 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: "", + ), + ); + + Future fetchDeviceInfo( + DeviceSettingInitialInfo event, Emitter emit) async { + try { + emit(SettingLoadingState()); + var response = await DevicesManagementApi.getDeviceInfo(deviceId); + deviceInfo = DeviceInfoModel.fromJson(response); + nameController.text = deviceInfo.name; + + emit(UpdateSettingState( + deviceName: nameController.text, + deviceInfo: deviceInfo, + )); + } catch (e) { + emit(ErrorState(message: e.toString())); + } + } + + bool editName = false; + final FocusNode focusNode = FocusNode(); + + void _changeName(ChangeNameEvent event, Emitter emit) { + emit(SettingLoadingState()); + editName = event.value!; + if (editName) { + Future.delayed(const Duration(milliseconds: 500), () { + focusNode.requestFocus(); + }); + } else { + add(const SaveNameEvent()); + focusNode.unfocus(); + } + emit(UpdateSettingState(deviceName: deviceName, deviceInfo: deviceInfo)); + } + + void deleteDevice( + DeleteDeviceEvent event, Emitter emit) async { + try { + emit(SettingLoadingState()); + var response = + await DevicesManagementApi.resetDevise(devicesUuid: deviceId); + CustomSnackBar.displaySnackBar('Reset Successfully'); + emit(UpdateSettingState( + deviceName: nameController.text, + deviceInfo: deviceInfo, + )); + } catch (e) { + emit(ErrorState(message: e.toString())); + return; + } + } +} diff --git a/lib/pages/device_managment/device_setting/bloc/setting_bloc_event.dart b/lib/pages/device_managment/device_setting/bloc/setting_bloc_event.dart new file mode 100644 index 00000000..737c8889 --- /dev/null +++ b/lib/pages/device_managment/device_setting/bloc/setting_bloc_event.dart @@ -0,0 +1,50 @@ +part of 'setting_bloc_bloc.dart'; + +abstract class SettingBlocEvent extends Equatable { + const SettingBlocEvent(); + @override + List get props => []; +} + +class SaveDeviceName extends SettingBlocEvent { + final String deviceName; + final String deviceId; + + const SaveDeviceName({required this.deviceName, required this.deviceId}); + + @override + List get props => [deviceName, deviceId]; +} + +class StartEditingName extends SettingBlocEvent {} + +class CancelEditingName extends SettingBlocEvent {} + +class ChangeEditingNameValue extends SettingBlocEvent { + final String value; + const ChangeEditingNameValue(this.value); + + @override + List get props => [value]; +} + +class FetchRoomsEvent extends SettingBlocEvent { + final String deviceId; + + const FetchRoomsEvent(this.deviceId); + + @override + List get props => [deviceId]; +} + +class SaveNameEvent extends SettingBlocEvent { + const SaveNameEvent(); +} + +class DeviceSettingInitialInfo extends SettingBlocEvent {} + +class ChangeNameEvent extends SettingBlocEvent { + final bool? value; + const ChangeNameEvent({this.value}); +} +class DeleteDeviceEvent extends SettingBlocEvent {} diff --git a/lib/pages/device_managment/device_setting/bloc/setting_bloc_state.dart b/lib/pages/device_managment/device_setting/bloc/setting_bloc_state.dart new file mode 100644 index 00000000..65907c67 --- /dev/null +++ b/lib/pages/device_managment/device_setting/bloc/setting_bloc_state.dart @@ -0,0 +1,69 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/bloc/device_info_model.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/bloc/sub_space_model.dart'; + +abstract class DeviceSettingsState extends Equatable { + const DeviceSettingsState(); + + @override + List get props => []; +} + +class SettingBlocInitial extends DeviceSettingsState { + final String deviceName; + final String deviceId; + final bool isEditingName; + final String editingNameValue; + + const SettingBlocInitial({ + this.deviceName = '', + this.deviceId = '', + this.isEditingName = false, + this.editingNameValue = '', + }); + + SettingBlocInitial copyWith({ + String? deviceName, + String? deviceId, + bool? isEditingName, + String? editingNameValue, + }) => + SettingBlocInitial( + deviceName: deviceName ?? this.deviceName, + deviceId: deviceId ?? this.deviceId, + isEditingName: isEditingName ?? this.isEditingName, + editingNameValue: editingNameValue ?? this.editingNameValue, + ); + + @override + List get props => + [deviceName, deviceId, isEditingName, editingNameValue]; +} + +class SettingLoadingState extends DeviceSettingsState {} + +class UpdateSettingState extends DeviceSettingsState { + final String deviceName; + final DeviceInfoModel? deviceInfo; + const UpdateSettingState({required this.deviceName, this.deviceInfo}); + + @override + List get props => [deviceName, deviceInfo]; +} + +class ErrorState extends DeviceSettingsState { + final String message; + + const ErrorState({required this.message}); + @override + List get props => [message]; +} + +class FetchRoomsState extends DeviceSettingsState { + final List roomsList; + + const FetchRoomsState({required this.roomsList}); + + @override + List get props => [roomsList]; +} diff --git a/lib/pages/device_managment/device_setting/bloc/sub_space_model.dart b/lib/pages/device_managment/device_setting/bloc/sub_space_model.dart new file mode 100644 index 00000000..bc68b33e --- /dev/null +++ b/lib/pages/device_managment/device_setting/bloc/sub_space_model.dart @@ -0,0 +1,35 @@ +import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; + +class SubSpaceModel { + final String? id; + final String? name; + List? devices; + + SubSpaceModel({ + required this.id, + required this.name, + required this.devices, + }); + + Map toJson() { + return { + 'id': id, + 'name': name, + 'devices': devices?.map((device) => device.toJson()).toList(), + }; + } + + factory SubSpaceModel.fromJson(Map json) { + List devices = []; + if (json['devices'] != null) { + for (var device in json['devices']) { + devices.add(DeviceModel.fromJson(device)); + } + } + return SubSpaceModel( + id: json['uuid'], + name: json['subspaceName'], + devices: devices, + ); + } +} diff --git a/lib/pages/device_managment/device_setting/device_settings_panel.dart b/lib/pages/device_managment/device_setting/device_settings_panel.dart new file mode 100644 index 00000000..2415ab90 --- /dev/null +++ b/lib/pages/device_managment/device_setting/device_settings_panel.dart @@ -0,0 +1,267 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/bloc/device_info_model.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_state.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/web_layout/default_container.dart'; + +class DeviceSettingsPanel extends StatelessWidget { + final VoidCallback? onClose; + final AllDevicesModel device; + + const DeviceSettingsPanel({this.onClose, super.key, required this.device}); + + @override + Widget build(BuildContext context) { + final sectionTitle = context.theme.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.bold, + color: ColorsManager.grayColor, + ); + Widget infoRow( + {required String label, required String value, Widget? trailing}) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 6.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: context.theme.textTheme.bodyMedium!.copyWith( + fontSize: 14, + color: ColorsManager.grayColor, + ), + ), + Expanded( + child: Text( + value, + textAlign: TextAlign.end, + style: context.theme.textTheme.bodyMedium!.copyWith( + fontSize: 14, + color: ColorsManager.blackColor, + ), + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(width: 8), + trailing ?? const SizedBox.shrink(), + ], + ), + ); + } + + return BlocProvider( + create: (context) => SettingBlocBloc( + deviceId: device.uuid ?? '', + )..add(DeviceSettingInitialInfo()), + child: BlocBuilder( + builder: (context, state) { + final iconPath = + DeviceTypeHelper.getDeviceIconByTypeCode(device.productType); + final _bloc = BlocProvider.of(context); + DeviceInfoModel deviceInfo = DeviceInfoModel.empty(); + if (state is UpdateSettingState) { + deviceInfo = state.deviceInfo!; + } + return Container( + width: MediaQuery.of(context).size.width * 0.3, + color: ColorsManager.grey25, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), + child: ListView( + children: [ + /// Header + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: SvgPicture.asset(Assets.closeSettingsIcon), + onPressed: onClose ?? () => Navigator.of(context).pop(), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Device Settings', + style: context.theme.textTheme.titleLarge!.copyWith( + fontWeight: FontWeight.bold, + color: ColorsManager.primaryColor)), + ], + ), + const SizedBox(height: 24), + + /// Device Name + Icon + DefaultContainer( + child: Row( + children: [ + CircleAvatar( + radius: 40, + backgroundColor: + const Color.fromARGB(177, 213, 213, 213), + child: CircleAvatar( + backgroundColor: ColorsManager.whiteColors, + radius: 36, + child: SvgPicture.asset( + iconPath, + fit: BoxFit.cover, + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: TextFormField( + maxLength: 30, + style: const TextStyle( + color: ColorsManager.blackColor, + ), + textAlign: TextAlign.center, + focusNode: _bloc.focusNode, + controller: _bloc.nameController, + enabled: _bloc.editName, + onFieldSubmitted: (value) { + _bloc.add(const ChangeNameEvent(value: false)); + }, + decoration: const InputDecoration( + border: InputBorder.none, + fillColor: Colors.white10, + counterText: '', + ), + ), + ), + const SizedBox(width: 8), + _bloc.editName == true + ? const SizedBox() + : GestureDetector( + onTap: () { + _bloc.add(const ChangeNameEvent(value: true)); + }, + child: SvgPicture.asset( + Assets.editNameIconSettings, + color: ColorsManager.grayColor, + height: 20, + width: 20, + ), + ), + ], + ), + ), + const SizedBox(height: 32), + + /// Device Management + Text('Device Management', style: sectionTitle), + DefaultContainer( + padding: EdgeInsets.zero, + child: Column( + children: [ + const SizedBox( + height: 5, + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: infoRow( + label: 'Sub-Space:', + value: device.subspace!.subspaceName, + trailing: const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.greyColor, + ), + ), + ), + const Divider(color: ColorsManager.dividerColor), + Padding( + padding: const EdgeInsets.all(10.0), + child: infoRow( + label: 'Virtual Address:', + value: deviceInfo.productUuid, + trailing: InkWell( + onTap: () { + Clipboard.setData( + ClipboardData(text: device.productUuid ?? ''), + ); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Virtual Address copied to clipboard'), + ), + ); + }, + child: const Icon( + Icons.copy, + size: 16, + color: ColorsManager.greyColor, + ), + ), + ), + ), + const Divider(color: ColorsManager.dividerColor), + Padding( + padding: const EdgeInsets.all(10.0), + child: infoRow( + label: 'MAC Address:', + value: deviceInfo.macAddress), + ), + const SizedBox( + height: 5, + ), + ], + ), + ), + const SizedBox(height: 32), + + /// Remove Device Button + SizedBox( + width: double.infinity, + child: InkWell( + onTap: () { + _bloc.add(DeleteDeviceEvent()); + }, + child: const DefaultContainer( + padding: EdgeInsets.all(25), + child: Center( + child: Text( + 'Remove Device', + style: TextStyle(color: ColorsManager.red), + ), + ), + ), + ), + ) + ], + ), + ); + })); + } +} + +class DeviceTypeHelper { + static const Map _iconMap = { + 'AC': Assets.ac, + 'GW': Assets.gateway, + 'CPS': Assets.sensors, + 'DL': Assets.doorLock, + 'WPS': Assets.sensors, + '3G': Assets.gangSwitch, + '2G': Assets.twoGang, + '1G': Assets.oneGang, + 'CUR': Assets.curtain, + 'WH': Assets.waterHeater, + 'DS': Assets.doorSensor, + '1GT': Assets.oneTouchSwitch, + '2GT': Assets.twoTouchSwitch, + '3GT': Assets.threeTouchSwitch, + 'GD': Assets.garageDoor, + 'WL': Assets.waterLeakNormal, + 'NCPS': Assets.sensors, + }; + + static String getDeviceIconByTypeCode(String? typeCode) { + if (typeCode == null) return Assets.logoHorizontal; + return _iconMap[typeCode] ?? Assets.logoHorizontal; + } +} diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index b4de6326..97ac95d8 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -91,7 +91,8 @@ class DevicesManagementApi { } } - Future deviceBatchControl(List uuids, String code, dynamic value) async { + Future deviceBatchControl( + List uuids, String code, dynamic value) async { try { final body = { 'devicesUuid': uuids, @@ -116,7 +117,8 @@ class DevicesManagementApi { } } - static Future> getDevicesByGatewayId(String gatewayId) async { + static Future> getDevicesByGatewayId( + String gatewayId) async { final response = await HTTPService().get( path: ApiEndpoints.gatewayApi.replaceAll('{gatewayUuid}', gatewayId), showServerMessage: false, @@ -150,7 +152,9 @@ class DevicesManagementApi { String code, ) async { final response = await HTTPService().get( - path: ApiEndpoints.getDeviceLogs.replaceAll('{uuid}', uuid).replaceAll('{code}', code), + path: ApiEndpoints.getDeviceLogs + .replaceAll('{uuid}', uuid) + .replaceAll('{code}', code), showServerMessage: false, expectedResponseModel: (json) { return DeviceReport.fromJson(json['data']); @@ -223,7 +227,8 @@ class DevicesManagementApi { } } - Future addScheduleRecord(ScheduleEntry sendSchedule, String uuid) async { + Future addScheduleRecord( + ScheduleEntry sendSchedule, String uuid) async { try { final response = await HTTPService().post( path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), @@ -240,7 +245,8 @@ class DevicesManagementApi { } } - Future> getDeviceSchedules(String uuid, String category) async { + Future> getDeviceSchedules( + String uuid, String category) async { try { final response = await HTTPService().get( path: ApiEndpoints.getScheduleByDeviceId @@ -263,7 +269,9 @@ class DevicesManagementApi { } Future updateScheduleRecord( - {required bool enable, required String uuid, required String scheduleId}) async { + {required bool enable, + required String uuid, + required String scheduleId}) async { try { final response = await HTTPService().put( path: ApiEndpoints.updateScheduleByDeviceId @@ -284,7 +292,8 @@ class DevicesManagementApi { } } - Future editScheduleRecord(String uuid, ScheduleEntry newSchedule) async { + Future editScheduleRecord( + String uuid, ScheduleEntry newSchedule) async { try { final response = await HTTPService().put( path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), @@ -335,4 +344,46 @@ class DevicesManagementApi { return false; } } + + static Future> putDeviceName( + {required String deviceId, required String deviceName}) async { + try { + final response = await HTTPService().put( + path: ApiEndpoints.deviceByUuid.replaceAll('{deviceUuid}', deviceId), + body: {"deviceName": deviceName}, + expectedResponseModel: (json) { + return json['data']; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future getDeviceInfo(String deviceId) async { + final response = await HTTPService().get( + path: ApiEndpoints.deviceByUuid.replaceAll('{deviceUuid}', deviceId), + showServerMessage: false, + expectedResponseModel: (json) { + return json['data'] as Map; + }); + return response; + } + static Future resetDevise({ + String? devicesUuid, + }) async { + final response = await HTTPService().post( + path: ApiEndpoints.resetDevice.replaceAll('{deviceUuid}', devicesUuid!), + showServerMessage: false, + body: { + "devicesUuid": [devicesUuid] + }, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } + } diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 19e219b6..048c7b40 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/bloc/sub_space_model.dart'; import 'package:syncrow_web/pages/space_tree/model/pagination_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart'; @@ -12,14 +13,16 @@ import 'package:syncrow_web/utils/constants/api_const.dart'; class CommunitySpaceManagementApi { // Community Management APIs - Future> fetchCommunities(String projectId, {int page = 1}) async { + Future> fetchCommunities(String projectId, + {int page = 1}) async { try { List allCommunities = []; bool hasNext = true; while (hasNext) { await HTTPService().get( - path: ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId), + path: ApiEndpoints.getCommunityList + .replaceAll('{projectId}', projectId), queryParameters: { 'page': page, }, @@ -55,8 +58,14 @@ class CommunitySpaceManagementApi { try { bool hasNext = false; await HTTPService().get( - path: ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId), - queryParameters: {'page': page, 'includeSpaces': true, 'size': 25, 'search': search}, + path: + ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId), + queryParameters: { + 'page': page, + 'includeSpaces': true, + 'size': 25, + 'search': search + }, expectedResponseModel: (json) { try { List jsonData = json['data'] ?? []; @@ -68,7 +77,10 @@ class CommunitySpaceManagementApi { page = currentPage + 1; paginationModel = PaginationModel( - pageNum: page, hasNext: hasNext, size: 25, communities: communityList); + pageNum: page, + hasNext: hasNext, + size: 25, + communities: communityList); return paginationModel; } catch (_) { hasNext = false; @@ -83,7 +95,8 @@ class CommunitySpaceManagementApi { Future getCommunityById(String communityId) async { try { final response = await HTTPService().get( - path: ApiEndpoints.getCommunityById.replaceAll('{communityId}', communityId), + path: ApiEndpoints.getCommunityById + .replaceAll('{communityId}', communityId), expectedResponseModel: (json) { return CommunityModel.fromJson(json['data']); }, @@ -95,7 +108,8 @@ class CommunitySpaceManagementApi { } } - Future createCommunity(String name, String description, String projectId) async { + Future createCommunity( + String name, String description, String projectId) async { try { final response = await HTTPService().post( path: ApiEndpoints.createCommunity.replaceAll('{projectId}', projectId), @@ -114,7 +128,8 @@ class CommunitySpaceManagementApi { } } - Future updateCommunity(String communityId, String name, String projectId) async { + Future updateCommunity( + String communityId, String name, String projectId) async { try { final response = await HTTPService().put( path: ApiEndpoints.updateCommunity @@ -151,7 +166,8 @@ class CommunitySpaceManagementApi { } } - Future fetchSpaces(String communityId, String projectId) async { + Future fetchSpaces( + String communityId, String projectId) async { try { final response = await HTTPService().get( path: ApiEndpoints.listSpaces @@ -177,7 +193,8 @@ class CommunitySpaceManagementApi { } } - Future getSpace(String communityId, String spaceId, String projectId) async { + Future getSpace( + String communityId, String spaceId, String projectId) async { try { final response = await HTTPService().get( path: ApiEndpoints.getSpace @@ -289,7 +306,8 @@ class CommunitySpaceManagementApi { } } - Future deleteSpace(String communityId, String spaceId, String projectId) async { + Future deleteSpace( + String communityId, String spaceId, String projectId) async { try { final response = await HTTPService().delete( path: ApiEndpoints.deleteSpace @@ -307,15 +325,17 @@ class CommunitySpaceManagementApi { } } - Future> getSpaceHierarchy(String communityId, String projectId) async { + Future> getSpaceHierarchy( + String communityId, String projectId) async { try { final response = await HTTPService().get( path: ApiEndpoints.getSpaceHierarchy .replaceAll('{communityId}', communityId) .replaceAll('{projectId}', projectId), expectedResponseModel: (json) { - final spaceModels = - (json['data'] as List).map((spaceJson) => SpaceModel.fromJson(spaceJson)).toList(); + final spaceModels = (json['data'] as List) + .map((spaceJson) => SpaceModel.fromJson(spaceJson)) + .toList(); return spaceModels; }, @@ -327,15 +347,17 @@ class CommunitySpaceManagementApi { } } - Future> getSpaceOnlyWithDevices({String? communityId, String? projectId}) async { + Future> getSpaceOnlyWithDevices( + {String? communityId, String? projectId}) async { try { final response = await HTTPService().get( path: ApiEndpoints.spaceOnlyWithDevices .replaceAll('{communityId}', communityId!) .replaceAll('{projectId}', projectId!), expectedResponseModel: (json) { - final spaceModels = - (json['data'] as List).map((spaceJson) => SpaceModel.fromJson(spaceJson)).toList(); + final spaceModels = (json['data'] as List) + .map((spaceJson) => SpaceModel.fromJson(spaceJson)) + .toList(); return spaceModels; }, ); @@ -345,4 +367,36 @@ class CommunitySpaceManagementApi { return []; } } + + static Future> getSubSpaceBySpaceId( + String communityId, String spaceId, String projectId) async { + try { + // Construct the API path + final path = ApiEndpoints.listSubspace + .replaceFirst('{communityUuid}', communityId) + .replaceFirst('{spaceUuid}', spaceId) + .replaceAll('{projectUuid}', projectId); + + final response = await HTTPService().get( + path: path, + queryParameters: {"page": 1, "pageSize": 10}, + showServerMessage: false, + expectedResponseModel: (json) { + List rooms = []; + if (json['data'] != null) { + for (var subspace in json['data']) { + rooms.add(SubSpaceModel.fromJson(subspace)); + } + } else { + print("Warning: 'data' key is missing or null in response JSON."); + } + return rooms; + }, + ); + + return response; + } catch (error, stackTrace) { + return []; // Return an empty list if there's an error + } + } } diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 41ceb29a..50170ed9 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -83,4 +83,7 @@ abstract class ColorsManager { static const Color maxPurpleDot = Color(0xFF5F00BD); static const Color minBlue = Color(0xFF93AAFD); static const Color minBlueDot = Color(0xFF023DFE); + static const Color grey25 = Color(0xFFF9F9F9); + + } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 454ec46d..472055bd 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -60,9 +60,12 @@ abstract class ApiEndpoints { '/devices/{uuid}/report-logs?code={code}&startTime={startTime}&endTime={endTime}'; static const String scheduleByDeviceId = '/schedule/{deviceUuid}'; - static const String getScheduleByDeviceId = '/schedule/{deviceUuid}?category={category}'; - static const String deleteScheduleByDeviceId = '/schedule/{deviceUuid}/{scheduleUuid}'; - static const String updateScheduleByDeviceId = '/schedule/enable/{deviceUuid}'; + static const String getScheduleByDeviceId = + '/schedule/{deviceUuid}?category={category}'; + static const String deleteScheduleByDeviceId = + '/schedule/{deviceUuid}/{scheduleUuid}'; + static const String updateScheduleByDeviceId = + '/schedule/enable/{deviceUuid}'; static const String factoryReset = '/devices/batch'; //product @@ -124,4 +127,10 @@ abstract class ApiEndpoints { '/projects/{projectId}/communities/{communityId}/spaces/{unitUuid}/automations'; static const String spaceOnlyWithDevices = '/projects/{projectId}/communities/{communityId}/spaces?onlyWithDevices=true'; + + static const String listSubspace = + '/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/subspaces'; + static const String deviceByUuid = '/devices/{deviceUuid}'; + + static const String resetDevice = '/factory/reset/{deviceUuid}'; } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 13d51ea5..515ede28 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -452,4 +452,9 @@ class Assets { 'assets/icons/refresh_status_icon.svg'; static const String energyConsumedIcon = 'assets/icons/energy_consumed_icon.svg'; + static const String closeSettingsIcon = + 'assets/icons/close_settings_icon.svg'; + + static const String editNameIconSettings = + 'assets/icons/edit_name_icon_settings.svg'; } diff --git a/lib/web_layout/default_container.dart b/lib/web_layout/default_container.dart new file mode 100644 index 00000000..e0a71b04 --- /dev/null +++ b/lib/web_layout/default_container.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; + +class DefaultContainer extends StatelessWidget { + const DefaultContainer({ + super.key, + required this.child, + this.height, + this.width, + this.color, + this.boxConstraints, + this.margin, + this.padding, + this.onTap, + this.borderRadius, + }); + + final double? height; + final double? width; + final Widget child; + final BoxConstraints? boxConstraints; + final EdgeInsets? margin; + final EdgeInsets? padding; + final Color? color; + final Function()? onTap; + final BorderRadius? borderRadius; + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(20), + child: Container( + height: height, + width: width, + margin: margin ?? const EdgeInsets.only(right: 3, bottom: 3), + constraints: boxConstraints, + decoration: BoxDecoration( + color: color ?? Colors.white, + borderRadius: borderRadius ?? BorderRadius.circular(20), + ), + padding: padding ?? const EdgeInsets.all(10), + child: child, + ), + ); + } +} From cf5e05a8885a947c7678284d758d6e5e7715b1fb Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 2 Jun 2025 12:52:48 +0300 Subject: [PATCH 2/6] Refactor code by adding new API endpoint for assigning a device to a room and removing redundant code in device management settings. --- .../bloc/setting_bloc_bloc.dart | 64 ++- .../bloc/setting_bloc_event.dart | 23 +- .../bloc/setting_bloc_state.dart | 18 +- .../device_icon_type_helper.dart | 28 ++ .../device_setting/device_settings_panel.dart | 438 +++++++++++------- .../device_info_model.dart | 59 +-- .../sub_space_model.dart | 6 +- .../device_setting/sub_space_dialog.dart | 178 +++++++ lib/services/devices_mang_api.dart | 2 + lib/services/space_mana_api.dart | 31 +- lib/utils/constants/api_const.dart | 3 + 11 files changed, 620 insertions(+), 230 deletions(-) create mode 100644 lib/pages/device_managment/device_setting/device_icon_type_helper.dart rename lib/pages/device_managment/device_setting/{bloc => settings_model}/device_info_model.dart (69%) rename lib/pages/device_managment/device_setting/{bloc => settings_model}/sub_space_model.dart (81%) create mode 100644 lib/pages/device_managment/device_setting/sub_space_dialog.dart diff --git a/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart b/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart index 55e5e74e..b9aae0b8 100644 --- a/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart +++ b/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart @@ -1,10 +1,12 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/device_managment/device_setting/bloc/device_info_model.dart'; +import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_state.dart'; -import 'package:syncrow_web/pages/device_managment/device_setting/bloc/sub_space_model.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; +import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; part 'setting_bloc_event.dart'; @@ -17,7 +19,8 @@ class SettingBlocBloc extends Bloc { on(saveName); on(_changeName); on(deleteDevice); - //on(_fetchRoomsAndDevices); + on(_fetchRooms); + on(_assignDevice); } static String deviceName = ''; final TextEditingController nameController = @@ -51,7 +54,7 @@ class SettingBlocBloc extends Bloc { if (_validateInputs()) return; try { emit(SettingLoadingState()); - var response = await DevicesManagementApi.putDeviceName( + await DevicesManagementApi.putDeviceName( deviceId: deviceId, deviceName: nameController.text); add(DeviceSettingInitialInfo()); CustomSnackBar.displaySnackBar('Save Successfully'); @@ -107,6 +110,7 @@ class SettingBlocBloc extends Bloc { emit(UpdateSettingState( deviceName: nameController.text, deviceInfo: deviceInfo, + roomsList: roomsList, )); } catch (e) { emit(ErrorState(message: e.toString())); @@ -127,19 +131,65 @@ class SettingBlocBloc extends Bloc { add(const SaveNameEvent()); focusNode.unfocus(); } - emit(UpdateSettingState(deviceName: deviceName, deviceInfo: deviceInfo)); + emit(UpdateSettingState( + deviceName: deviceName, + deviceInfo: deviceInfo, + roomsList: roomsList, + )); } void deleteDevice( DeleteDeviceEvent event, Emitter emit) async { try { emit(SettingLoadingState()); - var response = - await DevicesManagementApi.resetDevise(devicesUuid: deviceId); + await DevicesManagementApi.resetDevise(devicesUuid: deviceId); CustomSnackBar.displaySnackBar('Reset Successfully'); emit(UpdateSettingState( deviceName: nameController.text, deviceInfo: deviceInfo, + roomsList: roomsList, + )); + } catch (e) { + emit(ErrorState(message: e.toString())); + return; + } + } + + //=========================== assign device to room ========================== + + void _assignDevice( + AssignRoomEvent event, Emitter emit) async { + try { + emit(SettingLoadingState()); + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + await CommunitySpaceManagementApi.assignDeviceToRoom( + communityId: event.communityUuid, + spaceId: event.spaceUuid, + subSpaceId: event.subSpaceUuid, + deviceId: deviceId, + projectId: projectUuid); + add(DeviceSettingInitialInfo()); + CustomSnackBar.displaySnackBar('Save Successfully'); + emit(SaveSelectionSuccessState()); + } catch (e) { + emit(ErrorState(message: e.toString())); + return; + } + } + + void _fetchRooms( + FetchRoomsEvent event, Emitter emit) async { + try { + emit(SettingLoadingState()); + final projectUuid = await ProjectManager.getProjectUUID() ?? ''; + roomsList = await CommunitySpaceManagementApi.getSubSpaceBySpaceId( + communityId: event.communityUuid, + spaceId: event.spaceUuid, + projectId: projectUuid); + emit(UpdateSettingState( + deviceName: nameController.text, + deviceInfo: deviceInfo, + roomsList: roomsList, )); } catch (e) { emit(ErrorState(message: e.toString())); diff --git a/lib/pages/device_managment/device_setting/bloc/setting_bloc_event.dart b/lib/pages/device_managment/device_setting/bloc/setting_bloc_event.dart index 737c8889..66d9e09f 100644 --- a/lib/pages/device_managment/device_setting/bloc/setting_bloc_event.dart +++ b/lib/pages/device_managment/device_setting/bloc/setting_bloc_event.dart @@ -29,12 +29,13 @@ class ChangeEditingNameValue extends SettingBlocEvent { } class FetchRoomsEvent extends SettingBlocEvent { - final String deviceId; + final String communityUuid; + final String spaceUuid; - const FetchRoomsEvent(this.deviceId); + const FetchRoomsEvent({required this.communityUuid, required this.spaceUuid}); @override - List get props => [deviceId]; + List get props => [communityUuid, spaceUuid]; } class SaveNameEvent extends SettingBlocEvent { @@ -47,4 +48,20 @@ class ChangeNameEvent extends SettingBlocEvent { final bool? value; const ChangeNameEvent({this.value}); } + class DeleteDeviceEvent extends SettingBlocEvent {} + +class AssignRoomEvent extends SettingBlocEvent { + final String communityUuid; + final String spaceUuid; + final String subSpaceUuid; + + const AssignRoomEvent({ + required this.communityUuid, + required this.spaceUuid, + required this.subSpaceUuid, + }); + + @override + List get props => [spaceUuid, communityUuid, subSpaceUuid]; +} diff --git a/lib/pages/device_managment/device_setting/bloc/setting_bloc_state.dart b/lib/pages/device_managment/device_setting/bloc/setting_bloc_state.dart index 65907c67..eb30b70a 100644 --- a/lib/pages/device_managment/device_setting/bloc/setting_bloc_state.dart +++ b/lib/pages/device_managment/device_setting/bloc/setting_bloc_state.dart @@ -1,6 +1,6 @@ import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/device_managment/device_setting/bloc/device_info_model.dart'; -import 'package:syncrow_web/pages/device_managment/device_setting/bloc/sub_space_model.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart'; abstract class DeviceSettingsState extends Equatable { const DeviceSettingsState(); @@ -43,12 +43,16 @@ class SettingBlocInitial extends DeviceSettingsState { class SettingLoadingState extends DeviceSettingsState {} class UpdateSettingState extends DeviceSettingsState { - final String deviceName; + final String? deviceName; final DeviceInfoModel? deviceInfo; - const UpdateSettingState({required this.deviceName, this.deviceInfo}); + final List roomsList; - @override - List get props => [deviceName, deviceInfo]; + const UpdateSettingState({ + this.deviceName, + this.deviceInfo, + this.roomsList = const [], + }); + List get props => [deviceName, deviceInfo, roomsList]; } class ErrorState extends DeviceSettingsState { @@ -67,3 +71,5 @@ class FetchRoomsState extends DeviceSettingsState { @override List get props => [roomsList]; } + +class SaveSelectionSuccessState extends DeviceSettingsState {} diff --git a/lib/pages/device_managment/device_setting/device_icon_type_helper.dart b/lib/pages/device_managment/device_setting/device_icon_type_helper.dart new file mode 100644 index 00000000..13f8abfe --- /dev/null +++ b/lib/pages/device_managment/device_setting/device_icon_type_helper.dart @@ -0,0 +1,28 @@ +import 'package:syncrow_web/utils/constants/assets.dart'; + +class DeviceIconTypeHelper { + static const Map _iconMap = { + 'AC': Assets.ac, + 'GW': Assets.gateway, + 'CPS': Assets.sensors, + 'DL': Assets.doorLock, + 'WPS': Assets.sensors, + '3G': Assets.gangSwitch, + '2G': Assets.twoGang, + '1G': Assets.oneGang, + 'CUR': Assets.curtain, + 'WH': Assets.waterHeater, + 'DS': Assets.doorSensor, + '1GT': Assets.oneTouchSwitch, + '2GT': Assets.twoTouchSwitch, + '3GT': Assets.threeTouchSwitch, + 'GD': Assets.garageDoor, + 'WL': Assets.waterLeakNormal, + 'NCPS': Assets.sensors, + }; + + static String getDeviceIconByTypeCode(String? typeCode) { + if (typeCode == null) return Assets.logoHorizontal; + return _iconMap[typeCode] ?? Assets.logoHorizontal; + } +} diff --git a/lib/pages/device_managment/device_setting/device_settings_panel.dart b/lib/pages/device_managment/device_setting/device_settings_panel.dart index 2415ab90..6d960a20 100644 --- a/lib/pages/device_managment/device_setting/device_settings_panel.dart +++ b/lib/pages/device_managment/device_setting/device_settings_panel.dart @@ -3,9 +3,12 @@ import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; -import 'package:syncrow_web/pages/device_managment/device_setting/bloc/device_info_model.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/device_icon_type_helper.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_state.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/sub_space_dialog.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -14,9 +17,7 @@ import 'package:syncrow_web/web_layout/default_container.dart'; class DeviceSettingsPanel extends StatelessWidget { final VoidCallback? onClose; final AllDevicesModel device; - - const DeviceSettingsPanel({this.onClose, super.key, required this.device}); - + const DeviceSettingsPanel({super.key, this.onClose, required this.device}); @override Widget build(BuildContext context) { final sectionTitle = context.theme.textTheme.titleMedium!.copyWith( @@ -24,7 +25,10 @@ class DeviceSettingsPanel extends StatelessWidget { color: ColorsManager.grayColor, ); Widget infoRow( - {required String label, required String value, Widget? trailing}) { + {required String label, + required String value, + Widget? trailing, + required Color? valueColor}) { return Padding( padding: const EdgeInsets.symmetric(vertical: 6.0), child: Row( @@ -41,10 +45,8 @@ class DeviceSettingsPanel extends StatelessWidget { child: Text( value, textAlign: TextAlign.end, - style: context.theme.textTheme.bodyMedium!.copyWith( - fontSize: 14, - color: ColorsManager.blackColor, - ), + style: context.theme.textTheme.bodyMedium! + .copyWith(fontSize: 14, color: valueColor), overflow: TextOverflow.ellipsis, ), ), @@ -56,87 +58,114 @@ class DeviceSettingsPanel extends StatelessWidget { } return BlocProvider( - create: (context) => SettingBlocBloc( - deviceId: device.uuid ?? '', - )..add(DeviceSettingInitialInfo()), - child: BlocBuilder( - builder: (context, state) { + create: (context) => SettingBlocBloc( + deviceId: device.uuid ?? '', + ) + ..add(DeviceSettingInitialInfo()) + ..add(FetchRoomsEvent( + communityUuid: device.community!.uuid!, + spaceUuid: device.spaces!.first.uuid!, + )), + child: BlocBuilder( + builder: (context, state) { final iconPath = - DeviceTypeHelper.getDeviceIconByTypeCode(device.productType); + DeviceIconTypeHelper.getDeviceIconByTypeCode(device.productType); final _bloc = BlocProvider.of(context); DeviceInfoModel deviceInfo = DeviceInfoModel.empty(); + List subSpaces = []; if (state is UpdateSettingState) { deviceInfo = state.deviceInfo!; + subSpaces = state.roomsList; } - return Container( - width: MediaQuery.of(context).size.width * 0.3, - color: ColorsManager.grey25, - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), - child: ListView( - children: [ - /// Header - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + return Stack( + children: [ + Container( + width: MediaQuery.of(context).size.width * 0.3, + color: ColorsManager.grey25, + padding: + const EdgeInsets.symmetric(horizontal: 20, vertical: 24), + child: ListView( children: [ - IconButton( - icon: SvgPicture.asset(Assets.closeSettingsIcon), - onPressed: onClose ?? () => Navigator.of(context).pop(), + // Header + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: SvgPicture.asset(Assets.closeSettingsIcon), + onPressed: + onClose ?? () => Navigator.of(context).pop(), + ), + ], ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('Device Settings', - style: context.theme.textTheme.titleLarge!.copyWith( + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Device Settings', + style: context.theme.textTheme.titleLarge!.copyWith( fontWeight: FontWeight.bold, - color: ColorsManager.primaryColor)), - ], - ), - const SizedBox(height: 24), - - /// Device Name + Icon - DefaultContainer( - child: Row( - children: [ - CircleAvatar( - radius: 40, - backgroundColor: - const Color.fromARGB(177, 213, 213, 213), - child: CircleAvatar( - backgroundColor: ColorsManager.whiteColors, - radius: 36, - child: SvgPicture.asset( - iconPath, - fit: BoxFit.cover, + color: ColorsManager.primaryColor, ), ), - ), - const SizedBox(width: 12), - Expanded( - child: TextFormField( - maxLength: 30, - style: const TextStyle( - color: ColorsManager.blackColor, + ], + ), + const SizedBox(height: 24), + // Device Name + Icon + DefaultContainer( + child: Row( + children: [ + CircleAvatar( + radius: 40, + backgroundColor: + const Color.fromARGB(177, 213, 213, 213), + child: CircleAvatar( + backgroundColor: ColorsManager.whiteColors, + radius: 36, + child: SvgPicture.asset( + iconPath, + fit: BoxFit.cover, + ), + ), ), - textAlign: TextAlign.center, - focusNode: _bloc.focusNode, - controller: _bloc.nameController, - enabled: _bloc.editName, - onFieldSubmitted: (value) { - _bloc.add(const ChangeNameEvent(value: false)); - }, - decoration: const InputDecoration( - border: InputBorder.none, - fillColor: Colors.white10, - counterText: '', + const SizedBox(width: 12), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Device Name:', + style: context.textTheme.bodyMedium!.copyWith( + color: ColorsManager.grayColor, + ), + ), + TextFormField( + maxLength: 30, + style: const TextStyle( + color: ColorsManager.blackColor, + ), + textAlign: TextAlign.start, + focusNode: _bloc.focusNode, + controller: _bloc.nameController, + enabled: _bloc.editName, + onFieldSubmitted: (value) { + _bloc.add( + const ChangeNameEvent(value: false)); + }, + decoration: const InputDecoration( + border: InputBorder.none, + fillColor: Colors.white10, + counterText: '', + ), + ), + ], + ), ), - ), - ), - const SizedBox(width: 8), - _bloc.editName == true - ? const SizedBox() - : GestureDetector( + const SizedBox(width: 8), + Visibility( + visible: _bloc.editName != true, + replacement: const SizedBox(), + child: GestureDetector( onTap: () { _bloc.add(const ChangeNameEvent(value: true)); }, @@ -147,121 +176,170 @@ class DeviceSettingsPanel extends StatelessWidget { width: 20, ), ), - ], - ), - ), - const SizedBox(height: 32), - - /// Device Management - Text('Device Management', style: sectionTitle), - DefaultContainer( - padding: EdgeInsets.zero, - child: Column( - children: [ - const SizedBox( - height: 5, + ) + ], ), - Padding( - padding: const EdgeInsets.all(10.0), - child: infoRow( - label: 'Sub-Space:', - value: device.subspace!.subspaceName, - trailing: const Icon( - Icons.arrow_forward_ios, - size: 16, - color: ColorsManager.greyColor, - ), - ), - ), - const Divider(color: ColorsManager.dividerColor), - Padding( - padding: const EdgeInsets.all(10.0), - child: infoRow( - label: 'Virtual Address:', - value: deviceInfo.productUuid, - trailing: InkWell( - onTap: () { - Clipboard.setData( - ClipboardData(text: device.productUuid ?? ''), - ); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'Virtual Address copied to clipboard'), + ), + const SizedBox(height: 32), + // Device Management + Text('Device Management', style: sectionTitle), + DefaultContainer( + padding: EdgeInsets.zero, + child: Column( + children: [ + const SizedBox(height: 5), + Padding( + padding: const EdgeInsets.all(10.0), + child: InkWell( + onTap: () { + showSubSpaceDialog( + context, + communityUuid: device.community!.uuid!, + spaceUuid: device.spaces!.first.uuid!, + subSpaces: subSpaces, + selected: device.subspace!.uuid, + ); + }, + child: infoRow( + label: 'Sub-Space:', + value: deviceInfo.subspace.subspaceName, + valueColor: ColorsManager.textGray, + trailing: const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.greyColor, ), + ), + ), + ), + const Divider(color: ColorsManager.dividerColor), + Padding( + padding: const EdgeInsets.all(10.0), + child: infoRow( + label: 'Virtual Address:', + value: deviceInfo.productUuid, + valueColor: ColorsManager.blackColor, + trailing: InkWell( + onTap: () { + Clipboard.setData( + ClipboardData( + text: device.productUuid ?? ''), + ); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Virtual Address copied to clipboard'), + ), + ); + }, + child: const Icon( + Icons.copy, + size: 16, + color: ColorsManager.greyColor, + ), + ), + ), + ), + const Divider(color: ColorsManager.dividerColor), + Padding( + padding: const EdgeInsets.all(10.0), + child: infoRow( + label: 'MAC Address:', + valueColor: ColorsManager.blackColor, + value: deviceInfo.macAddress, + ), + ), + const SizedBox(height: 5), + ], + ), + ), + const SizedBox(height: 32), + + // Remove Device Button + SizedBox( + width: double.infinity, + child: InkWell( + onTap: () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text( + 'Remove Device', + style: context.textTheme.bodyMedium!.copyWith( + fontWeight: FontWeight.w700, + color: ColorsManager.red, + ), + ), + content: Text( + 'Are you sure you want to remove this device?', + style: context.textTheme.bodyMedium!.copyWith( + color: ColorsManager.grayColor, + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + 'Cancel', + style: context.textTheme.bodyMedium! + .copyWith( + color: ColorsManager.grayColor, + ), + ), + ), + TextButton( + onPressed: () { + _bloc.add(DeleteDeviceEvent()); + Navigator.of(context).pop(); + }, + child: Text( + 'Remove', + style: context.textTheme.bodyMedium! + .copyWith( + color: ColorsManager.red, + ), + ), + ), + ], ); }, - child: const Icon( - Icons.copy, - size: 16, - color: ColorsManager.greyColor, + ); + }, + child: DefaultContainer( + padding: const EdgeInsets.all(25), + child: Center( + child: Text( + 'Remove Device', + style: context.textTheme.bodyMedium!.copyWith( + fontSize: 14, + color: ColorsManager.red, + fontWeight: FontWeight.w700), ), ), ), ), - const Divider(color: ColorsManager.dividerColor), - Padding( - padding: const EdgeInsets.all(10.0), - child: infoRow( - label: 'MAC Address:', - value: deviceInfo.macAddress), - ), - const SizedBox( - height: 5, - ), - ], - ), + ), + ], ), - const SizedBox(height: 32), - - /// Remove Device Button - SizedBox( - width: double.infinity, - child: InkWell( - onTap: () { - _bloc.add(DeleteDeviceEvent()); - }, - child: const DefaultContainer( - padding: EdgeInsets.all(25), - child: Center( - child: Text( - 'Remove Device', - style: TextStyle(color: ColorsManager.red), - ), + ), + if (state is SettingLoadingState) + Positioned.fill( + child: Container( + color: Colors.black.withOpacity(0.1), + child: const Center( + child: CircularProgressIndicator( + color: ColorsManager.primaryColor, ), ), ), - ) - ], - ), + ), + ], ); - })); - } -} - -class DeviceTypeHelper { - static const Map _iconMap = { - 'AC': Assets.ac, - 'GW': Assets.gateway, - 'CPS': Assets.sensors, - 'DL': Assets.doorLock, - 'WPS': Assets.sensors, - '3G': Assets.gangSwitch, - '2G': Assets.twoGang, - '1G': Assets.oneGang, - 'CUR': Assets.curtain, - 'WH': Assets.waterHeater, - 'DS': Assets.doorSensor, - '1GT': Assets.oneTouchSwitch, - '2GT': Assets.twoTouchSwitch, - '3GT': Assets.threeTouchSwitch, - 'GD': Assets.garageDoor, - 'WL': Assets.waterLeakNormal, - 'NCPS': Assets.sensors, - }; - - static String getDeviceIconByTypeCode(String? typeCode) { - if (typeCode == null) return Assets.logoHorizontal; - return _iconMap[typeCode] ?? Assets.logoHorizontal; + }, + ), + ); } } diff --git a/lib/pages/device_managment/device_setting/bloc/device_info_model.dart b/lib/pages/device_managment/device_setting/settings_model/device_info_model.dart similarity index 69% rename from lib/pages/device_managment/device_setting/bloc/device_info_model.dart rename to lib/pages/device_managment/device_setting/settings_model/device_info_model.dart index 65a48508..ce9b6750 100644 --- a/lib/pages/device_managment/device_setting/bloc/device_info_model.dart +++ b/lib/pages/device_managment/device_setting/settings_model/device_info_model.dart @@ -55,31 +55,32 @@ class DeviceInfoModel { factory DeviceInfoModel.fromJson(Map json) { return DeviceInfoModel( - activeTime: json['activeTime'], - category: json['category'], - categoryName: json['categoryName'], - createTime: json['createTime'], - gatewayId: json['gatewayId'], - icon: json['icon'], - ip: json['ip'] ?? "", - lat: json['lat'], - localKey: json['localKey'], - lon: json['lon'], - model: json['model'], - name: json['name'], - nodeId: json['nodeId'], - online: json['online'], - ownerId: json['ownerId'], - productName: json['productName'], - sub: json['sub'], - timeZone: json['timeZone'], - updateTime: json['updateTime'], - uuid: json['uuid'], - productUuid: json['productUuid'], - productType: json['productType'], - permissionType: json['permissionType'] ?? '', - macAddress: json['macAddress'], - subspace: Subspace.fromJson(json['subspace']), + activeTime: json['activeTime'] as int? ?? 0, + category: json['category'] ?? '', + categoryName: json['categoryName'] as String? ?? '', + createTime: json['createTime'] as int? ?? 0, + gatewayId: json['gatewayId'] as String? ?? '', + icon: json['icon'] as String? ?? '', + ip: json['ip'] as String? ?? '', + lat: json['lat'] as String? ?? '', + localKey: json['localKey'] as String? ?? '', + lon: json['lon'] as String? ?? '', + model: json['model'] as String? ?? '', + name: json['name'] as String? ?? '', + nodeId: json['nodeId'] as String? ?? '', + online: json['online'] as bool? ?? false, + ownerId: json['ownerId'] as String? ?? '', + productName: json['productName'] as String? ?? '', + sub: json['sub'] as bool? ?? false, + timeZone: json['timeZone'] as String? ?? '', + updateTime: json['updateTime'] as int? ?? 0, + uuid: json['uuid'] as String? ?? '', + productUuid: json['productUuid'] as String? ?? '', + productType: json['productType'] as String? ?? '', + permissionType: json['permissionType'] as String? ?? '', + macAddress: json['macAddress'] as String? ?? '', + subspace: + Subspace.fromJson(json['subspace'] as Map? ?? {}), ); } @@ -164,10 +165,10 @@ class Subspace { factory Subspace.fromJson(Map json) { return Subspace( - uuid: json['uuid'], - createdAt: json['createdAt'], - updatedAt: json['updatedAt'], - subspaceName: json['subspaceName'], + uuid: json['uuid'] as String? ?? '', + createdAt: json['createdAt'] as String? ?? '', + updatedAt: json['updatedAt'] as String? ?? '', + subspaceName: json['subspaceName'] as String? ?? '', ); } diff --git a/lib/pages/device_managment/device_setting/bloc/sub_space_model.dart b/lib/pages/device_managment/device_setting/settings_model/sub_space_model.dart similarity index 81% rename from lib/pages/device_managment/device_setting/bloc/sub_space_model.dart rename to lib/pages/device_managment/device_setting/settings_model/sub_space_model.dart index bc68b33e..9d3f4036 100644 --- a/lib/pages/device_managment/device_setting/bloc/sub_space_model.dart +++ b/lib/pages/device_managment/device_setting/settings_model/sub_space_model.dart @@ -27,9 +27,9 @@ class SubSpaceModel { } } return SubSpaceModel( - id: json['uuid'], - name: json['subspaceName'], - devices: devices, + id: json['uuid'] as String? ?? '', + name: json['subspaceName'] as String? ?? '', + devices: devices.isNotEmpty ? devices : null as List?, ); } } diff --git a/lib/pages/device_managment/device_setting/sub_space_dialog.dart b/lib/pages/device_managment/device_setting/sub_space_dialog.dart new file mode 100644 index 00000000..f2fdfa3e --- /dev/null +++ b/lib/pages/device_managment/device_setting/sub_space_dialog.dart @@ -0,0 +1,178 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class SubSpaceDialog extends StatefulWidget { + final List subSpaces; + final String? selected; + final void Function(SubSpaceModel?) onConfirmed; + + const SubSpaceDialog({ + Key? key, + required this.subSpaces, + this.selected, + required this.onConfirmed, + }) : super(key: key); + + @override + State createState() => _SubSpaceDialogState(); +} + +class _SubSpaceDialogState extends State { + String? _selectedId; + + @override + void initState() { + super.initState(); + _selectedId = widget.selected; + } + + @override + Widget build(BuildContext context) { + return Dialog( + backgroundColor: ColorsManager.whiteColors, + insetPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 60), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(28), + ), + child: Container( + width: MediaQuery.of(context).size.width * 0.35, + padding: const EdgeInsets.fromLTRB(0, 24, 0, 0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Sub-Space', + style: context.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w700, + color: ColorsManager.blueColor, + fontSize: 20), + ), + const Divider(), + const SizedBox(height: 10), + ...widget.subSpaces.map((space) { + return RadioListTile( + value: space.id!, + groupValue: _selectedId, + onChanged: (value) { + setState(() { + _selectedId = value; + }); + }, + activeColor: Color(0xFF2962FF), + title: Text( + space.name ?? 'Unnamed Sub-Space', + style: context.textTheme.bodyMedium?.copyWith( + fontSize: 15, + color: ColorsManager.grayColor, + fontWeight: FontWeight.w400, + ), + ), + controlAffinity: ListTileControlAffinity.trailing, + contentPadding: const EdgeInsets.symmetric(horizontal: 24), + ); + }).toList(), + const SizedBox(height: 12), + const Divider(height: 1, thickness: 1), + SizedBox( + height: 50, + child: Row( + children: [ + Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.dividerColor, + width: 0.5, + ), + ), + ), + child: TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + 'Cancel', + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.textGray, + fontSize: 18, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border( + left: BorderSide( + color: ColorsManager.dividerColor, + width: 0.5, + ), + ), + ), + child: TextButton( + onPressed: _selectedId == null + ? null + : () { + final selectedModel = widget.subSpaces + .firstWhere( + (space) => space.id == _selectedId, + orElse: () => SubSpaceModel( + id: null, name: '', devices: [])); + widget.onConfirmed(selectedModel); + Navigator.of(context).pop(); + }, + child: Text( + 'Confirm', + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.secondaryColor, + fontSize: 14, + fontWeight: FontWeight.w400, + ), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} + +void showSubSpaceDialog( + BuildContext context, { + required List subSpaces, + String? selected, + required String communityUuid, + required String spaceUuid, +}) { + showDialog( + context: context, + barrierDismissible: true, + builder: (ctx) => SubSpaceDialog( + subSpaces: subSpaces, + selected: selected, + onConfirmed: (selectedModel) { + if (selectedModel != null) { + context.read().add( + AssignRoomEvent( + communityUuid: communityUuid, + spaceUuid: spaceUuid, + subSpaceUuid: selectedModel.id ?? '', + ), + ); + } + }, + ), + ); +} diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 97ac95d8..4d5200d4 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -386,4 +386,6 @@ class DevicesManagementApi { return response; } + + } diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 048c7b40..514be163 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/device_managment/device_setting/bloc/sub_space_model.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart'; import 'package:syncrow_web/pages/space_tree/model/pagination_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart'; @@ -369,7 +369,9 @@ class CommunitySpaceManagementApi { } static Future> getSubSpaceBySpaceId( - String communityId, String spaceId, String projectId) async { + {required String communityId, + required String spaceId, + required String projectId}) async { try { // Construct the API path final path = ApiEndpoints.listSubspace @@ -399,4 +401,29 @@ class CommunitySpaceManagementApi { return []; // Return an empty list if there's an error } } + + static Future> assignDeviceToRoom( + {required String communityId, + required String spaceId, + required String subSpaceId, + required String deviceId, + required String projectId}) async { + try { + final response = await HTTPService().post( + path: ApiEndpoints.assignDeviceToRoom + .replaceAll('{projectUuid}', projectId) + .replaceAll('{communityUuid}', communityId) + .replaceAll('{spaceUuid}', spaceId) + .replaceAll('{subSpaceUuid}', subSpaceId) + .replaceAll('{deviceUuid}', deviceId), + expectedResponseModel: (json) { + print('Assign Device Response: $json'); + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 472055bd..411e72a5 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -133,4 +133,7 @@ abstract class ApiEndpoints { static const String deviceByUuid = '/devices/{deviceUuid}'; static const String resetDevice = '/factory/reset/{deviceUuid}'; + + static const String assignDeviceToRoom = + '/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/subspaces/{subSpaceUuid}/devices/{deviceUuid}'; } From ba08fcf71f00fd4734cd51832ad8a3c69c0a8d66 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 2 Jun 2025 12:58:11 +0300 Subject: [PATCH 3/6] Refactor debug print statements in space management API --- lib/services/space_mana_api.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 514be163..31f3cebd 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -390,7 +390,8 @@ class CommunitySpaceManagementApi { rooms.add(SubSpaceModel.fromJson(subspace)); } } else { - print("Warning: 'data' key is missing or null in response JSON."); + debugPrint( + "Warning: 'data' key is missing or null in response JSON."); } return rooms; }, @@ -417,7 +418,6 @@ class CommunitySpaceManagementApi { .replaceAll('{subSpaceUuid}', subSpaceId) .replaceAll('{deviceUuid}', deviceId), expectedResponseModel: (json) { - print('Assign Device Response: $json'); return json; }, ); From cabd37a08a52ae2bf59744b2c5bb0d34507fe65a Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 2 Jun 2025 13:30:26 +0300 Subject: [PATCH 4/6] remove un use code --- .../device_setting/bloc/setting_bloc_bloc.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart b/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart index b9aae0b8..e4d6a835 100644 --- a/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart +++ b/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart @@ -61,9 +61,7 @@ class SettingBlocBloc extends Bloc { emit(UpdateSettingState(deviceName: nameController.text)); } catch (e) { emit(ErrorState(message: e.toString())); - } finally { - // isSaving = false; - } + } } DeviceInfoModel deviceInfo = DeviceInfoModel( From 906c2d0430393cd8e26f99ef9b500cbd7c2dd1a7 Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 3 Jun 2025 16:34:00 +0300 Subject: [PATCH 5/6] Refactor device management and space management APIs, update event and state classes, and add RemoveDeviceWidget for device removal functionality. --- .../bloc/setting_bloc_bloc.dart | 132 +++--- .../bloc/setting_bloc_event.dart | 29 +- .../bloc/setting_bloc_state.dart | 42 +- .../device_management_content.dart | 127 ++++++ .../device_setting/device_settings_panel.dart | 419 ++++++------------ .../device_setting/remove_device_widget.dart | 82 ++++ .../device_setting/sub_space_dialog.dart | 71 +-- .../subspace_dialog_buttons.dart | 114 +++++ lib/services/devices_mang_api.dart | 6 +- lib/services/space_mana_api.dart | 6 +- 10 files changed, 549 insertions(+), 479 deletions(-) create mode 100644 lib/pages/device_managment/device_setting/device_management_content.dart create mode 100644 lib/pages/device_managment/device_setting/remove_device_widget.dart create mode 100644 lib/pages/device_managment/device_setting/subspace_dialog_buttons.dart diff --git a/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart b/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart index e4d6a835..92d94a8f 100644 --- a/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart +++ b/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart @@ -10,26 +10,24 @@ import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; part 'setting_bloc_event.dart'; -class SettingBlocBloc extends Bloc { +class SettingDeviceBloc extends Bloc { final String deviceId; - SettingBlocBloc({ + SettingDeviceBloc({ required this.deviceId, - }) : super(const SettingBlocInitial()) { - on(fetchDeviceInfo); - on(saveName); + }) : super(const DeviceSettingsInitial()) { + on(_fetchDeviceInfo); + on(_saveName); on(_changeName); - on(deleteDevice); - on(_fetchRooms); - on(_assignDevice); + on(_deleteDevice); + on(_fetchRooms); + on(_onAssignDevice); } - static String deviceName = ''; - final TextEditingController nameController = - TextEditingController(text: deviceName); + final TextEditingController nameController = TextEditingController(); List roomsList = []; bool isEditingName = false; bool _validateInputs() { - final nameError = fullNameValidator(nameController.text); + final nameError = _fullNameValidator(nameController.text); if (nameError != null) { CustomSnackBar.displaySnackBar(nameError); return true; @@ -37,7 +35,7 @@ class SettingBlocBloc extends Bloc { return false; } - String? fullNameValidator(String? value) { + 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) { @@ -49,69 +47,35 @@ class SettingBlocBloc extends Bloc { return null; } - Future saveName( - SaveNameEvent event, Emitter emit) async { + Future _saveName( + SettingBlocSaveName event, Emitter emit) async { if (_validateInputs()) return; try { - emit(SettingLoadingState()); + emit(DeviceSettingsLoading()); await DevicesManagementApi.putDeviceName( deviceId: deviceId, deviceName: nameController.text); add(DeviceSettingInitialInfo()); CustomSnackBar.displaySnackBar('Save Successfully'); - emit(UpdateSettingState(deviceName: nameController.text)); + emit(DeviceSettingsUpdate(deviceName: nameController.text)); } catch (e) { - emit(ErrorState(message: e.toString())); - } + emit(DeviceSettingsError(message: e.toString())); + } } - 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: "", - ), - ); - - Future fetchDeviceInfo( + Future _fetchDeviceInfo( DeviceSettingInitialInfo event, Emitter emit) async { try { - emit(SettingLoadingState()); + emit(DeviceSettingsLoading()); var response = await DevicesManagementApi.getDeviceInfo(deviceId); - deviceInfo = DeviceInfoModel.fromJson(response); + DeviceInfoModel deviceInfo = DeviceInfoModel.fromJson(response); nameController.text = deviceInfo.name; - - emit(UpdateSettingState( + emit(DeviceSettingsUpdate( deviceName: nameController.text, deviceInfo: deviceInfo, roomsList: roomsList, )); } catch (e) { - emit(ErrorState(message: e.toString())); + emit(DeviceSettingsError(message: e.toString())); } } @@ -119,46 +83,50 @@ class SettingBlocBloc extends Bloc { final FocusNode focusNode = FocusNode(); void _changeName(ChangeNameEvent event, Emitter emit) { - emit(SettingLoadingState()); + emit(DeviceSettingsInitial( + deviceName: nameController.text, + deviceId: deviceId, + isEditingName: event.value ?? false, + editingNameValue: event.value?.toString() ?? '', + deviceInfo: state.deviceInfo, + )); editName = event.value!; if (editName) { Future.delayed(const Duration(milliseconds: 500), () { focusNode.requestFocus(); }); } else { - add(const SaveNameEvent()); + add(const SettingBlocSaveName()); focusNode.unfocus(); } - emit(UpdateSettingState( - deviceName: deviceName, - deviceInfo: deviceInfo, + emit(DeviceSettingsUpdate( + deviceName: nameController.text, + deviceInfo: state.deviceInfo, roomsList: roomsList, )); } - void deleteDevice( - DeleteDeviceEvent event, Emitter emit) async { + void _deleteDevice( + SettingBlocDeleteDevice event, Emitter emit) async { try { - emit(SettingLoadingState()); - await DevicesManagementApi.resetDevise(devicesUuid: deviceId); + emit(DeviceSettingsLoading()); + await DevicesManagementApi.resetDevice(devicesUuid: deviceId); CustomSnackBar.displaySnackBar('Reset Successfully'); - emit(UpdateSettingState( + emit(DeviceSettingsUpdate( deviceName: nameController.text, - deviceInfo: deviceInfo, + deviceInfo: state.deviceInfo, roomsList: roomsList, )); } catch (e) { - emit(ErrorState(message: e.toString())); + emit(DeviceSettingsError(message: e.toString())); return; } } - //=========================== assign device to room ========================== - - void _assignDevice( - AssignRoomEvent event, Emitter emit) async { + void _onAssignDevice( + SettingBlocAssignRoom event, Emitter emit) async { try { - emit(SettingLoadingState()); + emit(DeviceSettingsLoading()); final projectUuid = await ProjectManager.getProjectUUID() ?? ''; await CommunitySpaceManagementApi.assignDeviceToRoom( communityId: event.communityUuid, @@ -168,29 +136,29 @@ class SettingBlocBloc extends Bloc { projectId: projectUuid); add(DeviceSettingInitialInfo()); CustomSnackBar.displaySnackBar('Save Successfully'); - emit(SaveSelectionSuccessState()); + emit(DeviceSettingsSaveSelectionSuccess()); } catch (e) { - emit(ErrorState(message: e.toString())); + emit(DeviceSettingsError(message: e.toString())); return; } } void _fetchRooms( - FetchRoomsEvent event, Emitter emit) async { + SettingBlocFetchRooms event, Emitter emit) async { try { - emit(SettingLoadingState()); + emit(DeviceSettingsLoading()); final projectUuid = await ProjectManager.getProjectUUID() ?? ''; roomsList = await CommunitySpaceManagementApi.getSubSpaceBySpaceId( communityId: event.communityUuid, spaceId: event.spaceUuid, projectId: projectUuid); - emit(UpdateSettingState( + emit(DeviceSettingsUpdate( deviceName: nameController.text, - deviceInfo: deviceInfo, + deviceInfo: state.deviceInfo, roomsList: roomsList, )); } catch (e) { - emit(ErrorState(message: e.toString())); + emit(DeviceSettingsError(message: e.toString())); return; } } diff --git a/lib/pages/device_managment/device_setting/bloc/setting_bloc_event.dart b/lib/pages/device_managment/device_setting/bloc/setting_bloc_event.dart index 66d9e09f..ab62d8a0 100644 --- a/lib/pages/device_managment/device_setting/bloc/setting_bloc_event.dart +++ b/lib/pages/device_managment/device_setting/bloc/setting_bloc_event.dart @@ -6,40 +6,42 @@ abstract class SettingBlocEvent extends Equatable { List get props => []; } -class SaveDeviceName extends SettingBlocEvent { +class SettingBlocSaveDeviceName extends SettingBlocEvent { final String deviceName; final String deviceId; - const SaveDeviceName({required this.deviceName, required this.deviceId}); + const SettingBlocSaveDeviceName( + {required this.deviceName, required this.deviceId}); @override List get props => [deviceName, deviceId]; } -class StartEditingName extends SettingBlocEvent {} +class SettingBlocStartEditingName extends SettingBlocEvent {} -class CancelEditingName extends SettingBlocEvent {} +class SettingBlocCancelEditingName extends SettingBlocEvent {} -class ChangeEditingNameValue extends SettingBlocEvent { +class SettingBlocChangeEditingNameValue extends SettingBlocEvent { final String value; - const ChangeEditingNameValue(this.value); + const SettingBlocChangeEditingNameValue(this.value); @override List get props => [value]; } -class FetchRoomsEvent extends SettingBlocEvent { +class SettingBlocFetchRooms extends SettingBlocEvent { final String communityUuid; final String spaceUuid; - const FetchRoomsEvent({required this.communityUuid, required this.spaceUuid}); + const SettingBlocFetchRooms( + {required this.communityUuid, required this.spaceUuid}); @override List get props => [communityUuid, spaceUuid]; } -class SaveNameEvent extends SettingBlocEvent { - const SaveNameEvent(); +class SettingBlocSaveName extends SettingBlocEvent { + const SettingBlocSaveName(); } class DeviceSettingInitialInfo extends SettingBlocEvent {} @@ -49,14 +51,14 @@ class ChangeNameEvent extends SettingBlocEvent { const ChangeNameEvent({this.value}); } -class DeleteDeviceEvent extends SettingBlocEvent {} +class SettingBlocDeleteDevice extends SettingBlocEvent {} -class AssignRoomEvent extends SettingBlocEvent { +class SettingBlocAssignRoom extends SettingBlocEvent { final String communityUuid; final String spaceUuid; final String subSpaceUuid; - const AssignRoomEvent({ + const SettingBlocAssignRoom({ required this.communityUuid, required this.spaceUuid, required this.subSpaceUuid, @@ -65,3 +67,4 @@ class AssignRoomEvent extends SettingBlocEvent { @override List get props => [spaceUuid, communityUuid, subSpaceUuid]; } + diff --git a/lib/pages/device_managment/device_setting/bloc/setting_bloc_state.dart b/lib/pages/device_managment/device_setting/bloc/setting_bloc_state.dart index eb30b70a..55054c9a 100644 --- a/lib/pages/device_managment/device_setting/bloc/setting_bloc_state.dart +++ b/lib/pages/device_managment/device_setting/bloc/setting_bloc_state.dart @@ -3,32 +3,35 @@ import 'package:syncrow_web/pages/device_managment/device_setting/settings_model import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart'; abstract class DeviceSettingsState extends Equatable { - const DeviceSettingsState(); + const DeviceSettingsState({this.deviceInfo}); + + final DeviceInfoModel? deviceInfo; @override - List get props => []; + List get props => [deviceInfo]; } -class SettingBlocInitial extends DeviceSettingsState { +class DeviceSettingsInitial extends DeviceSettingsState { final String deviceName; final String deviceId; final bool isEditingName; final String editingNameValue; - const SettingBlocInitial({ + const DeviceSettingsInitial({ this.deviceName = '', this.deviceId = '', this.isEditingName = false, this.editingNameValue = '', + super.deviceInfo, }); - SettingBlocInitial copyWith({ + DeviceSettingsInitial copyWith({ String? deviceName, String? deviceId, bool? isEditingName, String? editingNameValue, }) => - SettingBlocInitial( + DeviceSettingsInitial( deviceName: deviceName ?? this.deviceName, deviceId: deviceId ?? this.deviceId, isEditingName: isEditingName ?? this.isEditingName, @@ -40,36 +43,39 @@ class SettingBlocInitial extends DeviceSettingsState { [deviceName, deviceId, isEditingName, editingNameValue]; } -class SettingLoadingState extends DeviceSettingsState {} +class DeviceSettingsLoading extends DeviceSettingsState {} -class UpdateSettingState extends DeviceSettingsState { +class DeviceSettingsUpdate extends DeviceSettingsState { final String? deviceName; - final DeviceInfoModel? deviceInfo; - final List roomsList; + final List roomsList; - const UpdateSettingState({ + const DeviceSettingsUpdate({ this.deviceName, - this.deviceInfo, - this.roomsList = const [], + super.deviceInfo, + this.roomsList = const [], }); + + @override List get props => [deviceName, deviceInfo, roomsList]; } -class ErrorState extends DeviceSettingsState { +class DeviceSettingsError extends DeviceSettingsState { final String message; - const ErrorState({required this.message}); + const DeviceSettingsError({required this.message}); @override List get props => [message]; } -class FetchRoomsState extends DeviceSettingsState { +class DeviceSettingsFetchRooms extends DeviceSettingsState { final List roomsList; - const FetchRoomsState({required this.roomsList}); + const DeviceSettingsFetchRooms({required this.roomsList}); @override List get props => [roomsList]; } -class SaveSelectionSuccessState extends DeviceSettingsState {} +class DeviceSettingsSaveSelectionSuccess extends DeviceSettingsState {} + +class ChangeNameState extends DeviceSettingsState {} diff --git a/lib/pages/device_managment/device_setting/device_management_content.dart b/lib/pages/device_managment/device_setting/device_management_content.dart new file mode 100644 index 00000000..9c758341 --- /dev/null +++ b/lib/pages/device_managment/device_setting/device_management_content.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/sub_space_dialog.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/web_layout/default_container.dart'; + +class DeviceManagementContent extends StatelessWidget { + const DeviceManagementContent({ + super.key, + required this.device, + required this.subSpaces, + required this.deviceInfo, + }); + + final AllDevicesModel device; + final List subSpaces; + final DeviceInfoModel deviceInfo; + + @override + Widget build(BuildContext context) { + Widget infoRow( + {required String label, + required String value, + Widget? trailing, + required Color? valueColor}) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 6.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: context.theme.textTheme.bodyMedium!.copyWith( + fontSize: 14, + color: ColorsManager.grayColor, + ), + ), + Expanded( + child: Text( + value, + textAlign: TextAlign.end, + style: context.theme.textTheme.bodyMedium! + .copyWith(fontSize: 14, color: valueColor), + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(width: 8), + trailing ?? const SizedBox.shrink(), + ], + ), + ); + } + + return DefaultContainer( + padding: EdgeInsets.zero, + child: Column( + children: [ + const SizedBox(height: 5), + Padding( + padding: const EdgeInsets.all(10.0), + child: InkWell( + onTap: () { + showSubSpaceDialog( + context, + communityUuid: device.community!.uuid!, + spaceUuid: device.spaces!.first.uuid!, + subSpaces: subSpaces, + selected: device.subspace!.uuid, + ); + }, + child: infoRow( + label: 'Sub-Space:', + value: deviceInfo.subspace.subspaceName, + valueColor: ColorsManager.textGray, + trailing: const Icon( + Icons.arrow_forward_ios, + size: 16, + color: ColorsManager.greyColor, + ), + ), + ), + ), + const Divider(color: ColorsManager.dividerColor), + Padding( + padding: const EdgeInsets.all(10.0), + child: infoRow( + label: 'Virtual Address:', + value: deviceInfo.productUuid, + valueColor: ColorsManager.blackColor, + trailing: InkWell( + onTap: () { + Clipboard.setData( + ClipboardData(text: device.productUuid ?? ''), + ); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Virtual Address copied to clipboard'), + ), + ); + }, + child: const Icon( + Icons.copy, + size: 16, + color: ColorsManager.greyColor, + ), + ), + ), + ), + const Divider(color: ColorsManager.dividerColor), + Padding( + padding: const EdgeInsets.all(10.0), + child: infoRow( + label: 'MAC Address:', + valueColor: ColorsManager.blackColor, + value: deviceInfo.macAddress, + ), + ), + const SizedBox(height: 5), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/device_setting/device_settings_panel.dart b/lib/pages/device_managment/device_setting/device_settings_panel.dart index 6d960a20..cebd80b3 100644 --- a/lib/pages/device_managment/device_setting/device_settings_panel.dart +++ b/lib/pages/device_managment/device_setting/device_settings_panel.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/device_icon_type_helper.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/device_management_content.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/remove_device_widget.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_state.dart'; -import 'package:syncrow_web/pages/device_managment/device_setting/sub_space_dialog.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -18,325 +18,164 @@ class DeviceSettingsPanel extends StatelessWidget { final VoidCallback? onClose; final AllDevicesModel device; const DeviceSettingsPanel({super.key, this.onClose, required this.device}); + @override Widget build(BuildContext context) { final sectionTitle = context.theme.textTheme.titleMedium!.copyWith( fontWeight: FontWeight.bold, color: ColorsManager.grayColor, ); - Widget infoRow( - {required String label, - required String value, - Widget? trailing, - required Color? valueColor}) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 6.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - label, - style: context.theme.textTheme.bodyMedium!.copyWith( - fontSize: 14, - color: ColorsManager.grayColor, - ), - ), - Expanded( - child: Text( - value, - textAlign: TextAlign.end, - style: context.theme.textTheme.bodyMedium! - .copyWith(fontSize: 14, color: valueColor), - overflow: TextOverflow.ellipsis, - ), - ), - const SizedBox(width: 8), - trailing ?? const SizedBox.shrink(), - ], - ), - ); - } - return BlocProvider( - create: (context) => SettingBlocBloc( + create: (context) => SettingDeviceBloc( deviceId: device.uuid ?? '', ) ..add(DeviceSettingInitialInfo()) - ..add(FetchRoomsEvent( + ..add(SettingBlocFetchRooms( communityUuid: device.community!.uuid!, spaceUuid: device.spaces!.first.uuid!, )), - child: BlocBuilder( - builder: (context, state) { - final iconPath = - DeviceIconTypeHelper.getDeviceIconByTypeCode(device.productType); - final _bloc = BlocProvider.of(context); - DeviceInfoModel deviceInfo = DeviceInfoModel.empty(); - List subSpaces = []; - if (state is UpdateSettingState) { - deviceInfo = state.deviceInfo!; - subSpaces = state.roomsList; - } - return Stack( - children: [ - Container( - width: MediaQuery.of(context).size.width * 0.3, - color: ColorsManager.grey25, - padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 24), - child: ListView( - children: [ - // Header - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Builder( + builder: (context) { + return BlocBuilder( + builder: (context, state) { + final _bloc = context.read(); + final iconPath = DeviceIconTypeHelper.getDeviceIconByTypeCode( + device.productType); + final deviceInfo = state is DeviceSettingsUpdate + ? state.deviceInfo ?? DeviceInfoModel.empty() + : DeviceInfoModel.empty(); + final subSpaces = + state is DeviceSettingsUpdate ? state.roomsList ?? [] : []; + return Stack( + children: [ + Container( + width: MediaQuery.of(context).size.width * 0.3, + color: ColorsManager.grey25, + padding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 24), + child: ListView( children: [ - IconButton( - icon: SvgPicture.asset(Assets.closeSettingsIcon), - onPressed: - onClose ?? () => Navigator.of(context).pop(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: SvgPicture.asset(Assets.closeSettingsIcon), + onPressed: + onClose ?? () => Navigator.of(context).pop(), + ), + ], ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Device Settings', - style: context.theme.textTheme.titleLarge!.copyWith( - fontWeight: FontWeight.bold, - color: ColorsManager.primaryColor, - ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Device Settings', + style: + context.theme.textTheme.titleLarge!.copyWith( + fontWeight: FontWeight.bold, + color: ColorsManager.primaryColor, + ), + ), + ], ), - ], - ), - const SizedBox(height: 24), - // Device Name + Icon - DefaultContainer( - child: Row( - children: [ - CircleAvatar( - radius: 40, - backgroundColor: - const Color.fromARGB(177, 213, 213, 213), - child: CircleAvatar( - backgroundColor: ColorsManager.whiteColors, - radius: 36, - child: SvgPicture.asset( - iconPath, - fit: BoxFit.cover, - ), - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Device Name:', - style: context.textTheme.bodyMedium!.copyWith( - color: ColorsManager.grayColor, + const SizedBox(height: 24), + DefaultContainer( + child: Row( + children: [ + CircleAvatar( + radius: 40, + backgroundColor: + const Color.fromARGB(177, 213, 213, 213), + child: CircleAvatar( + backgroundColor: ColorsManager.whiteColors, + radius: 36, + child: SvgPicture.asset( + iconPath, + fit: BoxFit.cover, ), ), - TextFormField( - maxLength: 30, - style: const TextStyle( - color: ColorsManager.blackColor, - ), - textAlign: TextAlign.start, - focusNode: _bloc.focusNode, - controller: _bloc.nameController, - enabled: _bloc.editName, - onFieldSubmitted: (value) { - _bloc.add( - const ChangeNameEvent(value: false)); - }, - decoration: const InputDecoration( - border: InputBorder.none, - fillColor: Colors.white10, - counterText: '', - ), - ), - ], - ), - ), - const SizedBox(width: 8), - Visibility( - visible: _bloc.editName != true, - replacement: const SizedBox(), - child: GestureDetector( - onTap: () { - _bloc.add(const ChangeNameEvent(value: true)); - }, - child: SvgPicture.asset( - Assets.editNameIconSettings, - color: ColorsManager.grayColor, - height: 20, - width: 20, - ), - ), - ) - ], - ), - ), - const SizedBox(height: 32), - // Device Management - Text('Device Management', style: sectionTitle), - DefaultContainer( - padding: EdgeInsets.zero, - child: Column( - children: [ - const SizedBox(height: 5), - Padding( - padding: const EdgeInsets.all(10.0), - child: InkWell( - onTap: () { - showSubSpaceDialog( - context, - communityUuid: device.community!.uuid!, - spaceUuid: device.spaces!.first.uuid!, - subSpaces: subSpaces, - selected: device.subspace!.uuid, - ); - }, - child: infoRow( - label: 'Sub-Space:', - value: deviceInfo.subspace.subspaceName, - valueColor: ColorsManager.textGray, - trailing: const Icon( - Icons.arrow_forward_ios, - size: 16, - color: ColorsManager.greyColor, - ), ), - ), - ), - const Divider(color: ColorsManager.dividerColor), - Padding( - padding: const EdgeInsets.all(10.0), - child: infoRow( - label: 'Virtual Address:', - value: deviceInfo.productUuid, - valueColor: ColorsManager.blackColor, - trailing: InkWell( - onTap: () { - Clipboard.setData( - ClipboardData( - text: device.productUuid ?? ''), - ); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'Virtual Address copied to clipboard'), - ), - ); - }, - child: const Icon( - Icons.copy, - size: 16, - color: ColorsManager.greyColor, - ), - ), - ), - ), - const Divider(color: ColorsManager.dividerColor), - Padding( - padding: const EdgeInsets.all(10.0), - child: infoRow( - label: 'MAC Address:', - valueColor: ColorsManager.blackColor, - value: deviceInfo.macAddress, - ), - ), - const SizedBox(height: 5), - ], - ), - ), - const SizedBox(height: 32), - - // Remove Device Button - SizedBox( - width: double.infinity, - child: InkWell( - onTap: () { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text( - 'Remove Device', - style: context.textTheme.bodyMedium!.copyWith( - fontWeight: FontWeight.w700, - color: ColorsManager.red, - ), - ), - content: Text( - 'Are you sure you want to remove this device?', - style: context.textTheme.bodyMedium!.copyWith( - color: ColorsManager.grayColor, - ), - ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text( - 'Cancel', + const SizedBox(width: 12), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Device Name:', style: context.textTheme.bodyMedium! .copyWith( color: ColorsManager.grayColor, ), ), - ), - TextButton( - onPressed: () { - _bloc.add(DeleteDeviceEvent()); - Navigator.of(context).pop(); - }, - child: Text( - 'Remove', - style: context.textTheme.bodyMedium! - .copyWith( - color: ColorsManager.red, + TextFormField( + maxLength: 30, + style: const TextStyle( + color: ColorsManager.blackColor, + ), + textAlign: TextAlign.start, + focusNode: _bloc.focusNode, + controller: _bloc.nameController, + enabled: _bloc.editName, + onFieldSubmitted: (value) { + _bloc.add(const ChangeNameEvent( + value: false)); + }, + decoration: const InputDecoration( + border: InputBorder.none, + fillColor: Colors.white10, + counterText: '', ), ), + ], + ), + ), + const SizedBox(width: 8), + Visibility( + visible: _bloc.editName != true, + replacement: const SizedBox(), + child: GestureDetector( + onTap: () { + _bloc.add( + const ChangeNameEvent(value: true)); + }, + child: SvgPicture.asset( + Assets.editNameIconSettings, + color: ColorsManager.grayColor, + height: 20, + width: 20, ), - ], - ); - }, - ); - }, - child: DefaultContainer( - padding: const EdgeInsets.all(25), - child: Center( - child: Text( - 'Remove Device', - style: context.textTheme.bodyMedium!.copyWith( - fontSize: 14, - color: ColorsManager.red, - fontWeight: FontWeight.w700), - ), + ), + ) + ], + ), + ), + const SizedBox(height: 32), + Text('Device Management', style: sectionTitle), + DeviceManagementContent( + device: device, + subSpaces: subSpaces.cast(), + deviceInfo: deviceInfo, + ), + const SizedBox(height: 32), + RemoveDeviceWidget(bloc: _bloc), + ], + ), + ), + if (state is DeviceSettingsLoading) + Positioned.fill( + child: Container( + color: Colors.black.withOpacity(0.1), + child: const Center( + child: CircularProgressIndicator( + color: ColorsManager.primaryColor, ), ), ), ), - ], - ), - ), - if (state is SettingLoadingState) - Positioned.fill( - child: Container( - color: Colors.black.withOpacity(0.1), - child: const Center( - child: CircularProgressIndicator( - color: ColorsManager.primaryColor, - ), - ), - ), - ), - ], + ], + ); + }, ); }, ), diff --git a/lib/pages/device_managment/device_setting/remove_device_widget.dart b/lib/pages/device_managment/device_setting/remove_device_widget.dart new file mode 100644 index 00000000..e65ee125 --- /dev/null +++ b/lib/pages/device_managment/device_setting/remove_device_widget.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/web_layout/default_container.dart'; + +class RemoveDeviceWidget extends StatelessWidget { + const RemoveDeviceWidget({ + super.key, + required SettingDeviceBloc bloc, + }) : _bloc = bloc; + + final SettingDeviceBloc _bloc; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.infinity, + child: InkWell( + onTap: () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text( + 'Remove Device', + style: context.textTheme.bodyMedium!.copyWith( + fontWeight: FontWeight.w700, + color: ColorsManager.red, + ), + ), + content: Text( + 'Are you sure you want to remove this device?', + style: context.textTheme.bodyMedium!.copyWith( + color: ColorsManager.grayColor, + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + 'Cancel', + style: context.textTheme.bodyMedium!.copyWith( + color: ColorsManager.grayColor, + ), + ), + ), + TextButton( + onPressed: () { + _bloc.add(SettingBlocDeleteDevice()); + Navigator.of(context).pop(); + }, + child: Text( + 'Remove', + style: context.textTheme.bodyMedium!.copyWith( + color: ColorsManager.red, + ), + ), + ), + ], + ); + }, + ); + }, + child: DefaultContainer( + padding: const EdgeInsets.all(25), + child: Center( + child: Text( + 'Remove Device', + style: context.textTheme.bodyMedium!.copyWith( + fontSize: 14, + color: ColorsManager.red, + fontWeight: FontWeight.w700), + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/device_setting/sub_space_dialog.dart b/lib/pages/device_managment/device_setting/sub_space_dialog.dart index f2fdfa3e..28350d4d 100644 --- a/lib/pages/device_managment/device_setting/sub_space_dialog.dart +++ b/lib/pages/device_managment/device_setting/sub_space_dialog.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart'; import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/subspace_dialog_buttons.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; @@ -77,71 +78,7 @@ class _SubSpaceDialogState extends State { }).toList(), const SizedBox(height: 12), const Divider(height: 1, thickness: 1), - SizedBox( - height: 50, - child: Row( - children: [ - Expanded( - child: Container( - decoration: const BoxDecoration( - border: Border( - right: BorderSide( - color: ColorsManager.dividerColor, - width: 0.5, - ), - ), - ), - child: TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text( - 'Cancel', - style: context.textTheme.bodyMedium?.copyWith( - color: ColorsManager.textGray, - fontSize: 18, - fontWeight: FontWeight.w500, - ), - ), - ), - ), - ), - Expanded( - child: Container( - decoration: const BoxDecoration( - border: Border( - left: BorderSide( - color: ColorsManager.dividerColor, - width: 0.5, - ), - ), - ), - child: TextButton( - onPressed: _selectedId == null - ? null - : () { - final selectedModel = widget.subSpaces - .firstWhere( - (space) => space.id == _selectedId, - orElse: () => SubSpaceModel( - id: null, name: '', devices: [])); - widget.onConfirmed(selectedModel); - Navigator.of(context).pop(); - }, - child: Text( - 'Confirm', - style: context.textTheme.bodyMedium?.copyWith( - color: ColorsManager.secondaryColor, - fontSize: 14, - fontWeight: FontWeight.w400, - ), - ), - ), - ), - ), - ], - ), - ), + SubSpaceDialogButtons(selectedId: _selectedId, widget: widget), ], ), ), @@ -164,8 +101,8 @@ void showSubSpaceDialog( selected: selected, onConfirmed: (selectedModel) { if (selectedModel != null) { - context.read().add( - AssignRoomEvent( + context.read().add( + SettingBlocAssignRoom( communityUuid: communityUuid, spaceUuid: spaceUuid, subSpaceUuid: selectedModel.id ?? '', diff --git a/lib/pages/device_managment/device_setting/subspace_dialog_buttons.dart b/lib/pages/device_managment/device_setting/subspace_dialog_buttons.dart new file mode 100644 index 00000000..80ece0cb --- /dev/null +++ b/lib/pages/device_managment/device_setting/subspace_dialog_buttons.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart'; +import 'package:syncrow_web/pages/device_managment/device_setting/sub_space_dialog.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class SubSpaceDialogButtons extends StatelessWidget { + const SubSpaceDialogButtons({ + super.key, + required String? selectedId, + required this.widget, + }) : _selectedId = selectedId; + + final String? _selectedId; + final SubSpaceDialog widget; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 50, + child: Row( + children: [ + Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.dividerColor, + width: 0.5, + ), + ), + ), + child: TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + 'Cancel', + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.textGray, + fontSize: 18, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + Expanded( + child: Container( + decoration: const BoxDecoration( + border: Border( + left: BorderSide( + color: ColorsManager.dividerColor, + width: 0.5, + ), + ), + ), + child: TextButton( + onPressed: _selectedId == null + ? null + : () { + final selectedModel = widget.subSpaces.firstWhere( + (space) => space.id == _selectedId, + orElse: () => + SubSpaceModel(id: null, name: '', devices: [])); + widget.onConfirmed(selectedModel); + Navigator.of(context).pop(); + }, + child: Text( + 'Confirm', + style: context.textTheme.bodyMedium?.copyWith( + color: ColorsManager.secondaryColor, + fontSize: 14, + fontWeight: FontWeight.w400, + ), + ), + ), + ), + ), + ], + ), + ); + } +} + +void showSubSpaceDialog( + BuildContext context, { + required List subSpaces, + String? selected, + required String communityUuid, + required String spaceUuid, +}) { + showDialog( + context: context, + barrierDismissible: true, + builder: (ctx) => SubSpaceDialog( + subSpaces: subSpaces, + selected: selected, + onConfirmed: (selectedModel) { + if (selectedModel != null) { + context.read().add( + SettingBlocAssignRoom( + communityUuid: communityUuid, + spaceUuid: spaceUuid, + subSpaceUuid: selectedModel.id ?? '', + ), + ); + } + }, + ), + ); +} diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 4d5200d4..6f60e34f 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -370,7 +370,8 @@ class DevicesManagementApi { }); return response; } - static Future resetDevise({ + + static Future resetDevice({ String? devicesUuid, }) async { final response = await HTTPService().post( @@ -385,7 +386,4 @@ class DevicesManagementApi { ); return response; } - - - } diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 31f3cebd..8f8d1d07 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -373,7 +373,6 @@ class CommunitySpaceManagementApi { required String spaceId, required String projectId}) async { try { - // Construct the API path final path = ApiEndpoints.listSubspace .replaceFirst('{communityUuid}', communityId) .replaceFirst('{spaceUuid}', spaceId) @@ -389,9 +388,6 @@ class CommunitySpaceManagementApi { for (var subspace in json['data']) { rooms.add(SubSpaceModel.fromJson(subspace)); } - } else { - debugPrint( - "Warning: 'data' key is missing or null in response JSON."); } return rooms; }, @@ -399,7 +395,7 @@ class CommunitySpaceManagementApi { return response; } catch (error, stackTrace) { - return []; // Return an empty list if there's an error + return []; } } From 2797dce63739d07a96cd2c1269e65e9ace72e729 Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 3 Jun 2025 16:55:24 +0300 Subject: [PATCH 6/6] Rename SettingBlocEvent to SettingEvent for consistency and clarity in event handling. --- .../bloc/setting_bloc_bloc.dart | 2 +- .../bloc/setting_bloc_event.dart | 25 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart b/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart index 92d94a8f..c996cf72 100644 --- a/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart +++ b/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart @@ -10,7 +10,7 @@ import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; part 'setting_bloc_event.dart'; -class SettingDeviceBloc extends Bloc { +class SettingDeviceBloc extends Bloc { final String deviceId; SettingDeviceBloc({ required this.deviceId, diff --git a/lib/pages/device_managment/device_setting/bloc/setting_bloc_event.dart b/lib/pages/device_managment/device_setting/bloc/setting_bloc_event.dart index ab62d8a0..7fb62ed9 100644 --- a/lib/pages/device_managment/device_setting/bloc/setting_bloc_event.dart +++ b/lib/pages/device_managment/device_setting/bloc/setting_bloc_event.dart @@ -1,12 +1,12 @@ part of 'setting_bloc_bloc.dart'; -abstract class SettingBlocEvent extends Equatable { - const SettingBlocEvent(); +abstract class DeviceSettingEvent extends Equatable { + const DeviceSettingEvent(); @override List get props => []; } -class SettingBlocSaveDeviceName extends SettingBlocEvent { +class SettingBlocSaveDeviceName extends DeviceSettingEvent { final String deviceName; final String deviceId; @@ -17,11 +17,11 @@ class SettingBlocSaveDeviceName extends SettingBlocEvent { List get props => [deviceName, deviceId]; } -class SettingBlocStartEditingName extends SettingBlocEvent {} +class SettingBlocStartEditingName extends DeviceSettingEvent {} -class SettingBlocCancelEditingName extends SettingBlocEvent {} +class SettingBlocCancelEditingName extends DeviceSettingEvent {} -class SettingBlocChangeEditingNameValue extends SettingBlocEvent { +class SettingBlocChangeEditingNameValue extends DeviceSettingEvent { final String value; const SettingBlocChangeEditingNameValue(this.value); @@ -29,7 +29,7 @@ class SettingBlocChangeEditingNameValue extends SettingBlocEvent { List get props => [value]; } -class SettingBlocFetchRooms extends SettingBlocEvent { +class SettingBlocFetchRooms extends DeviceSettingEvent { final String communityUuid; final String spaceUuid; @@ -40,20 +40,20 @@ class SettingBlocFetchRooms extends SettingBlocEvent { List get props => [communityUuid, spaceUuid]; } -class SettingBlocSaveName extends SettingBlocEvent { +class SettingBlocSaveName extends DeviceSettingEvent { const SettingBlocSaveName(); } -class DeviceSettingInitialInfo extends SettingBlocEvent {} +class DeviceSettingInitialInfo extends DeviceSettingEvent {} -class ChangeNameEvent extends SettingBlocEvent { +class ChangeNameEvent extends DeviceSettingEvent { final bool? value; const ChangeNameEvent({this.value}); } -class SettingBlocDeleteDevice extends SettingBlocEvent {} +class SettingBlocDeleteDevice extends DeviceSettingEvent {} -class SettingBlocAssignRoom extends SettingBlocEvent { +class SettingBlocAssignRoom extends DeviceSettingEvent { final String communityUuid; final String spaceUuid; final String subSpaceUuid; @@ -67,4 +67,3 @@ class SettingBlocAssignRoom extends SettingBlocEvent { @override List get props => [spaceUuid, communityUuid, subSpaceUuid]; } -