Refactor device management and space management APIs, update event and state classes, and add RemoveDeviceWidget for device removal functionality.

This commit is contained in:
mohammad
2025-06-03 16:34:00 +03:00
parent cabd37a08a
commit 906c2d0430
10 changed files with 549 additions and 479 deletions

View File

@ -10,26 +10,24 @@ 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';
class SettingBlocBloc extends Bloc<SettingBlocEvent, DeviceSettingsState> { class SettingDeviceBloc extends Bloc<SettingBlocEvent, DeviceSettingsState> {
final String deviceId; final String deviceId;
SettingBlocBloc({ SettingDeviceBloc({
required this.deviceId, required this.deviceId,
}) : super(const SettingBlocInitial()) { }) : super(const DeviceSettingsInitial()) {
on<DeviceSettingInitialInfo>(fetchDeviceInfo); on<DeviceSettingInitialInfo>(_fetchDeviceInfo);
on<SaveNameEvent>(saveName); on<SettingBlocSaveName>(_saveName);
on<ChangeNameEvent>(_changeName); on<ChangeNameEvent>(_changeName);
on<DeleteDeviceEvent>(deleteDevice); on<SettingBlocDeleteDevice>(_deleteDevice);
on<FetchRoomsEvent>(_fetchRooms); on<SettingBlocFetchRooms>(_fetchRooms);
on<AssignRoomEvent>(_assignDevice); on<SettingBlocAssignRoom>(_onAssignDevice);
} }
static String deviceName = ''; final TextEditingController nameController = TextEditingController();
final TextEditingController nameController =
TextEditingController(text: deviceName);
List<SubSpaceModel> roomsList = []; List<SubSpaceModel> roomsList = [];
bool isEditingName = false; bool isEditingName = false;
bool _validateInputs() { bool _validateInputs() {
final nameError = fullNameValidator(nameController.text); final nameError = _fullNameValidator(nameController.text);
if (nameError != null) { if (nameError != null) {
CustomSnackBar.displaySnackBar(nameError); CustomSnackBar.displaySnackBar(nameError);
return true; return true;
@ -37,7 +35,7 @@ class SettingBlocBloc extends Bloc<SettingBlocEvent, DeviceSettingsState> {
return false; return false;
} }
String? fullNameValidator(String? value) { String? _fullNameValidator(String? value) {
if (value == null) return 'name is required'; if (value == null) return 'name is required';
final withoutExtraSpaces = value.replaceAll(RegExp(r"\s+"), ' ').trim(); final withoutExtraSpaces = value.replaceAll(RegExp(r"\s+"), ' ').trim();
if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) { if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) {
@ -49,69 +47,35 @@ class SettingBlocBloc extends Bloc<SettingBlocEvent, DeviceSettingsState> {
return null; return null;
} }
Future<void> saveName( Future<void> _saveName(
SaveNameEvent event, Emitter<DeviceSettingsState> emit) async { SettingBlocSaveName event, Emitter<DeviceSettingsState> emit) async {
if (_validateInputs()) return; if (_validateInputs()) return;
try { try {
emit(SettingLoadingState()); emit(DeviceSettingsLoading());
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');
emit(UpdateSettingState(deviceName: nameController.text)); emit(DeviceSettingsUpdate(deviceName: nameController.text));
} catch (e) { } catch (e) {
emit(ErrorState(message: e.toString())); emit(DeviceSettingsError(message: e.toString()));
} }
} }
DeviceInfoModel deviceInfo = DeviceInfoModel( Future<void> _fetchDeviceInfo(
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<DeviceSettingsState> emit) async { DeviceSettingInitialInfo event, Emitter<DeviceSettingsState> emit) async {
try { try {
emit(SettingLoadingState()); emit(DeviceSettingsLoading());
var response = await DevicesManagementApi.getDeviceInfo(deviceId); var response = await DevicesManagementApi.getDeviceInfo(deviceId);
deviceInfo = DeviceInfoModel.fromJson(response); DeviceInfoModel deviceInfo = DeviceInfoModel.fromJson(response);
nameController.text = deviceInfo.name; nameController.text = deviceInfo.name;
emit(DeviceSettingsUpdate(
emit(UpdateSettingState(
deviceName: nameController.text, deviceName: nameController.text,
deviceInfo: deviceInfo, deviceInfo: deviceInfo,
roomsList: roomsList, roomsList: roomsList,
)); ));
} catch (e) { } catch (e) {
emit(ErrorState(message: e.toString())); emit(DeviceSettingsError(message: e.toString()));
} }
} }
@ -119,46 +83,50 @@ class SettingBlocBloc extends Bloc<SettingBlocEvent, DeviceSettingsState> {
final FocusNode focusNode = FocusNode(); final FocusNode focusNode = FocusNode();
void _changeName(ChangeNameEvent event, Emitter<DeviceSettingsState> emit) { void _changeName(ChangeNameEvent event, Emitter<DeviceSettingsState> emit) {
emit(SettingLoadingState()); emit(DeviceSettingsInitial(
deviceName: nameController.text,
deviceId: deviceId,
isEditingName: event.value ?? false,
editingNameValue: event.value?.toString() ?? '',
deviceInfo: state.deviceInfo,
));
editName = event.value!; editName = event.value!;
if (editName) { if (editName) {
Future.delayed(const Duration(milliseconds: 500), () { Future.delayed(const Duration(milliseconds: 500), () {
focusNode.requestFocus(); focusNode.requestFocus();
}); });
} else { } else {
add(const SaveNameEvent()); add(const SettingBlocSaveName());
focusNode.unfocus(); focusNode.unfocus();
} }
emit(UpdateSettingState( emit(DeviceSettingsUpdate(
deviceName: deviceName, deviceName: nameController.text,
deviceInfo: deviceInfo, deviceInfo: state.deviceInfo,
roomsList: roomsList, roomsList: roomsList,
)); ));
} }
void deleteDevice( void _deleteDevice(
DeleteDeviceEvent event, Emitter<DeviceSettingsState> emit) async { SettingBlocDeleteDevice event, Emitter<DeviceSettingsState> emit) async {
try { try {
emit(SettingLoadingState()); emit(DeviceSettingsLoading());
await DevicesManagementApi.resetDevise(devicesUuid: deviceId); await DevicesManagementApi.resetDevice(devicesUuid: deviceId);
CustomSnackBar.displaySnackBar('Reset Successfully'); CustomSnackBar.displaySnackBar('Reset Successfully');
emit(UpdateSettingState( emit(DeviceSettingsUpdate(
deviceName: nameController.text, deviceName: nameController.text,
deviceInfo: deviceInfo, deviceInfo: state.deviceInfo,
roomsList: roomsList, roomsList: roomsList,
)); ));
} catch (e) { } catch (e) {
emit(ErrorState(message: e.toString())); emit(DeviceSettingsError(message: e.toString()));
return; return;
} }
} }
//=========================== assign device to room ========================== void _onAssignDevice(
SettingBlocAssignRoom event, Emitter<DeviceSettingsState> emit) async {
void _assignDevice(
AssignRoomEvent event, Emitter<DeviceSettingsState> emit) async {
try { try {
emit(SettingLoadingState()); emit(DeviceSettingsLoading());
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
await CommunitySpaceManagementApi.assignDeviceToRoom( await CommunitySpaceManagementApi.assignDeviceToRoom(
communityId: event.communityUuid, communityId: event.communityUuid,
@ -168,29 +136,29 @@ class SettingBlocBloc extends Bloc<SettingBlocEvent, DeviceSettingsState> {
projectId: projectUuid); projectId: projectUuid);
add(DeviceSettingInitialInfo()); add(DeviceSettingInitialInfo());
CustomSnackBar.displaySnackBar('Save Successfully'); CustomSnackBar.displaySnackBar('Save Successfully');
emit(SaveSelectionSuccessState()); emit(DeviceSettingsSaveSelectionSuccess());
} catch (e) { } catch (e) {
emit(ErrorState(message: e.toString())); emit(DeviceSettingsError(message: e.toString()));
return; return;
} }
} }
void _fetchRooms( void _fetchRooms(
FetchRoomsEvent event, Emitter<DeviceSettingsState> emit) async { SettingBlocFetchRooms event, Emitter<DeviceSettingsState> emit) async {
try { try {
emit(SettingLoadingState()); emit(DeviceSettingsLoading());
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
roomsList = await CommunitySpaceManagementApi.getSubSpaceBySpaceId( roomsList = await CommunitySpaceManagementApi.getSubSpaceBySpaceId(
communityId: event.communityUuid, communityId: event.communityUuid,
spaceId: event.spaceUuid, spaceId: event.spaceUuid,
projectId: projectUuid); projectId: projectUuid);
emit(UpdateSettingState( emit(DeviceSettingsUpdate(
deviceName: nameController.text, deviceName: nameController.text,
deviceInfo: deviceInfo, deviceInfo: state.deviceInfo,
roomsList: roomsList, roomsList: roomsList,
)); ));
} catch (e) { } catch (e) {
emit(ErrorState(message: e.toString())); emit(DeviceSettingsError(message: e.toString()));
return; return;
} }
} }

View File

@ -6,40 +6,42 @@ abstract class SettingBlocEvent extends Equatable {
List<Object?> get props => []; List<Object?> get props => [];
} }
class SaveDeviceName extends SettingBlocEvent { class SettingBlocSaveDeviceName extends SettingBlocEvent {
final String deviceName; final String deviceName;
final String deviceId; final String deviceId;
const SaveDeviceName({required this.deviceName, required this.deviceId}); const SettingBlocSaveDeviceName(
{required this.deviceName, required this.deviceId});
@override @override
List<Object?> get props => [deviceName, deviceId]; List<Object?> 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; final String value;
const ChangeEditingNameValue(this.value); const SettingBlocChangeEditingNameValue(this.value);
@override @override
List<Object?> get props => [value]; List<Object?> get props => [value];
} }
class FetchRoomsEvent extends SettingBlocEvent { class SettingBlocFetchRooms extends SettingBlocEvent {
final String communityUuid; final String communityUuid;
final String spaceUuid; final String spaceUuid;
const FetchRoomsEvent({required this.communityUuid, required this.spaceUuid}); const SettingBlocFetchRooms(
{required this.communityUuid, required this.spaceUuid});
@override @override
List<Object?> get props => [communityUuid, spaceUuid]; List<Object?> get props => [communityUuid, spaceUuid];
} }
class SaveNameEvent extends SettingBlocEvent { class SettingBlocSaveName extends SettingBlocEvent {
const SaveNameEvent(); const SettingBlocSaveName();
} }
class DeviceSettingInitialInfo extends SettingBlocEvent {} class DeviceSettingInitialInfo extends SettingBlocEvent {}
@ -49,14 +51,14 @@ class ChangeNameEvent extends SettingBlocEvent {
const ChangeNameEvent({this.value}); 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 communityUuid;
final String spaceUuid; final String spaceUuid;
final String subSpaceUuid; final String subSpaceUuid;
const AssignRoomEvent({ const SettingBlocAssignRoom({
required this.communityUuid, required this.communityUuid,
required this.spaceUuid, required this.spaceUuid,
required this.subSpaceUuid, required this.subSpaceUuid,
@ -65,3 +67,4 @@ class AssignRoomEvent extends SettingBlocEvent {
@override @override
List<Object?> get props => [spaceUuid, communityUuid, subSpaceUuid]; List<Object?> get props => [spaceUuid, communityUuid, subSpaceUuid];
} }

View File

@ -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'; 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({this.deviceInfo});
final DeviceInfoModel? deviceInfo;
@override @override
List<Object?> get props => []; List<Object?> get props => [deviceInfo];
} }
class SettingBlocInitial extends DeviceSettingsState { class DeviceSettingsInitial extends DeviceSettingsState {
final String deviceName; final String deviceName;
final String deviceId; final String deviceId;
final bool isEditingName; final bool isEditingName;
final String editingNameValue; final String editingNameValue;
const SettingBlocInitial({ const DeviceSettingsInitial({
this.deviceName = '', this.deviceName = '',
this.deviceId = '', this.deviceId = '',
this.isEditingName = false, this.isEditingName = false,
this.editingNameValue = '', this.editingNameValue = '',
super.deviceInfo,
}); });
SettingBlocInitial copyWith({ DeviceSettingsInitial copyWith({
String? deviceName, String? deviceName,
String? deviceId, String? deviceId,
bool? isEditingName, bool? isEditingName,
String? editingNameValue, String? editingNameValue,
}) => }) =>
SettingBlocInitial( DeviceSettingsInitial(
deviceName: deviceName ?? this.deviceName, deviceName: deviceName ?? this.deviceName,
deviceId: deviceId ?? this.deviceId, deviceId: deviceId ?? this.deviceId,
isEditingName: isEditingName ?? this.isEditingName, isEditingName: isEditingName ?? this.isEditingName,
@ -40,36 +43,39 @@ class SettingBlocInitial extends DeviceSettingsState {
[deviceName, deviceId, isEditingName, editingNameValue]; [deviceName, deviceId, isEditingName, editingNameValue];
} }
class SettingLoadingState extends DeviceSettingsState {} class DeviceSettingsLoading extends DeviceSettingsState {}
class UpdateSettingState extends DeviceSettingsState { class DeviceSettingsUpdate extends DeviceSettingsState {
final String? deviceName; final String? deviceName;
final DeviceInfoModel? deviceInfo;
final List<SubSpaceModel> roomsList; final List<SubSpaceModel> roomsList;
const UpdateSettingState({ const DeviceSettingsUpdate({
this.deviceName, this.deviceName,
this.deviceInfo, super.deviceInfo,
this.roomsList = const [], this.roomsList = const [],
}); });
@override
List<Object?> get props => [deviceName, deviceInfo, roomsList]; List<Object?> get props => [deviceName, deviceInfo, roomsList];
} }
class ErrorState extends DeviceSettingsState { class DeviceSettingsError extends DeviceSettingsState {
final String message; final String message;
const ErrorState({required this.message}); const DeviceSettingsError({required this.message});
@override @override
List<Object?> get props => [message]; List<Object?> get props => [message];
} }
class FetchRoomsState extends DeviceSettingsState { class DeviceSettingsFetchRooms extends DeviceSettingsState {
final List<SubSpaceModel> roomsList; final List<SubSpaceModel> roomsList;
const FetchRoomsState({required this.roomsList}); const DeviceSettingsFetchRooms({required this.roomsList});
@override @override
List<Object?> get props => [roomsList]; List<Object?> get props => [roomsList];
} }
class SaveSelectionSuccessState extends DeviceSettingsState {} class DeviceSettingsSaveSelectionSuccess extends DeviceSettingsState {}
class ChangeNameState extends DeviceSettingsState {}

View File

@ -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<SubSpaceModel> 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),
],
),
);
}
}

View File

@ -1,13 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
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/device_icon_type_helper.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/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/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';
@ -18,75 +18,43 @@ 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({super.key, this.onClose, 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(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: ColorsManager.grayColor, 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( return BlocProvider(
create: (context) => SettingBlocBloc( create: (context) => SettingDeviceBloc(
deviceId: device.uuid ?? '', deviceId: device.uuid ?? '',
) )
..add(DeviceSettingInitialInfo()) ..add(DeviceSettingInitialInfo())
..add(FetchRoomsEvent( ..add(SettingBlocFetchRooms(
communityUuid: device.community!.uuid!, communityUuid: device.community!.uuid!,
spaceUuid: device.spaces!.first.uuid!, spaceUuid: device.spaces!.first.uuid!,
)), )),
child: BlocBuilder<SettingBlocBloc, DeviceSettingsState>( child: Builder(
builder: (context) {
return BlocBuilder<SettingDeviceBloc, DeviceSettingsState>(
builder: (context, state) { builder: (context, state) {
final iconPath = final _bloc = context.read<SettingDeviceBloc>();
DeviceIconTypeHelper.getDeviceIconByTypeCode(device.productType); final iconPath = DeviceIconTypeHelper.getDeviceIconByTypeCode(
final _bloc = BlocProvider.of<SettingBlocBloc>(context); device.productType);
DeviceInfoModel deviceInfo = DeviceInfoModel.empty(); final deviceInfo = state is DeviceSettingsUpdate
List<SubSpaceModel> subSpaces = []; ? state.deviceInfo ?? DeviceInfoModel.empty()
if (state is UpdateSettingState) { : DeviceInfoModel.empty();
deviceInfo = state.deviceInfo!; final subSpaces =
subSpaces = state.roomsList; state is DeviceSettingsUpdate ? state.roomsList ?? [] : [];
}
return Stack( return Stack(
children: [ children: [
Container( Container(
width: MediaQuery.of(context).size.width * 0.3, width: MediaQuery.of(context).size.width * 0.3,
color: ColorsManager.grey25, color: ColorsManager.grey25,
padding: padding: const EdgeInsets.symmetric(
const EdgeInsets.symmetric(horizontal: 20, vertical: 24), horizontal: 20, vertical: 24),
child: ListView( child: ListView(
children: [ children: [
// Header
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@ -102,7 +70,8 @@ class DeviceSettingsPanel extends StatelessWidget {
children: [ children: [
Text( Text(
'Device Settings', '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,
), ),
@ -110,7 +79,6 @@ class DeviceSettingsPanel extends StatelessWidget {
], ],
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
// Device Name + Icon
DefaultContainer( DefaultContainer(
child: Row( child: Row(
children: [ children: [
@ -135,7 +103,8 @@ class DeviceSettingsPanel extends StatelessWidget {
children: [ children: [
Text( Text(
'Device Name:', 'Device Name:',
style: context.textTheme.bodyMedium!.copyWith( style: context.textTheme.bodyMedium!
.copyWith(
color: ColorsManager.grayColor, color: ColorsManager.grayColor,
), ),
), ),
@ -149,8 +118,8 @@ class DeviceSettingsPanel extends StatelessWidget {
controller: _bloc.nameController, controller: _bloc.nameController,
enabled: _bloc.editName, enabled: _bloc.editName,
onFieldSubmitted: (value) { onFieldSubmitted: (value) {
_bloc.add( _bloc.add(const ChangeNameEvent(
const ChangeNameEvent(value: false)); value: false));
}, },
decoration: const InputDecoration( decoration: const InputDecoration(
border: InputBorder.none, border: InputBorder.none,
@ -167,7 +136,8 @@ class DeviceSettingsPanel extends StatelessWidget {
replacement: const SizedBox(), replacement: const SizedBox(),
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
_bloc.add(const ChangeNameEvent(value: true)); _bloc.add(
const ChangeNameEvent(value: true));
}, },
child: SvgPicture.asset( child: SvgPicture.asset(
Assets.editNameIconSettings, Assets.editNameIconSettings,
@ -181,151 +151,18 @@ class DeviceSettingsPanel extends StatelessWidget {
), ),
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
// Device Management
Text('Device Management', style: sectionTitle), Text('Device Management', style: sectionTitle),
DefaultContainer( DeviceManagementContent(
padding: EdgeInsets.zero, device: device,
child: Column( subSpaces: subSpaces.cast<SubSpaceModel>(),
children: [ deviceInfo: deviceInfo,
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), const SizedBox(height: 32),
RemoveDeviceWidget(bloc: _bloc),
// 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: 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),
),
),
),
),
),
], ],
), ),
), ),
if (state is SettingLoadingState) if (state is DeviceSettingsLoading)
Positioned.fill( Positioned.fill(
child: Container( child: Container(
color: Colors.black.withOpacity(0.1), color: Colors.black.withOpacity(0.1),
@ -339,6 +176,8 @@ class DeviceSettingsPanel extends StatelessWidget {
], ],
); );
}, },
);
},
), ),
); );
} }

View File

@ -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),
),
),
),
),
);
}
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/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/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/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -77,71 +78,7 @@ class _SubSpaceDialogState extends State<SubSpaceDialog> {
}).toList(), }).toList(),
const SizedBox(height: 12), const SizedBox(height: 12),
const Divider(height: 1, thickness: 1), const Divider(height: 1, thickness: 1),
SizedBox( SubSpaceDialogButtons(selectedId: _selectedId, widget: widget),
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,
),
),
),
),
),
],
),
),
], ],
), ),
), ),
@ -164,8 +101,8 @@ void showSubSpaceDialog(
selected: selected, selected: selected,
onConfirmed: (selectedModel) { onConfirmed: (selectedModel) {
if (selectedModel != null) { if (selectedModel != null) {
context.read<SettingBlocBloc>().add( context.read<SettingDeviceBloc>().add(
AssignRoomEvent( SettingBlocAssignRoom(
communityUuid: communityUuid, communityUuid: communityUuid,
spaceUuid: spaceUuid, spaceUuid: spaceUuid,
subSpaceUuid: selectedModel.id ?? '', subSpaceUuid: selectedModel.id ?? '',

View File

@ -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<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<SettingDeviceBloc>().add(
SettingBlocAssignRoom(
communityUuid: communityUuid,
spaceUuid: spaceUuid,
subSpaceUuid: selectedModel.id ?? '',
),
);
}
},
),
);
}

View File

@ -370,7 +370,8 @@ class DevicesManagementApi {
}); });
return response; return response;
} }
static Future resetDevise({
static Future resetDevice({
String? devicesUuid, String? devicesUuid,
}) async { }) async {
final response = await HTTPService().post( final response = await HTTPService().post(
@ -385,7 +386,4 @@ class DevicesManagementApi {
); );
return response; return response;
} }
} }

View File

@ -373,7 +373,6 @@ class CommunitySpaceManagementApi {
required String spaceId, required String spaceId,
required String projectId}) async { required String projectId}) async {
try { try {
// Construct the API path
final path = ApiEndpoints.listSubspace final path = ApiEndpoints.listSubspace
.replaceFirst('{communityUuid}', communityId) .replaceFirst('{communityUuid}', communityId)
.replaceFirst('{spaceUuid}', spaceId) .replaceFirst('{spaceUuid}', spaceId)
@ -389,9 +388,6 @@ class CommunitySpaceManagementApi {
for (var subspace in json['data']) { for (var subspace in json['data']) {
rooms.add(SubSpaceModel.fromJson(subspace)); rooms.add(SubSpaceModel.fromJson(subspace));
} }
} else {
debugPrint(
"Warning: 'data' key is missing or null in response JSON.");
} }
return rooms; return rooms;
}, },
@ -399,7 +395,7 @@ class CommunitySpaceManagementApi {
return response; return response;
} catch (error, stackTrace) { } catch (error, stackTrace) {
return []; // Return an empty list if there's an error return [];
} }
} }