Merge branch 'dev' of https://github.com/SyncrowIOT/web into feature/space-management

This commit is contained in:
hannathkadher
2024-11-11 20:57:11 +04:00
282 changed files with 19897 additions and 2847 deletions

View File

@ -1,5 +1,5 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
@ -14,6 +14,10 @@ class DeviceManagementBloc
int _offlineCount = 0;
int _lowBatteryCount = 0;
List<AllDevicesModel> _selectedDevices = [];
List<AllDevicesModel> _filteredDevices = [];
String currentProductName = '';
String? currentCommunity;
String? currentUnitName;
DeviceManagementBloc() : super(DeviceManagementInitial()) {
on<FetchDevices>(_onFetchDevices);
@ -21,6 +25,9 @@ class DeviceManagementBloc
on<SelectedFilterChanged>(_onSelectedFilterChanged);
on<SearchDevices>(_onSearchDevices);
on<SelectDevice>(_onSelectDevice);
on<ResetFilters>(_onResetFilters);
on<ResetSelectedDevices>(_onResetSelectedDevices);
on<UpdateSelection>(_onUpdateSelection);
}
Future<void> _onFetchDevices(
@ -28,14 +35,18 @@ class DeviceManagementBloc
emit(DeviceManagementLoading());
try {
final devices = await DevicesManagementApi().fetchDevices();
_selectedDevices.clear();
_devices = devices;
_filteredDevices = devices;
_calculateDeviceCounts();
emit(DeviceManagementLoaded(
devices: devices,
selectedIndex: _selectedIndex,
selectedIndex: 0,
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice: null,
isControlButtonEnabled: false,
));
} catch (e) {
emit(DeviceManagementInitial());
@ -43,9 +54,9 @@ class DeviceManagementBloc
}
void _onFilterDevices(
FilterDevices event, Emitter<DeviceManagementState> emit) {
FilterDevices event, Emitter<DeviceManagementState> emit) async {
if (_devices.isNotEmpty) {
final filteredDevices = _devices.where((device) {
_filteredDevices = List.from(_devices.where((device) {
switch (event.filter) {
case 'Online':
return device.online == true;
@ -56,13 +67,64 @@ class DeviceManagementBloc
default:
return true;
}
}).toList();
}).toList());
emit(DeviceManagementFiltered(
filteredDevices: filteredDevices,
filteredDevices: _filteredDevices,
selectedIndex: _selectedIndex,
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice: _selectedDevices.isNotEmpty ? _selectedDevices : null,
isControlButtonEnabled: _selectedDevices.isNotEmpty,
));
if (currentProductName.isNotEmpty) {
add(SearchDevices(productName: currentProductName));
}
}
}
Future<void> _onResetFilters(
ResetFilters event, Emitter<DeviceManagementState> emit) async {
currentProductName = '';
_selectedDevices.clear();
_filteredDevices = List.from(_devices);
_selectedIndex = 0;
emit(DeviceManagementLoaded(
devices: _devices,
selectedIndex: 0,
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice: null,
isControlButtonEnabled: false,
));
}
void _onResetSelectedDevices(
ResetSelectedDevices event, Emitter<DeviceManagementState> emit) {
_selectedDevices.clear();
if (state is DeviceManagementLoaded) {
emit(DeviceManagementLoaded(
devices: _devices,
selectedIndex: _selectedIndex,
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice: null,
isControlButtonEnabled: false,
));
} else if (state is DeviceManagementFiltered) {
emit(DeviceManagementFiltered(
filteredDevices: (state as DeviceManagementFiltered).filteredDevices,
selectedIndex: _selectedIndex,
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice: null,
isControlButtonEnabled: false,
));
}
}
@ -75,13 +137,18 @@ class DeviceManagementBloc
void _onSelectDevice(
SelectDevice event, Emitter<DeviceManagementState> emit) {
if (_selectedDevices.contains(event.selectedDevice)) {
_selectedDevices.remove(event.selectedDevice);
final selectedUuid = event.selectedDevice.uuid;
if (_selectedDevices.any((device) => device.uuid == selectedUuid)) {
_selectedDevices.removeWhere((device) => device.uuid == selectedUuid);
} else {
_selectedDevices.add(event.selectedDevice);
}
bool isControlButtonEnabled = _selectedDevices.length == 1;
List<AllDevicesModel> clonedSelectedDevices = List.from(_selectedDevices);
bool isControlButtonEnabled =
_checkIfControlButtonEnabled(clonedSelectedDevices);
if (state is DeviceManagementLoaded) {
emit(DeviceManagementLoaded(
@ -90,7 +157,9 @@ class DeviceManagementBloc
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice: isControlButtonEnabled ? _selectedDevices.first : null,
selectedDevice:
clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
isControlButtonEnabled: isControlButtonEnabled,
));
} else if (state is DeviceManagementFiltered) {
emit(DeviceManagementFiltered(
@ -99,11 +168,66 @@ class DeviceManagementBloc
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice: isControlButtonEnabled ? _selectedDevices.first : null,
selectedDevice:
clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null,
isControlButtonEnabled: isControlButtonEnabled,
));
}
}
void _onUpdateSelection(
UpdateSelection event, Emitter<DeviceManagementState> emit) {
List<AllDevicesModel> selectedDevices = [];
List<AllDevicesModel> devicesToSelectFrom = [];
if (state is DeviceManagementLoaded) {
devicesToSelectFrom = (state as DeviceManagementLoaded).devices;
} else if (state is DeviceManagementFiltered) {
devicesToSelectFrom = (state as DeviceManagementFiltered).filteredDevices;
}
for (int i = 0; i < event.selectedRows.length; i++) {
if (event.selectedRows[i]) {
selectedDevices.add(devicesToSelectFrom[i]);
}
}
if (state is DeviceManagementLoaded) {
final loadedState = state as DeviceManagementLoaded;
emit(DeviceManagementLoaded(
devices: loadedState.devices,
selectedIndex: loadedState.selectedIndex,
onlineCount: loadedState.onlineCount,
offlineCount: loadedState.offlineCount,
lowBatteryCount: loadedState.lowBatteryCount,
selectedDevice: selectedDevices,
isControlButtonEnabled: _checkIfControlButtonEnabled(selectedDevices),
));
} else if (state is DeviceManagementFiltered) {
final filteredState = state as DeviceManagementFiltered;
emit(DeviceManagementFiltered(
filteredDevices: filteredState.filteredDevices,
selectedIndex: filteredState.selectedIndex,
onlineCount: filteredState.onlineCount,
offlineCount: filteredState.offlineCount,
lowBatteryCount: filteredState.lowBatteryCount,
selectedDevice: selectedDevices,
isControlButtonEnabled: _checkIfControlButtonEnabled(selectedDevices),
));
}
}
bool _checkIfControlButtonEnabled(List<AllDevicesModel> selectedDevices) {
if (selectedDevices.length > 1) {
final productTypes =
selectedDevices.map((device) => device.productType).toSet();
return productTypes.length == 1;
} else if (selectedDevices.length == 1) {
return true;
}
return false;
}
void _calculateDeviceCounts() {
_onlineCount = _devices.where((device) => device.online == true).length;
_offlineCount = _devices.where((device) => device.online == false).length;
@ -128,27 +252,61 @@ class DeviceManagementBloc
void _onSearchDevices(
SearchDevices event, Emitter<DeviceManagementState> emit) {
if (_devices.isNotEmpty) {
final filteredDevices = _devices.where((device) {
if ((event.community == null || event.community!.isEmpty) &&
(event.unitName == null || event.unitName!.isEmpty) &&
(event.productName == null || event.productName!.isEmpty)) {
currentProductName = '';
if (state is DeviceManagementFiltered) {
add(FilterDevices(_getFilterFromIndex(_selectedIndex)));
} else {
return;
}
}
if (event.productName == currentProductName &&
event.community == currentCommunity &&
event.unitName == currentUnitName &&
event.searchField) {
return;
}
currentProductName = event.productName ?? '';
currentCommunity = event.community;
currentUnitName = event.unitName;
List<AllDevicesModel> devicesToSearch = _filteredDevices;
if (devicesToSearch.isNotEmpty) {
final filteredDevices = devicesToSearch.where((device) {
final matchesCommunity = event.community == null ||
event.community!.isEmpty ||
(device.room?.name
(device.community?.name
?.toLowerCase()
.contains(event.community!.toLowerCase()) ??
false);
final matchesUnit = event.unitName == null ||
event.unitName!.isEmpty ||
(device.unit?.name
?.toLowerCase()
.contains(event.unitName!.toLowerCase()) ??
false);
(device.spaces != null &&
device.spaces!.isNotEmpty &&
device.spaces![0].spaceName
!.toLowerCase()
.contains(event.unitName!.toLowerCase()));
final matchesProductName = event.productName == null ||
event.productName!.isEmpty ||
(device.name
?.toLowerCase()
.contains(event.productName!.toLowerCase()) ??
false);
return matchesCommunity && matchesUnit && matchesProductName;
final matchesDeviceName = event.productName == null ||
event.productName!.isEmpty ||
(device.categoryName
?.toLowerCase()
.contains(event.productName!.toLowerCase()) ??
false);
return matchesCommunity &&
matchesUnit &&
(matchesProductName || matchesDeviceName);
}).toList();
emit(DeviceManagementFiltered(
@ -157,6 +315,8 @@ class DeviceManagementBloc
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
selectedDevice: null,
isControlButtonEnabled: false,
));
}
}

View File

@ -31,11 +31,13 @@ class SearchDevices extends DeviceManagementEvent {
final String? community;
final String? unitName;
final String? productName;
final bool searchField;
const SearchDevices({
this.community,
this.unitName,
this.productName,
this.searchField = false,
});
@override
@ -50,3 +52,13 @@ class SelectDevice extends DeviceManagementEvent {
@override
List<Object?> get props => [selectedDevice];
}
class ResetFilters extends DeviceManagementEvent {}
class ResetSelectedDevices extends DeviceManagementEvent {}
class UpdateSelection extends DeviceManagementEvent {
final List<bool> selectedRows;
const UpdateSelection(this.selectedRows);
}

View File

@ -17,7 +17,8 @@ class DeviceManagementLoaded extends DeviceManagementState {
final int onlineCount;
final int offlineCount;
final int lowBatteryCount;
final AllDevicesModel? selectedDevice;
final List<AllDevicesModel>? selectedDevice;
final bool isControlButtonEnabled;
const DeviceManagementLoaded({
required this.devices,
@ -26,6 +27,7 @@ class DeviceManagementLoaded extends DeviceManagementState {
required this.offlineCount,
required this.lowBatteryCount,
this.selectedDevice,
required this.isControlButtonEnabled,
});
@override
@ -35,7 +37,8 @@ class DeviceManagementLoaded extends DeviceManagementState {
onlineCount,
offlineCount,
lowBatteryCount,
selectedDevice
selectedDevice,
isControlButtonEnabled
];
}
@ -45,7 +48,8 @@ class DeviceManagementFiltered extends DeviceManagementState {
final int onlineCount;
final int offlineCount;
final int lowBatteryCount;
final AllDevicesModel? selectedDevice;
final List<AllDevicesModel>? selectedDevice;
final bool isControlButtonEnabled;
const DeviceManagementFiltered({
required this.filteredDevices,
@ -54,6 +58,7 @@ class DeviceManagementFiltered extends DeviceManagementState {
required this.offlineCount,
required this.lowBatteryCount,
this.selectedDevice,
required this.isControlButtonEnabled,
});
@override
@ -63,7 +68,8 @@ class DeviceManagementFiltered extends DeviceManagementState {
onlineCount,
offlineCount,
lowBatteryCount,
selectedDevice
selectedDevice,
isControlButtonEnabled
];
}

View File

@ -1,34 +1,199 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/ac/view/ac_device_batch_control.dart';
import 'package:syncrow_web/pages/device_managment/ac/view/ac_device_control.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart';
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_status_view.dart';
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_status_view.dart';
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart';
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_control_view.dart';
import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_batch_control.dart';
import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart';
import 'package:syncrow_web/pages/device_managment/living_room_switch/view/living_room_device_control.dart';
import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_control_view.dart';
import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart';
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_clamp_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/view/smart_power_device_control.dart';
import 'package:syncrow_web/pages/device_managment/sos/view/sos_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/sos/view/sos_device_control_view.dart';
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart';
import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_device_control.dart';
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/view/water_heater_batch_control.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/view/water_heater_device_control.dart';
import 'package:syncrow_web/pages/device_managment/water_leak/view/water_leak_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/water_leak/view/water_leak_control_view.dart';
import '../../one_g_glass_switch/view/one_gang_glass_switch_control_view.dart';
mixin RouteControlsBasedCode {
Widget routeControlsWidgets({required AllDevicesModel device}) {
switch (device.productType) {
case '1G':
return WallLightDeviceControl(
deviceId: device.uuid!,
);
case '2G':
return TwoGangDeviceControlView(
deviceId: device.uuid!,
);
case '3G':
return LivingRoomDeviceControl(
return LivingRoomDeviceControlsView(
deviceId: device.uuid!,
);
case '1GT':
return OneGangGlassSwitchControlView(
deviceId: device.uuid!,
);
case '2GT':
return TwoGangGlassSwitchControlView(
deviceId: device.uuid!,
);
case '3GT':
return ThreeGangGlassSwitchControlView(
deviceId: device.uuid!,
);
case 'GW':
return GateWayControls(
return GateWayControlsView(
gatewayId: device.uuid!,
);
case 'DL':
return DoorLockView(device: device);
return DoorLockControlsView(device: device);
case 'WPS':
return WallSensorControls(device: device);
return WallSensorControlsView(device: device);
case 'CPS':
return CeilingSensorControls(
return CeilingSensorControlsView(
device: device,
);
case 'CUR':
return CurtainStatusControlsView(
deviceId: device.uuid!,
);
case 'AC':
return AcDeviceControl(device: device);
return AcDeviceControlsView(device: device);
case 'WH':
return WaterHeaterDeviceControlView(
device: device,
);
case 'DS':
return MainDoorSensorControlView(device: device);
case 'GD':
return GarageDoorControlView(
deviceId: device.uuid!,
);
case 'WL':
return WaterLeakView(
deviceId: device.uuid!,
);
case 'PC':
return SmartPowerDeviceControl(
deviceId: device.uuid!,
);
case 'SOS':
return SosDeviceControlsView(device: device);
default:
return const SizedBox();
}
}
/*
3G:
1G:
2G:
GW:
DL:
WPS:
CPS:
AC:
CUR:
WH:
DS:
*/
Widget routeBatchControlsWidgets({required List<AllDevicesModel> devices}) {
switch (devices.first.productType) {
case '1G':
return WallLightBatchControlView(
deviceIds: devices.where((e) => (e.productType == '1G')).map((e) => e.uuid!).toList(),
);
case '2G':
return TwoGangBatchControlView(
deviceIds: devices.where((e) => (e.productType == '2G')).map((e) => e.uuid!).toList(),
);
case '3G':
return LivingRoomBatchControlsView(
deviceIds: devices.where((e) => (e.productType == '3G')).map((e) => e.uuid!).toList(),
);
case '1GT':
return OneGangGlassSwitchBatchControlView(
deviceIds: devices.where((e) => (e.productType == '1GT')).map((e) => e.uuid!).toList(),
);
case '2GT':
return TwoGangGlassSwitchBatchControlView(
deviceIds: devices.where((e) => (e.productType == '2GT')).map((e) => e.uuid!).toList(),
);
case '3GT':
return ThreeGangGlassSwitchBatchControlView(
deviceIds: devices.where((e) => (e.productType == '3GT')).map((e) => e.uuid!).toList(),
);
case 'GW':
return GatewayBatchControlView(
gatewayIds: devices.where((e) => (e.productType == 'GW')).map((e) => e.uuid!).toList(),
);
case 'DL':
return DoorLockBatchControlView(
devicesIds: devices.where((e) => (e.productType == 'DL')).map((e) => e.uuid!).toList());
case 'WPS':
return WallSensorBatchControlView(
devicesIds: devices.where((e) => (e.productType == 'WPS')).map((e) => e.uuid!).toList());
case 'CPS':
return CeilingSensorBatchControlView(
devicesIds: devices.where((e) => (e.productType == 'CPS')).map((e) => e.uuid!).toList(),
);
case 'CUR':
return CurtainBatchStatusView(
devicesIds: devices.where((e) => (e.productType == 'CUR')).map((e) => e.uuid!).toList(),
);
case 'AC':
return AcDeviceBatchControlView(
devicesIds: devices.where((e) => (e.productType == 'AC')).map((e) => e.uuid!).toList());
case 'WH':
return WaterHEaterBatchControlView(
deviceIds: devices.where((e) => (e.productType == 'WH')).map((e) => e.uuid!).toList(),
);
case 'DS':
return MainDoorSensorBatchView(
devicesIds: devices.where((e) => (e.productType == 'DS')).map((e) => e.uuid!).toList(),
);
case 'GD':
return GarageDoorBatchControlView(
deviceIds: devices.where((e) => (e.productType == 'GD')).map((e) => e.uuid!).toList(),
);
case 'WL':
return WaterLeakBatchControlView(
deviceIds: devices.where((e) => (e.productType == 'WL')).map((e) => e.uuid!).toList(),
);
case 'PC':
return PowerClampBatchControlView(
deviceIds: devices.where((e) => (e.productType == 'PC')).map((e) => e.uuid!).toList(),
);
case 'SOS':
return SOSBatchControlView(
deviceIds: devices.where((e) => (e.productType == 'SOS')).map((e) => e.uuid!).toList(),
);
default:
return const SizedBox();
}

View File

@ -0,0 +1,18 @@
class DeviceCommunityModel {
String? uuid;
String? name;
DeviceCommunityModel({this.uuid, this.name});
DeviceCommunityModel.fromJson(Map<String, dynamic> json) {
uuid = json['uuid']?.toString();
name = json['name']?.toString();
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['uuid'] = uuid;
data['name'] = name;
return data;
}
}

View File

@ -13,11 +13,15 @@ class DeviceReport {
DeviceReport.fromJson(Map<String, dynamic> json)
: deviceUuid = json['deviceUuid'] as String?,
startTime = json['startTime'] as int?,
endTime = json['endTime'] as int?,
data = (json['data'] as List<dynamic>?)
?.map((e) => DeviceEvent.fromJson(e as Map<String, dynamic>))
.toList();
startTime = int.tryParse(json['startTime'].toString()) ??
json['startTime'] as int?,
endTime =
int.tryParse(json['endTime'].toString()) ?? json['endTime'] as int?,
data = json['data'] != null
? (json['data'] as List<dynamic>?)
?.map((e) => DeviceEvent.fromJson(e as Map<String, dynamic>))
.toList()
: [];
Map<String, dynamic> toJson() => {
'deviceUuid': deviceUuid,

View File

@ -0,0 +1,18 @@
class DeviceSpaceModel {
String? uuid;
String? spaceName;
DeviceSpaceModel({this.uuid, this.spaceName});
DeviceSpaceModel.fromJson(Map<String, dynamic> json) {
uuid = json['uuid']?.toString();
spaceName = json['spaceName']?.toString();
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['uuid'] = uuid;
data['spaceName'] = spaceName;
return data;
}
}

View File

@ -1,5 +1,8 @@
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_community.model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_space_model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class AllDevicesModel {
/*
@ -39,6 +42,7 @@ class AllDevicesModel {
DevicesModelRoom? room;
DevicesModelUnit? unit;
DeviceCommunityModel? community;
String? productUuid;
String? productType;
String? permissionType;
@ -62,10 +66,13 @@ class AllDevicesModel {
int? updateTime;
String? uuid;
int? batteryLevel;
String? productName;
List<DeviceSpaceModel>? spaces;
AllDevicesModel({
this.room,
this.unit,
this.community,
this.productUuid,
this.productType,
this.permissionType,
@ -89,6 +96,8 @@ class AllDevicesModel {
this.updateTime,
this.uuid,
this.batteryLevel,
this.productName,
this.spaces,
});
AllDevicesModel.fromJson(Map<String, dynamic> json) {
room = (json['room'] != null && (json['room'] is Map))
@ -97,6 +106,9 @@ class AllDevicesModel {
unit = (json['unit'] != null && (json['unit'] is Map))
? DevicesModelUnit.fromJson(json['unit'])
: null;
community = (json['community'] != null && (json['community'] is Map))
? DeviceCommunityModel.fromJson(json['community'])
: null;
productUuid = json['productUuid']?.toString();
productType = json['productType']?.toString();
permissionType = json['permissionType']?.toString();
@ -105,7 +117,7 @@ class AllDevicesModel {
categoryName = json['categoryName']?.toString();
createTime = int.tryParse(json['createTime']?.toString() ?? '');
gatewayId = json['gatewayId']?.toString();
icon = json['icon']?.toString();
icon = json['icon'] ?? _getDefaultIcon(productType);
ip = json['ip']?.toString();
lat = json['lat']?.toString();
localKey = json['localKey']?.toString();
@ -119,8 +131,43 @@ class AllDevicesModel {
timeZone = json['timeZone']?.toString();
updateTime = int.tryParse(json['updateTime']?.toString() ?? '');
uuid = json['uuid']?.toString();
batteryLevel = int.tryParse(json['batteryLevel']?.toString() ?? '');
batteryLevel = int.tryParse(json['battery']?.toString() ?? '');
productName = json['productName']?.toString();
if (json['spaces'] != null && json['spaces'] is List) {
spaces = (json['spaces'] as List)
.map((space) => DeviceSpaceModel.fromJson(space))
.toList();
}
}
String _getDefaultIcon(String? productType) {
switch (productType) {
case 'LightBulb':
return Assets.lightBulb;
case 'CeilingSensor':
case 'WallSensor':
return Assets.sensors;
case 'AC':
return Assets.ac;
case 'DoorLock':
return Assets.doorLock;
case 'Curtain':
return Assets.curtain;
case '3G':
case '2G':
case '1G':
return Assets.gangSwitch;
case 'Gateway':
return Assets.gateway;
case 'WH':
return Assets.blackLogo;
case 'DS':
return Assets.sensors;
default:
return Assets.logo;
}
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (room != null) {
@ -129,6 +176,9 @@ class AllDevicesModel {
if (unit != null) {
data['unit'] = unit!.toJson();
}
if (community != null) {
data['community'] = community!.toJson();
}
data['productUuid'] = productUuid;
data['productType'] = productType;
data['permissionType'] = permissionType;
@ -151,7 +201,74 @@ class AllDevicesModel {
data['timeZone'] = timeZone;
data['updateTime'] = updateTime;
data['uuid'] = uuid;
data['batteryLevel'] = batteryLevel;
data['battery'] = batteryLevel;
data['productName'] = productName;
if (spaces != null) {
data['spaces'] = spaces!.map((space) => space.toJson()).toList();
}
return data;
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is AllDevicesModel &&
other.room == room &&
other.unit == unit &&
other.productUuid == productUuid &&
other.productType == productType &&
other.permissionType == permissionType &&
other.activeTime == activeTime &&
other.category == category &&
other.categoryName == categoryName &&
other.createTime == createTime &&
other.gatewayId == gatewayId &&
other.icon == icon &&
other.ip == ip &&
other.lat == lat &&
other.localKey == localKey &&
other.lon == lon &&
other.model == model &&
other.name == name &&
other.nodeId == nodeId &&
other.online == online &&
other.ownerId == ownerId &&
other.sub == sub &&
other.timeZone == timeZone &&
other.updateTime == updateTime &&
other.uuid == uuid &&
other.productName == productName &&
other.batteryLevel == batteryLevel;
}
@override
int get hashCode {
return room.hashCode ^
unit.hashCode ^
productUuid.hashCode ^
productType.hashCode ^
permissionType.hashCode ^
activeTime.hashCode ^
category.hashCode ^
categoryName.hashCode ^
createTime.hashCode ^
gatewayId.hashCode ^
icon.hashCode ^
ip.hashCode ^
lat.hashCode ^
localKey.hashCode ^
lon.hashCode ^
model.hashCode ^
name.hashCode ^
nodeId.hashCode ^
online.hashCode ^
ownerId.hashCode ^
sub.hashCode ^
timeZone.hashCode ^
updateTime.hashCode ^
uuid.hashCode ^
productName.hashCode ^
batteryLevel.hashCode;
}
}

View File

@ -0,0 +1,55 @@
import 'package:flutter/foundation.dart';
class FactoryResetModel {
final List<String> devicesUuid;
FactoryResetModel({
required this.devicesUuid,
});
factory FactoryResetModel.fromJson(Map<String, dynamic> json) {
return FactoryResetModel(
devicesUuid: List<String>.from(json['devicesUuid']),
);
}
Map<String, dynamic> toJson() {
return {
'devicesUuid': devicesUuid,
};
}
FactoryResetModel copyWith({
List<String>? devicesUuid,
}) {
return FactoryResetModel(
devicesUuid: devicesUuid ?? this.devicesUuid,
);
}
Map<String, dynamic> toMap() {
return {
'devicesUuid': devicesUuid,
};
}
factory FactoryResetModel.fromMap(Map<String, dynamic> map) {
return FactoryResetModel(
devicesUuid: List<String>.from(map['devicesUuid']),
);
}
@override
String toString() => 'FactoryReset(devicesUuid: $devicesUuid)';
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is FactoryResetModel &&
listEquals(other.devicesUuid, devicesUuid);
}
@override
int get hashCode => devicesUuid.hashCode;
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -13,24 +14,25 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
return BlocProvider(
create: (context) => DeviceManagementBloc()..add(FetchDevices()),
child: WebScaffold(
appBarTitle: Text(
'Device Management',
style: Theme.of(context).textTheme.headlineLarge,
appBarTitle: FittedBox(
child: Text(
'Device Management',
style: Theme.of(context).textTheme.headlineLarge,
),
),
enableMenuSideba: isLargeScreenSize(context),
rightBody: const NavigateHomeGridView(),
scaffoldBody: BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
builder: (context, state) {
if (state is DeviceManagementLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is DeviceManagementLoaded ||
state is DeviceManagementFiltered) {
} else if (state is DeviceManagementLoaded || state is DeviceManagementFiltered) {
final devices = state is DeviceManagementLoaded
? state.devices
: (state as DeviceManagementFiltered).filteredDevices;
return DeviceManagementBody(devices: devices);
} else {
return const Center(child: Text('No Devices Found'));
return const Center(child: Text('Error fetching Devices'));
}
},
),
@ -38,3 +40,6 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
);
}
}

View File

@ -1,13 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/core/extension/build_context_x.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/common/custom_table.dart';
import 'package:syncrow_web/pages/common/filter/filter_widget.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/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/shared/device_control_dialog.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.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/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';
@ -27,6 +27,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
int offlineCount = 0;
int lowBatteryCount = 0;
bool isControlButtonEnabled = false;
List<AllDevicesModel> selectedDevices = [];
if (state is DeviceManagementLoaded) {
devicesToShow = state.devices;
@ -34,87 +35,108 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
onlineCount = state.onlineCount;
offlineCount = state.offlineCount;
lowBatteryCount = state.lowBatteryCount;
isControlButtonEnabled = state.selectedDevice != null;
isControlButtonEnabled = state.isControlButtonEnabled;
selectedDevices = state.selectedDevice ?? [];
} else if (state is DeviceManagementFiltered) {
devicesToShow = state.filteredDevices;
selectedIndex = state.selectedIndex;
onlineCount = state.onlineCount;
offlineCount = state.offlineCount;
lowBatteryCount = state.lowBatteryCount;
isControlButtonEnabled = state.selectedDevice != null;
isControlButtonEnabled = state.isControlButtonEnabled;
selectedDevices = state.selectedDevice ?? [];
} else if (state is DeviceManagementInitial) {
devicesToShow = [];
selectedIndex = 0;
isControlButtonEnabled = false;
}
final tabs = [
'All (${devices.length})',
'All',
'Online ($onlineCount)',
'Offline ($offlineCount)',
'Low Battery ($lowBatteryCount)',
];
return CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Container(
padding: isLargeScreenSize(context)
? const EdgeInsets.all(30)
: const EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FilterWidget(
size: MediaQuery.of(context).size,
tabs: tabs,
selectedIndex: selectedIndex,
onTabChanged: (index) {
context
.read<DeviceManagementBloc>()
.add(SelectedFilterChanged(index));
},
),
const SizedBox(height: 20),
const DeviceSearchFilters(),
const SizedBox(height: 12),
Container(
height: 43,
width: isSmallScreenSize(context) ? double.infinity : 100,
decoration: containerDecoration,
child: Center(
child: DefaultButton(
onPressed: isControlButtonEnabled
? () {
final selectedDevice = context
.read<DeviceManagementBloc>()
.selectedDevices
.first;
final buttonLabel =
(selectedDevices.length > 1) ? 'Batch Control' : 'Control';
return Column(
children: [
Container(
padding: isLargeScreenSize(context)
? const EdgeInsets.all(30)
: const EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FilterWidget(
size: MediaQuery.of(context).size,
tabs: tabs,
selectedIndex: selectedIndex,
onTabChanged: (index) {
context
.read<DeviceManagementBloc>()
.add(SelectedFilterChanged(index));
},
),
const SizedBox(height: 20),
const DeviceSearchFilters(),
const SizedBox(height: 12),
Container(
height: 45,
width: 125,
decoration: containerDecoration,
child: Center(
child: DefaultButton(
onPressed: isControlButtonEnabled
? () {
if (selectedDevices.length == 1) {
showDialog(
context: context,
builder: (context) => DeviceControlDialog(
device: selectedDevice),
device: selectedDevices.first,
),
);
} else if (selectedDevices.length > 1) {
final productTypes = selectedDevices
.map((device) => device.productType)
.toSet();
if (productTypes.length == 1) {
showDialog(
context: context,
builder: (context) =>
DeviceBatchControlDialog(
devices: selectedDevices,
),
);
}
}
: null,
borderRadius: 9,
child: Text(
'Control',
style: TextStyle(
color: isControlButtonEnabled
? Colors.white
: Colors.grey,
),
}
: null,
borderRadius: 9,
child: Text(
buttonLabel,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: isControlButtonEnabled
? Colors.white
: Colors.grey,
),
),
),
),
],
),
),
],
),
),
SliverFillRemaining(
Expanded(
child: Padding(
padding: isLargeScreenSize(context)
? const EdgeInsets.all(30)
: const EdgeInsets.all(15),
child: DynamicTable(
withSelectAll: true,
cellDecoration: containerDecoration,
onRowSelected: (index, isSelected, row) {
final selectedDevice = devicesToShow[index];
@ -123,28 +145,42 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
.add(SelectDevice(selectedDevice));
},
withCheckBox: true,
size: context.screenSize,
size: MediaQuery.of(context).size,
uuidIndex: 2,
headers: const [
'Device Name',
'Product Name',
'Device ID',
'Unit Name',
'Room',
'Space Name',
'location',
'Battery Level',
'Installation Date and Time',
'Status',
'Last Offline Date and Time',
],
data: devicesToShow.map((device) {
final combinedSpaceNames = device.spaces != null
? device.spaces!
.map((space) => space.spaceName)
.join(' > ') +
(device.community != null
? ' > ${device.community!.name}'
: '')
: (device.community != null
? device.community!.name
: '');
return [
device.categoryName ?? '',
device.name ?? '',
device.productName ?? '',
device.uuid ?? '',
device.unit?.name ?? '',
device.room?.name ?? '',
(device.spaces != null && device.spaces!.isNotEmpty)
? device.spaces![0].spaceName
: '',
combinedSpaceNames,
device.batteryLevel != null
? '${device.batteryLevel}%'
: '',
: '-',
formatDateTime(DateTime.fromMillisecondsSinceEpoch(
(device.createTime ?? 0) * 1000)),
device.online == true ? 'Online' : 'Offline',
@ -152,10 +188,20 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
(device.updateTime ?? 0) * 1000)),
];
}).toList(),
onSelectionChanged: (selectedRows) {
context
.read<DeviceManagementBloc>()
.add(UpdateSelection(selectedRows));
},
initialSelectedIds: context
.read<DeviceManagementBloc>()
.selectedDevices
.map((device) => device.uuid!)
.toList(),
isEmpty: devicesToShow.isEmpty,
),
),
),
)
],
);
},

View File

@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/style.dart';
class DeviceSearchFilters extends StatefulWidget {
const DeviceSearchFilters({super.key});
@ -28,12 +29,12 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
@override
Widget build(BuildContext context) {
return isLargeScreenSize(context)
return isExtraLargeScreenSize(context)
? Row(
children: [
_buildSearchField("Community", communityController, 200),
const SizedBox(width: 20),
_buildSearchField("Unit Name", unitNameController, 200),
_buildSearchField("Space Name", unitNameController, 200),
const SizedBox(width: 20),
_buildSearchField(
"Device Name / Product Name", productNameController, 300),
@ -45,10 +46,17 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
spacing: 20,
runSpacing: 10,
children: [
_buildSearchField("Community", communityController, 200),
_buildSearchField("Unit Name", unitNameController, 200),
_buildSearchField(
"Device Name / Product Name", productNameController, 300),
"Community",
communityController,
200,
),
_buildSearchField("Space Name", unitNameController, 200),
_buildSearchField(
"Device Name / Product Name",
productNameController,
300,
),
_buildSearchResetButtons(),
],
);
@ -56,11 +64,20 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
Widget _buildSearchField(
String title, TextEditingController controller, double width) {
return StatefulTextField(
title: title,
width: width,
elevation: 2,
controller: controller,
return Container(
child: StatefulTextField(
title: title,
width: width,
elevation: 2,
controller: controller,
onSubmitted: () {
context.read<DeviceManagementBloc>().add(SearchDevices(
productName: productNameController.text,
unitName: unitNameController.text,
community: communityController.text,
searchField: true));
},
),
);
}
@ -68,16 +85,18 @@ class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
return SearchResetButtons(
onSearch: () {
context.read<DeviceManagementBloc>().add(SearchDevices(
community: communityController.text,
unitName: unitNameController.text,
productName: productNameController.text,
));
community: communityController.text,
unitName: unitNameController.text,
productName: productNameController.text,
searchField: true));
},
onReset: () {
communityController.clear();
unitNameController.clear();
productNameController.clear();
context.read<DeviceManagementBloc>().add(FetchDevices());
context.read<DeviceManagementBloc>()
..add(ResetFilters())
..add(FetchDevices());
},
);
}