push fetch devices and connecting the filters

This commit is contained in:
ashrafzarkanisala
2024-08-24 16:37:10 +03:00
parent 0c047de9c1
commit 2597cdc311
68 changed files with 1800 additions and 989 deletions

View File

@ -1,13 +1,130 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/models/devices_model.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
part 'device_managment_event.dart';
part 'device_managment_state.dart';
class DeviceManagmentBloc extends Bloc<DeviceManagmentEvent, DeviceManagmentState> {
DeviceManagmentBloc() : super(DeviceManagmentInitial()) {
on<DeviceManagmentEvent>((event, emit) {
// TODO: implement event handler
});
class DeviceManagementBloc
extends Bloc<DeviceManagementEvent, DeviceManagementState> {
int _selectedIndex = 0;
List<AllDevicesModel> _devices = [];
int _onlineCount = 0;
int _offlineCount = 0;
int _lowBatteryCount = 0;
DeviceManagementBloc() : super(DeviceManagementInitial()) {
on<FetchDevices>(_onFetchDevices);
on<FilterDevices>(_onFilterDevices);
on<SelectedFilterChanged>(_onSelectedFilterChanged);
on<SearchDevices>(_onSearchDevices);
}
Future<void> _onFetchDevices(
FetchDevices event, Emitter<DeviceManagementState> emit) async {
emit(DeviceManagementLoading());
try {
final devices = await DevicesManagementApi().fetchDevices();
_devices = devices;
_calculateDeviceCounts();
emit(DeviceManagementLoaded(
devices: devices,
selectedIndex: _selectedIndex,
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
));
} catch (e) {
emit(DeviceManagementInitial());
}
}
void _onFilterDevices(
FilterDevices event, Emitter<DeviceManagementState> emit) {
if (_devices.isNotEmpty) {
final filteredDevices = _devices.where((device) {
switch (event.filter) {
case 'Online':
return device.online == true;
case 'Offline':
return device.online == false;
case 'Low Battery':
return device.batteryLevel != null && device.batteryLevel! < 20;
default:
return true;
}
}).toList();
emit(DeviceManagementFiltered(
filteredDevices: filteredDevices,
selectedIndex: _selectedIndex,
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
));
}
}
void _onSelectedFilterChanged(
SelectedFilterChanged event, Emitter<DeviceManagementState> emit) {
_selectedIndex = event.selectedIndex;
add(FilterDevices(_getFilterFromIndex(_selectedIndex)));
}
void _calculateDeviceCounts() {
_onlineCount = _devices.where((device) => device.online == true).length;
_offlineCount = _devices.where((device) => device.online == false).length;
_lowBatteryCount = _devices
.where((device) =>
device.batteryLevel != null && device.batteryLevel! < 20)
.length;
}
String _getFilterFromIndex(int index) {
switch (index) {
case 1:
return 'Online';
case 2:
return 'Offline';
case 3:
return 'Low Battery';
default:
return 'All';
}
}
void _onSearchDevices(
SearchDevices event, Emitter<DeviceManagementState> emit) {
if (_devices.isNotEmpty) {
final filteredDevices = _devices.where((device) {
final matchesCommunity = event.community == null ||
event.community!.isEmpty ||
(device.room?.name
?.toLowerCase()
.contains(event.community!.toLowerCase()) ??
false);
final matchesUnit = event.unitName == null ||
event.unitName!.isEmpty ||
(device.unit?.name
?.toLowerCase()
.contains(event.unitName!.toLowerCase()) ??
false);
final matchesProductName = event.productName == null ||
event.productName!.isEmpty ||
(device.name
?.toLowerCase()
.contains(event.productName!.toLowerCase()) ??
false);
return matchesCommunity && matchesUnit && matchesProductName;
}).toList();
emit(DeviceManagementFiltered(
filteredDevices: filteredDevices,
selectedIndex: _selectedIndex,
onlineCount: _onlineCount,
offlineCount: _offlineCount,
lowBatteryCount: _lowBatteryCount,
));
}
}
}

View File

@ -1,8 +1,43 @@
part of 'device_managment_bloc.dart';
sealed class DeviceManagmentEvent extends Equatable {
const DeviceManagmentEvent();
abstract class DeviceManagementEvent extends Equatable {
const DeviceManagementEvent();
@override
List<Object> get props => [];
List<Object?> get props => [];
}
class FetchDevices extends DeviceManagementEvent {}
class FilterDevices extends DeviceManagementEvent {
final String filter;
const FilterDevices(this.filter);
@override
List<Object?> get props => [filter];
}
class SelectedFilterChanged extends DeviceManagementEvent {
final int selectedIndex;
const SelectedFilterChanged(this.selectedIndex);
@override
List<Object?> get props => [selectedIndex];
}
class SearchDevices extends DeviceManagementEvent {
final String? community;
final String? unitName;
final String? productName;
const SearchDevices({
this.community,
this.unitName,
this.productName,
});
@override
List<Object?> get props => [community, unitName, productName];
}

View File

@ -1,10 +1,57 @@
part of 'device_managment_bloc.dart';
sealed class DeviceManagmentState extends Equatable {
const DeviceManagmentState();
abstract class DeviceManagementState extends Equatable {
const DeviceManagementState();
@override
List<Object> get props => [];
List<Object?> get props => [];
}
final class DeviceManagmentInitial extends DeviceManagmentState {}
class DeviceManagementInitial extends DeviceManagementState {}
class DeviceManagementLoading extends DeviceManagementState {}
class DeviceManagementLoaded extends DeviceManagementState {
final List<AllDevicesModel> devices;
final int selectedIndex;
final int onlineCount;
final int offlineCount;
final int lowBatteryCount;
const DeviceManagementLoaded({
required this.devices,
required this.selectedIndex,
required this.onlineCount,
required this.offlineCount,
required this.lowBatteryCount,
});
@override
List<Object?> get props =>
[devices, selectedIndex, onlineCount, offlineCount, lowBatteryCount];
}
class DeviceManagementFiltered extends DeviceManagementState {
final List<AllDevicesModel> filteredDevices;
final int selectedIndex;
final int onlineCount;
final int offlineCount;
final int lowBatteryCount;
const DeviceManagementFiltered({
required this.filteredDevices,
required this.selectedIndex,
required this.onlineCount,
required this.offlineCount,
required this.lowBatteryCount,
});
@override
List<Object?> get props => [
filteredDevices,
selectedIndex,
onlineCount,
offlineCount,
lowBatteryCount
];
}

View File

@ -0,0 +1,157 @@
import 'package:syncrow_web/pages/device_managment/models/room.dart';
import 'package:syncrow_web/pages/device_managment/models/unit.dart';
class AllDevicesModel {
/*
{
"room": {
"uuid": "75ea7d60-5104-4726-b5f8-ea426c0c6a1b",
"name": "Room 1"
},
"unit": {
"uuid": "04fd1dcf-f24a-40db-970d-d0be884ed30f",
"name": "unit 1"
},
"productUuid": "894aad5c-ce03-423a-9d61-2fd0c3f67ebf",
"productType": "3G",
"permissionType": "CONTROLLABLE",
"activeTime": 1722173778,
"category": "kg",
"categoryName": "Switch",
"createTime": 1722173778,
"gatewayId": "bf0294123ed2c19067skrk",
"icon": "smart/icon/bay1642572935385vcsA/2b1f5efbaa5bbf81c3164fa312cf2032.png",
"ip": "",
"lat": "31.97",
"localKey": "T/39+<l/![iv>:9M",
"lon": "35.89",
"model": "S01ZLSWBSA3",
"name": "3 Gang Button Switch L-L",
"nodeId": "60a423fffed5a7f6",
"online": true,
"ownerId": "199200732",
"sub": true,
"timeZone": "+03:00",
"updateTime": 1723626515,
"uuid": "5b31dae4-ce9c-4c70-b52b-7e15011163bf"
}
*/
DevicesModelRoom? room;
DevicesModelUnit? unit;
String? productUuid;
String? productType;
String? permissionType;
int? activeTime;
String? category;
String? categoryName;
int? createTime;
String? gatewayId;
String? icon;
String? ip;
String? lat;
String? localKey;
String? lon;
String? model;
String? name;
String? nodeId;
bool? online;
String? ownerId;
bool? sub;
String? timeZone;
int? updateTime;
String? uuid;
int? batteryLevel;
AllDevicesModel({
this.room,
this.unit,
this.productUuid,
this.productType,
this.permissionType,
this.activeTime,
this.category,
this.categoryName,
this.createTime,
this.gatewayId,
this.icon,
this.ip,
this.lat,
this.localKey,
this.lon,
this.model,
this.name,
this.nodeId,
this.online,
this.ownerId,
this.sub,
this.timeZone,
this.updateTime,
this.uuid,
this.batteryLevel,
});
AllDevicesModel.fromJson(Map<String, dynamic> json) {
room = (json['room'] != null && (json['room'] is Map))
? DevicesModelRoom.fromJson(json['room'])
: null;
unit = (json['unit'] != null && (json['unit'] is Map))
? DevicesModelUnit.fromJson(json['unit'])
: null;
productUuid = json['productUuid']?.toString();
productType = json['productType']?.toString();
permissionType = json['permissionType']?.toString();
activeTime = int.tryParse(json['activeTime']?.toString() ?? '');
category = json['category']?.toString();
categoryName = json['categoryName']?.toString();
createTime = int.tryParse(json['createTime']?.toString() ?? '');
gatewayId = json['gatewayId']?.toString();
icon = json['icon']?.toString();
ip = json['ip']?.toString();
lat = json['lat']?.toString();
localKey = json['localKey']?.toString();
lon = json['lon']?.toString();
model = json['model']?.toString();
name = json['name']?.toString();
nodeId = json['nodeId']?.toString();
online = json['online'];
ownerId = json['ownerId']?.toString();
sub = json['sub'];
timeZone = json['timeZone']?.toString();
updateTime = int.tryParse(json['updateTime']?.toString() ?? '');
uuid = json['uuid']?.toString();
batteryLevel = int.tryParse(json['batteryLevel']?.toString() ?? '');
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (room != null) {
data['room'] = room!.toJson();
}
if (unit != null) {
data['unit'] = unit!.toJson();
}
data['productUuid'] = productUuid;
data['productType'] = productType;
data['permissionType'] = permissionType;
data['activeTime'] = activeTime;
data['category'] = category;
data['categoryName'] = categoryName;
data['createTime'] = createTime;
data['gatewayId'] = gatewayId;
data['icon'] = icon;
data['ip'] = ip;
data['lat'] = lat;
data['localKey'] = localKey;
data['lon'] = lon;
data['model'] = model;
data['name'] = name;
data['nodeId'] = nodeId;
data['online'] = online;
data['ownerId'] = ownerId;
data['sub'] = sub;
data['timeZone'] = timeZone;
data['updateTime'] = updateTime;
data['uuid'] = uuid;
data['batteryLevel'] = batteryLevel;
return data;
}
}

View File

@ -0,0 +1,26 @@
class DevicesModelRoom {
/*
{
"uuid": "75ea7d60-5104-4726-b5f8-ea426c0c6a1b",
"name": "Room 1"
}
*/
String? uuid;
String? name;
DevicesModelRoom({
this.uuid,
this.name,
});
DevicesModelRoom.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

@ -0,0 +1,26 @@
class DevicesModelUnit {
/*
{
"uuid": "04fd1dcf-f24a-40db-970d-d0be884ed30f",
"name": "unit 1"
}
*/
String? uuid;
String? name;
DevicesModelUnit({
this.uuid,
this.name,
});
DevicesModelUnit.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

@ -1,4 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/widgets/device_managment_body.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
@ -7,17 +9,31 @@ class DeviceManagementPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return WebScaffold(
enableMenuSideba: true,
appBarTitle: Row(
children: [
Text(
'Device Management',
style: Theme.of(context).textTheme.headlineLarge,
)
],
return BlocProvider(
create: (context) => DeviceManagementBloc()..add(FetchDevices()),
child: WebScaffold(
appBarTitle: Text(
'Device Management',
style: Theme.of(context).textTheme.headlineLarge,
),
enableMenuSideba: true,
scaffoldBody: BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
builder: (context, state) {
if (state is DeviceManagementLoading) {
return const Center(child: CircularProgressIndicator());
} 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'));
}
},
),
),
scaffoldBody: const DeviceManagementBody(),
);
}
}

View File

@ -1,92 +1,94 @@
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/search_reset_buttons.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/common/text_field/custom_text_field.dart';
import 'package:syncrow_web/pages/device_managment/bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/models/devices_model.dart';
import 'package:syncrow_web/pages/device_managment/widgets/device_search_filters.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';
class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
const DeviceManagementBody({super.key});
const DeviceManagementBody({super.key, required this.devices});
final List<AllDevicesModel> devices;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(30),
height: context.screenHeight,
width: context.screenWidth,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FilterWidget(
size: context.screenSize,
tabs: ['All', 'Online', 'Offline', 'Low Battery'],
selectedIndex: 0,
onTabChanged: (index) {},
),
const SizedBox(
height: 20,
),
if (isLargeScreenSize(context)) ...[
Row(
children: [
const StatefulTextField(
title: "Community",
width: 200,
elevation: 2,
return BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
builder: (context, state) {
List<AllDevicesModel> devicesToShow = [];
int selectedIndex = 0;
int onlineCount = 0;
int offlineCount = 0;
int lowBatteryCount = 0;
if (state is DeviceManagementLoaded) {
devicesToShow = state.devices;
selectedIndex = state.selectedIndex;
onlineCount = state.onlineCount;
offlineCount = state.offlineCount;
lowBatteryCount = state.lowBatteryCount;
} else if (state is DeviceManagementFiltered) {
devicesToShow = state.filteredDevices;
selectedIndex = state.selectedIndex;
onlineCount = state.onlineCount;
offlineCount = state.offlineCount;
lowBatteryCount = state.lowBatteryCount;
}
// Create tab labels with counts
final tabs = [
'All (${devices.length})',
'Online ($onlineCount)',
'Offline ($offlineCount)',
'Low Battery ($lowBatteryCount)',
];
return Container(
padding: const EdgeInsets.all(30),
height: context.screenHeight,
width: context.screenWidth,
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: 100,
decoration: containerDecoration,
child: Center(
child: DefaultButton(
onPressed: () {},
borderRadius: 9,
child: const Text('Control'),
),
),
const SizedBox(width: 20),
const StatefulTextField(
title: "Unit Name",
width: 200,
elevation: 2,
),
const SizedBox(width: 20),
const StatefulTextField(
title: "Device Name / Product Name",
width: 300,
elevation: 2,
),
const SizedBox(width: 20),
SearchResetButtons(
onSearch: () {},
onReset: () {},
),
],
),
] else ...[
Wrap(
spacing: 20,
runSpacing: 10,
children: [
const StatefulTextField(
title: "Community",
width: 200,
elevation: 2,
),
const StatefulTextField(
title: "Unit Name",
width: 200,
elevation: 2,
),
const StatefulTextField(
title: "Device Name / Product Name",
width: 300,
elevation: 2,
),
SearchResetButtons(
onSearch: () {},
onReset: () {},
),
],
),
],
const SizedBox(
height: 12,
),
Expanded(
child: DynamicTable(
),
const SizedBox(
height: 12,
),
Expanded(
child: DynamicTable(
cellDecoration: containerDecoration,
selectAll: (p0) {
// visitorBloc.selectedDeviceIds.clear();
@ -101,29 +103,38 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
withCheckBox: true,
size: context.screenSize,
headers: const [
'Device Name',
'Product Name',
'Device ID',
'Unit Name',
'Room',
'Battery Level',
'Installation Date and Time',
'Status',
'Last Offline Date and Time',
],
data: []
// state.data.map((item) {
// return [
// item.name.toString(),
// item.uuid.toString(),
// item.productType.toString(),
// '',
// item.online.value.toString(),
// ];
// }).toList(),
)),
],
),
'Device Name',
'Product Name',
'Device ID',
'Unit Name',
'Room',
'Battery Level',
'Installation Date and Time',
'Status',
'Last Offline Date and Time',
],
data: devicesToShow.map((device) {
return [
device.categoryName ?? '',
device.name ?? '',
device.uuid ?? '',
device.unit?.name ?? '',
device.room?.name ?? '',
device.batteryLevel?.toString() ?? '',
formatDateTime(DateTime.fromMillisecondsSinceEpoch(
device.createTime ?? 0)),
device.online == true ? 'Online' : 'Offline',
formatDateTime(DateTime.fromMillisecondsSinceEpoch(
device.updateTime ?? 0)),
];
}).toList(),
isEmpty: devicesToShow.isEmpty,
),
),
],
),
);
},
);
}
}

View File

@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/text_field/custom_text_field.dart';
import 'package:syncrow_web/pages/device_managment/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';
class DeviceSearchFilters extends StatefulWidget {
const DeviceSearchFilters({super.key});
@override
State<DeviceSearchFilters> createState() => _DeviceSearchFiltersState();
}
class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
with HelperResponsiveLayout {
final TextEditingController communityController = TextEditingController();
final TextEditingController unitNameController = TextEditingController();
final TextEditingController productNameController = TextEditingController();
@override
void dispose() {
communityController.dispose();
unitNameController.dispose();
productNameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return isLargeScreenSize(context)
? Row(
children: [
_buildSearchField("Community", communityController, 200),
const SizedBox(width: 20),
_buildSearchField("Unit Name", unitNameController, 200),
const SizedBox(width: 20),
_buildSearchField(
"Device Name / Product Name", productNameController, 300),
const SizedBox(width: 20),
_buildSearchResetButtons(),
],
)
: Wrap(
spacing: 20,
runSpacing: 10,
children: [
_buildSearchField("Community", communityController, 200),
_buildSearchField("Unit Name", unitNameController, 200),
_buildSearchField(
"Device Name / Product Name", productNameController, 300),
_buildSearchResetButtons(),
],
);
}
Widget _buildSearchField(
String title, TextEditingController controller, double width) {
return StatefulTextField(
title: title,
width: width,
elevation: 2,
controller: controller,
);
}
Widget _buildSearchResetButtons() {
return SearchResetButtons(
onSearch: () {
context.read<DeviceManagementBloc>().add(SearchDevices(
community: communityController.text,
unitName: unitNameController.text,
productName: productNameController.text,
));
},
onReset: () {
communityController.clear();
unitNameController.clear();
productNameController.clear();
context.read<DeviceManagementBloc>().add(FetchDevices());
},
);
}
}