Refactor code by adding new API endpoint for assigning a device to a room and removing redundant code in device management settings.

This commit is contained in:
mohammad
2025-06-02 12:52:48 +03:00
parent a44d4231f1
commit cf5e05a888
11 changed files with 620 additions and 230 deletions

View File

@ -1,10 +1,12 @@
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/material.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/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/devices_mang_api.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
import 'package:syncrow_web/utils/snack_bar.dart'; import 'package:syncrow_web/utils/snack_bar.dart';
part 'setting_bloc_event.dart'; part 'setting_bloc_event.dart';
@ -17,7 +19,8 @@ class SettingBlocBloc extends Bloc<SettingBlocEvent, DeviceSettingsState> {
on<SaveNameEvent>(saveName); on<SaveNameEvent>(saveName);
on<ChangeNameEvent>(_changeName); on<ChangeNameEvent>(_changeName);
on<DeleteDeviceEvent>(deleteDevice); on<DeleteDeviceEvent>(deleteDevice);
//on<FetchRoomsEvent>(_fetchRoomsAndDevices); on<FetchRoomsEvent>(_fetchRooms);
on<AssignRoomEvent>(_assignDevice);
} }
static String deviceName = ''; static String deviceName = '';
final TextEditingController nameController = final TextEditingController nameController =
@ -51,7 +54,7 @@ class SettingBlocBloc extends Bloc<SettingBlocEvent, DeviceSettingsState> {
if (_validateInputs()) return; if (_validateInputs()) return;
try { try {
emit(SettingLoadingState()); emit(SettingLoadingState());
var response = await DevicesManagementApi.putDeviceName( await DevicesManagementApi.putDeviceName(
deviceId: deviceId, deviceName: nameController.text); deviceId: deviceId, deviceName: nameController.text);
add(DeviceSettingInitialInfo()); add(DeviceSettingInitialInfo());
CustomSnackBar.displaySnackBar('Save Successfully'); CustomSnackBar.displaySnackBar('Save Successfully');
@ -107,6 +110,7 @@ class SettingBlocBloc extends Bloc<SettingBlocEvent, DeviceSettingsState> {
emit(UpdateSettingState( emit(UpdateSettingState(
deviceName: nameController.text, deviceName: nameController.text,
deviceInfo: deviceInfo, deviceInfo: deviceInfo,
roomsList: roomsList,
)); ));
} catch (e) { } catch (e) {
emit(ErrorState(message: e.toString())); emit(ErrorState(message: e.toString()));
@ -127,19 +131,65 @@ class SettingBlocBloc extends Bloc<SettingBlocEvent, DeviceSettingsState> {
add(const SaveNameEvent()); add(const SaveNameEvent());
focusNode.unfocus(); focusNode.unfocus();
} }
emit(UpdateSettingState(deviceName: deviceName, deviceInfo: deviceInfo)); emit(UpdateSettingState(
deviceName: deviceName,
deviceInfo: deviceInfo,
roomsList: roomsList,
));
} }
void deleteDevice( void deleteDevice(
DeleteDeviceEvent event, Emitter<DeviceSettingsState> emit) async { DeleteDeviceEvent event, Emitter<DeviceSettingsState> emit) async {
try { try {
emit(SettingLoadingState()); emit(SettingLoadingState());
var response = await DevicesManagementApi.resetDevise(devicesUuid: deviceId);
await DevicesManagementApi.resetDevise(devicesUuid: deviceId);
CustomSnackBar.displaySnackBar('Reset Successfully'); CustomSnackBar.displaySnackBar('Reset Successfully');
emit(UpdateSettingState( emit(UpdateSettingState(
deviceName: nameController.text, deviceName: nameController.text,
deviceInfo: deviceInfo, deviceInfo: deviceInfo,
roomsList: roomsList,
));
} catch (e) {
emit(ErrorState(message: e.toString()));
return;
}
}
//=========================== assign device to room ==========================
void _assignDevice(
AssignRoomEvent event, Emitter<DeviceSettingsState> 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<DeviceSettingsState> 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) { } catch (e) {
emit(ErrorState(message: e.toString())); emit(ErrorState(message: e.toString()));

View File

@ -29,12 +29,13 @@ class ChangeEditingNameValue extends SettingBlocEvent {
} }
class FetchRoomsEvent 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 @override
List<Object?> get props => [deviceId]; List<Object?> get props => [communityUuid, spaceUuid];
} }
class SaveNameEvent extends SettingBlocEvent { class SaveNameEvent extends SettingBlocEvent {
@ -47,4 +48,20 @@ class ChangeNameEvent extends SettingBlocEvent {
final bool? value; final bool? value;
const ChangeNameEvent({this.value}); const ChangeNameEvent({this.value});
} }
class DeleteDeviceEvent extends SettingBlocEvent {} 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<Object?> get props => [spaceUuid, communityUuid, subSpaceUuid];
}

View File

@ -1,6 +1,6 @@
import 'package:equatable/equatable.dart'; 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/settings_model/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/sub_space_model.dart';
abstract class DeviceSettingsState extends Equatable { abstract class DeviceSettingsState extends Equatable {
const DeviceSettingsState(); const DeviceSettingsState();
@ -43,12 +43,16 @@ class SettingBlocInitial extends DeviceSettingsState {
class SettingLoadingState extends DeviceSettingsState {} class SettingLoadingState extends DeviceSettingsState {}
class UpdateSettingState extends DeviceSettingsState { class UpdateSettingState extends DeviceSettingsState {
final String deviceName; final String? deviceName;
final DeviceInfoModel? deviceInfo; final DeviceInfoModel? deviceInfo;
const UpdateSettingState({required this.deviceName, this.deviceInfo}); final List<SubSpaceModel> roomsList;
@override const UpdateSettingState({
List<Object?> get props => [deviceName, deviceInfo]; this.deviceName,
this.deviceInfo,
this.roomsList = const [],
});
List<Object?> get props => [deviceName, deviceInfo, roomsList];
} }
class ErrorState extends DeviceSettingsState { class ErrorState extends DeviceSettingsState {
@ -67,3 +71,5 @@ class FetchRoomsState extends DeviceSettingsState {
@override @override
List<Object?> get props => [roomsList]; List<Object?> get props => [roomsList];
} }
class SaveSelectionSuccessState extends DeviceSettingsState {}

View File

@ -0,0 +1,28 @@
import 'package:syncrow_web/utils/constants/assets.dart';
class DeviceIconTypeHelper {
static const Map<String, String> _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;
}
}

View File

@ -3,9 +3,12 @@ import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.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/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_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/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/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.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 { class DeviceSettingsPanel extends StatelessWidget {
final VoidCallback? onClose; final VoidCallback? onClose;
final AllDevicesModel device; final AllDevicesModel device;
const DeviceSettingsPanel({super.key, this.onClose, required this.device});
const DeviceSettingsPanel({this.onClose, super.key, required this.device});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final sectionTitle = context.theme.textTheme.titleMedium!.copyWith( final sectionTitle = context.theme.textTheme.titleMedium!.copyWith(
@ -24,7 +25,10 @@ class DeviceSettingsPanel extends StatelessWidget {
color: ColorsManager.grayColor, color: ColorsManager.grayColor,
); );
Widget infoRow( Widget infoRow(
{required String label, required String value, Widget? trailing}) { {required String label,
required String value,
Widget? trailing,
required Color? valueColor}) {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 6.0), padding: const EdgeInsets.symmetric(vertical: 6.0),
child: Row( child: Row(
@ -41,10 +45,8 @@ class DeviceSettingsPanel extends StatelessWidget {
child: Text( child: Text(
value, value,
textAlign: TextAlign.end, textAlign: TextAlign.end,
style: context.theme.textTheme.bodyMedium!.copyWith( style: context.theme.textTheme.bodyMedium!
fontSize: 14, .copyWith(fontSize: 14, color: valueColor),
color: ColorsManager.blackColor,
),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
@ -56,87 +58,114 @@ class DeviceSettingsPanel extends StatelessWidget {
} }
return BlocProvider( return BlocProvider(
create: (context) => SettingBlocBloc( create: (context) => SettingBlocBloc(
deviceId: device.uuid ?? '', deviceId: device.uuid ?? '',
)..add(DeviceSettingInitialInfo()), )
child: BlocBuilder<SettingBlocBloc, DeviceSettingsState>( ..add(DeviceSettingInitialInfo())
builder: (context, state) { ..add(FetchRoomsEvent(
communityUuid: device.community!.uuid!,
spaceUuid: device.spaces!.first.uuid!,
)),
child: BlocBuilder<SettingBlocBloc, DeviceSettingsState>(
builder: (context, state) {
final iconPath = final iconPath =
DeviceTypeHelper.getDeviceIconByTypeCode(device.productType); DeviceIconTypeHelper.getDeviceIconByTypeCode(device.productType);
final _bloc = BlocProvider.of<SettingBlocBloc>(context); final _bloc = BlocProvider.of<SettingBlocBloc>(context);
DeviceInfoModel deviceInfo = DeviceInfoModel.empty(); DeviceInfoModel deviceInfo = DeviceInfoModel.empty();
List<SubSpaceModel> subSpaces = [];
if (state is UpdateSettingState) { if (state is UpdateSettingState) {
deviceInfo = state.deviceInfo!; deviceInfo = state.deviceInfo!;
subSpaces = state.roomsList;
} }
return Container( return Stack(
width: MediaQuery.of(context).size.width * 0.3, children: [
color: ColorsManager.grey25, Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), width: MediaQuery.of(context).size.width * 0.3,
child: ListView( color: ColorsManager.grey25,
children: [ padding:
/// Header const EdgeInsets.symmetric(horizontal: 20, vertical: 24),
Row( child: ListView(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
IconButton( // Header
icon: SvgPicture.asset(Assets.closeSettingsIcon), Row(
onPressed: onClose ?? () => Navigator.of(context).pop(), mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: SvgPicture.asset(Assets.closeSettingsIcon),
onPressed:
onClose ?? () => Navigator.of(context).pop(),
),
],
), ),
], Row(
), mainAxisAlignment: MainAxisAlignment.center,
Row( children: [
mainAxisAlignment: MainAxisAlignment.center, Text(
children: [ 'Device Settings',
Text('Device Settings', style: context.theme.textTheme.titleLarge!.copyWith(
style: context.theme.textTheme.titleLarge!.copyWith(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: ColorsManager.primaryColor)), 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( const SizedBox(height: 24),
child: TextFormField( // Device Name + Icon
maxLength: 30, DefaultContainer(
style: const TextStyle( child: Row(
color: ColorsManager.blackColor, 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, const SizedBox(width: 12),
focusNode: _bloc.focusNode, Expanded(
controller: _bloc.nameController, child: Column(
enabled: _bloc.editName, mainAxisAlignment: MainAxisAlignment.start,
onFieldSubmitted: (value) { crossAxisAlignment: CrossAxisAlignment.start,
_bloc.add(const ChangeNameEvent(value: false)); children: [
}, Text(
decoration: const InputDecoration( 'Device Name:',
border: InputBorder.none, style: context.textTheme.bodyMedium!.copyWith(
fillColor: Colors.white10, color: ColorsManager.grayColor,
counterText: '', ),
),
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(
const SizedBox(width: 8), visible: _bloc.editName != true,
_bloc.editName == true replacement: const SizedBox(),
? const SizedBox() child: GestureDetector(
: GestureDetector(
onTap: () { onTap: () {
_bloc.add(const ChangeNameEvent(value: true)); _bloc.add(const ChangeNameEvent(value: true));
}, },
@ -147,121 +176,170 @@ class DeviceSettingsPanel extends StatelessWidget {
width: 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), const SizedBox(height: 32),
child: infoRow( // Device Management
label: 'Sub-Space:', Text('Device Management', style: sectionTitle),
value: device.subspace!.subspaceName, DefaultContainer(
trailing: const Icon( padding: EdgeInsets.zero,
Icons.arrow_forward_ios, child: Column(
size: 16, children: [
color: ColorsManager.greyColor, const SizedBox(height: 5),
), Padding(
), padding: const EdgeInsets.all(10.0),
), child: InkWell(
const Divider(color: ColorsManager.dividerColor), onTap: () {
Padding( showSubSpaceDialog(
padding: const EdgeInsets.all(10.0), context,
child: infoRow( communityUuid: device.community!.uuid!,
label: 'Virtual Address:', spaceUuid: device.spaces!.first.uuid!,
value: deviceInfo.productUuid, subSpaces: subSpaces,
trailing: InkWell( selected: device.subspace!.uuid,
onTap: () { );
Clipboard.setData( },
ClipboardData(text: device.productUuid ?? ''), child: infoRow(
); label: 'Sub-Space:',
ScaffoldMessenger.of(context).showSnackBar( value: deviceInfo.subspace.subspaceName,
const SnackBar( valueColor: ColorsManager.textGray,
content: Text( trailing: const Icon(
'Virtual Address copied to clipboard'), 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, child: DefaultContainer(
color: ColorsManager.greyColor, 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), ),
if (state is SettingLoadingState)
/// Remove Device Button Positioned.fill(
SizedBox( child: Container(
width: double.infinity, color: Colors.black.withOpacity(0.1),
child: InkWell( child: const Center(
onTap: () { child: CircularProgressIndicator(
_bloc.add(DeleteDeviceEvent()); color: ColorsManager.primaryColor,
},
child: const DefaultContainer(
padding: EdgeInsets.all(25),
child: Center(
child: Text(
'Remove Device',
style: TextStyle(color: ColorsManager.red),
),
), ),
), ),
), ),
) ),
], ],
),
); );
})); },
} ),
} );
class DeviceTypeHelper {
static const Map<String, String> _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;
} }
} }

View File

@ -55,31 +55,32 @@ class DeviceInfoModel {
factory DeviceInfoModel.fromJson(Map<String, dynamic> json) { factory DeviceInfoModel.fromJson(Map<String, dynamic> json) {
return DeviceInfoModel( return DeviceInfoModel(
activeTime: json['activeTime'], activeTime: json['activeTime'] as int? ?? 0,
category: json['category'], category: json['category'] ?? '',
categoryName: json['categoryName'], categoryName: json['categoryName'] as String? ?? '',
createTime: json['createTime'], createTime: json['createTime'] as int? ?? 0,
gatewayId: json['gatewayId'], gatewayId: json['gatewayId'] as String? ?? '',
icon: json['icon'], icon: json['icon'] as String? ?? '',
ip: json['ip'] ?? "", ip: json['ip'] as String? ?? '',
lat: json['lat'], lat: json['lat'] as String? ?? '',
localKey: json['localKey'], localKey: json['localKey'] as String? ?? '',
lon: json['lon'], lon: json['lon'] as String? ?? '',
model: json['model'], model: json['model'] as String? ?? '',
name: json['name'], name: json['name'] as String? ?? '',
nodeId: json['nodeId'], nodeId: json['nodeId'] as String? ?? '',
online: json['online'], online: json['online'] as bool? ?? false,
ownerId: json['ownerId'], ownerId: json['ownerId'] as String? ?? '',
productName: json['productName'], productName: json['productName'] as String? ?? '',
sub: json['sub'], sub: json['sub'] as bool? ?? false,
timeZone: json['timeZone'], timeZone: json['timeZone'] as String? ?? '',
updateTime: json['updateTime'], updateTime: json['updateTime'] as int? ?? 0,
uuid: json['uuid'], uuid: json['uuid'] as String? ?? '',
productUuid: json['productUuid'], productUuid: json['productUuid'] as String? ?? '',
productType: json['productType'], productType: json['productType'] as String? ?? '',
permissionType: json['permissionType'] ?? '', permissionType: json['permissionType'] as String? ?? '',
macAddress: json['macAddress'], macAddress: json['macAddress'] as String? ?? '',
subspace: Subspace.fromJson(json['subspace']), subspace:
Subspace.fromJson(json['subspace'] as Map<String, dynamic>? ?? {}),
); );
} }
@ -164,10 +165,10 @@ class Subspace {
factory Subspace.fromJson(Map<String, dynamic> json) { factory Subspace.fromJson(Map<String, dynamic> json) {
return Subspace( return Subspace(
uuid: json['uuid'], uuid: json['uuid'] as String? ?? '',
createdAt: json['createdAt'], createdAt: json['createdAt'] as String? ?? '',
updatedAt: json['updatedAt'], updatedAt: json['updatedAt'] as String? ?? '',
subspaceName: json['subspaceName'], subspaceName: json['subspaceName'] as String? ?? '',
); );
} }

View File

@ -27,9 +27,9 @@ class SubSpaceModel {
} }
} }
return SubSpaceModel( return SubSpaceModel(
id: json['uuid'], id: json['uuid'] as String? ?? '',
name: json['subspaceName'], name: json['subspaceName'] as String? ?? '',
devices: devices, devices: devices.isNotEmpty ? devices : null as List<DeviceModel>?,
); );
} }
} }

View File

@ -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<SubSpaceModel> 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<SubSpaceDialog> createState() => _SubSpaceDialogState();
}
class _SubSpaceDialogState extends State<SubSpaceDialog> {
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<String>(
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<SubSpaceModel> 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<SettingBlocBloc>().add(
AssignRoomEvent(
communityUuid: communityUuid,
spaceUuid: spaceUuid,
subSpaceUuid: selectedModel.id ?? '',
),
);
}
},
),
);
}

View File

@ -386,4 +386,6 @@ class DevicesManagementApi {
return response; return response;
} }
} }

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; 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/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/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart';
@ -369,7 +369,9 @@ class CommunitySpaceManagementApi {
} }
static Future<List<SubSpaceModel>> getSubSpaceBySpaceId( static Future<List<SubSpaceModel>> getSubSpaceBySpaceId(
String communityId, String spaceId, String projectId) async { {required String communityId,
required String spaceId,
required String projectId}) async {
try { try {
// Construct the API path // Construct the API path
final path = ApiEndpoints.listSubspace final path = ApiEndpoints.listSubspace
@ -399,4 +401,29 @@ class CommunitySpaceManagementApi {
return []; // Return an empty list if there's an error return []; // Return an empty list if there's an error
} }
} }
static Future<Map<String, dynamic>> 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;
}
}
} }

View File

@ -133,4 +133,7 @@ abstract class ApiEndpoints {
static const String deviceByUuid = '/devices/{deviceUuid}'; static const String deviceByUuid = '/devices/{deviceUuid}';
static const String resetDevice = '/factory/reset/{deviceUuid}'; static const String resetDevice = '/factory/reset/{deviceUuid}';
static const String assignDeviceToRoom =
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{spaceUuid}/subspaces/{subSpaceUuid}/devices/{deviceUuid}';
} }