From a44d4231f1bd391dc2c6568541cda162f50a2569 Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 29 May 2025 14:26:24 +0300 Subject: [PATCH 01/13] 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 02/13] 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 03/13] 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 04/13] 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 7cc46d464fdc47e38557a966d46a7621f5b104e9 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 3 Jun 2025 12:24:38 +0300 Subject: [PATCH 05/13] SP-1510-show date instead of index in occupancy chart. --- .../analytics/modules/occupancy/widgets/occupancy_chart.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart b/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart index 4ff85841..70087c46 100644 --- a/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart +++ b/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart @@ -16,7 +16,7 @@ class OccupancyChart extends StatelessWidget { Widget build(BuildContext context) { return BarChart( BarChartData( - maxY: 100.0, + maxY: 100.001, gridData: EnergyManagementChartsHelper.gridData().copyWith( checkToShowHorizontalLine: (value) => true, horizontalInterval: 20, @@ -134,7 +134,7 @@ class OccupancyChart extends StatelessWidget { alignment: AlignmentDirectional.bottomCenter, fit: BoxFit.scaleDown, child: Text( - (value + 1).toString(), + chartData[value.toInt()].date.day.toString(), style: context.textTheme.bodySmall?.copyWith( color: ColorsManager.greyColor, fontSize: 8, From 46feb0ea28d88fa8acb1183a57732c9ac3afddf6 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 3 Jun 2025 15:20:30 +0300 Subject: [PATCH 06/13] SP-1509 attatch space uuid to analytics device dropdown on energy management tab. --- .../analytics/models/analytics_device.dart | 27 +++++++++++-------- ...y_consumption_per_device_devices_list.dart | 2 +- .../power_clamp_energy_data_widget.dart | 2 +- .../widgets/analytics_sidebar_header.dart | 5 ++-- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/lib/pages/analytics/models/analytics_device.dart b/lib/pages/analytics/models/analytics_device.dart index 88f18ec5..eaac8b2b 100644 --- a/lib/pages/analytics/models/analytics_device.dart +++ b/lib/pages/analytics/models/analytics_device.dart @@ -23,17 +23,18 @@ class AnalyticsDevice { return AnalyticsDevice( uuid: json['uuid'] as String, name: json['name'] as String, - createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null, - updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null, + createdAt: json['createdAt'] != null + ? DateTime.parse(json['createdAt'] as String) + : null, + updatedAt: json['updatedAt'] != null + ? DateTime.parse(json['updatedAt'] as String) + : null, deviceTuyaUuid: json['deviceTuyaUuid'] as String?, isActive: json['isActive'] as bool?, - productDevice: json['productDevice'] != null - ? ProductDevice.fromJson(json['productDevice'] as Map) - : null, - spaceUuid: (json['spaces'] as List?) - ?.map((e) => e['uuid']) - .firstOrNull - ?.toString(), + productDevice: json['productDevice'] != null + ? ProductDevice.fromJson(json['productDevice'] as Map) + : null, + spaceUuid: json['spaceUuid'] as String?, ); } } @@ -60,8 +61,12 @@ class ProductDevice { factory ProductDevice.fromJson(Map json) { return ProductDevice( uuid: json['uuid'] as String?, - createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null, - updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null, + createdAt: json['createdAt'] != null + ? DateTime.parse(json['createdAt'] as String) + : null, + updatedAt: json['updatedAt'] != null + ? DateTime.parse(json['updatedAt'] as String) + : null, catName: json['catName'] as String?, prodId: json['prodId'] as String?, name: json['name'] as String?, diff --git a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_devices_list.dart b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_devices_list.dart index b7205424..f0cb5d64 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_devices_list.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_devices_list.dart @@ -41,7 +41,7 @@ class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget { .color; return Tooltip( - message: '${device.name}\n${device.productDevice?.uuid ?? ''}', + message: '${device.name}\n${device.spaceUuid ?? ''}', child: ChartInformativeCell(title: Text(device.name), color: deviceColor), ); } diff --git a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart b/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart index 4d04a36b..f95ff7d1 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart @@ -41,7 +41,7 @@ class PowerClampEnergyDataWidget extends StatelessWidget { AnalyticsErrorWidget(state.errorMessage), AnalyticsSidebarHeader( title: 'Smart Power Clamp', - showSpaceUuid: true, + showSpaceUuidInDevicesDropdown: true, onChanged: (device) { FetchEnergyManagementDataHelper.loadEnergyConsumptionByPhases( context, diff --git a/lib/pages/analytics/widgets/analytics_sidebar_header.dart b/lib/pages/analytics/widgets/analytics_sidebar_header.dart index 5e454ea4..5ff1d042 100644 --- a/lib/pages/analytics/widgets/analytics_sidebar_header.dart +++ b/lib/pages/analytics/widgets/analytics_sidebar_header.dart @@ -10,13 +10,13 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; class AnalyticsSidebarHeader extends StatelessWidget { const AnalyticsSidebarHeader({ required this.title, - this.showSpaceUuid = false, + this.showSpaceUuidInDevicesDropdown = false, this.onChanged, super.key, }); final String title; - final bool showSpaceUuid; + final bool showSpaceUuidInDevicesDropdown; final void Function(AnalyticsDevice device)? onChanged; @override @@ -49,6 +49,7 @@ class AnalyticsSidebarHeader extends StatelessWidget { alignment: AlignmentDirectional.centerEnd, fit: BoxFit.scaleDown, child: AnalyticsDeviceDropdown( + showSpaceUuid: showSpaceUuidInDevicesDropdown, onChanged: (value) { context.read().add( SelectAnalyticsDeviceEvent(value), From 0135b6711eec7fe17cfdb3d400411a64052288ee Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 3 Jun 2025 16:01:45 +0300 Subject: [PATCH 07/13] removed getting energy management data using communityUuid. --- .../helpers/fetch_energy_management_data_helper.dart | 7 ------- .../params/get_energy_consumption_per_device_param.dart | 3 --- .../params/get_total_energy_consumption_param.dart | 3 --- 3 files changed, 13 deletions(-) diff --git a/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart b/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart index a6fe4703..8de92098 100644 --- a/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart +++ b/lib/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart @@ -16,7 +16,6 @@ import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_ abstract final class FetchEnergyManagementDataHelper { const FetchEnergyManagementDataHelper._(); - // static const String _powerClampId = 'cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa'; static AnalyticsDevice? getSelectedDevice(BuildContext context) { return context.read().state.selectedDevice; } @@ -48,7 +47,6 @@ abstract final class FetchEnergyManagementDataHelper { loadTotalEnergyConsumption( context, selectedDate: selectedDate0, - communityId: communityId, spaceId: spaceId, ); final selectedDevice = getSelectedDevice(context); @@ -61,7 +59,6 @@ abstract final class FetchEnergyManagementDataHelper { } loadEnergyConsumptionPerDevice( context, - communityId: communityId, spaceId: spaceId, selectedDate: selectedDate0, ); @@ -84,12 +81,10 @@ abstract final class FetchEnergyManagementDataHelper { static void loadTotalEnergyConsumption( BuildContext context, { DateTime? selectedDate, - required String communityId, required String spaceId, }) { final param = GetTotalEnergyConsumptionParam( spaceId: spaceId, - communityId: communityId, monthDate: selectedDate, ); context.read().add( @@ -100,12 +95,10 @@ abstract final class FetchEnergyManagementDataHelper { static void loadEnergyConsumptionPerDevice( BuildContext context, { DateTime? selectedDate, - required String communityId, required String spaceId, }) { final param = GetEnergyConsumptionPerDeviceParam( spaceId: spaceId, - communityId: communityId, monthDate: selectedDate, ); context.read().add( diff --git a/lib/pages/analytics/params/get_energy_consumption_per_device_param.dart b/lib/pages/analytics/params/get_energy_consumption_per_device_param.dart index ba659ae7..79d0f2f4 100644 --- a/lib/pages/analytics/params/get_energy_consumption_per_device_param.dart +++ b/lib/pages/analytics/params/get_energy_consumption_per_device_param.dart @@ -2,18 +2,15 @@ class GetEnergyConsumptionPerDeviceParam { const GetEnergyConsumptionPerDeviceParam({ this.monthDate, this.spaceId, - this.communityId, }); final DateTime? monthDate; final String? spaceId; - final String? communityId; Map toJson() => { 'monthDate': '${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}', if (spaceId == null || spaceId == null) 'spaceUuid': spaceId, - 'communityUuid': communityId, 'groupByDevice': true, }; } diff --git a/lib/pages/analytics/params/get_total_energy_consumption_param.dart b/lib/pages/analytics/params/get_total_energy_consumption_param.dart index c47e5bfe..6428fd30 100644 --- a/lib/pages/analytics/params/get_total_energy_consumption_param.dart +++ b/lib/pages/analytics/params/get_total_energy_consumption_param.dart @@ -1,12 +1,10 @@ class GetTotalEnergyConsumptionParam { final DateTime? monthDate; final String? spaceId; - final String? communityId; const GetTotalEnergyConsumptionParam({ this.monthDate, this.spaceId, - this.communityId, }); Map toJson() { @@ -14,7 +12,6 @@ class GetTotalEnergyConsumptionParam { 'monthDate': '${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}', if (spaceId == null || spaceId == null) 'spaceUuid': spaceId, - 'communityUuid': communityId, 'groupByDevice': false, }; } From c2c58e6a7a39d2e1c3d9c15847d10ee534f55748 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 3 Jun 2025 16:17:14 +0300 Subject: [PATCH 08/13] SP-1658-the-analytics-chart-padding-is-not-aligned-with-the-design. --- ...ergy_consumption_per_device_chart_box.dart | 3 ++- .../total_energy_consumption_chart_box.dart | 3 ++- .../widgets/occupancy_chart_box.dart | 5 +++-- .../widgets/occupancy_heat_map_box.dart | 5 +++-- .../widgets/analytics_error_widget.dart | 19 +++++++++++-------- 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart index f22517d5..be5faf57 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart @@ -23,7 +23,6 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget { ), padding: const EdgeInsets.all(30), child: Column( - spacing: 20, crossAxisAlignment: CrossAxisAlignment.start, children: [ AnalyticsErrorWidget(state.errorMessage), @@ -52,7 +51,9 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget { ), ], ), + const SizedBox(height: 20), const Divider(height: 0), + const SizedBox(height: 20), Expanded( child: EnergyConsumptionPerDeviceChart(chartData: state.chartData), ), diff --git a/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart_box.dart b/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart_box.dart index 9e70e45e..e197c297 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart_box.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart_box.dart @@ -19,7 +19,6 @@ class TotalEnergyConsumptionChartBox extends StatelessWidget { ), padding: const EdgeInsets.all(30), child: Column( - spacing: 20, crossAxisAlignment: CrossAxisAlignment.start, children: [ AnalyticsErrorWidget(state.errorMessage), @@ -39,7 +38,9 @@ class TotalEnergyConsumptionChartBox extends StatelessWidget { const Spacer(flex: 4), ], ), + const SizedBox(height: 20), const Divider(), + const SizedBox(height: 20), TotalEnergyConsumptionChart(chartData: state.chartData), ], ), diff --git a/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart_box.dart b/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart_box.dart index ab1d1699..08f7223f 100644 --- a/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart_box.dart +++ b/lib/pages/analytics/modules/occupancy/widgets/occupancy_chart_box.dart @@ -22,7 +22,6 @@ class OccupancyChartBox extends StatelessWidget { padding: const EdgeInsets.all(30), decoration: containerWhiteDecoration, child: Column( - spacing: 20, mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -65,7 +64,9 @@ class OccupancyChartBox extends StatelessWidget { ), ], ), - const Divider(height: 0), + const SizedBox(height: 20), + const Divider(), + const SizedBox(height: 20), Expanded(child: OccupancyChart(chartData: state.chartData)), ], ), diff --git a/lib/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_box.dart b/lib/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_box.dart index cab9eab4..c3b537e0 100644 --- a/lib/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_box.dart +++ b/lib/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_box.dart @@ -22,7 +22,6 @@ class OccupancyHeatMapBox extends StatelessWidget { padding: const EdgeInsets.all(30), decoration: containerWhiteDecoration, child: Column( - spacing: 20, mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -66,7 +65,9 @@ class OccupancyHeatMapBox extends StatelessWidget { ), ], ), - const Divider(height: 0), + const SizedBox(height: 20), + const Divider(), + const SizedBox(height: 20), Expanded( child: OccupancyHeatMap( heatMapData: state.heatMapData.asMap().map( diff --git a/lib/pages/analytics/widgets/analytics_error_widget.dart b/lib/pages/analytics/widgets/analytics_error_widget.dart index 60167992..7c560da4 100644 --- a/lib/pages/analytics/widgets/analytics_error_widget.dart +++ b/lib/pages/analytics/widgets/analytics_error_widget.dart @@ -11,14 +11,17 @@ class AnalyticsErrorWidget extends StatelessWidget { Widget build(BuildContext context) { return Visibility( visible: errorMessage != null || (errorMessage?.isNotEmpty ?? false), - child: Text( - errorMessage ?? 'Something went wrong', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.red, - fontWeight: FontWeight.w400, - fontSize: 8, + child: Padding( + padding: const EdgeInsetsDirectional.only(bottom: 10), + child: Text( + errorMessage ?? 'Something went wrong', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.red, + fontWeight: FontWeight.w400, + fontSize: 8, + ), ), ), ); From e86c25c74ab3413eb784d48201eda52e8e660885 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 3 Jun 2025 16:18:57 +0300 Subject: [PATCH 09/13] includes min in all left titles charts. --- .../helpers/energy_management_charts_helper.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart b/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart index b1af85c8..2ed68e76 100644 --- a/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart +++ b/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart @@ -38,7 +38,7 @@ abstract final class EnergyManagementChartsHelper { sideTitles: SideTitles( showTitles: true, maxIncluded: false, - minIncluded: false, + minIncluded: true, interval: leftTitlesInterval, reservedSize: 110, getTitlesWidget: (value, meta) => Padding( From 906c2d0430393cd8e26f99ef9b500cbd7c2dd1a7 Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 3 Jun 2025 16:34:00 +0300 Subject: [PATCH 10/13] 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 11/13] 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]; } - From 8e8fdf0fc69a1ae5e7468fcf5895447d68746c0f Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 4 Jun 2025 09:26:56 +0300 Subject: [PATCH 12/13] Rename dialog buttons for clarity: 'Cancel' to 'Back' and 'Confirm' to 'Save' --- lib/pages/routines/helper/save_routine_helper.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/routines/helper/save_routine_helper.dart b/lib/pages/routines/helper/save_routine_helper.dart index e2066374..f8b52dab 100644 --- a/lib/pages/routines/helper/save_routine_helper.dart +++ b/lib/pages/routines/helper/save_routine_helper.dart @@ -109,12 +109,12 @@ class SaveRoutineHelper { spacing: 16, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - DialogFooterButton( - text: 'Cancel', + DialogFooterButton( + text: 'Back', onTap: () => Navigator.pop(context), ), DialogFooterButton( - text: 'Confirm', + text: 'Save', onTap: () { if (state.isAutomation) { if (state.isUpdate ?? false) { From 5f8eb9de0652223f130c14cd901a1e0eb82d415e Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 4 Jun 2025 10:32:31 +0300 Subject: [PATCH 13/13] Add settings button SVG and refactor settings icon implementation in dynamic table --- assets/icons/settings_button.svg | 24 +++++ lib/pages/common/custom_table.dart | 135 +++++++++++++++++++---------- lib/utils/constants/assets.dart | 108 +++++++++++++++-------- 3 files changed, 186 insertions(+), 81 deletions(-) create mode 100644 assets/icons/settings_button.svg diff --git a/assets/icons/settings_button.svg b/assets/icons/settings_button.svg new file mode 100644 index 00000000..43cad368 --- /dev/null +++ b/assets/icons/settings_button.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart index 0abe075b..f23daa45 100644 --- a/lib/pages/common/custom_table.dart +++ b/lib/pages/common/custom_table.dart @@ -162,31 +162,34 @@ class _DynamicTableState extends State { child: SingleChildScrollView( scrollDirection: Axis.horizontal, controller: _horizontalBodyScrollController, - child: SizedBox( - width: widget.size.width, - child: widget.isEmpty - ? _buildEmptyState() - : Column( - children: - List.generate(widget.data.length, (rowIndex) { - final row = widget.data[rowIndex]; - return Row( - children: [ - if (widget.withCheckBox) - _buildRowCheckbox( - rowIndex, widget.size.height * 0.08), - ...row.asMap().entries.map((entry) { - return _buildTableCell( - entry.value.toString(), - widget.size.height * 0.08, - rowIndex: rowIndex, - columnIndex: entry.key, - ); - }).toList(), - ], - ); - }), - ), + child: Container( + color: ColorsManager.whiteColors, + child: SizedBox( + width: widget.size.width, + child: widget.isEmpty + ? _buildEmptyState() + : Column( + children: List.generate(widget.data.length, + (rowIndex) { + final row = widget.data[rowIndex]; + return Row( + children: [ + if (widget.withCheckBox) + _buildRowCheckbox(rowIndex, + widget.size.height * 0.08), + ...row.asMap().entries.map((entry) { + return _buildTableCell( + entry.value.toString(), + widget.size.height * 0.08, + rowIndex: rowIndex, + columnIndex: entry.key, + ); + }).toList(), + ], + ); + }), + ), + ), ), ), ), @@ -211,7 +214,6 @@ class _DynamicTableState extends State { onChanged: widget.withSelectAll && widget.data.isNotEmpty ? _toggleSelectAll : null, - ), ); } @@ -282,7 +284,6 @@ 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( @@ -303,7 +304,6 @@ class _DynamicTableState extends State { required int rowIndex, required int columnIndex, }) { - bool isBatteryLevel = content.endsWith('%'); double? batteryLevel; @@ -313,9 +313,13 @@ class _DynamicTableState extends State { bool isSettingsColumn = widget.headers[columnIndex] == 'Settings'; if (isSettingsColumn) { - return _buildSettingsIcon(rowIndex, size); + return buildSettingsIcon( + width: 120, + height: 60, + iconSize: 40, + onTap: () => widget.onSettingsPressed?.call(rowIndex), + ); } - Color? statusColor; switch (content) { @@ -368,22 +372,63 @@ class _DynamicTableState extends State { ); } - Widget _buildSettingsIcon(int rowIndex, double size) { - return Container( - height: size, - width: 120, - padding: const EdgeInsets.all(5.0), - decoration: const BoxDecoration( - border: Border( - bottom: BorderSide(color: ColorsManager.boxDivider, width: 1.0), + Widget buildSettingsIcon( + {double width = 120, + double height = 60, + double iconSize = 40, + VoidCallback? onTap}) { + return Column( + children: [ + Container( + padding: const EdgeInsets.only(top: 10, bottom: 15, left: 10), + margin: const EdgeInsets.only(right: 15), + decoration: const BoxDecoration( + color: ColorsManager.whiteColors, + border: Border( + bottom: BorderSide( + color: ColorsManager.boxDivider, + width: 1.0, + ), + ), + ), + width: width, + child: Padding( + padding: const EdgeInsets.only( + right: 16.0, + left: 17.0, + ), + child: Container( + width: 50, + decoration: BoxDecoration( + color: const Color(0xFFF7F8FA), + borderRadius: BorderRadius.circular(height / 2), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.17), + blurRadius: 14, + offset: const Offset(0, 4), + ), + ], + ), + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: SvgPicture.asset( + Assets.settings, // ضع المسار الصحيح هنا + width: 40, + height: 22, + color: ColorsManager + .primaryColor, // نفس لون الأيقونة في الصورة + ), + ), + ), + ), + ), + ), ), - color: Colors.white, - ), - alignment: Alignment.center, - child: IconButton( - icon: SvgPicture.asset(Assets.settings), - onPressed: () => widget.onSettingsPressed?.call(rowIndex), - ), + ], ); } } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 5eb0eb05..dfc0b394 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -14,7 +14,8 @@ class Assets { static const String rightLine = "assets/images/right_line.png"; static const String google = "assets/images/google.svg"; static const String facebook = "assets/images/facebook.svg"; - static const String invisiblePassword = "assets/images/Password_invisible.svg"; + static const String invisiblePassword = + "assets/images/Password_invisible.svg"; static const String visiblePassword = "assets/images/password_visible.svg"; static const String accessIcon = "assets/images/access_icon.svg"; static const String spaseManagementIcon = @@ -33,7 +34,8 @@ class Assets { static const String emptyTable = "assets/images/empty_table.svg"; // General assets - static const String motionlessDetection = "assets/icons/motionless_detection.svg"; + static const String motionlessDetection = + "assets/icons/motionless_detection.svg"; static const String acHeating = "assets/icons/ac_heating.svg"; static const String acPowerOff = "assets/icons/ac_power_off.svg"; static const String acFanMiddle = "assets/icons/ac_fan_middle.svg"; @@ -70,19 +72,22 @@ class Assets { "assets/icons/automation_functions/temp_password_unlock.svg"; static const String doorlockNormalOpen = "assets/icons/automation_functions/doorlock_normal_open.svg"; - static const String doorbell = "assets/icons/automation_functions/doorbell.svg"; + static const String doorbell = + "assets/icons/automation_functions/doorbell.svg"; static const String remoteUnlockViaApp = "assets/icons/automation_functions/remote_unlock_via_app.svg"; static const String doubleLock = "assets/icons/automation_functions/double_lock.svg"; static const String selfTestResult = "assets/icons/automation_functions/self_test_result.svg"; - static const String lockAlarm = "assets/icons/automation_functions/lock_alarm.svg"; + static const String lockAlarm = + "assets/icons/automation_functions/lock_alarm.svg"; static const String presenceState = "assets/icons/automation_functions/presence_state.svg"; static const String currentTemp = "assets/icons/automation_functions/current_temp.svg"; - static const String presence = "assets/icons/automation_functions/presence.svg"; + static const String presence = + "assets/icons/automation_functions/presence.svg"; static const String residualElectricity = "assets/icons/automation_functions/residual_electricity.svg"; static const String hijackAlarm = @@ -99,12 +104,15 @@ class Assets { // Presence Sensor Assets static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg"; - static const String sensorPresenceIcon = "assets/icons/sensor_presence_ic.svg"; + static const String sensorPresenceIcon = + "assets/icons/sensor_presence_ic.svg"; static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg"; static const String illuminanceRecordIcon = "assets/icons/illuminance_record_ic.svg"; - static const String presenceRecordIcon = "assets/icons/presence_record_ic.svg"; - static const String helpDescriptionIcon = "assets/icons/help_description_ic.svg"; + static const String presenceRecordIcon = + "assets/icons/presence_record_ic.svg"; + static const String helpDescriptionIcon = + "assets/icons/help_description_ic.svg"; static const String lightPulp = "assets/icons/light_pulb.svg"; static const String acDevice = "assets/icons/ac_device.svg"; @@ -154,10 +162,12 @@ class Assets { static const String unit = 'assets/icons/unit_icon.svg'; static const String villa = 'assets/icons/villa_icon.svg'; static const String iconEdit = 'assets/icons/icon_edit_icon.svg'; - static const String textFieldSearch = 'assets/icons/textfield_search_icon.svg'; + static const String textFieldSearch = + 'assets/icons/textfield_search_icon.svg'; static const String roundedAddIcon = 'assets/icons/rounded_add_icon.svg'; static const String addIcon = 'assets/icons/add_icon.svg'; - static const String smartThermostatIcon = 'assets/icons/smart_thermostat_icon.svg'; + static const String smartThermostatIcon = + 'assets/icons/smart_thermostat_icon.svg'; static const String smartLightIcon = 'assets/icons/smart_light_icon.svg'; static const String presenceSensor = 'assets/icons/presence_sensor.svg'; static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg'; @@ -205,7 +215,8 @@ class Assets { //assets/icons/water_leak_normal.svg static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg'; //assets/icons/water_leak_detected.svg - static const String waterLeakDetected = 'assets/icons/water_leak_detected.svg'; + static const String waterLeakDetected = + 'assets/icons/water_leak_detected.svg'; //assets/icons/automation_records.svg static const String automationRecords = 'assets/icons/automation_records.svg'; @@ -276,13 +287,16 @@ class Assets { "assets/icons/functions_icons/sensitivity.svg"; static const String assetsSensitivityOperationIcon = "assets/icons/functions_icons/sesitivity_operation_icon.svg"; - static const String assetsAcPower = "assets/icons/functions_icons/ac_power.svg"; + static const String assetsAcPower = + "assets/icons/functions_icons/ac_power.svg"; static const String assetsAcPowerOFF = "assets/icons/functions_icons/ac_power_off.svg"; static const String assetsChildLock = "assets/icons/functions_icons/child_lock.svg"; - static const String assetsFreezing = "assets/icons/functions_icons/freezing.svg"; - static const String assetsFanSpeed = "assets/icons/functions_icons/fan_speed.svg"; + static const String assetsFreezing = + "assets/icons/functions_icons/freezing.svg"; + static const String assetsFanSpeed = + "assets/icons/functions_icons/fan_speed.svg"; static const String assetsAcCooling = "assets/icons/functions_icons/ac_cooling.svg"; static const String assetsAcHeating = @@ -291,7 +305,8 @@ class Assets { "assets/icons/functions_icons/celsius_degrees.svg"; static const String assetsTempreture = "assets/icons/functions_icons/tempreture.svg"; - static const String assetsAcFanLow = "assets/icons/functions_icons/ac_fan_low.svg"; + static const String assetsAcFanLow = + "assets/icons/functions_icons/ac_fan_low.svg"; static const String assetsAcFanMiddle = "assets/icons/functions_icons/ac_fan_middle.svg"; static const String assetsAcFanHigh = @@ -310,7 +325,8 @@ class Assets { "assets/icons/functions_icons/far_detection.svg"; static const String assetsFarDetectionFunction = "assets/icons/functions_icons/far_detection_function.svg"; - static const String assetsIndicator = "assets/icons/functions_icons/indicator.svg"; + static const String assetsIndicator = + "assets/icons/functions_icons/indicator.svg"; static const String assetsMotionDetection = "assets/icons/functions_icons/motion_detection.svg"; static const String assetsMotionlessDetection = @@ -323,7 +339,8 @@ class Assets { "assets/icons/functions_icons/master_state.svg"; static const String assetsSwitchAlarmSound = "assets/icons/functions_icons/switch_alarm_sound.svg"; - static const String assetsResetOff = "assets/icons/functions_icons/reset_off.svg"; + static const String assetsResetOff = + "assets/icons/functions_icons/reset_off.svg"; // Assets for automation_functions static const String assetsCardUnlock = @@ -367,12 +384,14 @@ class Assets { static const String activeUser = 'assets/icons/active_user.svg'; static const String deActiveUser = 'assets/icons/deactive_user.svg'; static const String invitedIcon = 'assets/icons/invited_icon.svg'; - static const String rectangleCheckBox = 'assets/icons/rectangle_check_box.png'; + static const String rectangleCheckBox = + 'assets/icons/rectangle_check_box.png'; static const String CheckBoxChecked = 'assets/icons/box_checked.png'; static const String emptyBox = 'assets/icons/empty_box.png'; static const String completeProcessIcon = 'assets/icons/compleate_process_icon.svg'; - static const String currentProcessIcon = 'assets/icons/current_process_icon.svg'; + static const String currentProcessIcon = + 'assets/icons/current_process_icon.svg'; static const String uncomplete_ProcessIcon = 'assets/icons/uncompleate_process_icon.svg'; static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg'; @@ -393,9 +412,11 @@ class Assets { static const String successIcon = 'assets/icons/success_icon.svg'; static const String spaceLocationIcon = 'assets/icons/spaseLocationIcon.svg'; static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.png'; - static const String scenesPlayIconCheck = 'assets/icons/scenesPlayIconCheck.png'; + static const String scenesPlayIconCheck = + 'assets/icons/scenesPlayIconCheck.png'; static const String presenceStateIcon = 'assets/icons/presence_state.svg'; - static const String currentDistanceIcon = 'assets/icons/current_distance_icon.svg'; + static const String currentDistanceIcon = + 'assets/icons/current_distance_icon.svg'; static const String farDetectionIcon = 'assets/icons/far_detection_icon.svg'; static const String motionDetectionSensitivityIcon = @@ -418,29 +439,44 @@ class Assets { static const String cpsMode4 = 'assets/icons/cps_mode4.svg'; static const String closeToMotion = 'assets/icons/close_to_motion.svg'; static const String farAwayMotion = 'assets/icons/far_away_motion.svg'; - static const String communicationFault = 'assets/icons/communication_fault.svg'; + static const String communicationFault = + 'assets/icons/communication_fault.svg'; static const String radarFault = 'assets/icons/radar_fault.svg'; - static const String selfTestingSuccess = 'assets/icons/self_testing_success.svg'; - static const String selfTestingFailure = 'assets/icons/self_testing_failure.svg'; - static const String selfTestingTimeout = 'assets/icons/self_testing_timeout.svg'; + static const String selfTestingSuccess = + 'assets/icons/self_testing_success.svg'; + static const String selfTestingFailure = + 'assets/icons/self_testing_failure.svg'; + static const String selfTestingTimeout = + 'assets/icons/self_testing_timeout.svg'; static const String movingSpeed = 'assets/icons/moving_speed.svg'; static const String boundary = 'assets/icons/boundary.svg'; static const String motionMeter = 'assets/icons/motion_meter.svg'; - static const String spatialStaticValue = 'assets/icons/spatial_static_value.svg'; - static const String spatialMotionValue = 'assets/icons/spatial_motion_value.svg'; + static const String spatialStaticValue = + 'assets/icons/spatial_static_value.svg'; + static const String spatialMotionValue = + 'assets/icons/spatial_motion_value.svg'; static const String presenceJudgementThrshold = 'assets/icons/presence_judgement_threshold.svg'; static const String spaceType = 'assets/icons/space_type.svg'; static const String sportsPara = 'assets/icons/sports_para.svg'; - static const String sensitivityFeature1 = 'assets/icons/sensitivity_feature_1.svg'; - static const String sensitivityFeature2 = 'assets/icons/sensitivity_feature_2.svg'; - static const String sensitivityFeature3 = 'assets/icons/sensitivity_feature_3.svg'; - static const String sensitivityFeature4 = 'assets/icons/sensitivity_feature_4.svg'; - static const String sensitivityFeature5 = 'assets/icons/sensitivity_feature_5.svg'; - static const String sensitivityFeature6 = 'assets/icons/sensitivity_feature_6.svg'; - static const String sensitivityFeature7 = 'assets/icons/sensitivity_feature_7.svg'; - static const String sensitivityFeature8 = 'assets/icons/sensitivity_feature_8.svg'; - static const String sensitivityFeature9 = 'assets/icons/sensitivity_feature_9.svg'; + static const String sensitivityFeature1 = + 'assets/icons/sensitivity_feature_1.svg'; + static const String sensitivityFeature2 = + 'assets/icons/sensitivity_feature_2.svg'; + static const String sensitivityFeature3 = + 'assets/icons/sensitivity_feature_3.svg'; + static const String sensitivityFeature4 = + 'assets/icons/sensitivity_feature_4.svg'; + static const String sensitivityFeature5 = + 'assets/icons/sensitivity_feature_5.svg'; + static const String sensitivityFeature6 = + 'assets/icons/sensitivity_feature_6.svg'; + static const String sensitivityFeature7 = + 'assets/icons/sensitivity_feature_7.svg'; + static const String sensitivityFeature8 = + 'assets/icons/sensitivity_feature_8.svg'; + static const String sensitivityFeature9 = + 'assets/icons/sensitivity_feature_9.svg'; static const String deviceTagIcon = 'assets/icons/device_tag_ic.svg'; static const String targetConfirmTimeIcon = 'assets/icons/target_confirm_time_icon.svg';