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}'; }