diff --git a/assets/icons/close_settings_icon.svg b/assets/icons/close_settings_icon.svg
new file mode 100644
index 00000000..93e615d8
--- /dev/null
+++ b/assets/icons/close_settings_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/edit_name_icon_settings.svg b/assets/icons/edit_name_icon_settings.svg
new file mode 100644
index 00000000..54bee0af
--- /dev/null
+++ b/assets/icons/edit_name_icon_settings.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart
index 60abc0d2..a40faeb3 100644
--- a/lib/pages/common/custom_table.dart
+++ b/lib/pages/common/custom_table.dart
@@ -21,6 +21,7 @@ class DynamicTable extends StatefulWidget {
final List? initialSelectedIds;
final int uuidIndex;
final Function(dynamic selectedRows)? onSelectionChanged;
+ final Function(int rowIndex)? onSettingsPressed;
const DynamicTable({
super.key,
required this.headers,
@@ -37,6 +38,7 @@ class DynamicTable extends StatefulWidget {
this.initialSelectedIds,
required this.uuidIndex,
this.onSelectionChanged,
+ this.onSettingsPressed,
});
@override
@@ -63,7 +65,8 @@ class _DynamicTableState extends State {
}
}
- bool _compareListOfLists(List> oldList, List> newList) {
+ bool _compareListOfLists(
+ List> oldList, List> newList) {
// Check if the old and new lists are the same
if (oldList.length != newList.length) return false;
@@ -132,7 +135,8 @@ class _DynamicTableState extends State {
children: [
if (widget.withCheckBox) _buildSelectAllCheckbox(),
...List.generate(widget.headers.length, (index) {
- return _buildTableHeaderCell(widget.headers[index], index);
+ return _buildTableHeaderCell(
+ widget.headers[index], index);
})
//...widget.headers.map((header) => _buildTableHeaderCell(header)),
],
@@ -153,11 +157,14 @@ class _DynamicTableState extends State {
height: 15,
),
Text(
- widget.tableName == 'AccessManagement' ? 'No Password ' : 'No Devices',
+ widget.tableName == 'AccessManagement'
+ ? 'No Password '
+ : 'No Devices',
style: Theme.of(context)
.textTheme
.bodySmall!
- .copyWith(color: ColorsManager.grayColor),
+ .copyWith(
+ color: ColorsManager.grayColor),
)
],
),
@@ -166,16 +173,28 @@ class _DynamicTableState extends State {
],
)
: Column(
- children: List.generate(widget.data.length, (index) {
- final row = widget.data[index];
+ children:
+ List.generate(widget.data.length, (rowIndex) {
+ final row = widget.data[rowIndex];
return Row(
children: [
- if (widget.withCheckBox) _buildRowCheckbox(index, widget.size.height * 0.08),
- ...row.map((cell) => _buildTableCell(cell.toString(), widget.size.height * 0.08)),
+ if (widget.withCheckBox)
+ _buildRowCheckbox(
+ rowIndex, widget.size.height * 0.08),
+ ...row.asMap().entries.map((entry) {
+ int columnIndex = entry.key;
+ dynamic cell = entry.value;
+ return _buildTableCell(
+ cell.toString(),
+ widget.size.height * 0.08,
+ rowIndex: rowIndex,
+ columnIndex: columnIndex,
+ );
+ }).toList(),
],
);
}),
- ),
+ )
],
),
),
@@ -196,7 +215,9 @@ class _DynamicTableState extends State {
),
child: Checkbox(
value: _selectAll,
- onChanged: widget.withSelectAll && widget.data.isNotEmpty ? _toggleSelectAll : null,
+ onChanged: widget.withSelectAll && widget.data.isNotEmpty
+ ? _toggleSelectAll
+ : null,
),
);
}
@@ -238,7 +259,9 @@ class _DynamicTableState extends State {
constraints: const BoxConstraints.expand(height: 40),
alignment: Alignment.centerLeft,
child: Padding(
- padding: EdgeInsets.symmetric(horizontal: index == widget.headers.length - 1 ? 12 : 8.0, vertical: 4),
+ padding: EdgeInsets.symmetric(
+ horizontal: index == widget.headers.length - 1 ? 12 : 8.0,
+ vertical: 4),
child: Text(
title,
style: context.textTheme.titleSmall!.copyWith(
@@ -253,13 +276,23 @@ class _DynamicTableState extends State {
);
}
- Widget _buildTableCell(String content, double size) {
+ Widget _buildTableCell(
+ String content,
+ double size, {
+ required int rowIndex,
+ required int columnIndex,
+ }) {
bool isBatteryLevel = content.endsWith('%');
double? batteryLevel;
if (isBatteryLevel) {
batteryLevel = double.tryParse(content.replaceAll('%', '').trim());
}
+ bool isSettingsColumn = widget.headers[columnIndex] == 'Settings';
+
+ if (isSettingsColumn) {
+ return _buildSettingsIcon(rowIndex, size);
+ }
Color? statusColor;
switch (content) {
@@ -311,4 +344,22 @@ class _DynamicTableState extends State {
),
);
}
+
+ Widget _buildSettingsIcon(int rowIndex, double size) {
+ return Container(
+ height: size,
+ padding: const EdgeInsets.all(5.0),
+ decoration: const BoxDecoration(
+ border: Border(
+ bottom: BorderSide(color: ColorsManager.boxDivider, width: 1.0),
+ ),
+ color: Colors.white,
+ ),
+ alignment: Alignment.center,
+ child: IconButton(
+ icon: SvgPicture.asset(Assets.settings),
+ onPressed: () => widget.onSettingsPressed?.call(rowIndex),
+ ),
+ );
+ }
}
diff --git a/lib/pages/device_managment/all_devices/view/device_managment_page.dart b/lib/pages/device_managment/all_devices/view/device_managment_page.dart
index fd3a2574..755bc8b7 100644
--- a/lib/pages/device_managment/all_devices/view/device_managment_page.dart
+++ b/lib/pages/device_managment/all_devices/view/device_managment_page.dart
@@ -95,7 +95,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return const RoutinesView();
}
if (state.createRoutineView) {
- return CreateNewRoutineView();
+ return const CreateNewRoutineView();
}
return BlocBuilder(
diff --git a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart
index a3c975c1..f4baad0c 100644
--- a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart
+++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart
@@ -6,9 +6,11 @@ import 'package:syncrow_web/pages/common/filter/filter_widget.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart';
+import 'package:syncrow_web/pages/device_managment/device_setting/device_settings_panel.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart';
import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
+import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/format_date_time.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/style.dart';
@@ -58,7 +60,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
'Low Battery ($lowBatteryCount)',
];
- final buttonLabel = (selectedDevices.length > 1) ? 'Batch Control' : 'Control';
+ final buttonLabel =
+ (selectedDevices.length > 1) ? 'Batch Control' : 'Control';
return Row(
children: [
@@ -105,18 +108,23 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
if (selectedDevices.length == 1) {
showDialog(
context: context,
- builder: (context) => DeviceControlDialog(
+ builder: (context) =>
+ DeviceControlDialog(
device: selectedDevices.first,
),
);
- } else if (selectedDevices.length > 1) {
- final productTypes = selectedDevices
- .map((device) => device.productType)
- .toSet();
+ } else if (selectedDevices.length >
+ 1) {
+ final productTypes =
+ selectedDevices
+ .map((device) =>
+ device.productType)
+ .toSet();
if (productTypes.length == 1) {
showDialog(
context: context,
- builder: (context) => DeviceBatchControlDialog(
+ builder: (context) =>
+ DeviceBatchControlDialog(
devices: selectedDevices,
),
);
@@ -130,7 +138,9 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
- color: isControlButtonEnabled ? Colors.white : Colors.grey,
+ color: isControlButtonEnabled
+ ? Colors.white
+ : Colors.grey,
),
),
),
@@ -166,29 +176,40 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
'Installation Date and Time',
'Status',
'Last Offline Date and Time',
+ 'Settings'
],
data: devicesToShow.map((device) {
final combinedSpaceNames = device.spaces != null
- ? device.spaces!.map((space) => space.spaceName).join(' > ') +
+ ? device.spaces!
+ .map((space) => space.spaceName)
+ .join(' > ') +
(device.community != null
? ' > ${device.community!.name}'
: '')
- : (device.community != null ? device.community!.name : '');
+ : (device.community != null
+ ? device.community!.name
+ : '');
return [
device.name ?? '',
device.productName ?? '',
device.uuid ?? '',
- (device.spaces != null && device.spaces!.isNotEmpty)
+ (device.spaces != null &&
+ device.spaces!.isNotEmpty)
? device.spaces![0].spaceName
: '',
combinedSpaceNames,
- device.batteryLevel != null ? '${device.batteryLevel}%' : '-',
- formatDateTime(DateTime.fromMillisecondsSinceEpoch(
- (device.createTime ?? 0) * 1000)),
+ device.batteryLevel != null
+ ? '${device.batteryLevel}%'
+ : '-',
+ formatDateTime(
+ DateTime.fromMillisecondsSinceEpoch(
+ (device.createTime ?? 0) * 1000)),
device.online == true ? 'Online' : 'Offline',
- formatDateTime(DateTime.fromMillisecondsSinceEpoch(
- (device.updateTime ?? 0) * 1000)),
+ formatDateTime(
+ DateTime.fromMillisecondsSinceEpoch(
+ (device.updateTime ?? 0) * 1000)),
+ 'Settings',
];
}).toList(),
onSelectionChanged: (selectedRows) {
@@ -202,6 +223,10 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
.map((device) => device.uuid!)
.toList(),
isEmpty: devicesToShow.isEmpty,
+ onSettingsPressed: (rowIndex) {
+ final device = devicesToShow[rowIndex];
+ showDeviceSettingsSidebar(context, device);
+ },
),
),
)
@@ -213,4 +238,37 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
},
);
}
+
+ void showDeviceSettingsSidebar(BuildContext context, AllDevicesModel device) {
+ showGeneralDialog(
+ context: context,
+ barrierDismissible: true,
+ barrierLabel: "Device Settings",
+ transitionDuration: const Duration(milliseconds: 300),
+ pageBuilder: (context, anim1, anim2) {
+ return Align(
+ alignment: Alignment.centerRight,
+ child: Material(
+ child: Container(
+ width: MediaQuery.of(context).size.width * 0.3,
+ color: ColorsManager.whiteColors,
+ child: DeviceSettingsPanel(
+ device: device,
+ onClose: () => Navigator.of(context).pop(),
+ ),
+ ),
+ ),
+ );
+ },
+ transitionBuilder: (context, anim1, anim2, child) {
+ return SlideTransition(
+ position: Tween(
+ begin: const Offset(1, 0),
+ end: Offset.zero,
+ ).animate(anim1),
+ child: child,
+ );
+ },
+ );
+ }
}
diff --git a/lib/pages/device_managment/device_setting/bloc/device_info_model.dart b/lib/pages/device_managment/device_setting/bloc/device_info_model.dart
new file mode 100644
index 00000000..65a48508
--- /dev/null
+++ b/lib/pages/device_managment/device_setting/bloc/device_info_model.dart
@@ -0,0 +1,182 @@
+class DeviceInfoModel {
+ final int activeTime;
+ final String category;
+ final String categoryName;
+ final int createTime;
+ final String gatewayId;
+ final String icon;
+ final String ip;
+ final String lat;
+ final String localKey;
+ final String lon;
+ final String model;
+ final String name;
+ final String nodeId;
+ final bool online;
+ final String ownerId;
+ final String productName;
+ final bool sub;
+ final String timeZone;
+ final int updateTime;
+ final String uuid;
+ final String productUuid;
+ final String productType;
+ final String permissionType;
+ final String macAddress;
+ final Subspace subspace;
+
+ DeviceInfoModel({
+ required this.activeTime,
+ required this.category,
+ required this.categoryName,
+ required this.createTime,
+ required this.gatewayId,
+ required this.icon,
+ required this.ip,
+ required this.lat,
+ required this.localKey,
+ required this.lon,
+ required this.model,
+ required this.name,
+ required this.nodeId,
+ required this.online,
+ required this.ownerId,
+ required this.productName,
+ required this.sub,
+ required this.timeZone,
+ required this.updateTime,
+ required this.uuid,
+ required this.productUuid,
+ required this.productType,
+ required this.permissionType,
+ required this.macAddress,
+ required this.subspace,
+ });
+
+ factory DeviceInfoModel.fromJson(Map json) {
+ return DeviceInfoModel(
+ activeTime: json['activeTime'],
+ category: json['category'],
+ categoryName: json['categoryName'],
+ createTime: json['createTime'],
+ gatewayId: json['gatewayId'],
+ icon: json['icon'],
+ ip: json['ip'] ?? "",
+ lat: json['lat'],
+ localKey: json['localKey'],
+ lon: json['lon'],
+ model: json['model'],
+ name: json['name'],
+ nodeId: json['nodeId'],
+ online: json['online'],
+ ownerId: json['ownerId'],
+ productName: json['productName'],
+ sub: json['sub'],
+ timeZone: json['timeZone'],
+ updateTime: json['updateTime'],
+ uuid: json['uuid'],
+ productUuid: json['productUuid'],
+ productType: json['productType'],
+ permissionType: json['permissionType'] ?? '',
+ macAddress: json['macAddress'],
+ subspace: Subspace.fromJson(json['subspace']),
+ );
+ }
+
+ Map toJson() {
+ return {
+ 'activeTime': activeTime,
+ 'category': category,
+ 'categoryName': categoryName,
+ 'createTime': createTime,
+ 'gatewayId': gatewayId,
+ 'icon': icon,
+ 'ip': ip,
+ 'lat': lat,
+ 'localKey': localKey,
+ 'lon': lon,
+ 'model': model,
+ 'name': name,
+ 'nodeId': nodeId,
+ 'online': online,
+ 'ownerId': ownerId,
+ 'productName': productName,
+ 'sub': sub,
+ 'timeZone': timeZone,
+ 'updateTime': updateTime,
+ 'uuid': uuid,
+ 'productUuid': productUuid,
+ 'productType': productType,
+ 'permissionType': permissionType,
+ 'macAddress': macAddress,
+ 'subspace': subspace.toJson(),
+ };
+ }
+
+ static DeviceInfoModel empty() {
+ return DeviceInfoModel(
+ activeTime: 0,
+ category: '',
+ categoryName: '',
+ createTime: 0,
+ gatewayId: '',
+ icon: '',
+ ip: '',
+ lat: '',
+ localKey: '',
+ lon: '',
+ model: '',
+ name: '',
+ nodeId: '',
+ online: false,
+ ownerId: '',
+ productName: '',
+ sub: false,
+ timeZone: '',
+ updateTime: 0,
+ uuid: '',
+ productUuid: '',
+ productType: '',
+ permissionType: '',
+ macAddress: '',
+ subspace: Subspace(
+ uuid: '',
+ createdAt: '',
+ updatedAt: '',
+ subspaceName: '',
+ ),
+ );
+ }
+}
+
+class Subspace {
+ final String uuid;
+ final String createdAt;
+ final String updatedAt;
+ final String subspaceName;
+
+ Subspace({
+ required this.uuid,
+ required this.createdAt,
+ required this.updatedAt,
+ required this.subspaceName,
+ });
+
+ factory Subspace.fromJson(Map json) {
+ return Subspace(
+ uuid: json['uuid'],
+ createdAt: json['createdAt'],
+ updatedAt: json['updatedAt'],
+ subspaceName: json['subspaceName'],
+ );
+ }
+
+ Map toJson() {
+ return {
+ 'uuid': uuid,
+ 'createdAt': createdAt,
+ 'updatedAt': updatedAt,
+ 'subspaceName': subspaceName,
+ };
+ }
+}
diff --git a/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart b/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart
new file mode 100644
index 00000000..55e5e74e
--- /dev/null
+++ b/lib/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart
@@ -0,0 +1,149 @@
+import 'package:bloc/bloc.dart';
+import 'package:equatable/equatable.dart';
+import 'package:flutter/material.dart';
+import 'package:syncrow_web/pages/device_managment/device_setting/bloc/device_info_model.dart';
+import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_state.dart';
+import 'package:syncrow_web/pages/device_managment/device_setting/bloc/sub_space_model.dart';
+import 'package:syncrow_web/services/devices_mang_api.dart';
+import 'package:syncrow_web/utils/snack_bar.dart';
+part 'setting_bloc_event.dart';
+
+class SettingBlocBloc extends Bloc {
+ final String deviceId;
+ SettingBlocBloc({
+ required this.deviceId,
+ }) : super(const SettingBlocInitial()) {
+ on(fetchDeviceInfo);
+ on(saveName);
+ on(_changeName);
+ on(deleteDevice);
+ //on(_fetchRoomsAndDevices);
+ }
+ static String deviceName = '';
+ final TextEditingController nameController =
+ TextEditingController(text: deviceName);
+ List roomsList = [];
+ bool isEditingName = false;
+
+ bool _validateInputs() {
+ final nameError = fullNameValidator(nameController.text);
+ if (nameError != null) {
+ CustomSnackBar.displaySnackBar(nameError);
+ return true;
+ }
+ return false;
+ }
+
+ String? fullNameValidator(String? value) {
+ if (value == null) return 'name is required';
+ final withoutExtraSpaces = value.replaceAll(RegExp(r"\s+"), ' ').trim();
+ if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) {
+ return 'name must be between 2 and 30 characters long';
+ }
+ if (RegExp(r"/[^ a-zA-Z0-9-\']/").hasMatch(withoutExtraSpaces)) {
+ return 'Only alphanumeric characters, space, dash and single quote are allowed';
+ }
+ return null;
+ }
+
+ Future saveName(
+ SaveNameEvent event, Emitter emit) async {
+ if (_validateInputs()) return;
+ try {
+ emit(SettingLoadingState());
+ var response = await DevicesManagementApi.putDeviceName(
+ deviceId: deviceId, deviceName: nameController.text);
+ add(DeviceSettingInitialInfo());
+ CustomSnackBar.displaySnackBar('Save Successfully');
+ emit(UpdateSettingState(deviceName: nameController.text));
+ } catch (e) {
+ emit(ErrorState(message: e.toString()));
+ } finally {
+ // isSaving = false;
+ }
+ }
+
+ DeviceInfoModel deviceInfo = DeviceInfoModel(
+ activeTime: 0,
+ category: "",
+ categoryName: "",
+ createTime: 0,
+ gatewayId: "",
+ icon: "",
+ ip: "",
+ lat: "",
+ localKey: "",
+ lon: "",
+ model: "",
+ name: "",
+ nodeId: "",
+ online: false,
+ ownerId: "",
+ productName: "",
+ sub: false,
+ timeZone: "",
+ updateTime: 0,
+ uuid: "",
+ productUuid: "",
+ productType: "",
+ permissionType: "",
+ macAddress: "",
+ subspace: Subspace(
+ uuid: "",
+ createdAt: "",
+ updatedAt: "",
+ subspaceName: "",
+ ),
+ );
+
+ Future fetchDeviceInfo(
+ DeviceSettingInitialInfo event, Emitter emit) async {
+ try {
+ emit(SettingLoadingState());
+ var response = await DevicesManagementApi.getDeviceInfo(deviceId);
+ deviceInfo = DeviceInfoModel.fromJson(response);
+ nameController.text = deviceInfo.name;
+
+ emit(UpdateSettingState(
+ deviceName: nameController.text,
+ deviceInfo: deviceInfo,
+ ));
+ } catch (e) {
+ emit(ErrorState(message: e.toString()));
+ }
+ }
+
+ bool editName = false;
+ final FocusNode focusNode = FocusNode();
+
+ void _changeName(ChangeNameEvent event, Emitter emit) {
+ emit(SettingLoadingState());
+ editName = event.value!;
+ if (editName) {
+ Future.delayed(const Duration(milliseconds: 500), () {
+ focusNode.requestFocus();
+ });
+ } else {
+ add(const SaveNameEvent());
+ focusNode.unfocus();
+ }
+ emit(UpdateSettingState(deviceName: deviceName, deviceInfo: deviceInfo));
+ }
+
+ void deleteDevice(
+ DeleteDeviceEvent event, Emitter emit) async {
+ try {
+ emit(SettingLoadingState());
+ var response =
+ await DevicesManagementApi.resetDevise(devicesUuid: deviceId);
+ CustomSnackBar.displaySnackBar('Reset Successfully');
+ emit(UpdateSettingState(
+ deviceName: nameController.text,
+ deviceInfo: deviceInfo,
+ ));
+ } catch (e) {
+ emit(ErrorState(message: e.toString()));
+ return;
+ }
+ }
+}
diff --git a/lib/pages/device_managment/device_setting/bloc/setting_bloc_event.dart b/lib/pages/device_managment/device_setting/bloc/setting_bloc_event.dart
new file mode 100644
index 00000000..737c8889
--- /dev/null
+++ b/lib/pages/device_managment/device_setting/bloc/setting_bloc_event.dart
@@ -0,0 +1,50 @@
+part of 'setting_bloc_bloc.dart';
+
+abstract class SettingBlocEvent extends Equatable {
+ const SettingBlocEvent();
+ @override
+ List