mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-17 02:25:31 +00:00
Compare commits
64 Commits
SP-1513-FE
...
charts-rew
Author | SHA1 | Date | |
---|---|---|---|
000fe70663 | |||
4257f7f0f3 | |||
b2bf3866a9 | |||
fd2a09cada | |||
4c2802acfc | |||
15343be258 | |||
c21842cc6d | |||
4326559e14 | |||
625f737791 | |||
494ae1c941 | |||
f67d0e2912 | |||
17aad13b2a | |||
a849c1dafb | |||
3e3e17019a | |||
b1bae3cb15 | |||
051bf657ed | |||
5191c1e456 | |||
7a073f10aa | |||
900d47faae | |||
e35a7fdc70 | |||
d80f5e1f3a | |||
c07b53107e | |||
39d125ac7e | |||
ad15d0e138 | |||
e6d272a60d | |||
8dfe8d10d4 | |||
5279020d08 | |||
da481536c4 | |||
f21366268a | |||
c3aef736fd | |||
887ac58f40 | |||
c709477500 | |||
63e7b3faa2 | |||
0e61e52bf8 | |||
7515b347ce | |||
3dfbcb5935 | |||
4fd4a9b5bf | |||
14fa1b355e | |||
78d4e58996 | |||
23b9cb5b78 | |||
401d0a9788 | |||
ac2b0d3fac | |||
3be7a377c0 | |||
e4ee456384 | |||
f02c5d71ba | |||
d45ff262c7 | |||
ad227febc1 | |||
a9d6c6f4ee | |||
4d9e57c8b5 | |||
d1bb8da484 | |||
300f9ae358 | |||
c1dab3400b | |||
46815585cb | |||
7f9d044f7e | |||
996a847a27 | |||
5645fb7826 | |||
e8f7c29652 | |||
36c5712c79 | |||
c7fef11aec | |||
ef29d78d70 | |||
cd9941f544 | |||
71aa64ba9e | |||
2262d3b2ba | |||
b7ef9da35d |
1
.gitignore
vendored
1
.gitignore
vendored
@ -30,6 +30,7 @@ migrate_working_dir/
|
|||||||
.pub-cache/
|
.pub-cache/
|
||||||
.pub/
|
.pub/
|
||||||
/build/
|
/build/
|
||||||
|
pubspec.lock
|
||||||
|
|
||||||
# Symbolication related
|
# Symbolication related
|
||||||
app.*.symbols
|
app.*.symbols
|
||||||
|
12
assets/icons/refresh_status_icon.svg
Normal file
12
assets/icons/refresh_status_icon.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_7305_15779)">
|
||||||
|
<path d="M17.0872 11.5142C17.0872 13.2025 16.427 14.8021 15.2211 15.9954C14.0278 17.2014 12.4283 17.8615 10.7399 17.8615C9.05141 17.8615 7.45185 17.2014 6.25856 15.9954C5.05262 14.8021 4.39249 13.2025 4.39249 11.5142C4.39249 9.82574 5.05266 8.22618 6.25856 7.03289C7.45185 5.8269 9.05141 5.16681 10.7399 5.16681C11.8063 5.16681 12.8471 5.43337 13.7866 5.95388L11.2984 8.97523H21.0861L18.6486 0L16.2113 2.97053C14.5737 1.91691 12.6948 1.35835 10.7398 1.35835C8.02314 1.35835 5.47142 2.41197 3.55459 4.32888C1.63765 6.24578 0.583984 8.79747 0.583984 11.5142C0.583984 14.2309 1.63765 16.7825 3.55459 18.6994C5.47146 20.6163 8.0231 21.67 10.7398 21.67C13.4565 21.67 16.0082 20.6163 17.925 18.6994C19.8419 16.7825 20.8956 14.2309 20.8956 11.5142V10.8794H17.0872V11.5142Z" fill="#77DD00"/>
|
||||||
|
<path d="M17.0876 10.8799H20.8961V11.5146C20.8961 14.2313 19.8424 16.7829 17.9254 18.6998C16.0086 20.6168 13.4569 21.6704 10.7402 21.6704V17.862C12.4287 17.862 14.0282 17.2019 15.2215 15.9959C16.4275 14.8026 17.0876 13.203 17.0876 11.5147V10.8799H17.0876Z" fill="#66BB00"/>
|
||||||
|
<path d="M13.787 5.95388C12.8475 5.43333 11.8066 5.16681 10.7402 5.16681V1.35835C12.6952 1.35835 14.5741 1.91691 16.2117 2.97057L18.6491 0L21.0866 8.97523H11.2989L13.787 5.95388Z" fill="#66BB00"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_7305_15779">
|
||||||
|
<rect width="21.67" height="21.67" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
71
lib/pages/analytics/models/analytics_device.dart
Normal file
71
lib/pages/analytics/models/analytics_device.dart
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
class AnalyticsDevice {
|
||||||
|
const AnalyticsDevice({
|
||||||
|
required this.uuid,
|
||||||
|
required this.name,
|
||||||
|
this.createdAt,
|
||||||
|
this.updatedAt,
|
||||||
|
this.deviceTuyaUuid,
|
||||||
|
this.isActive,
|
||||||
|
this.productDevice,
|
||||||
|
this.spaceUuid,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String uuid;
|
||||||
|
final String name;
|
||||||
|
final DateTime? createdAt;
|
||||||
|
final DateTime? updatedAt;
|
||||||
|
final String? deviceTuyaUuid;
|
||||||
|
final bool? isActive;
|
||||||
|
final ProductDevice? productDevice;
|
||||||
|
final String? spaceUuid;
|
||||||
|
|
||||||
|
factory AnalyticsDevice.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AnalyticsDevice(
|
||||||
|
uuid: json['uuid'] as String,
|
||||||
|
name: json['name'] as String,
|
||||||
|
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null,
|
||||||
|
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null,
|
||||||
|
deviceTuyaUuid: json['deviceTuyaUuid'] as String?,
|
||||||
|
isActive: json['isActive'] as bool?,
|
||||||
|
productDevice: json['productDevice'] != null
|
||||||
|
? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>)
|
||||||
|
: null,
|
||||||
|
spaceUuid: (json['spaces'] as List<dynamic>?)
|
||||||
|
?.map((e) => e['uuid'])
|
||||||
|
.firstOrNull
|
||||||
|
?.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProductDevice {
|
||||||
|
const ProductDevice({
|
||||||
|
this.uuid,
|
||||||
|
this.createdAt,
|
||||||
|
this.updatedAt,
|
||||||
|
this.catName,
|
||||||
|
this.prodId,
|
||||||
|
this.name,
|
||||||
|
this.prodType,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String? uuid;
|
||||||
|
final DateTime? createdAt;
|
||||||
|
final DateTime? updatedAt;
|
||||||
|
final String? catName;
|
||||||
|
final String? prodId;
|
||||||
|
final String? name;
|
||||||
|
final String? prodType;
|
||||||
|
|
||||||
|
factory ProductDevice.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ProductDevice(
|
||||||
|
uuid: json['uuid'] as String?,
|
||||||
|
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null,
|
||||||
|
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null,
|
||||||
|
catName: json['catName'] as String?,
|
||||||
|
prodId: json['prodId'] as String?,
|
||||||
|
name: json['name'] as String?,
|
||||||
|
prodType: json['prodType'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +1,28 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
class OccupancyHeatMapModel extends Equatable {
|
class OccupancyHeatMapModel extends Equatable {
|
||||||
final DateTime date;
|
final String uuid;
|
||||||
|
|
||||||
final int occupancy;
|
final DateTime eventDate;
|
||||||
|
|
||||||
|
final int countTotalPresenceDetected;
|
||||||
|
|
||||||
const OccupancyHeatMapModel({
|
const OccupancyHeatMapModel({
|
||||||
required this.date,
|
required this.uuid,
|
||||||
required this.occupancy,
|
required this.eventDate,
|
||||||
|
required this.countTotalPresenceDetected,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory OccupancyHeatMapModel.fromJson(Map<String, dynamic> json) {
|
factory OccupancyHeatMapModel.fromJson(Map<String, dynamic> json) {
|
||||||
return OccupancyHeatMapModel(
|
return OccupancyHeatMapModel(
|
||||||
date: DateTime.parse(json['date'] as String),
|
uuid: json['uuid'] as String? ?? '',
|
||||||
occupancy: json['occupancy'] as int,
|
eventDate: DateTime.parse(
|
||||||
|
json['event_date'] as String? ?? '${DateTime.now()}',
|
||||||
|
),
|
||||||
|
countTotalPresenceDetected: json['count_total_presence_detected'] as int? ?? 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [date, occupancy];
|
List<Object?> get props => [uuid, eventDate, countTotalPresenceDetected];
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,66 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
class PhasesEnergyConsumption extends Equatable {
|
class PhasesEnergyConsumption extends Equatable {
|
||||||
final int month;
|
final String uuid;
|
||||||
final double phaseA;
|
final DateTime createdAt;
|
||||||
final double phaseB;
|
final DateTime updatedAt;
|
||||||
final double phaseC;
|
final String deviceUuid;
|
||||||
|
final DateTime date;
|
||||||
|
final double energyConsumedKw;
|
||||||
|
final double energyConsumedA;
|
||||||
|
final double energyConsumedB;
|
||||||
|
final double energyConsumedC;
|
||||||
|
|
||||||
const PhasesEnergyConsumption({
|
const PhasesEnergyConsumption({
|
||||||
required this.month,
|
required this.uuid,
|
||||||
required this.phaseA,
|
required this.createdAt,
|
||||||
required this.phaseB,
|
required this.updatedAt,
|
||||||
required this.phaseC,
|
required this.deviceUuid,
|
||||||
|
required this.date,
|
||||||
|
required this.energyConsumedKw,
|
||||||
|
required this.energyConsumedA,
|
||||||
|
required this.energyConsumedB,
|
||||||
|
required this.energyConsumedC,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [month, phaseA, phaseB, phaseC];
|
List<Object?> get props => [
|
||||||
|
uuid,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
deviceUuid,
|
||||||
|
date,
|
||||||
|
energyConsumedKw,
|
||||||
|
energyConsumedA,
|
||||||
|
energyConsumedB,
|
||||||
|
energyConsumedC,
|
||||||
|
];
|
||||||
|
|
||||||
factory PhasesEnergyConsumption.fromJson(Map<String, dynamic> json) {
|
factory PhasesEnergyConsumption.fromJson(Map<String, dynamic> json) {
|
||||||
return PhasesEnergyConsumption(
|
return PhasesEnergyConsumption(
|
||||||
month: json['month'] as int,
|
uuid: json['uuid'] as String,
|
||||||
phaseA: (json['phaseA'] as num).toDouble(),
|
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||||
phaseB: (json['phaseB'] as num).toDouble(),
|
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||||||
phaseC: (json['phaseC'] as num).toDouble(),
|
deviceUuid: json['deviceUuid'] as String,
|
||||||
|
date: DateTime.parse(json['date'] as String),
|
||||||
|
energyConsumedKw: double.parse(json['energyConsumedKw']),
|
||||||
|
energyConsumedA: double.parse(json['energyConsumedA']),
|
||||||
|
energyConsumedB: double.parse(json['energyConsumedB']),
|
||||||
|
energyConsumedC: double.parse(json['energyConsumedC']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'uuid': uuid,
|
||||||
|
'createdAt': createdAt.toIso8601String(),
|
||||||
|
'updatedAt': updatedAt.toIso8601String(),
|
||||||
|
'deviceUuid': deviceUuid,
|
||||||
|
'date': date.toIso8601String().split('T')[0],
|
||||||
|
'energyConsumedKw': energyConsumedKw.toString(),
|
||||||
|
'energyConsumedA': energyConsumedA.toString(),
|
||||||
|
'energyConsumedB': energyConsumedB.toString(),
|
||||||
|
'energyConsumedC': energyConsumedC.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart';
|
||||||
|
|
||||||
|
part 'analytics_devices_event.dart';
|
||||||
|
part 'analytics_devices_state.dart';
|
||||||
|
|
||||||
|
class AnalyticsDevicesBloc
|
||||||
|
extends Bloc<AnalyticsDevicesEvent, AnalyticsDevicesState> {
|
||||||
|
AnalyticsDevicesBloc(
|
||||||
|
this._analyticsDevicesService,
|
||||||
|
) : super(const AnalyticsDevicesState()) {
|
||||||
|
on<LoadAnalyticsDevicesEvent>(_onLoadAnalyticsDevices);
|
||||||
|
on<SelectAnalyticsDeviceEvent>(_onSelectAnalyticsDevice);
|
||||||
|
on<ClearAnalyticsDeviceEvent>(_onClearAnalyticsDevice);
|
||||||
|
}
|
||||||
|
final AnalyticsDevicesService _analyticsDevicesService;
|
||||||
|
|
||||||
|
Future<void> _onLoadAnalyticsDevices(
|
||||||
|
LoadAnalyticsDevicesEvent event,
|
||||||
|
Emitter<AnalyticsDevicesState> emit,
|
||||||
|
) async {
|
||||||
|
emit(const AnalyticsDevicesState(status: AnalyticsDevicesStatus.loading));
|
||||||
|
|
||||||
|
try {
|
||||||
|
final devices = await _analyticsDevicesService.getDevices(event.param);
|
||||||
|
emit(
|
||||||
|
AnalyticsDevicesState(
|
||||||
|
status: AnalyticsDevicesStatus.loaded,
|
||||||
|
devices: devices,
|
||||||
|
selectedDevice: devices.firstOrNull,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (devices.isNotEmpty) {
|
||||||
|
event.onSuccess(devices.first);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
AnalyticsDevicesState(
|
||||||
|
status: AnalyticsDevicesStatus.failure,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSelectAnalyticsDevice(
|
||||||
|
SelectAnalyticsDeviceEvent event,
|
||||||
|
Emitter<AnalyticsDevicesState> emit,
|
||||||
|
) {
|
||||||
|
emit(
|
||||||
|
AnalyticsDevicesState(
|
||||||
|
selectedDevice: event.device,
|
||||||
|
devices: state.devices,
|
||||||
|
errorMessage: state.errorMessage,
|
||||||
|
status: state.status,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onClearAnalyticsDevice(
|
||||||
|
ClearAnalyticsDeviceEvent event,
|
||||||
|
Emitter<AnalyticsDevicesState> emit,
|
||||||
|
) {
|
||||||
|
emit(const AnalyticsDevicesState());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
part of 'analytics_devices_bloc.dart';
|
||||||
|
|
||||||
|
sealed class AnalyticsDevicesEvent extends Equatable {
|
||||||
|
const AnalyticsDevicesEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class LoadAnalyticsDevicesEvent extends AnalyticsDevicesEvent {
|
||||||
|
const LoadAnalyticsDevicesEvent({required this.param, required this.onSuccess});
|
||||||
|
|
||||||
|
final GetAnalyticsDevicesParam param;
|
||||||
|
final void Function(AnalyticsDevice device) onSuccess;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [param];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class SelectAnalyticsDeviceEvent extends AnalyticsDevicesEvent {
|
||||||
|
const SelectAnalyticsDeviceEvent(this.device);
|
||||||
|
|
||||||
|
final AnalyticsDevice device;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [device];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ClearAnalyticsDeviceEvent extends AnalyticsDevicesEvent {
|
||||||
|
const ClearAnalyticsDeviceEvent();
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
part of 'analytics_devices_bloc.dart';
|
||||||
|
|
||||||
|
enum AnalyticsDevicesStatus { initial, loading, loaded, failure }
|
||||||
|
|
||||||
|
final class AnalyticsDevicesState extends Equatable {
|
||||||
|
const AnalyticsDevicesState({
|
||||||
|
this.status = AnalyticsDevicesStatus.initial,
|
||||||
|
this.devices = const [],
|
||||||
|
this.errorMessage,
|
||||||
|
this.selectedDevice,
|
||||||
|
});
|
||||||
|
|
||||||
|
final AnalyticsDevicesStatus status;
|
||||||
|
final List<AnalyticsDevice> devices;
|
||||||
|
final AnalyticsDevice? selectedDevice;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [status, devices, errorMessage, selectedDevice];
|
||||||
|
}
|
@ -14,13 +14,25 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
|||||||
CommunityModel community,
|
CommunityModel community,
|
||||||
List<SpaceModel> spaces,
|
List<SpaceModel> spaces,
|
||||||
) {
|
) {
|
||||||
|
// Add to space tree bloc first
|
||||||
context.read<SpaceTreeBloc>().add(
|
context.read<SpaceTreeBloc>().add(
|
||||||
OnCommunitySelected(
|
OnCommunitySelected(
|
||||||
community.uuid,
|
community.uuid,
|
||||||
spaces,
|
spaces,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
FetchEnergyManagementDataHelper.loadEnergyManagementData(context);
|
|
||||||
|
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||||
|
if (spaceTreeState.selectedCommunities.contains(community.uuid)) {
|
||||||
|
clearData(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FetchEnergyManagementDataHelper.loadEnergyManagementData(
|
||||||
|
context,
|
||||||
|
communityId: community.uuid,
|
||||||
|
spaceId: spaces.isNotEmpty ? spaces.first.uuid ?? '' : '',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -36,7 +48,19 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
|||||||
space.children,
|
space.children,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
FetchEnergyManagementDataHelper.loadEnergyManagementData(context);
|
|
||||||
|
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||||
|
if (spaceTreeState.selectedCommunities.contains(community.uuid) ||
|
||||||
|
spaceTreeState.selectedSpaces.contains(space.uuid)) {
|
||||||
|
clearData(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FetchEnergyManagementDataHelper.loadEnergyManagementData(
|
||||||
|
context,
|
||||||
|
communityId: community.uuid,
|
||||||
|
spaceId: space.uuid ?? '',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -45,14 +69,7 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
|||||||
CommunityModel community,
|
CommunityModel community,
|
||||||
SpaceModel child,
|
SpaceModel child,
|
||||||
) {
|
) {
|
||||||
context.read<SpaceTreeBloc>().add(
|
// Do nothing else as per original implementation
|
||||||
OnSpaceSelected(
|
|
||||||
community,
|
|
||||||
child.uuid ?? '',
|
|
||||||
child.children,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
FetchEnergyManagementDataHelper.loadEnergyManagementData(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -17,10 +17,20 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
|
|||||||
context.read<SpaceTreeBloc>().add(
|
context.read<SpaceTreeBloc>().add(
|
||||||
OnCommunitySelected(
|
OnCommunitySelected(
|
||||||
community.uuid,
|
community.uuid,
|
||||||
spaces,
|
spaces.isNotEmpty ? [spaces.first] : [],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
FetchOccupancyDataHelper.loadOccupancyData(context);
|
|
||||||
|
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||||
|
if (spaceTreeState.selectedCommunities.contains(community.uuid)) {
|
||||||
|
clearData(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FetchOccupancyDataHelper.loadOccupancyData(
|
||||||
|
context,
|
||||||
|
communityId: community.uuid,
|
||||||
|
spaceId: spaces.isNotEmpty ? spaces.first.uuid ?? '' : '',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -29,14 +39,32 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
|
|||||||
CommunityModel community,
|
CommunityModel community,
|
||||||
SpaceModel space,
|
SpaceModel space,
|
||||||
) {
|
) {
|
||||||
context.read<SpaceTreeBloc>().add(
|
final spaceTreeBloc = context.read<SpaceTreeBloc>();
|
||||||
OnSpaceSelected(
|
final selectedSpacesIds = spaceTreeBloc.state.selectedSpaces;
|
||||||
community,
|
final isSpaceSelected = selectedSpacesIds.contains(space.uuid);
|
||||||
space.uuid ?? '',
|
|
||||||
space.children,
|
if (selectedSpacesIds.isEmpty) {
|
||||||
),
|
spaceTreeBloc.add(OnCommunitySelected(community.uuid, [space]));
|
||||||
);
|
} else if (isSpaceSelected) {
|
||||||
FetchOccupancyDataHelper.loadOccupancyData(context);
|
spaceTreeBloc.add(const SpaceTreeClearSelectionEvent());
|
||||||
|
} else {
|
||||||
|
spaceTreeBloc
|
||||||
|
..add(const SpaceTreeClearSelectionEvent())
|
||||||
|
..add(OnSpaceSelected(community, space.uuid ?? '', []));
|
||||||
|
}
|
||||||
|
|
||||||
|
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||||
|
if (spaceTreeState.selectedCommunities.contains(community.uuid) ||
|
||||||
|
spaceTreeState.selectedSpaces.contains(space.uuid)) {
|
||||||
|
clearData(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FetchOccupancyDataHelper.loadOccupancyData(
|
||||||
|
context,
|
||||||
|
communityId: community.uuid,
|
||||||
|
spaceId: space.uuid ?? '',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -45,18 +73,12 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
|
|||||||
CommunityModel community,
|
CommunityModel community,
|
||||||
SpaceModel child,
|
SpaceModel child,
|
||||||
) {
|
) {
|
||||||
context.read<SpaceTreeBloc>().add(
|
// Do nothing
|
||||||
OnSpaceSelected(
|
|
||||||
community,
|
|
||||||
child.uuid ?? '',
|
|
||||||
child.children,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
FetchOccupancyDataHelper.loadOccupancyData(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void clearData(BuildContext context) {
|
void clearData(BuildContext context) {
|
||||||
context.read<SpaceTreeBloc>().add(const SpaceTreeClearSelectionEvent());
|
context.read<SpaceTreeBloc>().add(const SpaceTreeClearSelectionEvent());
|
||||||
|
FetchOccupancyDataHelper.clearAllData(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_communities_sidebar.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_communities_sidebar.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart';
|
||||||
@ -11,10 +12,13 @@ import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/real
|
|||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/fake_energy_consumption_by_phases_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/fake_energy_consumption_per_device_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/remote_energy_consumption_by_phases_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/remote_energy_consumption_per_device_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/occupacy/fake_occupacy_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/occupacy/fake_occupacy_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/fake_occupancy_heat_map_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/remote_occupancy_heat_map_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/remote_power_clamp_info_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/remote_power_clamp_info_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/remote_total_energy_consumption_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/remote_total_energy_consumption_service.dart';
|
||||||
@ -23,9 +27,22 @@ import 'package:syncrow_web/services/api/http_service.dart';
|
|||||||
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
||||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||||
|
|
||||||
class AnalyticsPage extends StatelessWidget {
|
class AnalyticsPage extends StatefulWidget {
|
||||||
const AnalyticsPage({super.key});
|
const AnalyticsPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AnalyticsPage> createState() => _AnalyticsPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AnalyticsPageState extends State<AnalyticsPage> {
|
||||||
|
late final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_httpService = HTTPService();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
@ -35,22 +52,22 @@ class AnalyticsPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => TotalEnergyConsumptionBloc(
|
create: (context) => TotalEnergyConsumptionBloc(
|
||||||
RemoteTotalEnergyConsumptionService(HTTPService()),
|
RemoteTotalEnergyConsumptionService(_httpService),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => EnergyConsumptionByPhasesBloc(
|
create: (context) => EnergyConsumptionByPhasesBloc(
|
||||||
FakeEnergyConsumptionByPhasesService(),
|
RemoteEnergyConsumptionByPhasesService(_httpService),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => EnergyConsumptionPerDeviceBloc(
|
create: (context) => EnergyConsumptionPerDeviceBloc(
|
||||||
FakeEnergyConsumptionPerDeviceService(),
|
RemoteEnergyConsumptionPerDeviceService(_httpService),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => PowerClampInfoBloc(
|
create: (context) => PowerClampInfoBloc(
|
||||||
RemotePowerClampInfoService(HTTPService()),
|
RemotePowerClampInfoService(_httpService),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider<RealtimeDeviceChangesBloc>(
|
BlocProvider<RealtimeDeviceChangesBloc>(
|
||||||
@ -60,9 +77,19 @@ class AnalyticsPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
BlocProvider(create: (context) => OccupancyBloc(FakeOccupacyService())),
|
BlocProvider(create: (context) => OccupancyBloc(FakeOccupacyService())),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => OccupancyHeatMapBloc(FakeOccupancyHeatMapService()),
|
create: (context) => OccupancyHeatMapBloc(
|
||||||
|
RemoteOccupancyHeatMapService(_httpService),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
BlocProvider(create: (context) => AnalyticsDatePickerBloc()),
|
BlocProvider(create: (context) => AnalyticsDatePickerBloc()),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => AnalyticsDevicesBloc(
|
||||||
|
AnalyticsDevicesServiceDelegate(
|
||||||
|
RemoteOccupancyAnalyticsDevicesService(_httpService),
|
||||||
|
RemoteEnergyManagementAnalyticsDevicesService(_httpService),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: const AnalyticsPageForm(),
|
child: const AnalyticsPageForm(),
|
||||||
);
|
);
|
||||||
|
@ -9,29 +9,21 @@ class AnalyticsCommunitiesSidebar extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Builder(
|
final selectedTab = context.watch<AnalyticsTabBloc>().state;
|
||||||
builder: (context) {
|
final strategy = AnalyticsDataLoadingStrategyFactory.getStrategy(selectedTab);
|
||||||
final selectedTab = context.read<AnalyticsTabBloc>().state;
|
|
||||||
final strategy =
|
|
||||||
AnalyticsDataLoadingStrategyFactory.getStrategy(selectedTab);
|
|
||||||
|
|
||||||
// Clear data when tab changes
|
return Expanded(
|
||||||
strategy.clearData(context);
|
child: AnalyticsSpaceTreeView(
|
||||||
|
onSelectCommunity: (community, spaces) {
|
||||||
return Expanded(
|
strategy.onCommunitySelected(context, community, spaces);
|
||||||
child: AnalyticsSpaceTreeView(
|
},
|
||||||
onSelectCommunity: (community, spaces) {
|
onSelectSpace: (community, space) {
|
||||||
strategy.onCommunitySelected(context, community, spaces);
|
strategy.onSpaceSelected(context, community, space);
|
||||||
},
|
},
|
||||||
onSelectSpace: (community, space) {
|
onSelectChildSpace: (community, child) {
|
||||||
strategy.onSpaceSelected(context, community, space);
|
strategy.onChildSpaceSelected(context, community, child);
|
||||||
},
|
},
|
||||||
onSelectChildSpace: (community, child) {
|
),
|
||||||
strategy.onChildSpaceSelected(context, community, child);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_pa
|
|||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_date_filter_button.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_date_filter_button.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tab_button.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tab_button.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||||
import 'package:syncrow_web/utils/style.dart';
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
||||||
@ -53,32 +54,40 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
_buildAnimation(
|
Visibility(
|
||||||
child: Visibility(
|
key: ValueKey(selectedTab),
|
||||||
key: ValueKey(selectedTab),
|
visible: selectedTab == AnalyticsPageTab.energyManagement,
|
||||||
visible: selectedTab == AnalyticsPageTab.energyManagement,
|
child: Expanded(
|
||||||
child: Expanded(
|
flex: 2,
|
||||||
flex: 2,
|
child: FittedBox(
|
||||||
child: FittedBox(
|
fit: BoxFit.scaleDown,
|
||||||
fit: BoxFit.scaleDown,
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
alignment: AlignmentDirectional.centerEnd,
|
child: AnalyticsDateFilterButton(
|
||||||
child: AnalyticsDateFilterButton(
|
onDateSelected: (DateTime value) {
|
||||||
onDateSelected: (DateTime value) {
|
context.read<AnalyticsDatePickerBloc>().add(
|
||||||
context.read<AnalyticsDatePickerBloc>().add(
|
UpdateAnalyticsDatePickerEvent(montlyDate: value),
|
||||||
UpdateAnalyticsDatePickerEvent(
|
);
|
||||||
montlyDate: value),
|
|
||||||
);
|
final spaceTreeState =
|
||||||
|
context.read<SpaceTreeBloc>().state;
|
||||||
|
if (spaceTreeState.selectedSpaces.isNotEmpty) {
|
||||||
FetchEnergyManagementDataHelper
|
FetchEnergyManagementDataHelper
|
||||||
.fetchEnergyManagementData(
|
.loadEnergyManagementData(
|
||||||
context,
|
context,
|
||||||
|
shouldFetchAnalyticsDevices: false,
|
||||||
selectedDate: value,
|
selectedDate: value,
|
||||||
|
communityId:
|
||||||
|
spaceTreeState.selectedCommunities.firstOrNull ??
|
||||||
|
'',
|
||||||
|
spaceId:
|
||||||
|
spaceTreeState.selectedSpaces.firstOrNull ?? '',
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
selectedDate: context
|
},
|
||||||
.watch<AnalyticsDatePickerBloc>()
|
selectedDate: context
|
||||||
.state
|
.watch<AnalyticsDatePickerBloc>()
|
||||||
.monthlyDate,
|
.state
|
||||||
),
|
.monthlyDate,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -45,7 +45,7 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Dialog(
|
return Dialog(
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsetsDirectional.all(20),
|
padding: const EdgeInsetsDirectional.all(20),
|
||||||
width: 320,
|
width: 320,
|
||||||
@ -121,6 +121,7 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Row _buildYearSelector() {
|
Row _buildYearSelector() {
|
||||||
|
final currentYear = DateTime.now().year;
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@ -134,17 +135,35 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
|
|||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => setState(() => _currentYear = _currentYear - 1),
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_currentYear = _currentYear - 1;
|
||||||
|
});
|
||||||
|
},
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.chevron_left,
|
Icons.chevron_left,
|
||||||
color: ColorsManager.grey700,
|
color: ColorsManager.grey700,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => setState(() => _currentYear = _currentYear + 1),
|
onPressed: _currentYear < currentYear
|
||||||
icon: const Icon(
|
? () {
|
||||||
|
setState(() {
|
||||||
|
_currentYear = _currentYear + 1;
|
||||||
|
// Clear selected month if it becomes invalid in the new year
|
||||||
|
if (_currentYear == currentYear &&
|
||||||
|
_selectedMonth != null &&
|
||||||
|
_selectedMonth! > DateTime.now().month - 1) {
|
||||||
|
_selectedMonth = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
icon: Icon(
|
||||||
Icons.chevron_right,
|
Icons.chevron_right,
|
||||||
color: ColorsManager.grey700,
|
color: _currentYear < currentYear
|
||||||
|
? ColorsManager.grey700
|
||||||
|
: ColorsManager.grey700.withValues(alpha: 0.3),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -152,11 +171,13 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildMonthsGrid() {
|
Widget _buildMonthsGrid() {
|
||||||
|
final currentDate = DateTime.now();
|
||||||
|
final isCurrentYear = _currentYear == currentDate.year;
|
||||||
|
|
||||||
return GridView.builder(
|
return GridView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: 12,
|
itemCount: 12,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 3,
|
crossAxisCount: 3,
|
||||||
childAspectRatio: 2.5,
|
childAspectRatio: 2.5,
|
||||||
@ -165,25 +186,43 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
|
|||||||
),
|
),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final isSelected = _selectedMonth == index;
|
final isSelected = _selectedMonth == index;
|
||||||
|
final isFutureMonth = isCurrentYear && index > currentDate.month - 1;
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => setState(() => _selectedMonth = index),
|
onTap: isFutureMonth ? null : () => setState(() => _selectedMonth = index),
|
||||||
child: Container(
|
child: DecoratedBox(
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isSelected
|
color: const Color(0xFFEDF2F7),
|
||||||
? ColorsManager.vividBlue.withValues(alpha: 0.7)
|
borderRadius: BorderRadius.only(
|
||||||
: const Color(0xFFEDF2F7),
|
topLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero,
|
||||||
borderRadius:
|
bottomLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero,
|
||||||
isSelected ? BorderRadius.circular(15) : BorderRadius.zero,
|
topRight: index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
|
||||||
|
bottomRight:
|
||||||
|
index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Container(
|
||||||
_monthNames[index],
|
alignment: Alignment.center,
|
||||||
style: context.textTheme.titleSmall?.copyWith(
|
decoration: BoxDecoration(
|
||||||
fontSize: 12,
|
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? ColorsManager.whiteColors
|
? ColorsManager.vividBlue.withValues(alpha: 0.7)
|
||||||
: ColorsManager.blackColor.withValues(alpha: 0.8),
|
: isFutureMonth
|
||||||
fontWeight: FontWeight.w500,
|
? ColorsManager.grey700.withValues(alpha: 0.1)
|
||||||
|
: const Color(0xFFEDF2F7),
|
||||||
|
borderRadius:
|
||||||
|
isSelected ? BorderRadius.circular(15) : BorderRadius.zero,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
_monthNames[index],
|
||||||
|
style: context.textTheme.titleSmall?.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
color: isSelected
|
||||||
|
? ColorsManager.whiteColors
|
||||||
|
: isFutureMonth
|
||||||
|
? ColorsManager.blackColor.withValues(alpha: 0.3)
|
||||||
|
: ColorsManager.blackColor.withValues(alpha: 0.8),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -20,9 +20,9 @@ class _YearPickerWidgetState extends State<YearPickerWidget> {
|
|||||||
late int _currentYear;
|
late int _currentYear;
|
||||||
|
|
||||||
static final years = List.generate(
|
static final years = List.generate(
|
||||||
DateTime.now().year - 2020 + 1,
|
DateTime.now().year - (DateTime.now().year - 5) + 1,
|
||||||
(index) => (2020 + index),
|
(index) => (2020 + index),
|
||||||
);
|
).where((year) => year <= DateTime.now().year).toList();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -33,7 +33,7 @@ class _YearPickerWidgetState extends State<YearPickerWidget> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Dialog(
|
return Dialog(
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsetsDirectional.all(20),
|
padding: const EdgeInsetsDirectional.all(20),
|
||||||
width: 320,
|
width: 320,
|
||||||
@ -109,7 +109,6 @@ class _YearPickerWidgetState extends State<YearPickerWidget> {
|
|||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: years.length,
|
itemCount: years.length,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 3,
|
crossAxisCount: 3,
|
||||||
childAspectRatio: 2.5,
|
childAspectRatio: 2.5,
|
||||||
@ -120,23 +119,35 @@ class _YearPickerWidgetState extends State<YearPickerWidget> {
|
|||||||
final isSelected = _currentYear == years[index];
|
final isSelected = _currentYear == years[index];
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => setState(() => _currentYear = years[index]),
|
onTap: () => setState(() => _currentYear = years[index]),
|
||||||
child: Container(
|
child: DecoratedBox(
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isSelected
|
color: const Color(0xFFEDF2F7),
|
||||||
? ColorsManager.vividBlue.withValues(alpha: 0.7)
|
borderRadius: BorderRadius.only(
|
||||||
: const Color(0xFFEDF2F7),
|
topLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero,
|
||||||
borderRadius:
|
bottomLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero,
|
||||||
isSelected ? BorderRadius.circular(15) : BorderRadius.zero,
|
topRight: index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
|
||||||
|
bottomRight:
|
||||||
|
index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Container(
|
||||||
years[index].toString(),
|
alignment: Alignment.center,
|
||||||
style: context.textTheme.titleSmall?.copyWith(
|
decoration: BoxDecoration(
|
||||||
fontSize: 12,
|
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? ColorsManager.whiteColors
|
? ColorsManager.vividBlue.withValues(alpha: 0.7)
|
||||||
: ColorsManager.blackColor.withValues(alpha: 0.8),
|
: const Color(0xFFEDF2F7),
|
||||||
fontWeight: FontWeight.w500,
|
borderRadius:
|
||||||
|
isSelected ? BorderRadius.circular(15) : BorderRadius.zero,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
years[index].toString(),
|
||||||
|
style: context.textTheme.titleSmall?.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
color: isSelected
|
||||||
|
? ColorsManager.whiteColors
|
||||||
|
: ColorsManager.blackColor.withValues(alpha: 0.8),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
|
|
||||||
|
|
||||||
abstract final class EnergyConsumptionByPhasesChartHelper {
|
|
||||||
const EnergyConsumptionByPhasesChartHelper._();
|
|
||||||
|
|
||||||
static const fakeData = <PhasesEnergyConsumption>[
|
|
||||||
PhasesEnergyConsumption(month: 1, phaseA: 200, phaseB: 300, phaseC: 400),
|
|
||||||
PhasesEnergyConsumption(month: 2, phaseA: 300, phaseB: 400, phaseC: 500),
|
|
||||||
PhasesEnergyConsumption(month: 3, phaseA: 400, phaseB: 500, phaseC: 600),
|
|
||||||
PhasesEnergyConsumption(month: 4, phaseA: 100, phaseB: 100, phaseC: 100),
|
|
||||||
PhasesEnergyConsumption(month: 5, phaseA: 300, phaseB: 400, phaseC: 500),
|
|
||||||
PhasesEnergyConsumption(month: 6, phaseA: 300, phaseB: 100, phaseC: 400),
|
|
||||||
PhasesEnergyConsumption(month: 7, phaseA: 300, phaseB: 100, phaseC: 400),
|
|
||||||
PhasesEnergyConsumption(month: 8, phaseA: 500, phaseB: 100, phaseC: 100),
|
|
||||||
PhasesEnergyConsumption(month: 9, phaseA: 500, phaseB: 100, phaseC: 200),
|
|
||||||
PhasesEnergyConsumption(month: 10, phaseA: 100, phaseB: 50, phaseC: 50),
|
|
||||||
PhasesEnergyConsumption(month: 11, phaseA: 600, phaseB: 750, phaseC: 130),
|
|
||||||
PhasesEnergyConsumption(month: 12, phaseA: 100, phaseB: 80, phaseC: 100),
|
|
||||||
];
|
|
||||||
}
|
|
@ -21,12 +21,13 @@ abstract final class EnergyManagementChartsHelper {
|
|||||||
reservedSize: 32,
|
reservedSize: 32,
|
||||||
showTitles: true,
|
showTitles: true,
|
||||||
maxIncluded: true,
|
maxIncluded: true,
|
||||||
|
minIncluded: true,
|
||||||
getTitlesWidget: (value, meta) => Padding(
|
getTitlesWidget: (value, meta) => Padding(
|
||||||
padding: const EdgeInsetsDirectional.only(top: 20.0),
|
padding: const EdgeInsetsDirectional.only(top: 20.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
(value + 1).toString(),
|
value.toString(),
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
color: ColorsManager.greyColor,
|
color: ColorsManager.lightGreyColor,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -36,7 +37,8 @@ abstract final class EnergyManagementChartsHelper {
|
|||||||
leftTitles: AxisTitles(
|
leftTitles: AxisTitles(
|
||||||
sideTitles: SideTitles(
|
sideTitles: SideTitles(
|
||||||
showTitles: true,
|
showTitles: true,
|
||||||
maxIncluded: true,
|
maxIncluded: false,
|
||||||
|
minIncluded: true,
|
||||||
interval: leftTitlesInterval,
|
interval: leftTitlesInterval,
|
||||||
reservedSize: 110,
|
reservedSize: 110,
|
||||||
getTitlesWidget: (value, meta) => Padding(
|
getTitlesWidget: (value, meta) => Padding(
|
||||||
@ -70,7 +72,7 @@ abstract final class EnergyManagementChartsHelper {
|
|||||||
static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) {
|
static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) {
|
||||||
return touchedSpots.map((spot) {
|
return touchedSpots.map((spot) {
|
||||||
return LineTooltipItem(
|
return LineTooltipItem(
|
||||||
getToolTipLabel(spot.x + 1, spot.y),
|
getToolTipLabel(spot.x, spot.y),
|
||||||
const TextStyle(
|
const TextStyle(
|
||||||
color: ColorsManager.textPrimaryColor,
|
color: ColorsManager.textPrimaryColor,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@ -91,31 +93,38 @@ abstract final class EnergyManagementChartsHelper {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static FlBorderData borderData() {
|
|
||||||
return FlBorderData(
|
|
||||||
show: true,
|
|
||||||
border: const Border.symmetric(
|
|
||||||
horizontal: BorderSide(
|
|
||||||
color: ColorsManager.greyColor,
|
|
||||||
style: BorderStyle.solid,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static FlGridData gridData() {
|
static FlGridData gridData() {
|
||||||
return const FlGridData(
|
return FlGridData(
|
||||||
show: true,
|
show: true,
|
||||||
drawVerticalLine: false,
|
drawVerticalLine: false,
|
||||||
drawHorizontalLine: true,
|
drawHorizontalLine: true,
|
||||||
|
horizontalInterval: 250,
|
||||||
|
getDrawingHorizontalLine: (value) {
|
||||||
|
return FlLine(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
strokeWidth: 1,
|
||||||
|
dashArray: value == 0 ? null : [5, 5],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FlBorderData borderData() {
|
||||||
|
return FlBorderData(
|
||||||
|
border: const Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
style: BorderStyle.solid,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
show: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static LineTouchData lineTouchData() {
|
static LineTouchData lineTouchData() {
|
||||||
return LineTouchData(
|
return LineTouchData(
|
||||||
handleBuiltInTouches: true,
|
handleBuiltInTouches: true,
|
||||||
touchSpotThreshold: 2,
|
touchSpotThreshold: 16,
|
||||||
touchTooltipData: EnergyManagementChartsHelper.lineTouchTooltipData(),
|
touchTooltipData: EnergyManagementChartsHelper.lineTouchTooltipData(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,75 +1,80 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_by_phases/energy_consumption_by_phases_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_by_phases/energy_consumption_by_phases_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_per_device/energy_consumption_per_device_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_per_device/energy_consumption_per_device_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
|
||||||
|
|
||||||
abstract final class FetchEnergyManagementDataHelper {
|
abstract final class FetchEnergyManagementDataHelper {
|
||||||
const FetchEnergyManagementDataHelper._();
|
const FetchEnergyManagementDataHelper._();
|
||||||
|
|
||||||
static void fetchEnergyManagementData(
|
// static const String _powerClampId = 'cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa';
|
||||||
BuildContext context, {
|
static AnalyticsDevice? getSelectedDevice(BuildContext context) {
|
||||||
DateTime? selectedDate,
|
return context.read<AnalyticsDevicesBloc>().state.selectedDevice;
|
||||||
}) {
|
}
|
||||||
final (selectedCommunities, selectedSpaces) =
|
|
||||||
getSelectedCommunitiesAndSpaces(context);
|
|
||||||
|
|
||||||
if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) {
|
static void loadEnergyManagementData(
|
||||||
|
BuildContext context, {
|
||||||
|
required String communityId,
|
||||||
|
required String spaceId,
|
||||||
|
DateTime? selectedDate,
|
||||||
|
bool shouldFetchAnalyticsDevices = true,
|
||||||
|
}) {
|
||||||
|
if (communityId.isEmpty && spaceId.isEmpty) {
|
||||||
clearAllData(context);
|
clearAllData(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final datePickerState = context.read<AnalyticsDatePickerBloc>().state;
|
final datePickerState = context.read<AnalyticsDatePickerBloc>().state;
|
||||||
|
final selectedDate0 = selectedDate ?? datePickerState.monthlyDate;
|
||||||
loadTotalEnergyConsumption(context, selectedDate: datePickerState.monthlyDate);
|
if (shouldFetchAnalyticsDevices) {
|
||||||
loadEnergyConsumptionByPhases(
|
loadAnalyticsDevices(
|
||||||
context,
|
context,
|
||||||
selectedDate: datePickerState.monthlyDate,
|
communityUuid: communityId,
|
||||||
);
|
spaceUuid: spaceId,
|
||||||
loadEnergyConsumptionPerDevice(context);
|
selectedDate: selectedDate0,
|
||||||
return;
|
);
|
||||||
}
|
loadRealtimeDeviceChanges(context);
|
||||||
|
loadPowerClampInfo(context);
|
||||||
static void loadEnergyManagementData(BuildContext context) {
|
|
||||||
final (selectedCommunities, selectedSpaces) =
|
|
||||||
FetchEnergyManagementDataHelper.getSelectedCommunitiesAndSpaces(context);
|
|
||||||
if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) return;
|
|
||||||
|
|
||||||
FetchEnergyManagementDataHelper.fetchEnergyManagementData(context,
|
|
||||||
selectedDate: DateTime.now());
|
|
||||||
FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges(context);
|
|
||||||
context.read<PowerClampInfoBloc>().add(const ClearPowerClampInfoEvent());
|
|
||||||
if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) {
|
|
||||||
context.read<PowerClampInfoBloc>().add(
|
|
||||||
const ClearPowerClampInfoEvent(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
FetchEnergyManagementDataHelper.loadPowerClampInfo(context);
|
|
||||||
}
|
}
|
||||||
}
|
loadTotalEnergyConsumption(
|
||||||
|
context,
|
||||||
static (List<String> selectedCommunities, List<String> selectedSpaces)
|
selectedDate: selectedDate0,
|
||||||
getSelectedCommunitiesAndSpaces(BuildContext context) {
|
communityId: communityId,
|
||||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
spaceId: spaceId,
|
||||||
final selectedCommunities = spaceTreeState.selectedCommunities;
|
);
|
||||||
final selectedSpaces = spaceTreeState.selectedSpaces;
|
final selectedDevice = getSelectedDevice(context);
|
||||||
|
if (selectedDevice case final AnalyticsDevice device) {
|
||||||
return (selectedCommunities, selectedSpaces);
|
loadEnergyConsumptionByPhases(
|
||||||
|
context,
|
||||||
|
powerClampUuid: device.uuid,
|
||||||
|
selectedDate: selectedDate0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
loadEnergyConsumptionPerDevice(
|
||||||
|
context,
|
||||||
|
communityId: communityId,
|
||||||
|
spaceId: spaceId,
|
||||||
|
selectedDate: selectedDate0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loadEnergyConsumptionByPhases(
|
static void loadEnergyConsumptionByPhases(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
|
required String powerClampUuid,
|
||||||
DateTime? selectedDate,
|
DateTime? selectedDate,
|
||||||
}) {
|
}) {
|
||||||
final param = GetEnergyConsumptionByPhasesParam(
|
final param = GetEnergyConsumptionByPhasesParam(
|
||||||
startDate: selectedDate,
|
date: selectedDate,
|
||||||
spaceId: '',
|
powerClampUuid: powerClampUuid,
|
||||||
);
|
);
|
||||||
context.read<EnergyConsumptionByPhasesBloc>().add(
|
context.read<EnergyConsumptionByPhasesBloc>().add(
|
||||||
LoadEnergyConsumptionByPhasesEvent(param: param),
|
LoadEnergyConsumptionByPhasesEvent(param: param),
|
||||||
@ -79,13 +84,12 @@ abstract final class FetchEnergyManagementDataHelper {
|
|||||||
static void loadTotalEnergyConsumption(
|
static void loadTotalEnergyConsumption(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
DateTime? selectedDate,
|
DateTime? selectedDate,
|
||||||
|
required String communityId,
|
||||||
|
required String spaceId,
|
||||||
}) {
|
}) {
|
||||||
final (selectedCommunities, selectedSpaces) =
|
|
||||||
getSelectedCommunitiesAndSpaces(context);
|
|
||||||
|
|
||||||
final param = GetTotalEnergyConsumptionParam(
|
final param = GetTotalEnergyConsumptionParam(
|
||||||
spaceId: selectedCommunities.firstOrNull,
|
spaceId: spaceId,
|
||||||
communityId: selectedCommunities.firstOrNull,
|
communityId: communityId,
|
||||||
monthDate: selectedDate,
|
monthDate: selectedDate,
|
||||||
);
|
);
|
||||||
context.read<TotalEnergyConsumptionBloc>().add(
|
context.read<TotalEnergyConsumptionBloc>().add(
|
||||||
@ -93,33 +97,81 @@ abstract final class FetchEnergyManagementDataHelper {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loadEnergyConsumptionPerDevice(BuildContext context) {
|
static void loadEnergyConsumptionPerDevice(
|
||||||
const param = GetEnergyConsumptionPerDeviceParam();
|
BuildContext context, {
|
||||||
|
DateTime? selectedDate,
|
||||||
|
required String communityId,
|
||||||
|
required String spaceId,
|
||||||
|
}) {
|
||||||
|
final param = GetEnergyConsumptionPerDeviceParam(
|
||||||
|
spaceId: spaceId,
|
||||||
|
communityId: communityId,
|
||||||
|
monthDate: selectedDate,
|
||||||
|
);
|
||||||
context.read<EnergyConsumptionPerDeviceBloc>().add(
|
context.read<EnergyConsumptionPerDeviceBloc>().add(
|
||||||
const LoadEnergyConsumptionPerDeviceEvent(param),
|
LoadEnergyConsumptionPerDeviceEvent(param),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loadPowerClampInfo(BuildContext context) {
|
static void loadPowerClampInfo(BuildContext context) {
|
||||||
context.read<PowerClampInfoBloc>().add(
|
final selectedDevice = getSelectedDevice(context);
|
||||||
const LoadPowerClampInfoEvent('cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa'),
|
if (selectedDevice case final AnalyticsDevice device) {
|
||||||
|
context.read<PowerClampInfoBloc>().add(
|
||||||
|
LoadPowerClampInfoEvent(device.uuid),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadRealtimeDeviceChanges(
|
||||||
|
BuildContext context, {
|
||||||
|
String? deviceUuid,
|
||||||
|
}) {
|
||||||
|
final selectedDevice = getSelectedDevice(context);
|
||||||
|
|
||||||
|
context.read<RealtimeDeviceChangesBloc>().add(
|
||||||
|
RealtimeDeviceChangesStarted(deviceUuid ?? selectedDevice?.uuid ?? ''),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loadRealtimeDeviceChanges(BuildContext context) {
|
static void loadAnalyticsDevices(
|
||||||
context.read<RealtimeDeviceChangesBloc>().add(
|
BuildContext context, {
|
||||||
const RealtimeDeviceChangesStarted('cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa'),
|
required String communityUuid,
|
||||||
|
required String spaceUuid,
|
||||||
|
required DateTime selectedDate,
|
||||||
|
}) {
|
||||||
|
context.read<AnalyticsDevicesBloc>().add(
|
||||||
|
LoadAnalyticsDevicesEvent(
|
||||||
|
onSuccess: (device) {
|
||||||
|
context.read<PowerClampInfoBloc>().add(
|
||||||
|
LoadPowerClampInfoEvent(device.uuid),
|
||||||
|
);
|
||||||
|
loadEnergyConsumptionByPhases(
|
||||||
|
context,
|
||||||
|
powerClampUuid: device.uuid,
|
||||||
|
selectedDate: selectedDate,
|
||||||
|
);
|
||||||
|
context.read<RealtimeDeviceChangesBloc>().add(
|
||||||
|
RealtimeDeviceChangesStarted(device.uuid),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
param: GetAnalyticsDevicesParam(
|
||||||
|
communityUuid: communityUuid,
|
||||||
|
spaceUuid: spaceUuid,
|
||||||
|
deviceTypes: ['PC'],
|
||||||
|
requestType: AnalyticsDeviceRequestType.energyManagement,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void clearAllData(BuildContext context) {
|
static void clearAllData(BuildContext context) {
|
||||||
|
context.read<PowerClampInfoBloc>().add(
|
||||||
|
const ClearPowerClampInfoEvent(),
|
||||||
|
);
|
||||||
context.read<RealtimeDeviceChangesBloc>().add(
|
context.read<RealtimeDeviceChangesBloc>().add(
|
||||||
const RealtimeDeviceChangesClosed(),
|
const RealtimeDeviceChangesClosed(),
|
||||||
);
|
);
|
||||||
|
|
||||||
context.read<PowerClampInfoBloc>().add(
|
|
||||||
const ClearPowerClampInfoEvent(),
|
|
||||||
);
|
|
||||||
context.read<EnergyConsumptionPerDeviceBloc>().add(
|
context.read<EnergyConsumptionPerDeviceBloc>().add(
|
||||||
const ClearEnergyConsumptionPerDeviceEvent(),
|
const ClearEnergyConsumptionPerDeviceEvent(),
|
||||||
);
|
);
|
||||||
@ -131,5 +183,6 @@ abstract final class FetchEnergyManagementDataHelper {
|
|||||||
context.read<EnergyConsumptionByPhasesBloc>().add(
|
context.read<EnergyConsumptionByPhasesBloc>().add(
|
||||||
const ClearEnergyConsumptionByPhasesEvent(),
|
const ClearEnergyConsumptionByPhasesEvent(),
|
||||||
);
|
);
|
||||||
|
context.read<AnalyticsDevicesBloc>().add(const ClearAnalyticsDeviceEvent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart_box.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart_box.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||||
|
|
||||||
class AnalyticsEnergyManagementView extends StatefulWidget {
|
class AnalyticsEnergyManagementView extends StatefulWidget {
|
||||||
const AnalyticsEnergyManagementView({super.key});
|
const AnalyticsEnergyManagementView({super.key});
|
||||||
@ -16,7 +18,14 @@ class _AnalyticsEnergyManagementViewState
|
|||||||
extends State<AnalyticsEnergyManagementView> {
|
extends State<AnalyticsEnergyManagementView> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
FetchEnergyManagementDataHelper.loadEnergyManagementData(context);
|
final spaceTreeBloc = context.read<SpaceTreeBloc>();
|
||||||
|
final communityId = spaceTreeBloc.state.selectedCommunities.firstOrNull;
|
||||||
|
final spaceId = spaceTreeBloc.state.selectedSpaces.firstOrNull;
|
||||||
|
FetchEnergyManagementDataHelper.loadEnergyManagementData(
|
||||||
|
context,
|
||||||
|
communityId: communityId ?? '',
|
||||||
|
spaceId: spaceId ?? '',
|
||||||
|
);
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,108 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class AnalyticsDeviceDropdown extends StatelessWidget {
|
||||||
|
const AnalyticsDeviceDropdown({required this.onChanged, super.key});
|
||||||
|
|
||||||
|
final ValueChanged<AnalyticsDevice> onChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<AnalyticsDevicesBloc, AnalyticsDevicesState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Visibility(
|
||||||
|
visible: state.devices.isNotEmpty,
|
||||||
|
replacement: _buildNoDevicesFound(context),
|
||||||
|
child: _buildDevicesDropdown(context, state),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const _defaultPadding = EdgeInsetsDirectional.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
vertical: 2,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildNoDevicesFound(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: _defaultPadding,
|
||||||
|
child: Text(
|
||||||
|
'no devices found',
|
||||||
|
style: _getTextStyle(context),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDevicesDropdown(BuildContext context, AnalyticsDevicesState state) {
|
||||||
|
final spaceUuid = state.selectedDevice?.spaceUuid;
|
||||||
|
return DropdownButton<AnalyticsDevice?>(
|
||||||
|
value: state.selectedDevice,
|
||||||
|
isDense: true,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
dropdownColor: ColorsManager.whiteColors,
|
||||||
|
underline: const SizedBox.shrink(),
|
||||||
|
icon: const RotatedBox(
|
||||||
|
quarterTurns: 1,
|
||||||
|
child: Icon(Icons.chevron_right, size: 16),
|
||||||
|
),
|
||||||
|
style: _getTextStyle(context),
|
||||||
|
padding: _defaultPadding,
|
||||||
|
selectedItemBuilder: (context) {
|
||||||
|
return state.devices.map((e) => Text(e.name)).toList();
|
||||||
|
},
|
||||||
|
items: state.devices.map((e) {
|
||||||
|
return DropdownMenuItem(
|
||||||
|
value: e,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(e.name),
|
||||||
|
if (spaceUuid != null)
|
||||||
|
FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
child: Text(
|
||||||
|
spaceUuid,
|
||||||
|
style: _getTextStyle(context)?.copyWith(
|
||||||
|
fontSize: 10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value case final AnalyticsDevice device) {
|
||||||
|
context.read<AnalyticsDevicesBloc>().add(
|
||||||
|
SelectAnalyticsDeviceEvent(device),
|
||||||
|
);
|
||||||
|
onChanged.call(device);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextStyle? _getTextStyle(BuildContext context) {
|
||||||
|
return context.textTheme.labelSmall?.copyWith(
|
||||||
|
color: ColorsManager.textPrimaryColor,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 14,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/helpers/get_month_name_from_int.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
|
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
@ -18,7 +18,10 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BarChart(
|
return BarChart(
|
||||||
BarChartData(
|
BarChartData(
|
||||||
gridData: EnergyManagementChartsHelper.gridData(),
|
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
||||||
|
checkToShowHorizontalLine: (value) => true,
|
||||||
|
horizontalInterval: 250,
|
||||||
|
),
|
||||||
borderData: EnergyManagementChartsHelper.borderData(),
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
barTouchData: _barTouchData(context),
|
barTouchData: _barTouchData(context),
|
||||||
titlesData: _titlesData(context),
|
titlesData: _titlesData(context),
|
||||||
@ -31,25 +34,29 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
|
|||||||
barRods: [
|
barRods: [
|
||||||
BarChartRodData(
|
BarChartRodData(
|
||||||
color: ColorsManager.vividBlue.withValues(alpha: 0.1),
|
color: ColorsManager.vividBlue.withValues(alpha: 0.1),
|
||||||
toY: data.phaseA + data.phaseB + data.phaseC,
|
toY: data.energyConsumedA +
|
||||||
|
data.energyConsumedB +
|
||||||
|
data.energyConsumedC,
|
||||||
rodStackItems: [
|
rodStackItems: [
|
||||||
BarChartRodStackItem(
|
BarChartRodStackItem(
|
||||||
0,
|
0,
|
||||||
data.phaseA,
|
data.energyConsumedA,
|
||||||
ColorsManager.vividBlue.withValues(alpha: 0.8),
|
ColorsManager.vividBlue.withValues(alpha: 0.8),
|
||||||
),
|
),
|
||||||
BarChartRodStackItem(
|
BarChartRodStackItem(
|
||||||
data.phaseA,
|
data.energyConsumedA,
|
||||||
data.phaseA + data.phaseB,
|
data.energyConsumedA + data.energyConsumedB,
|
||||||
ColorsManager.vividBlue.withValues(alpha: 0.4),
|
ColorsManager.vividBlue.withValues(alpha: 0.4),
|
||||||
),
|
),
|
||||||
BarChartRodStackItem(
|
BarChartRodStackItem(
|
||||||
data.phaseA + data.phaseB,
|
data.energyConsumedA + data.energyConsumedB,
|
||||||
data.phaseA + data.phaseB + data.phaseC,
|
data.energyConsumedA +
|
||||||
|
data.energyConsumedB +
|
||||||
|
data.energyConsumedC,
|
||||||
ColorsManager.vividBlue.withValues(alpha: 0.15),
|
ColorsManager.vividBlue.withValues(alpha: 0.15),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
width: 16,
|
width: 8,
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: const BorderRadius.only(
|
||||||
topLeft: Radius.circular(8),
|
topLeft: Radius.circular(8),
|
||||||
topRight: Radius.circular(8),
|
topRight: Radius.circular(8),
|
||||||
@ -59,6 +66,7 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
|
duration: Duration.zero,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,18 +99,27 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
|
|||||||
}) {
|
}) {
|
||||||
final data = energyData;
|
final data = energyData;
|
||||||
|
|
||||||
final month = data[group.x.toInt()].month.getMonthName;
|
final date = DateFormat('dd/MM/yyyy').format(data[group.x.toInt()].date);
|
||||||
final phaseA = data[group.x.toInt()].phaseA;
|
final phaseA = data[group.x.toInt()].energyConsumedA;
|
||||||
final phaseB = data[group.x.toInt()].phaseB;
|
final phaseB = data[group.x.toInt()].energyConsumedB;
|
||||||
final phaseC = data[group.x.toInt()].phaseC;
|
final phaseC = data[group.x.toInt()].energyConsumedC;
|
||||||
|
final total = data[group.x.toInt()].energyConsumedKw;
|
||||||
|
|
||||||
return BarTooltipItem(
|
return BarTooltipItem(
|
||||||
'$month\n',
|
'$date\n',
|
||||||
context.textTheme.bodyMedium!.copyWith(
|
context.textTheme.bodyMedium!.copyWith(
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
|
textAlign: TextAlign.start,
|
||||||
children: [
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: 'Total: $total\n',
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: 'Phase A: $phaseA\n',
|
text: 'Phase A: $phaseA\n',
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
@ -144,9 +161,9 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
|
|||||||
sideTitles: SideTitles(
|
sideTitles: SideTitles(
|
||||||
showTitles: true,
|
showTitles: true,
|
||||||
getTitlesWidget: (value, _) {
|
getTitlesWidget: (value, _) {
|
||||||
final month = energyData[value.toInt()].month.getMonthName;
|
final month = DateFormat('d').format(energyData[value.toInt()].date);
|
||||||
return FittedBox(
|
return FittedBox(
|
||||||
alignment: AlignmentDirectional.bottomCenter,
|
alignment: AlignmentDirectional.center,
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
child: RotatedBox(
|
child: RotatedBox(
|
||||||
quarterTurns: 3,
|
quarterTurns: 3,
|
||||||
@ -160,7 +177,7 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
reservedSize: 36,
|
reservedSize: 18,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -16,7 +16,11 @@ class EnergyConsumptionPerDeviceChart extends StatelessWidget {
|
|||||||
context,
|
context,
|
||||||
leftTitlesInterval: 250,
|
leftTitlesInterval: 250,
|
||||||
),
|
),
|
||||||
gridData: EnergyManagementChartsHelper.gridData(),
|
|
||||||
|
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
||||||
|
checkToShowHorizontalLine: (value) => true,
|
||||||
|
horizontalInterval: 250,
|
||||||
|
),
|
||||||
borderData: EnergyManagementChartsHelper.borderData(),
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
|
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
|
||||||
lineBarsData: chartData.map((e) {
|
lineBarsData: chartData.map((e) {
|
||||||
@ -33,7 +37,7 @@ class EnergyConsumptionPerDeviceChart extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
duration: Durations.extralong1,
|
duration: Duration.zero,
|
||||||
curve: Curves.easeIn,
|
curve: Curves.easeIn,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_per_device/energy_consumption_per_device_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_per_device/energy_consumption_per_device_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart';
|
||||||
@ -46,6 +47,7 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
|
|||||||
flex: 2,
|
flex: 2,
|
||||||
child: EnergyConsumptionPerDeviceDevicesList(
|
child: EnergyConsumptionPerDeviceDevicesList(
|
||||||
chartData: state.chartData,
|
chartData: state.chartData,
|
||||||
|
devices: context.watch<AnalyticsDevicesBloc>().state.devices,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget {
|
class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget {
|
||||||
const EnergyConsumptionPerDeviceDevicesList({required this.chartData, super.key});
|
const EnergyConsumptionPerDeviceDevicesList({
|
||||||
|
required this.chartData,
|
||||||
|
required this.devices,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<AnalyticsDevice> devices;
|
||||||
final List<DeviceEnergyDataModel> chartData;
|
final List<DeviceEnergyDataModel> chartData;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -16,45 +22,60 @@ class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: chartData.map((e) => _buildDeviceCell(context, e)).toList(),
|
children: devices.map((e) => _buildDeviceCell(context, e)).toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDeviceCell(BuildContext context, DeviceEnergyDataModel device) {
|
Widget _buildDeviceCell(BuildContext context, AnalyticsDevice device) {
|
||||||
return Container(
|
final deviceColor = chartData
|
||||||
height: MediaQuery.sizeOf(context).height * 0.0365,
|
.firstWhere(
|
||||||
padding: const EdgeInsetsDirectional.symmetric(
|
(element) => element.deviceId == device.uuid,
|
||||||
vertical: 8,
|
orElse: () => const DeviceEnergyDataModel(
|
||||||
horizontal: 12,
|
energy: [],
|
||||||
),
|
deviceName: '',
|
||||||
decoration: BoxDecoration(
|
deviceId: '',
|
||||||
borderRadius: BorderRadiusDirectional.circular(8),
|
color: Colors.red,
|
||||||
border: Border.all(
|
),
|
||||||
color: ColorsManager.greyColor,
|
)
|
||||||
width: 1,
|
.color;
|
||||||
|
|
||||||
|
return Tooltip(
|
||||||
|
message: '${device.name}\n${device.productDevice?.uuid ?? ''}',
|
||||||
|
child: Container(
|
||||||
|
height: MediaQuery.sizeOf(context).height * 0.0365,
|
||||||
|
padding: const EdgeInsetsDirectional.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
horizontal: 12,
|
||||||
),
|
),
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
child: FittedBox(
|
borderRadius: BorderRadiusDirectional.circular(8),
|
||||||
fit: BoxFit.scaleDown,
|
border: Border.all(
|
||||||
alignment: Alignment.center,
|
color: ColorsManager.greyColor,
|
||||||
child: Row(
|
width: 1,
|
||||||
spacing: 6,
|
),
|
||||||
children: [
|
),
|
||||||
CircleAvatar(
|
child: FittedBox(
|
||||||
radius: 4,
|
fit: BoxFit.scaleDown,
|
||||||
backgroundColor: device.color,
|
alignment: Alignment.center,
|
||||||
),
|
child: Row(
|
||||||
Text(
|
spacing: 6,
|
||||||
device.deviceName,
|
children: [
|
||||||
textAlign: TextAlign.center,
|
CircleAvatar(
|
||||||
style: const TextStyle(
|
radius: 4,
|
||||||
color: ColorsManager.blackColor,
|
backgroundColor: deviceColor,
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
),
|
||||||
),
|
Text(
|
||||||
],
|
device.name,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
|
||||||
|
|
||||||
class PowerClampEnergyDataDeviceDropdown extends StatelessWidget {
|
|
||||||
const PowerClampEnergyDataDeviceDropdown({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
border: Border.all(
|
|
||||||
color: ColorsManager.greyColor,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: DropdownButton<String>(
|
|
||||||
value: 'Device 1',
|
|
||||||
isDense: true,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
dropdownColor: ColorsManager.whiteColors,
|
|
||||||
underline: const SizedBox.shrink(),
|
|
||||||
icon: const RotatedBox(
|
|
||||||
quarterTurns: 1,
|
|
||||||
child: Icon(Icons.chevron_right, size: 16),
|
|
||||||
),
|
|
||||||
style: context.textTheme.labelSmall?.copyWith(
|
|
||||||
color: ColorsManager.textPrimaryColor,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
padding: const EdgeInsetsDirectional.symmetric(
|
|
||||||
horizontal: 20,
|
|
||||||
vertical: 2,
|
|
||||||
),
|
|
||||||
items: [
|
|
||||||
for (var i = 1; i < 10; i++)
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: 'Device $i',
|
|
||||||
child: Text(
|
|
||||||
'Device $i',
|
|
||||||
style: context.textTheme.labelSmall?.copyWith(
|
|
||||||
color: ColorsManager.textPrimaryColor,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onChanged: (value) {},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart';
|
import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart_box.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart_box.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_device_dropdown.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
||||||
@ -50,7 +53,8 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
SelectableText(
|
SelectableText(
|
||||||
state.powerClampModel?.productUuid ?? 'N/A',
|
context.watch<AnalyticsDevicesBloc>().state.selectedDevice?.uuid ??
|
||||||
|
'N/A',
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
@ -107,7 +111,7 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 3,
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
@ -122,11 +126,25 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
const Expanded(
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
alignment: AlignmentDirectional.centerEnd,
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
child: PowerClampEnergyDataDeviceDropdown(),
|
child: AnalyticsDeviceDropdown(
|
||||||
|
onChanged: (value) {
|
||||||
|
FetchEnergyManagementDataHelper.loadEnergyConsumptionByPhases(
|
||||||
|
context,
|
||||||
|
powerClampUuid: value.uuid,
|
||||||
|
selectedDate:
|
||||||
|
context.read<AnalyticsDatePickerBloc>().state.monthlyDate,
|
||||||
|
);
|
||||||
|
FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges(
|
||||||
|
context,
|
||||||
|
deviceUuid: value.uuid,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -48,6 +48,9 @@ class PowerClampEnergyStatusWidget extends StatelessWidget {
|
|||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
|
softWrap: true,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
trailing: Text.rich(
|
trailing: Text.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
|
@ -4,15 +4,6 @@ import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
|||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
// energy_consumption_chart will return id, name and consumption
|
|
||||||
const phasesJson = {
|
|
||||||
"1": {
|
|
||||||
"phaseOne": 1000,
|
|
||||||
"phaseTwo": 2000,
|
|
||||||
"phaseThree": 3000,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class TotalEnergyConsumptionChart extends StatelessWidget {
|
class TotalEnergyConsumptionChart extends StatelessWidget {
|
||||||
const TotalEnergyConsumptionChart({required this.chartData, super.key});
|
const TotalEnergyConsumptionChart({required this.chartData, super.key});
|
||||||
|
|
||||||
@ -23,13 +14,19 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
|
|||||||
return Expanded(
|
return Expanded(
|
||||||
child: LineChart(
|
child: LineChart(
|
||||||
LineChartData(
|
LineChartData(
|
||||||
titlesData: EnergyManagementChartsHelper.titlesData(context),
|
titlesData: EnergyManagementChartsHelper.titlesData(
|
||||||
gridData: EnergyManagementChartsHelper.gridData(),
|
context,
|
||||||
|
leftTitlesInterval: 250,
|
||||||
|
),
|
||||||
|
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
||||||
|
checkToShowHorizontalLine: (value) => true,
|
||||||
|
horizontalInterval: 250,
|
||||||
|
),
|
||||||
borderData: EnergyManagementChartsHelper.borderData(),
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
|
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
|
||||||
lineBarsData: _lineBarsData,
|
lineBarsData: _lineBarsData,
|
||||||
),
|
),
|
||||||
duration: Durations.extralong1,
|
duration: Duration.zero,
|
||||||
curve: Curves.easeIn,
|
curve: Curves.easeIn,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -46,7 +43,7 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
|
|||||||
.entries
|
.entries
|
||||||
.map(
|
.map(
|
||||||
(entry) => FlSpot(
|
(entry) => FlSpot(
|
||||||
entry.key.toDouble(),
|
entry.value.date.day.toDouble(),
|
||||||
entry.value.value,
|
entry.value.value,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -1,57 +1,114 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart';
|
||||||
|
|
||||||
abstract final class FetchOccupancyDataHelper {
|
abstract final class FetchOccupancyDataHelper {
|
||||||
const FetchOccupancyDataHelper._();
|
const FetchOccupancyDataHelper._();
|
||||||
|
|
||||||
static void loadOccupancyData(BuildContext context) {
|
static void loadOccupancyData(
|
||||||
final (selectedCommunities, selectedSpaces) =
|
BuildContext context, {
|
||||||
FetchEnergyManagementDataHelper.getSelectedCommunitiesAndSpaces(context);
|
required String communityId,
|
||||||
if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) {
|
required String spaceId,
|
||||||
context.read<OccupancyBloc>().add(
|
}) {
|
||||||
const ClearOccupancyEvent(),
|
if (communityId.isEmpty && spaceId.isEmpty) {
|
||||||
);
|
clearAllData(context);
|
||||||
context.read<OccupancyHeatMapBloc>().add(
|
|
||||||
const ClearOccupancyHeatMapEvent(),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final datePickerState = context.read<AnalyticsDatePickerBloc>().state;
|
final datePickerState = context.read<AnalyticsDatePickerBloc>().state;
|
||||||
|
|
||||||
|
loadAnalyticsDevices(context, communityUuid: communityId, spaceUuid: spaceId);
|
||||||
|
final selectedDevice = context.read<AnalyticsDevicesBloc>().state.selectedDevice;
|
||||||
|
|
||||||
|
loadOccupancyChartData(
|
||||||
|
context,
|
||||||
|
communityUuid: communityId,
|
||||||
|
spaceUuid: spaceId,
|
||||||
|
date: datePickerState.monthlyDate,
|
||||||
|
);
|
||||||
|
loadHeatMapData(context, spaceUuid: spaceId, year: datePickerState.yearlyDate);
|
||||||
|
|
||||||
|
if (selectedDevice case final AnalyticsDevice device) {
|
||||||
|
context.read<RealtimeDeviceChangesBloc>()
|
||||||
|
..add(const RealtimeDeviceChangesClosed())
|
||||||
|
..add(
|
||||||
|
RealtimeDeviceChangesStarted(device.uuid),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadHeatMapData(
|
||||||
|
BuildContext context, {
|
||||||
|
required String spaceUuid,
|
||||||
|
required DateTime year,
|
||||||
|
}) {
|
||||||
|
context.read<OccupancyHeatMapBloc>().add(
|
||||||
|
LoadOccupancyHeatMapEvent(
|
||||||
|
GetOccupancyHeatMapParam(spaceUuid: spaceUuid, year: year),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadOccupancyChartData(
|
||||||
|
BuildContext context, {
|
||||||
|
required String communityUuid,
|
||||||
|
required String spaceUuid,
|
||||||
|
required DateTime date,
|
||||||
|
}) {
|
||||||
context.read<OccupancyBloc>().add(
|
context.read<OccupancyBloc>().add(
|
||||||
LoadOccupancyEvent(
|
LoadOccupancyEvent(
|
||||||
GetOccupancyParam(
|
GetOccupancyParam(
|
||||||
monthDate:
|
monthDate: '${date.year}-${date.month}',
|
||||||
'${datePickerState.monthlyDate.year}-${datePickerState.monthlyDate.month}',
|
spaceUuid: spaceUuid,
|
||||||
spaceUuid: selectedSpaces.firstOrNull,
|
communityUuid: communityUuid,
|
||||||
communityUuid: selectedCommunities.first,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadAnalyticsDevices(
|
||||||
|
BuildContext context, {
|
||||||
|
required String communityUuid,
|
||||||
|
required String spaceUuid,
|
||||||
|
}) {
|
||||||
|
context.read<AnalyticsDevicesBloc>().add(
|
||||||
|
LoadAnalyticsDevicesEvent(
|
||||||
|
param: GetAnalyticsDevicesParam(
|
||||||
|
communityUuid: communityUuid,
|
||||||
|
spaceUuid: spaceUuid,
|
||||||
|
deviceTypes: ['WPS', 'CPS'],
|
||||||
|
requestType: AnalyticsDeviceRequestType.occupancy,
|
||||||
|
),
|
||||||
|
onSuccess: (device) {
|
||||||
|
context.read<RealtimeDeviceChangesBloc>()
|
||||||
|
..add(const RealtimeDeviceChangesClosed())
|
||||||
|
..add(RealtimeDeviceChangesStarted(device.uuid));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clearAllData(BuildContext context) {
|
||||||
|
context.read<OccupancyBloc>().add(
|
||||||
|
const ClearOccupancyEvent(),
|
||||||
|
);
|
||||||
context.read<OccupancyHeatMapBloc>().add(
|
context.read<OccupancyHeatMapBloc>().add(
|
||||||
LoadOccupancyHeatMapEvent(
|
const ClearOccupancyHeatMapEvent(),
|
||||||
GetOccupancyHeatMapParam(
|
);
|
||||||
spaceId: selectedSpaces.isNotEmpty ? selectedSpaces.first : '',
|
context.read<RealtimeDeviceChangesBloc>().add(
|
||||||
communityId:
|
const RealtimeDeviceChangesClosed(),
|
||||||
selectedCommunities.isNotEmpty ? selectedCommunities.first : '',
|
|
||||||
year: datePickerState.yearlyDate,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
context.read<RealtimeDeviceChangesBloc>()
|
context.read<AnalyticsDevicesBloc>().add(
|
||||||
..add(const RealtimeDeviceChangesClosed())
|
const ClearAnalyticsDeviceEvent(),
|
||||||
..add(
|
);
|
||||||
const RealtimeDeviceChangesStarted('14fe6e7e-47af-4a07-ae0a-7c4a26ef8135'),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_chart_box.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_chart_box.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_end_side_bar.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_end_side_bar.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_box.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_box.dart';
|
||||||
|
|
||||||
class AnalyticsOccupancyView extends StatefulWidget {
|
class AnalyticsOccupancyView extends StatelessWidget {
|
||||||
const AnalyticsOccupancyView({super.key});
|
const AnalyticsOccupancyView({super.key});
|
||||||
|
|
||||||
static const _padding = EdgeInsetsDirectional.all(32);
|
static const _padding = EdgeInsetsDirectional.all(32);
|
||||||
|
|
||||||
@override
|
|
||||||
State<AnalyticsOccupancyView> createState() => _AnalyticsOccupancyViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AnalyticsOccupancyViewState extends State<AnalyticsOccupancyView> {
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
FetchOccupancyDataHelper.loadOccupancyData(context);
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final height = MediaQuery.sizeOf(context).height;
|
final height = MediaQuery.sizeOf(context).height;
|
||||||
@ -28,13 +16,13 @@ class _AnalyticsOccupancyViewState extends State<AnalyticsOccupancyView> {
|
|||||||
final isMediumOrLess = constraints.maxWidth <= 900;
|
final isMediumOrLess = constraints.maxWidth <= 900;
|
||||||
if (isMediumOrLess) {
|
if (isMediumOrLess) {
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
padding: AnalyticsOccupancyView._padding,
|
padding: _padding,
|
||||||
child: Column(
|
child: Column(
|
||||||
spacing: 32,
|
spacing: 32,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: height * 0.45, child: const OccupancyEndSideBar()),
|
SizedBox(height: height * 0.46, child: const OccupancyEndSideBar()),
|
||||||
SizedBox(height: height * 0.5, child: const OccupancyChartBox()),
|
SizedBox(height: height * 0.5, child: const OccupancyChartBox()),
|
||||||
SizedBox(height: height * 0.5, child: const Placeholder()),
|
SizedBox(height: height * 0.5, child: const OccupancyHeatMapBox()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -42,7 +30,7 @@ class _AnalyticsOccupancyViewState extends State<AnalyticsOccupancyView> {
|
|||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: AnalyticsOccupancyView._padding,
|
padding: _padding,
|
||||||
height: height * 0.9,
|
height: height * 0.9,
|
||||||
child: const Row(
|
child: const Row(
|
||||||
spacing: 32,
|
spacing: 32,
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class HeatMapTooltip extends StatelessWidget {
|
||||||
|
const HeatMapTooltip({
|
||||||
|
required this.date,
|
||||||
|
required this.value,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DateTime date;
|
||||||
|
final int value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.topStart,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.grey700,
|
||||||
|
borderRadius: BorderRadius.circular(3),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
DateFormat('MMM d, yyyy').format(date),
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 2, thickness: 1),
|
||||||
|
Text(
|
||||||
|
'$value Occupants',
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/heat_map_tooltip.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_painter.dart';
|
||||||
|
|
||||||
|
class InteractiveHeatMap extends StatefulWidget {
|
||||||
|
const InteractiveHeatMap({
|
||||||
|
required this.items,
|
||||||
|
required this.maxValue,
|
||||||
|
required this.cellSize,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<OccupancyPaintItem> items;
|
||||||
|
final int maxValue;
|
||||||
|
final double cellSize;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<InteractiveHeatMap> createState() => _InteractiveHeatMapState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InteractiveHeatMapState extends State<InteractiveHeatMap> {
|
||||||
|
OccupancyPaintItem? _hoveredItem;
|
||||||
|
OverlayEntry? _overlayEntry;
|
||||||
|
final LayerLink _layerLink = LayerLink();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_removeOverlay();
|
||||||
|
_overlayEntry?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _removeOverlay() {
|
||||||
|
_overlayEntry?.remove();
|
||||||
|
_overlayEntry = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showTooltip(OccupancyPaintItem item, Offset localPosition) {
|
||||||
|
_removeOverlay();
|
||||||
|
|
||||||
|
final column = item.index ~/ 7;
|
||||||
|
final row = item.index % 7;
|
||||||
|
final x = column * widget.cellSize;
|
||||||
|
final y = row * widget.cellSize;
|
||||||
|
|
||||||
|
_overlayEntry = OverlayEntry(
|
||||||
|
builder: (context) => Positioned(
|
||||||
|
child: CompositedTransformFollower(
|
||||||
|
link: _layerLink,
|
||||||
|
offset: Offset(x + widget.cellSize, y),
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Transform.translate(
|
||||||
|
offset: Offset(-(widget.cellSize * 2.5), -50),
|
||||||
|
child: HeatMapTooltip(date: item.date, value: item.value),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Overlay.of(context).insert(_overlayEntry!);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CompositedTransformTarget(
|
||||||
|
link: _layerLink,
|
||||||
|
child: MouseRegion(
|
||||||
|
onHover: (event) {
|
||||||
|
final column = event.localPosition.dx ~/ widget.cellSize;
|
||||||
|
final row = event.localPosition.dy ~/ widget.cellSize;
|
||||||
|
final index = column * 7 + row;
|
||||||
|
|
||||||
|
if (index >= 0 && index < widget.items.length) {
|
||||||
|
final item = widget.items[index];
|
||||||
|
if (_hoveredItem != item) {
|
||||||
|
setState(() => _hoveredItem = item);
|
||||||
|
_showTooltip(item, event.localPosition);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_removeOverlay();
|
||||||
|
setState(() => _hoveredItem = null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onExit: (_) {
|
||||||
|
_removeOverlay();
|
||||||
|
setState(() => _hoveredItem = null);
|
||||||
|
},
|
||||||
|
child: CustomPaint(
|
||||||
|
isComplex: true,
|
||||||
|
size: _painterSize,
|
||||||
|
painter: OccupancyPainter(
|
||||||
|
items: widget.items,
|
||||||
|
maxValue: widget.maxValue,
|
||||||
|
hoveredItem: _hoveredItem,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Size get _painterSize {
|
||||||
|
final height = 7 * widget.cellSize;
|
||||||
|
final width = widget.items.length ~/ 7 * widget.cellSize;
|
||||||
|
return Size(width, height);
|
||||||
|
}
|
||||||
|
}
|
@ -19,11 +19,18 @@ class OccupancyChart extends StatelessWidget {
|
|||||||
maxY: 1.0,
|
maxY: 1.0,
|
||||||
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
||||||
checkToShowHorizontalLine: (value) => true,
|
checkToShowHorizontalLine: (value) => true,
|
||||||
horizontalInterval: 0.25,
|
horizontalInterval: 0.2,
|
||||||
),
|
),
|
||||||
borderData: EnergyManagementChartsHelper.borderData(),
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
barTouchData: _barTouchData(context),
|
barTouchData: _barTouchData(context),
|
||||||
titlesData: _titlesData(context),
|
titlesData: _titlesData(context).copyWith(
|
||||||
|
leftTitles: _titlesData(context).leftTitles.copyWith(
|
||||||
|
sideTitles: _titlesData(context).leftTitles.sideTitles.copyWith(
|
||||||
|
maxIncluded: true,
|
||||||
|
minIncluded: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
barGroups: List.generate(chartData.length, (index) {
|
barGroups: List.generate(chartData.length, (index) {
|
||||||
final actual = chartData[index];
|
final actual = chartData[index];
|
||||||
return BarChartGroupData(
|
return BarChartGroupData(
|
||||||
@ -101,7 +108,7 @@ class OccupancyChart extends StatelessWidget {
|
|||||||
final leftTitles = titlesData.leftTitles.copyWith(
|
final leftTitles = titlesData.leftTitles.copyWith(
|
||||||
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
||||||
reservedSize: 70,
|
reservedSize: 70,
|
||||||
interval: 0.25,
|
interval: 0.2,
|
||||||
getTitlesWidget: (value, meta) => Padding(
|
getTitlesWidget: (value, meta) => Padding(
|
||||||
padding: const EdgeInsetsDirectional.only(end: 12),
|
padding: const EdgeInsetsDirectional.only(end: 12),
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
|
@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/oc
|
|||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_chart.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||||
import 'package:syncrow_web/utils/style.dart';
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
class OccupancyChartBox extends StatelessWidget {
|
class OccupancyChartBox extends StatelessWidget {
|
||||||
@ -14,6 +15,7 @@ class OccupancyChartBox extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final spaceTreeState = context.watch<SpaceTreeBloc>().state;
|
||||||
return BlocBuilder<OccupancyBloc, OccupancyState>(
|
return BlocBuilder<OccupancyBloc, OccupancyState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Container(
|
return Container(
|
||||||
@ -45,7 +47,17 @@ class OccupancyChartBox extends StatelessWidget {
|
|||||||
context.read<AnalyticsDatePickerBloc>().add(
|
context.read<AnalyticsDatePickerBloc>().add(
|
||||||
UpdateAnalyticsDatePickerEvent(montlyDate: value),
|
UpdateAnalyticsDatePickerEvent(montlyDate: value),
|
||||||
);
|
);
|
||||||
FetchOccupancyDataHelper.loadOccupancyData(context);
|
if (spaceTreeState.selectedSpaces.isNotEmpty) {
|
||||||
|
FetchOccupancyDataHelper.loadOccupancyChartData(
|
||||||
|
context,
|
||||||
|
communityUuid:
|
||||||
|
spaceTreeState.selectedCommunities.firstOrNull ??
|
||||||
|
'',
|
||||||
|
spaceUuid:
|
||||||
|
spaceTreeState.selectedSpaces.firstOrNull ?? '',
|
||||||
|
date: value,
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
selectedDate: context
|
selectedDate: context
|
||||||
.watch<AnalyticsDatePickerBloc>()
|
.watch<AnalyticsDatePickerBloc>()
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart';
|
import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_device_dropdown.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
import 'package:syncrow_web/utils/style.dart';
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
|
||||||
|
|
||||||
class OccupancyEndSideBar extends StatelessWidget {
|
class OccupancyEndSideBar extends StatelessWidget {
|
||||||
const OccupancyEndSideBar({super.key});
|
const OccupancyEndSideBar({super.key});
|
||||||
@ -37,7 +38,8 @@ class OccupancyEndSideBar extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
SelectableText(
|
SelectableText(
|
||||||
(const Uuid().v4()),
|
context.watch<AnalyticsDevicesBloc>().state.selectedDevice?.uuid ??
|
||||||
|
'N/A',
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
@ -105,7 +107,7 @@ class OccupancyEndSideBar extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 3,
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
@ -120,11 +122,18 @@ class OccupancyEndSideBar extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
const Expanded(
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
alignment: AlignmentDirectional.centerEnd,
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
child: PowerClampEnergyDataDeviceDropdown(),
|
child: AnalyticsDeviceDropdown(
|
||||||
|
onChanged: (value) =>
|
||||||
|
FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges(
|
||||||
|
context,
|
||||||
|
deviceUuid: value.uuid,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:math' as math show max;
|
import 'dart:math' as math show max;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/interactive_heat_map.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_days.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_days.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_gradient.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_gradient.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_months.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_months.dart';
|
||||||
@ -15,7 +16,7 @@ class OccupancyHeatMap extends StatelessWidget {
|
|||||||
static const _totalWeeks = 53;
|
static const _totalWeeks = 53;
|
||||||
|
|
||||||
int get _maxValue => heatMapData.isNotEmpty
|
int get _maxValue => heatMapData.isNotEmpty
|
||||||
? heatMapData.keys.map((key) => heatMapData[key]!).reduce(math.max)
|
? heatMapData.keys.map((key) => heatMapData[key] ?? 0).reduce(math.max)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
DateTime _getStartingDate() {
|
DateTime _getStartingDate() {
|
||||||
@ -28,7 +29,7 @@ class OccupancyHeatMap extends StatelessWidget {
|
|||||||
return List.generate(_totalWeeks * 7, (index) {
|
return List.generate(_totalWeeks * 7, (index) {
|
||||||
final date = startDate.add(Duration(days: index));
|
final date = startDate.add(Duration(days: index));
|
||||||
final value = heatMapData[date] ?? 0;
|
final value = heatMapData[date] ?? 0;
|
||||||
return OccupancyPaintItem(index: index, value: value);
|
return OccupancyPaintItem(index: index, value: value, date: date);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,15 +59,13 @@ class OccupancyHeatMap extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const OccupancyHeatMapDays(cellSize: _cellSize),
|
const OccupancyHeatMapDays(cellSize: _cellSize),
|
||||||
CustomPaint(
|
SizedBox(
|
||||||
size: const Size(_totalWeeks * _cellSize, 7 * _cellSize),
|
width: _totalWeeks * _cellSize,
|
||||||
child: CustomPaint(
|
height: 7 * _cellSize,
|
||||||
isComplex: true,
|
child: InteractiveHeatMap(
|
||||||
size: const Size(_totalWeeks * _cellSize, 7 * _cellSize),
|
items: paintItems,
|
||||||
painter: OccupancyPainter(
|
maxValue: _maxValue,
|
||||||
items: paintItems,
|
cellSize: _cellSize,
|
||||||
maxValue: _maxValue,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_he
|
|||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/helpers/fetch_occupancy_data_helper.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||||
import 'package:syncrow_web/utils/style.dart';
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
class OccupancyHeatMapBox extends StatelessWidget {
|
class OccupancyHeatMapBox extends StatelessWidget {
|
||||||
@ -14,6 +15,7 @@ class OccupancyHeatMapBox extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final spaceTreeState = context.watch<SpaceTreeBloc>().state;
|
||||||
return BlocBuilder<OccupancyHeatMapBloc, OccupancyHeatMapState>(
|
return BlocBuilder<OccupancyHeatMapBloc, OccupancyHeatMapState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Container(
|
return Container(
|
||||||
@ -45,7 +47,14 @@ class OccupancyHeatMapBox extends StatelessWidget {
|
|||||||
context.read<AnalyticsDatePickerBloc>().add(
|
context.read<AnalyticsDatePickerBloc>().add(
|
||||||
UpdateAnalyticsDatePickerEvent(yearlyDate: value),
|
UpdateAnalyticsDatePickerEvent(yearlyDate: value),
|
||||||
);
|
);
|
||||||
FetchOccupancyDataHelper.loadOccupancyData(context);
|
if (spaceTreeState.selectedSpaces.isNotEmpty) {
|
||||||
|
FetchOccupancyDataHelper.loadHeatMapData(
|
||||||
|
context,
|
||||||
|
spaceUuid:
|
||||||
|
spaceTreeState.selectedSpaces.firstOrNull ?? '',
|
||||||
|
year: value,
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
datePickerType: DatePickerType.year,
|
datePickerType: DatePickerType.year,
|
||||||
selectedDate: context
|
selectedDate: context
|
||||||
@ -61,7 +70,10 @@ class OccupancyHeatMapBox extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: OccupancyHeatMap(
|
child: OccupancyHeatMap(
|
||||||
heatMapData: state.heatMapData.asMap().map(
|
heatMapData: state.heatMapData.asMap().map(
|
||||||
(_, value) => MapEntry(value.date, value.occupancy),
|
(_, value) => MapEntry(
|
||||||
|
value.eventDate,
|
||||||
|
value.countTotalPresenceDetected,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -4,18 +4,25 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
|||||||
class OccupancyPaintItem {
|
class OccupancyPaintItem {
|
||||||
final int index;
|
final int index;
|
||||||
final int value;
|
final int value;
|
||||||
|
final DateTime date;
|
||||||
|
|
||||||
const OccupancyPaintItem({required this.index, required this.value});
|
const OccupancyPaintItem({
|
||||||
|
required this.index,
|
||||||
|
required this.value,
|
||||||
|
required this.date,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class OccupancyPainter extends CustomPainter {
|
class OccupancyPainter extends CustomPainter {
|
||||||
OccupancyPainter({
|
OccupancyPainter({
|
||||||
required this.items,
|
required this.items,
|
||||||
required this.maxValue,
|
required this.maxValue,
|
||||||
|
this.hoveredItem,
|
||||||
});
|
});
|
||||||
|
|
||||||
final List<OccupancyPaintItem> items;
|
final List<OccupancyPaintItem> items;
|
||||||
final int maxValue;
|
final int maxValue;
|
||||||
|
final OccupancyPaintItem? hoveredItem;
|
||||||
|
|
||||||
static const double cellSize = 16.0;
|
static const double cellSize = 16.0;
|
||||||
|
|
||||||
@ -25,6 +32,10 @@ class OccupancyPainter extends CustomPainter {
|
|||||||
final Paint borderPaint = Paint()
|
final Paint borderPaint = Paint()
|
||||||
..color = ColorsManager.grayBorder.withValues(alpha: 0.4)
|
..color = ColorsManager.grayBorder.withValues(alpha: 0.4)
|
||||||
..style = PaintingStyle.stroke;
|
..style = PaintingStyle.stroke;
|
||||||
|
final Paint hoveredBorderPaint = Paint()
|
||||||
|
..color = Colors.black
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 1.5;
|
||||||
|
|
||||||
for (final item in items) {
|
for (final item in items) {
|
||||||
final column = item.index ~/ 7;
|
final column = item.index ~/ 7;
|
||||||
@ -37,22 +48,27 @@ class OccupancyPainter extends CustomPainter {
|
|||||||
final rect = Rect.fromLTWH(x, y, cellSize, cellSize);
|
final rect = Rect.fromLTWH(x, y, cellSize, cellSize);
|
||||||
canvas.drawRect(rect, fillPaint);
|
canvas.drawRect(rect, fillPaint);
|
||||||
|
|
||||||
_drawDashedLine(
|
// Highlight the hovered item
|
||||||
canvas,
|
if (hoveredItem != null && hoveredItem!.index == item.index) {
|
||||||
Offset(x, y),
|
canvas.drawRect(rect, hoveredBorderPaint);
|
||||||
Offset(x + cellSize, y),
|
} else {
|
||||||
borderPaint,
|
_drawDashedLine(
|
||||||
);
|
canvas,
|
||||||
_drawDashedLine(
|
Offset(x, y),
|
||||||
canvas,
|
Offset(x + cellSize, y),
|
||||||
Offset(x, y + cellSize),
|
borderPaint,
|
||||||
Offset(x + cellSize, y + cellSize),
|
);
|
||||||
borderPaint,
|
_drawDashedLine(
|
||||||
);
|
canvas,
|
||||||
|
Offset(x, y + cellSize),
|
||||||
|
Offset(x + cellSize, y + cellSize),
|
||||||
|
borderPaint,
|
||||||
|
);
|
||||||
|
|
||||||
canvas.drawLine(Offset(x, y), Offset(x, y + cellSize), borderPaint);
|
canvas.drawLine(Offset(x, y), Offset(x, y + cellSize), borderPaint);
|
||||||
canvas.drawLine(
|
canvas.drawLine(Offset(x + cellSize, y), Offset(x + cellSize, y + cellSize),
|
||||||
Offset(x + cellSize, y), Offset(x + cellSize, y + cellSize), borderPaint);
|
borderPaint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,5 +96,6 @@ class OccupancyPainter extends CustomPainter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
bool shouldRepaint(covariant OccupancyPainter oldDelegate) =>
|
||||||
|
oldDelegate.hoveredItem != hoveredItem;
|
||||||
}
|
}
|
||||||
|
22
lib/pages/analytics/params/get_analytics_devices_param.dart
Normal file
22
lib/pages/analytics/params/get_analytics_devices_param.dart
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
enum AnalyticsDeviceRequestType { energyManagement, occupancy }
|
||||||
|
|
||||||
|
class GetAnalyticsDevicesParam {
|
||||||
|
final String? spaceUuid;
|
||||||
|
final List<String> deviceTypes;
|
||||||
|
final String? communityUuid;
|
||||||
|
final AnalyticsDeviceRequestType requestType;
|
||||||
|
|
||||||
|
const GetAnalyticsDevicesParam({
|
||||||
|
required this.requestType,
|
||||||
|
required this.spaceUuid,
|
||||||
|
required this.deviceTypes,
|
||||||
|
required this.communityUuid,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return <String, dynamic>{
|
||||||
|
if (spaceUuid != null) 'spaceUuid': spaceUuid,
|
||||||
|
if (communityUuid != null) 'communityUuid': communityUuid,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,24 +1,20 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
class GetEnergyConsumptionByPhasesParam extends Equatable {
|
class GetEnergyConsumptionByPhasesParam extends Equatable {
|
||||||
final DateTime? startDate;
|
final String powerClampUuid;
|
||||||
final DateTime? endDate;
|
final DateTime? date;
|
||||||
final String? spaceId;
|
|
||||||
|
|
||||||
const GetEnergyConsumptionByPhasesParam({
|
const GetEnergyConsumptionByPhasesParam({
|
||||||
this.startDate,
|
required this.powerClampUuid,
|
||||||
this.endDate,
|
this.date,
|
||||||
this.spaceId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {
|
||||||
'startDate': startDate?.toIso8601String(),
|
'monthDate': '${date?.year}-${date?.month.toString().padLeft(2, '0')}',
|
||||||
'endDate': endDate?.toIso8601String(),
|
|
||||||
'spaceId': spaceId,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [startDate, endDate, spaceId];
|
List<Object?> get props => [powerClampUuid, date];
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,19 @@
|
|||||||
class GetEnergyConsumptionPerDeviceParam {
|
class GetEnergyConsumptionPerDeviceParam {
|
||||||
const GetEnergyConsumptionPerDeviceParam();
|
const GetEnergyConsumptionPerDeviceParam({
|
||||||
|
this.monthDate,
|
||||||
|
this.spaceId,
|
||||||
|
this.communityId,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DateTime? monthDate;
|
||||||
|
final String? spaceId;
|
||||||
|
final String? communityId;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'monthDate':
|
||||||
|
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
|
||||||
|
if (spaceId == null || spaceId == null) 'spaceUuid': spaceId,
|
||||||
|
'communityUuid': communityId,
|
||||||
|
'groupByDevice': true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,13 @@
|
|||||||
class GetOccupancyHeatMapParam {
|
class GetOccupancyHeatMapParam {
|
||||||
final DateTime year;
|
final DateTime year;
|
||||||
final String communityId;
|
final String spaceUuid;
|
||||||
final String spaceId;
|
|
||||||
|
|
||||||
const GetOccupancyHeatMapParam({
|
const GetOccupancyHeatMapParam({
|
||||||
required this.year,
|
required this.year,
|
||||||
required this.communityId,
|
required this.spaceUuid,
|
||||||
required this.spaceId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {'year': year.year};
|
||||||
'year': year.toIso8601String(),
|
|
||||||
'communityId': communityId,
|
|
||||||
'spaceId': spaceId,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ class GetTotalEnergyConsumptionParam {
|
|||||||
return {
|
return {
|
||||||
'monthDate':
|
'monthDate':
|
||||||
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
|
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
|
||||||
if (communityId == null || communityId!.isEmpty) 'spaceUuid': spaceId,
|
if (spaceId == null || spaceId == null) 'spaceUuid': spaceId,
|
||||||
'communityUuid': communityId,
|
'communityUuid': communityId,
|
||||||
'groupByDevice': false,
|
'groupByDevice': false,
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
|
||||||
|
|
||||||
|
abstract interface class AnalyticsDevicesService {
|
||||||
|
Future<List<AnalyticsDevice>> getDevices(GetAnalyticsDevicesParam param);
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart';
|
||||||
|
|
||||||
|
class AnalyticsDevicesServiceDelegate implements AnalyticsDevicesService {
|
||||||
|
const AnalyticsDevicesServiceDelegate(
|
||||||
|
this._occupancyService,
|
||||||
|
this._energyManagementService,
|
||||||
|
);
|
||||||
|
|
||||||
|
final AnalyticsDevicesService _occupancyService;
|
||||||
|
final AnalyticsDevicesService _energyManagementService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<AnalyticsDevice>> getDevices(
|
||||||
|
GetAnalyticsDevicesParam param,
|
||||||
|
) {
|
||||||
|
return switch (param.requestType) {
|
||||||
|
AnalyticsDeviceRequestType.occupancy => _occupancyService.getDevices(param),
|
||||||
|
AnalyticsDeviceRequestType.energyManagement =>
|
||||||
|
_energyManagementService.getDevices(param),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
|
final class RemoteEnergyManagementAnalyticsDevicesService
|
||||||
|
implements AnalyticsDevicesService {
|
||||||
|
const RemoteEnergyManagementAnalyticsDevicesService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<AnalyticsDevice>> getDevices(GetAnalyticsDevicesParam param) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: '/devices-space-community/recursive-child',
|
||||||
|
queryParameters: param.toJson()
|
||||||
|
..addAll({'productType': param.deviceTypes.first}),
|
||||||
|
expectedResponseModel: (response) {
|
||||||
|
final json = response as Map<String, dynamic>;
|
||||||
|
final dailyData = json['data'] as List<dynamic>? ?? <dynamic>[];
|
||||||
|
|
||||||
|
final result = dailyData.map(
|
||||||
|
(json) => AnalyticsDevice.fromJson(json as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
return result.toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load total energy consumption: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
|
class RemoteOccupancyAnalyticsDevicesService implements AnalyticsDevicesService {
|
||||||
|
const RemoteOccupancyAnalyticsDevicesService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<AnalyticsDevice>> getDevices(GetAnalyticsDevicesParam param) async {
|
||||||
|
try {
|
||||||
|
final requests = await Future.wait<List<AnalyticsDevice>>(
|
||||||
|
param.deviceTypes.map((e) {
|
||||||
|
final mappedParam = GetAnalyticsDevicesParam(
|
||||||
|
requestType: AnalyticsDeviceRequestType.occupancy,
|
||||||
|
spaceUuid: param.spaceUuid,
|
||||||
|
deviceTypes: [e],
|
||||||
|
communityUuid: param.communityUuid,
|
||||||
|
);
|
||||||
|
return _makeRequest(mappedParam);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = requests.map((e) => e.first).toList();
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load total energy consumption: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<AnalyticsDevice>> _makeRequest(GetAnalyticsDevicesParam param) async {
|
||||||
|
try {
|
||||||
|
final projectUuid = await ProjectManager.getProjectUUID();
|
||||||
|
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path:
|
||||||
|
'/projects/$projectUuid/communities/${param.communityUuid}/spaces/${param.spaceUuid}/devices',
|
||||||
|
queryParameters: {
|
||||||
|
'communityUuid': param.communityUuid,
|
||||||
|
'spaceUuid': param.spaceUuid,
|
||||||
|
'productType': param.deviceTypes.first,
|
||||||
|
},
|
||||||
|
expectedResponseModel: (response) {
|
||||||
|
final json = response as Map<String, dynamic>;
|
||||||
|
final dailyData = json['data'] as List<dynamic>? ?? <dynamic>[];
|
||||||
|
|
||||||
|
final result = dailyData.map(
|
||||||
|
(json) => AnalyticsDevice.fromJson(json as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
return result.toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,29 +0,0 @@
|
|||||||
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/energy_consumption_by_phases_service.dart';
|
|
||||||
|
|
||||||
class FakeEnergyConsumptionByPhasesService
|
|
||||||
implements EnergyConsumptionByPhasesService {
|
|
||||||
@override
|
|
||||||
Future<List<PhasesEnergyConsumption>> load(
|
|
||||||
GetEnergyConsumptionByPhasesParam param,
|
|
||||||
) {
|
|
||||||
return Future.delayed(
|
|
||||||
const Duration(milliseconds: 500),
|
|
||||||
() => const [
|
|
||||||
PhasesEnergyConsumption(month: 1, phaseA: 200, phaseB: 300, phaseC: 400),
|
|
||||||
PhasesEnergyConsumption(month: 2, phaseA: 300, phaseB: 400, phaseC: 500),
|
|
||||||
PhasesEnergyConsumption(month: 3, phaseA: 400, phaseB: 500, phaseC: 600),
|
|
||||||
PhasesEnergyConsumption(month: 4, phaseA: 100, phaseB: 100, phaseC: 100),
|
|
||||||
PhasesEnergyConsumption(month: 5, phaseA: 300, phaseB: 400, phaseC: 500),
|
|
||||||
PhasesEnergyConsumption(month: 6, phaseA: 300, phaseB: 100, phaseC: 400),
|
|
||||||
PhasesEnergyConsumption(month: 7, phaseA: 300, phaseB: 100, phaseC: 400),
|
|
||||||
PhasesEnergyConsumption(month: 8, phaseA: 500, phaseB: 100, phaseC: 100),
|
|
||||||
PhasesEnergyConsumption(month: 9, phaseA: 500, phaseB: 100, phaseC: 200),
|
|
||||||
PhasesEnergyConsumption(month: 10, phaseA: 100, phaseB: 50, phaseC: 50),
|
|
||||||
PhasesEnergyConsumption(month: 11, phaseA: 600, phaseB: 750, phaseC: 130),
|
|
||||||
PhasesEnergyConsumption(month: 12, phaseA: 100, phaseB: 100, phaseC: 100),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,8 +15,9 @@ final class RemoteEnergyConsumptionByPhasesService
|
|||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
final response = await _httpService.get(
|
final response = await _httpService.get(
|
||||||
path: 'endpoint',
|
path: '/power-clamp/${param.powerClampUuid}/historical',
|
||||||
showServerMessage: true,
|
showServerMessage: true,
|
||||||
|
queryParameters: param.toJson(),
|
||||||
expectedResponseModel: (data) {
|
expectedResponseModel: (data) {
|
||||||
final json = data as Map<String, dynamic>? ?? {};
|
final json = data as Map<String, dynamic>? ?? {};
|
||||||
final mappedData = json['data'] as List<dynamic>? ?? [];
|
final mappedData = json['data'] as List<dynamic>? ?? [];
|
||||||
@ -28,7 +29,7 @@ final class RemoteEnergyConsumptionByPhasesService
|
|||||||
);
|
);
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Exception('Failed to load energy consumption per device: $e');
|
throw Exception('Failed to load energy consumption per phase: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
import 'dart:math' as math show Random;
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart';
|
|
||||||
|
|
||||||
class FakeEnergyConsumptionPerDeviceService
|
|
||||||
implements EnergyConsumptionPerDeviceService {
|
|
||||||
@override
|
|
||||||
Future<List<DeviceEnergyDataModel>> load(
|
|
||||||
GetEnergyConsumptionPerDeviceParam param,
|
|
||||||
) {
|
|
||||||
final random = math.Random();
|
|
||||||
return Future.delayed(const Duration(milliseconds: 500), () {
|
|
||||||
return [
|
|
||||||
(Colors.redAccent, 1),
|
|
||||||
(Colors.lightBlueAccent, 2),
|
|
||||||
(Colors.purpleAccent, 3),
|
|
||||||
].map((e) {
|
|
||||||
final (color, index) = e;
|
|
||||||
return DeviceEnergyDataModel(
|
|
||||||
color: color,
|
|
||||||
energy: List.generate(30, (i) => i)
|
|
||||||
.map(
|
|
||||||
(index) => EnergyDataModel(
|
|
||||||
date: DateTime(2025, 1, index + 1),
|
|
||||||
value: random.nextInt(100) + (index * 100),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
deviceName: 'Device $index',
|
|
||||||
deviceId: 'device_$index',
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,6 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart';
|
||||||
import 'package:syncrow_web/services/api/http_service.dart';
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
@ -15,16 +17,10 @@ class RemoteEnergyConsumptionPerDeviceService
|
|||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
final response = await _httpService.get(
|
final response = await _httpService.get(
|
||||||
path: 'endpoint',
|
path: '/power-clamp/historical',
|
||||||
showServerMessage: true,
|
showServerMessage: true,
|
||||||
expectedResponseModel: (data) {
|
queryParameters: param.toJson(),
|
||||||
final json = data as Map<String, dynamic>? ?? {};
|
expectedResponseModel: _EnergyConsumptionPerDeviceMapper.map,
|
||||||
final mappedData = json['data'] as List<dynamic>? ?? [];
|
|
||||||
return mappedData.map((e) {
|
|
||||||
final jsonData = e as Map<String, dynamic>;
|
|
||||||
return DeviceEnergyDataModel.fromJson(jsonData);
|
|
||||||
}).toList();
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -32,3 +28,30 @@ class RemoteEnergyConsumptionPerDeviceService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract final class _EnergyConsumptionPerDeviceMapper {
|
||||||
|
const _EnergyConsumptionPerDeviceMapper._();
|
||||||
|
static List<DeviceEnergyDataModel> map(dynamic data) {
|
||||||
|
final json = data as Map<String, dynamic>? ?? {};
|
||||||
|
final mappedData = json['data'] as List<dynamic>? ?? [];
|
||||||
|
return mappedData.map((e) {
|
||||||
|
final deviceData = e as Map<String, dynamic>;
|
||||||
|
final energyData = deviceData['data'] as List<dynamic>;
|
||||||
|
|
||||||
|
return DeviceEnergyDataModel(
|
||||||
|
deviceId: deviceData['deviceUuid'] as String,
|
||||||
|
deviceName: deviceData['deviceName'] as String,
|
||||||
|
color: Color((DateTime.now().microsecondsSinceEpoch +
|
||||||
|
deviceData['deviceUuid'].hashCode) |
|
||||||
|
0xFF000000),
|
||||||
|
energy: energyData.map((data) {
|
||||||
|
final energyJson = data as Map<String, dynamic>;
|
||||||
|
return EnergyDataModel(
|
||||||
|
date: DateTime.parse(energyJson['date'] as String),
|
||||||
|
value: double.parse(energyJson['total_energy_consumed_kw'] as String),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
import 'package:syncrow_web/pages/analytics/models/occupancy_heat_map_model.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/occupancy_heat_map_service.dart';
|
|
||||||
|
|
||||||
class FakeOccupancyHeatMapService implements OccupancyHeatMapService {
|
|
||||||
@override
|
|
||||||
Future<List<OccupancyHeatMapModel>> load(GetOccupancyHeatMapParam param) {
|
|
||||||
return Future.delayed(const Duration(milliseconds: 200), () {
|
|
||||||
final now = DateTime.now();
|
|
||||||
final startOfYear = DateTime(now.year, 1, 1);
|
|
||||||
final endOfYear = DateTime(now.year, 12, 31);
|
|
||||||
final daysInYear = endOfYear.difference(startOfYear).inDays + 1;
|
|
||||||
|
|
||||||
final List<OccupancyHeatMapModel> data = List.generate(
|
|
||||||
daysInYear,
|
|
||||||
(index) => OccupancyHeatMapModel(
|
|
||||||
date: startOfYear.add(Duration(days: index)),
|
|
||||||
occupancy: ((index + 1) * 10) % 100,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,35 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/occupancy_heat_map_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/occupancy_heat_map_service.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
|
final class RemoteOccupancyHeatMapService implements OccupancyHeatMapService {
|
||||||
|
const RemoteOccupancyHeatMapService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<OccupancyHeatMapModel>> load(GetOccupancyHeatMapParam param) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: '/occupancy/heat-map/space/${param.spaceUuid}',
|
||||||
|
showServerMessage: true,
|
||||||
|
queryParameters: param.toJson(),
|
||||||
|
expectedResponseModel: (response) {
|
||||||
|
final json = response as Map<String, dynamic>;
|
||||||
|
final dailyData = json['data'] as List<dynamic>? ?? <dynamic>[];
|
||||||
|
|
||||||
|
final result = dailyData.map(
|
||||||
|
(json) => OccupancyHeatMapModel.fromJson(json as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
return result.toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load total energy consumption:');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,7 @@ class AnalyticsErrorWidget extends StatelessWidget {
|
|||||||
return Visibility(
|
return Visibility(
|
||||||
visible: errorMessage != null || (errorMessage?.isNotEmpty ?? false),
|
visible: errorMessage != null || (errorMessage?.isNotEmpty ?? false),
|
||||||
child: Text(
|
child: Text(
|
||||||
'$errorMessage ?? "Something went wrong"',
|
errorMessage ?? 'Something went wrong',
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
@ -12,6 +12,7 @@ import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/
|
|||||||
import 'package:syncrow_web/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart';
|
import 'package:syncrow_web/pages/routines/models/gang_switches/three_gang_switch/three_gang_switch.dart';
|
||||||
import 'package:syncrow_web/pages/routines/models/gang_switches/two_gang_switch/two_gang_switch.dart';
|
import 'package:syncrow_web/pages/routines/models/gang_switches/two_gang_switch/two_gang_switch.dart';
|
||||||
import 'package:syncrow_web/pages/routines/models/gateway.dart';
|
import 'package:syncrow_web/pages/routines/models/gateway.dart';
|
||||||
|
import 'package:syncrow_web/pages/routines/models/water_heater/water_heater_functions.dart';
|
||||||
import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart';
|
import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart';
|
||||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart';
|
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/ceiling_sensor_helper.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
@ -358,7 +359,10 @@ SOS
|
|||||||
case 'NCPS':
|
case 'NCPS':
|
||||||
return [
|
return [
|
||||||
FlushPresenceDelayFunction(
|
FlushPresenceDelayFunction(
|
||||||
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF',),
|
deviceId: uuid ?? '',
|
||||||
|
deviceName: name ?? '',
|
||||||
|
type: 'IF',
|
||||||
|
),
|
||||||
|
|
||||||
FlushIlluminanceFunction(
|
FlushIlluminanceFunction(
|
||||||
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
|
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
|
||||||
@ -378,6 +382,17 @@ SOS
|
|||||||
FlushTriggerLevelFunction(
|
FlushTriggerLevelFunction(
|
||||||
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
|
deviceId: uuid ?? '', deviceName: name ?? '', type: 'THEN'),
|
||||||
];
|
];
|
||||||
|
case 'WH':
|
||||||
|
return [
|
||||||
|
WHRestartStatusFunction(
|
||||||
|
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
|
||||||
|
WHSwitchFunction(
|
||||||
|
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
|
||||||
|
TimerConfirmTimeFunction(
|
||||||
|
deviceId: uuid ?? '', deviceName: name ?? '', type: 'BOTH'),
|
||||||
|
BacklightFunction(
|
||||||
|
deviceId: uuid ?? '', deviceName: name ?? '', type: 'IF'),
|
||||||
|
];
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
|
@ -536,7 +536,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
// 'entityId': 'tab_to_run',
|
// 'entityId': 'tab_to_run',
|
||||||
// 'uniqueCustomId': const Uuid().v4(),
|
// 'uniqueCustomId': const Uuid().v4(),
|
||||||
// 'deviceId': 'tab_to_run',
|
// 'deviceId': 'tab_to_run',
|
||||||
// 'title': 'Tab to run',
|
// 'title': 'Tap to run',
|
||||||
// 'productType': 'tab_to_run',
|
// 'productType': 'tab_to_run',
|
||||||
// 'imagePath': Assets.tabToRun,
|
// 'imagePath': Assets.tabToRun,
|
||||||
// }
|
// }
|
||||||
@ -771,7 +771,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
'entityId': 'tab_to_run',
|
'entityId': 'tab_to_run',
|
||||||
'uniqueCustomId': const Uuid().v4(),
|
'uniqueCustomId': const Uuid().v4(),
|
||||||
'deviceId': 'tab_to_run',
|
'deviceId': 'tab_to_run',
|
||||||
'title': 'Tab to run',
|
'title': 'Tap to run',
|
||||||
'productType': 'tab_to_run',
|
'productType': 'tab_to_run',
|
||||||
'imagePath': Assets.tabToRun,
|
'imagePath': Assets.tabToRun,
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/one_gang_swit
|
|||||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart';
|
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/three_gang_switch_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart';
|
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/two_gang_switch_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart';
|
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wall_presence_sensor.dart';
|
||||||
|
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/water_heater/water_heater_presence_sensor.dart';
|
||||||
|
|
||||||
class DeviceDialogHelper {
|
class DeviceDialogHelper {
|
||||||
static Future<Map<String, dynamic>?> showDeviceDialog({
|
static Future<Map<String, dynamic>?> showDeviceDialog({
|
||||||
@ -126,6 +127,15 @@ class DeviceDialogHelper {
|
|||||||
dialogType: dialogType,
|
dialogType: dialogType,
|
||||||
device: data['device'],
|
device: data['device'],
|
||||||
);
|
);
|
||||||
|
case 'WH':
|
||||||
|
return WaterHeaterDialogRoutines.showWHFunctionsDialog(
|
||||||
|
context: context,
|
||||||
|
functions: functions,
|
||||||
|
uniqueCustomId: data['uniqueCustomId'],
|
||||||
|
deviceSelectedFunctions: deviceSelectedFunctions,
|
||||||
|
dialogType: dialogType,
|
||||||
|
device: data['device'],
|
||||||
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
|
@ -162,7 +162,7 @@ class SaveRoutineHelper {
|
|||||||
width: 24,
|
width: 24,
|
||||||
height: 24,
|
height: 24,
|
||||||
),
|
),
|
||||||
title: const Text('Tab to run'),
|
title: const Text('Tap to run'),
|
||||||
),
|
),
|
||||||
if (state.isAutomation)
|
if (state.isAutomation)
|
||||||
...state.ifItems.map((item) {
|
...state.ifItems.map((item) {
|
||||||
|
@ -0,0 +1,134 @@
|
|||||||
|
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
||||||
|
import 'package:syncrow_web/pages/routines/models/water_heater/water_heater_operational_value.dart';
|
||||||
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
|
||||||
|
abstract class WaterHeaterFunctions
|
||||||
|
extends DeviceFunction<WaterHeaterStatusModel> {
|
||||||
|
final String type;
|
||||||
|
|
||||||
|
WaterHeaterFunctions({
|
||||||
|
required super.deviceId,
|
||||||
|
required super.deviceName,
|
||||||
|
required super.code,
|
||||||
|
required super.operationName,
|
||||||
|
required super.icon,
|
||||||
|
required this.type,
|
||||||
|
});
|
||||||
|
|
||||||
|
List<WaterHeaterOperationalValue> getOperationalValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
class WHRestartStatusFunction extends WaterHeaterFunctions {
|
||||||
|
final int min;
|
||||||
|
WHRestartStatusFunction({
|
||||||
|
required super.deviceId,
|
||||||
|
required super.deviceName,
|
||||||
|
required super.type,
|
||||||
|
}) : min = 0,
|
||||||
|
super(
|
||||||
|
code: 'relay_status',
|
||||||
|
operationName: 'Restart Status',
|
||||||
|
icon: Assets.refreshStatusIcon,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<WaterHeaterOperationalValue> getOperationalValues() {
|
||||||
|
return [
|
||||||
|
WaterHeaterOperationalValue(
|
||||||
|
icon: Assets.assetsAcPowerOFF,
|
||||||
|
description: 'Power OFF',
|
||||||
|
value: "off",
|
||||||
|
),
|
||||||
|
WaterHeaterOperationalValue(
|
||||||
|
icon: Assets.assetsAcPower,
|
||||||
|
description: 'Power ON',
|
||||||
|
value: 'on',
|
||||||
|
),
|
||||||
|
WaterHeaterOperationalValue(
|
||||||
|
icon: Assets.refreshStatusIcon,
|
||||||
|
description: "Restart Memory",
|
||||||
|
value: 'memory',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WHSwitchFunction extends WaterHeaterFunctions {
|
||||||
|
final int min;
|
||||||
|
WHSwitchFunction({
|
||||||
|
required super.deviceId,
|
||||||
|
required super.deviceName,
|
||||||
|
required super.type,
|
||||||
|
}) : min = 0,
|
||||||
|
super(
|
||||||
|
code: 'switch_1',
|
||||||
|
operationName: 'Switch',
|
||||||
|
icon: Assets.assetsAcPower,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<WaterHeaterOperationalValue> getOperationalValues() {
|
||||||
|
return [
|
||||||
|
WaterHeaterOperationalValue(
|
||||||
|
icon: Assets.assetsAcPower,
|
||||||
|
description: 'ON',
|
||||||
|
value: true,
|
||||||
|
),
|
||||||
|
WaterHeaterOperationalValue(
|
||||||
|
icon: Assets.assetsAcPowerOFF,
|
||||||
|
description: 'OFF',
|
||||||
|
value: false,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TimerConfirmTimeFunction extends WaterHeaterFunctions {
|
||||||
|
TimerConfirmTimeFunction({
|
||||||
|
required super.deviceId,
|
||||||
|
required super.deviceName,
|
||||||
|
required super.type,
|
||||||
|
}) : super(
|
||||||
|
code: 'countdown_1',
|
||||||
|
operationName: 'Timer',
|
||||||
|
icon: Assets.targetConfirmTimeIcon,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<WaterHeaterOperationalValue> getOperationalValues() {
|
||||||
|
final values = <WaterHeaterOperationalValue>[];
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BacklightFunction extends WaterHeaterFunctions {
|
||||||
|
final int min;
|
||||||
|
BacklightFunction({
|
||||||
|
required super.deviceId,
|
||||||
|
required super.deviceName,
|
||||||
|
required super.type,
|
||||||
|
}) : min = 0,
|
||||||
|
super(
|
||||||
|
code: 'switch_backlight',
|
||||||
|
operationName: 'Backlight',
|
||||||
|
icon: Assets.indicator,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<WaterHeaterOperationalValue> getOperationalValues() {
|
||||||
|
return [
|
||||||
|
WaterHeaterOperationalValue(
|
||||||
|
icon: Assets.assetsAcPower,
|
||||||
|
description: 'ON',
|
||||||
|
value: true,
|
||||||
|
),
|
||||||
|
WaterHeaterOperationalValue(
|
||||||
|
icon: Assets.assetsAcPowerOFF,
|
||||||
|
description: 'OFF',
|
||||||
|
value: false,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
class WaterHeaterOperationalValue {
|
||||||
|
final String icon;
|
||||||
|
final String description;
|
||||||
|
final dynamic value;
|
||||||
|
|
||||||
|
WaterHeaterOperationalValue({
|
||||||
|
required this.icon,
|
||||||
|
required this.description,
|
||||||
|
required this.value,
|
||||||
|
});
|
||||||
|
}
|
@ -29,11 +29,11 @@ class ConditionsRoutinesDevicesView extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
DraggableCard(
|
DraggableCard(
|
||||||
imagePath: Assets.tabToRun,
|
imagePath: Assets.tabToRun,
|
||||||
title: 'Tab to run',
|
title: 'Tap to run',
|
||||||
deviceData: {
|
deviceData: {
|
||||||
'deviceId': 'tab_to_run',
|
'deviceId': 'tab_to_run',
|
||||||
'type': 'trigger',
|
'type': 'trigger',
|
||||||
'name': 'Tab to run',
|
'name': 'Tap to run',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
DraggableCard(
|
DraggableCard(
|
||||||
|
@ -43,7 +43,7 @@ class IfContainer extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
DraggableCard(
|
DraggableCard(
|
||||||
imagePath: Assets.tabToRun,
|
imagePath: Assets.tabToRun,
|
||||||
title: 'Tab to run',
|
title: 'Tap to run',
|
||||||
deviceData: {},
|
deviceData: {},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -76,7 +76,8 @@ class IfContainer extends StatelessWidget {
|
|||||||
'WPS',
|
'WPS',
|
||||||
'GW',
|
'GW',
|
||||||
'CPS',
|
'CPS',
|
||||||
'NCPS'
|
'NCPS',
|
||||||
|
'WH',
|
||||||
].contains(state.ifItems[index]
|
].contains(state.ifItems[index]
|
||||||
['productType'])) {
|
['productType'])) {
|
||||||
|
|
||||||
@ -136,7 +137,7 @@ class IfContainer extends StatelessWidget {
|
|||||||
context
|
context
|
||||||
.read<RoutineBloc>()
|
.read<RoutineBloc>()
|
||||||
.add(AddToIfContainer(mutableData, false));
|
.add(AddToIfContainer(mutableData, false));
|
||||||
} else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS', 'NCPS']
|
} else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS', 'NCPS','WH']
|
||||||
.contains(mutableData['productType'])) {
|
.contains(mutableData['productType'])) {
|
||||||
context
|
context
|
||||||
.read<RoutineBloc>()
|
.read<RoutineBloc>()
|
||||||
|
@ -26,7 +26,7 @@ class FetchRoutineScenesAutomation extends StatelessWidget
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
_buildListTitle(context, "Scenes (Tab to Run)"),
|
_buildListTitle(context, "Scenes (Tap to Run)"),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Visibility(
|
Visibility(
|
||||||
visible: state.scenes.isNotEmpty,
|
visible: state.scenes.isNotEmpty,
|
||||||
|
@ -25,7 +25,8 @@ class _RoutineDevicesState extends State<RoutineDevices> {
|
|||||||
'WPS',
|
'WPS',
|
||||||
'GW',
|
'GW',
|
||||||
'CPS',
|
'CPS',
|
||||||
'NCPS'
|
'NCPS',
|
||||||
|
'WH',
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.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/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
|
||||||
import 'package:syncrow_web/pages/routines/models/flush/flush_operational_value.dart';
|
import 'package:syncrow_web/pages/routines/models/flush/flush_operational_value.dart';
|
||||||
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/time_wheel.dart';
|
|
||||||
|
|
||||||
class FlushOperationalValuesList extends StatelessWidget {
|
class FlushOperationalValuesList extends StatelessWidget {
|
||||||
final List<FlushOperationalValue> values;
|
final List<FlushOperationalValue> values;
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/routines/models/water_heater/water_heater_operational_value.dart';
|
||||||
|
|
||||||
|
class WaterHeaterOperationalValuesList extends StatelessWidget {
|
||||||
|
final List<WaterHeaterOperationalValue> values;
|
||||||
|
final dynamic selectedValue;
|
||||||
|
final AllDevicesModel? device;
|
||||||
|
final String operationName;
|
||||||
|
final String selectCode;
|
||||||
|
final ValueChanged<WaterHeaterOperationalValue> onSelect;
|
||||||
|
const WaterHeaterOperationalValuesList({
|
||||||
|
required this.values,
|
||||||
|
required this.selectedValue,
|
||||||
|
required this.device,
|
||||||
|
required this.operationName,
|
||||||
|
required this.selectCode,
|
||||||
|
required this.onSelect,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView.builder(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
itemCount: values.length,
|
||||||
|
itemBuilder: (context, index) => _buildValueItem(context, values[index]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildValueItem(
|
||||||
|
BuildContext context, WaterHeaterOperationalValue value) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
SvgPicture.asset(
|
||||||
|
value.icon,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
Expanded(child: _buildValueDescription(value)),
|
||||||
|
_buildValueRadio(context, value),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildValueDescription(WaterHeaterOperationalValue value) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
|
child: Text(value.description),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildValueRadio(context, WaterHeaterOperationalValue value) {
|
||||||
|
return Radio<dynamic>(
|
||||||
|
value: value.value,
|
||||||
|
groupValue: selectedValue,
|
||||||
|
onChanged: (_) => onSelect(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,203 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.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/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
||||||
|
import 'package:syncrow_web/pages/routines/models/water_heater/water_heater_functions.dart';
|
||||||
|
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
|
||||||
|
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
|
||||||
|
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/water_heater/water_heater_value_selector_widget.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class WaterHeaterDialogRoutines extends StatefulWidget {
|
||||||
|
final List<DeviceFunction> functions;
|
||||||
|
final AllDevicesModel? device;
|
||||||
|
final List<DeviceFunctionData>? deviceSelectedFunctions;
|
||||||
|
final String? uniqueCustomId;
|
||||||
|
final String dialogType;
|
||||||
|
|
||||||
|
const WaterHeaterDialogRoutines({
|
||||||
|
super.key,
|
||||||
|
required this.functions,
|
||||||
|
this.device,
|
||||||
|
this.deviceSelectedFunctions,
|
||||||
|
this.uniqueCustomId,
|
||||||
|
required this.dialogType,
|
||||||
|
});
|
||||||
|
|
||||||
|
static Future<Map<String, dynamic>?> showWHFunctionsDialog({
|
||||||
|
required BuildContext context,
|
||||||
|
required List<DeviceFunction> functions,
|
||||||
|
AllDevicesModel? device,
|
||||||
|
List<DeviceFunctionData>? deviceSelectedFunctions,
|
||||||
|
String? uniqueCustomId,
|
||||||
|
required String dialogType,
|
||||||
|
}) async {
|
||||||
|
return showDialog<Map<String, dynamic>?>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => WaterHeaterDialogRoutines(
|
||||||
|
functions: functions,
|
||||||
|
device: device,
|
||||||
|
deviceSelectedFunctions: deviceSelectedFunctions,
|
||||||
|
uniqueCustomId: uniqueCustomId,
|
||||||
|
dialogType: dialogType,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<WaterHeaterDialogRoutines> createState() =>
|
||||||
|
_WaterHeaterDialogRoutinesState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WaterHeaterDialogRoutinesState extends State<WaterHeaterDialogRoutines> {
|
||||||
|
late final List<WaterHeaterFunctions> _waterHeaterFunctions;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_waterHeaterFunctions =
|
||||||
|
widget.functions.whereType<WaterHeaterFunctions>().where((function) {
|
||||||
|
if (widget.dialogType == 'THEN') {
|
||||||
|
return function.type == 'THEN' || function.type == 'BOTH';
|
||||||
|
}
|
||||||
|
return function.type == 'IF' || function.type == 'BOTH';
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (_) => FunctionBloc()
|
||||||
|
..add(InitializeFunctions(widget.deviceSelectedFunctions ?? [])),
|
||||||
|
child: _buildDialogContent(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDialogContent() {
|
||||||
|
return AlertDialog(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final selectedFunction = state.selectedFunction;
|
||||||
|
return Container(
|
||||||
|
width: selectedFunction != null ? 600 : 360,
|
||||||
|
height: 450,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.only(top: 20),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const DialogHeader('Water Heater Condition'),
|
||||||
|
Expanded(child: _buildMainContent(context, state)),
|
||||||
|
_buildDialogFooter(context, state),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMainContent(BuildContext context, FunctionBlocState state) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
_buildFunctionList(context),
|
||||||
|
if (state.selectedFunction != null) _buildValueSelector(context, state),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFunctionList(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
width: 360,
|
||||||
|
child: ListView.separated(
|
||||||
|
shrinkWrap: false,
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
itemCount: _waterHeaterFunctions.length,
|
||||||
|
separatorBuilder: (context, index) => const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 40.0),
|
||||||
|
child: Divider(color: ColorsManager.dividerColor),
|
||||||
|
),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final function = _waterHeaterFunctions[index];
|
||||||
|
return ListTile(
|
||||||
|
leading: SvgPicture.asset(
|
||||||
|
function.icon,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
placeholderBuilder: (context) => const SizedBox(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
function.operationName,
|
||||||
|
style: context.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
trailing: const Icon(
|
||||||
|
Icons.arrow_forward_ios,
|
||||||
|
size: 16,
|
||||||
|
color: ColorsManager.textGray,
|
||||||
|
),
|
||||||
|
onTap: () => context.read<FunctionBloc>().add(
|
||||||
|
SelectFunction(
|
||||||
|
functionCode: function.code,
|
||||||
|
operationName: function.operationName,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildValueSelector(BuildContext context, FunctionBlocState state) {
|
||||||
|
final selectedFunction = state.selectedFunction ?? '';
|
||||||
|
final functionData = state.addedFunctions.firstWhere(
|
||||||
|
(f) => f.functionCode == selectedFunction,
|
||||||
|
orElse: () => DeviceFunctionData(
|
||||||
|
entityId: '',
|
||||||
|
functionCode: selectedFunction,
|
||||||
|
operationName: state.selectedOperationName ?? '',
|
||||||
|
value: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Expanded(
|
||||||
|
child: WaterHeaterValueSelectorWidget(
|
||||||
|
selectedFunction: selectedFunction,
|
||||||
|
functionData: functionData,
|
||||||
|
whFunctions: _waterHeaterFunctions,
|
||||||
|
device: widget.device,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDialogFooter(BuildContext context, FunctionBlocState state) {
|
||||||
|
return DialogFooter(
|
||||||
|
onCancel: () => Navigator.pop(context),
|
||||||
|
onConfirm: state.addedFunctions.isNotEmpty
|
||||||
|
? () {
|
||||||
|
context.read<RoutineBloc>().add(
|
||||||
|
AddFunctionToRoutine(
|
||||||
|
state.addedFunctions,
|
||||||
|
widget.uniqueCustomId!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Navigator.pop(
|
||||||
|
context,
|
||||||
|
{'deviceId': widget.functions.first.deviceId},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
isConfirmEnabled: state.selectedFunction != null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,196 @@
|
|||||||
|
import 'package:flutter/material.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/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart';
|
||||||
|
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
||||||
|
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
|
||||||
|
import 'package:syncrow_web/pages/routines/models/water_heater/water_heater_functions.dart';
|
||||||
|
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/water_heater/water_heater_operational_values_list.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class WaterHeaterValueSelectorWidget extends StatelessWidget {
|
||||||
|
final String selectedFunction;
|
||||||
|
final DeviceFunctionData functionData;
|
||||||
|
final List<WaterHeaterFunctions> whFunctions;
|
||||||
|
final AllDevicesModel? device;
|
||||||
|
|
||||||
|
const WaterHeaterValueSelectorWidget({
|
||||||
|
required this.selectedFunction,
|
||||||
|
required this.functionData,
|
||||||
|
required this.whFunctions,
|
||||||
|
required this.device,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final selectedFn = whFunctions.firstWhere(
|
||||||
|
(f) => f.code == selectedFunction,
|
||||||
|
orElse: () => WHSwitchFunction(
|
||||||
|
deviceId: '',
|
||||||
|
deviceName: '',
|
||||||
|
type: '',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (selectedFunction == 'countdown_1') {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
_buildConditionToggle(
|
||||||
|
context,
|
||||||
|
functionData.condition,
|
||||||
|
selectedFunction,
|
||||||
|
device,
|
||||||
|
selectedFn.operationName,
|
||||||
|
functionData,
|
||||||
|
),
|
||||||
|
_buildCountDownDisplay(
|
||||||
|
context,
|
||||||
|
functionData.value,
|
||||||
|
device,
|
||||||
|
selectedFn.operationName,
|
||||||
|
functionData,
|
||||||
|
selectedFunction,
|
||||||
|
),
|
||||||
|
_buildCountDownSlider(
|
||||||
|
context,
|
||||||
|
functionData.value,
|
||||||
|
device,
|
||||||
|
selectedFn.operationName,
|
||||||
|
functionData,
|
||||||
|
selectedFunction,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return WaterHeaterOperationalValuesList(
|
||||||
|
values: selectedFn.getOperationalValues(),
|
||||||
|
selectedValue: functionData.value,
|
||||||
|
device: device,
|
||||||
|
operationName: selectedFn.operationName,
|
||||||
|
selectCode: selectedFunction,
|
||||||
|
onSelect: (selectedValue) async {
|
||||||
|
context.read<FunctionBloc>().add(
|
||||||
|
AddFunction(
|
||||||
|
functionData: DeviceFunctionData(
|
||||||
|
entityId: device?.uuid ?? '',
|
||||||
|
functionCode: selectedFunction,
|
||||||
|
operationName: functionData.operationName,
|
||||||
|
value: selectedValue.value,
|
||||||
|
condition: functionData.condition,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Widget _buildCountDownDisplay(
|
||||||
|
BuildContext context,
|
||||||
|
dynamic initialValue,
|
||||||
|
AllDevicesModel? device,
|
||||||
|
String operationName,
|
||||||
|
DeviceFunctionData? selectedFunctionData,
|
||||||
|
String selectCode) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0),
|
||||||
|
style: context.textTheme.headlineMedium!.copyWith(
|
||||||
|
color: ColorsManager.primaryColorWithOpacity,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Widget _buildCountDownSlider(
|
||||||
|
BuildContext context,
|
||||||
|
dynamic initialValue,
|
||||||
|
AllDevicesModel? device,
|
||||||
|
String operationName,
|
||||||
|
DeviceFunctionData? selectedFunctionData,
|
||||||
|
String selectCode,
|
||||||
|
) {
|
||||||
|
const twelveHoursInSeconds = 43200.0;
|
||||||
|
final operationalValues = SwitchOperationalValue(
|
||||||
|
icon: '',
|
||||||
|
description: "sec",
|
||||||
|
value: 0.0,
|
||||||
|
minValue: 0,
|
||||||
|
maxValue: twelveHoursInSeconds,
|
||||||
|
stepValue: 1,
|
||||||
|
);
|
||||||
|
return Slider(
|
||||||
|
value: (initialValue ?? 0).toDouble(),
|
||||||
|
min: operationalValues.minValue?.toDouble() ?? 0.0,
|
||||||
|
max: operationalValues.maxValue?.toDouble() ?? 0.0,
|
||||||
|
divisions: (((operationalValues.maxValue ?? 0) -
|
||||||
|
(operationalValues.minValue ?? 0)) /
|
||||||
|
(operationalValues.stepValue ?? 1))
|
||||||
|
.round(),
|
||||||
|
onChanged: (value) {
|
||||||
|
context.read<FunctionBloc>().add(
|
||||||
|
AddFunction(
|
||||||
|
functionData: DeviceFunctionData(
|
||||||
|
entityId: device?.uuid ?? '',
|
||||||
|
functionCode: selectCode,
|
||||||
|
operationName: operationName,
|
||||||
|
value: value,
|
||||||
|
condition: selectedFunctionData?.condition,
|
||||||
|
valueDescription: selectedFunctionData?.valueDescription,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Widget _buildConditionToggle(
|
||||||
|
BuildContext context,
|
||||||
|
String? currentCondition,
|
||||||
|
String selectCode,
|
||||||
|
AllDevicesModel? device,
|
||||||
|
String operationName,
|
||||||
|
DeviceFunctionData? selectedFunctionData,
|
||||||
|
) {
|
||||||
|
final conditions = ["<", "==", ">"];
|
||||||
|
|
||||||
|
return ToggleButtons(
|
||||||
|
onPressed: (int index) {
|
||||||
|
context.read<FunctionBloc>().add(
|
||||||
|
AddFunction(
|
||||||
|
functionData: DeviceFunctionData(
|
||||||
|
entityId: device?.uuid ?? '',
|
||||||
|
functionCode: selectCode,
|
||||||
|
operationName: operationName,
|
||||||
|
condition: conditions[index],
|
||||||
|
value: selectedFunctionData?.value ?? 0,
|
||||||
|
valueDescription: selectedFunctionData?.valueDescription,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
selectedBorderColor: ColorsManager.primaryColorWithOpacity,
|
||||||
|
selectedColor: Colors.white,
|
||||||
|
fillColor: ColorsManager.primaryColorWithOpacity,
|
||||||
|
color: ColorsManager.primaryColorWithOpacity,
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
minHeight: 40.0,
|
||||||
|
minWidth: 40.0,
|
||||||
|
),
|
||||||
|
isSelected:
|
||||||
|
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
|
||||||
|
children: conditions.map((c) => Text(c)).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -116,7 +116,8 @@ class ThenContainer extends StatelessWidget {
|
|||||||
'WPS',
|
'WPS',
|
||||||
'CPS',
|
'CPS',
|
||||||
"GW",
|
"GW",
|
||||||
"NCPS"
|
"NCPS",
|
||||||
|
'WH',
|
||||||
].contains(state.thenItems[index]
|
].contains(state.thenItems[index]
|
||||||
['productType'])) {
|
['productType'])) {
|
||||||
context.read<RoutineBloc>().add(
|
context.read<RoutineBloc>().add(
|
||||||
@ -232,8 +233,17 @@ class ThenContainer extends StatelessWidget {
|
|||||||
dialogType: "THEN");
|
dialogType: "THEN");
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
context.read<RoutineBloc>().add(AddToThenContainer(mutableData));
|
context.read<RoutineBloc>().add(AddToThenContainer(mutableData));
|
||||||
} else if (!['AC', '1G', '2G', '3G', 'WPS', 'GW', 'CPS', "NCPS"]
|
} else if (![
|
||||||
.contains(mutableData['productType'])) {
|
'AC',
|
||||||
|
'1G',
|
||||||
|
'2G',
|
||||||
|
'3G',
|
||||||
|
'WPS',
|
||||||
|
'GW',
|
||||||
|
'CPS',
|
||||||
|
"NCPS",
|
||||||
|
"WH"
|
||||||
|
].contains(mutableData['productType'])) {
|
||||||
context.read<RoutineBloc>().add(AddToThenContainer(mutableData));
|
context.read<RoutineBloc>().add(AddToThenContainer(mutableData));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -481,4 +481,5 @@ class Assets {
|
|||||||
static const String indentLevelIcon = 'assets/icons/indent_level_icon.svg';
|
static const String indentLevelIcon = 'assets/icons/indent_level_icon.svg';
|
||||||
static const String triggerLevelIcon = 'assets/icons/trigger_level_icon.svg';
|
static const String triggerLevelIcon = 'assets/icons/trigger_level_icon.svg';
|
||||||
static const String blankCalendar = 'assets/icons/blank_calendar.svg';
|
static const String blankCalendar = 'assets/icons/blank_calendar.svg';
|
||||||
|
static const String refreshStatusIcon = 'assets/icons/refresh_status_icon.svg';
|
||||||
}
|
}
|
||||||
|
906
pubspec.lock
906
pubspec.lock
@ -1,906 +0,0 @@
|
|||||||
# Generated by pub
|
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
|
||||||
packages:
|
|
||||||
_flutterfire_internals:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: _flutterfire_internals
|
|
||||||
sha256: e051259913915ea5bc8fe18664596bea08592fd123930605d562969cd7315fcd
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.3.51"
|
|
||||||
args:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: args
|
|
||||||
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.5.0"
|
|
||||||
async:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: async
|
|
||||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.11.0"
|
|
||||||
bloc:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: bloc
|
|
||||||
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "8.1.4"
|
|
||||||
boolean_selector:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: boolean_selector
|
|
||||||
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.1.1"
|
|
||||||
characters:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: characters
|
|
||||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.3.0"
|
|
||||||
clock:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: clock
|
|
||||||
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.1"
|
|
||||||
collection:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: collection
|
|
||||||
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.19.0"
|
|
||||||
crypto:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: crypto
|
|
||||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.0.6"
|
|
||||||
csslib:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: csslib
|
|
||||||
sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.17.3"
|
|
||||||
cupertino_icons:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: cupertino_icons
|
|
||||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.8"
|
|
||||||
data_table_2:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: data_table_2
|
|
||||||
sha256: f02ec9b24f44420816a87370ff4f4e533e15b274f6267e4c9a88a585ad1a0473
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.5.15"
|
|
||||||
dio:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: dio
|
|
||||||
sha256: e17f6b3097b8c51b72c74c9f071a605c47bcc8893839bd66732457a5ebe73714
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "5.5.0+1"
|
|
||||||
dio_web_adapter:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: dio_web_adapter
|
|
||||||
sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.1"
|
|
||||||
dropdown_button2:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: dropdown_button2
|
|
||||||
sha256: b0fe8d49a030315e9eef6c7ac84ca964250155a6224d491c1365061bc974a9e1
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.3.9"
|
|
||||||
dropdown_search:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: dropdown_search
|
|
||||||
sha256: "55106e8290acaa97ed15bea1fdad82c3cf0c248dd410e651f5a8ac6870f783ab"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "5.0.6"
|
|
||||||
equatable:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: equatable
|
|
||||||
sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.5"
|
|
||||||
fake_async:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: fake_async
|
|
||||||
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.3.1"
|
|
||||||
ffi:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: ffi
|
|
||||||
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.1.2"
|
|
||||||
file:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: file
|
|
||||||
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "7.0.0"
|
|
||||||
firebase_analytics:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: firebase_analytics
|
|
||||||
sha256: "47428047a0778f72af53a3c7cb5d556e1cb25e2327cc8aa40d544971dc6245b2"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "11.4.2"
|
|
||||||
firebase_analytics_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: firebase_analytics_platform_interface
|
|
||||||
sha256: "1076f4b041f76143e14878c70f0758f17fe5910c0cd992db9e93bd3c3584512b"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "4.3.2"
|
|
||||||
firebase_analytics_web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: firebase_analytics_web
|
|
||||||
sha256: "8f6dd64ea6d28b7f5b9e739d183a9e1c7f17027794a3e9aba1879621d42426ef"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.5.10+8"
|
|
||||||
firebase_core:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: firebase_core
|
|
||||||
sha256: "93dc4dd12f9b02c5767f235307f609e61ed9211047132d07f9e02c668f0bfc33"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.11.0"
|
|
||||||
firebase_core_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: firebase_core_platform_interface
|
|
||||||
sha256: d7253d255ff10f85cfd2adaba9ac17bae878fa3ba577462451163bd9f1d1f0bf
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "5.4.0"
|
|
||||||
firebase_core_web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: firebase_core_web
|
|
||||||
sha256: "0e13c80f0de8acaa5d0519cbe23c8b4cc138a2d5d508b5755c861bdfc9762678"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.20.0"
|
|
||||||
firebase_crashlytics:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: firebase_crashlytics
|
|
||||||
sha256: "6273ed71bcd8a6fb4d0ca13d3abddbb3301796807efaad8782b5f90156f26f03"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "4.3.2"
|
|
||||||
firebase_crashlytics_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: firebase_crashlytics_platform_interface
|
|
||||||
sha256: "94f3986e1a10e5a883f2ad5e3d719aef98a8a0f9a49357f6e45b7d3696ea6a97"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.8.2"
|
|
||||||
firebase_database:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: firebase_database
|
|
||||||
sha256: cd2354dfef68e52c0713b5efbb7f4e10dfc2aff2f945c7bc8db34d1934170627
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "11.3.2"
|
|
||||||
firebase_database_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: firebase_database_platform_interface
|
|
||||||
sha256: d430983f4d877c9f72f88b3d715cca9a50021dd7ccd8e3ae6fb79603853317de
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.2.6+2"
|
|
||||||
firebase_database_web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: firebase_database_web
|
|
||||||
sha256: f64edae62c5beaa08e9e611a0736d64ab11a812983a0aa132695d2d191311ea7
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.2.6+8"
|
|
||||||
fixnum:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: fixnum
|
|
||||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.1"
|
|
||||||
fl_chart:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: fl_chart
|
|
||||||
sha256: "94307bef3a324a0d329d3ab77b2f0c6e5ed739185ffc029ed28c0f9b019ea7ef"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.69.0"
|
|
||||||
flutter:
|
|
||||||
dependency: "direct main"
|
|
||||||
description: flutter
|
|
||||||
source: sdk
|
|
||||||
version: "0.0.0"
|
|
||||||
flutter_bloc:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: flutter_bloc
|
|
||||||
sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "8.1.5"
|
|
||||||
flutter_dotenv:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: flutter_dotenv
|
|
||||||
sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "5.1.0"
|
|
||||||
flutter_html:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: flutter_html
|
|
||||||
sha256: "02ad69e813ecfc0728a455e4bf892b9379983e050722b1dce00192ee2e41d1ee"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.0.0-beta.2"
|
|
||||||
flutter_lints:
|
|
||||||
dependency: "direct dev"
|
|
||||||
description:
|
|
||||||
name: flutter_lints
|
|
||||||
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.0.2"
|
|
||||||
flutter_secure_storage:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: flutter_secure_storage
|
|
||||||
sha256: "165164745e6afb5c0e3e3fcc72a012fb9e58496fb26ffb92cf22e16a821e85d0"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "9.2.2"
|
|
||||||
flutter_secure_storage_linux:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: flutter_secure_storage_linux
|
|
||||||
sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.2.1"
|
|
||||||
flutter_secure_storage_macos:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: flutter_secure_storage_macos
|
|
||||||
sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.1.2"
|
|
||||||
flutter_secure_storage_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: flutter_secure_storage_platform_interface
|
|
||||||
sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.2"
|
|
||||||
flutter_secure_storage_web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: flutter_secure_storage_web
|
|
||||||
sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.2.1"
|
|
||||||
flutter_secure_storage_windows:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: flutter_secure_storage_windows
|
|
||||||
sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.1.2"
|
|
||||||
flutter_svg:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: flutter_svg
|
|
||||||
sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.10+1"
|
|
||||||
flutter_test:
|
|
||||||
dependency: "direct dev"
|
|
||||||
description: flutter
|
|
||||||
source: sdk
|
|
||||||
version: "0.0.0"
|
|
||||||
flutter_web_plugins:
|
|
||||||
dependency: transitive
|
|
||||||
description: flutter
|
|
||||||
source: sdk
|
|
||||||
version: "0.0.0"
|
|
||||||
get_it:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: get_it
|
|
||||||
sha256: d85128a5dae4ea777324730dc65edd9c9f43155c109d5cc0a69cab74139fbac1
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "7.7.0"
|
|
||||||
go_router:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: go_router
|
|
||||||
sha256: "2ddb88e9ad56ae15ee144ed10e33886777eb5ca2509a914850a5faa7b52ff459"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "14.2.7"
|
|
||||||
graphview:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: graphview
|
|
||||||
sha256: bdba183583b23c30c71edea09ad5f0beef612572d3e39e855467a925bd08392f
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.2.0"
|
|
||||||
html:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: html
|
|
||||||
sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.15.4"
|
|
||||||
http:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: http
|
|
||||||
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.2.2"
|
|
||||||
http_parser:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: http_parser
|
|
||||||
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "4.0.2"
|
|
||||||
intl:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: intl
|
|
||||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.19.0"
|
|
||||||
intl_phone_field:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: intl_phone_field
|
|
||||||
sha256: "73819d3dfcb68d2c85663606f6842597c3ddf6688ac777f051b17814fe767bbf"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.2.0"
|
|
||||||
js:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: js
|
|
||||||
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.6.7"
|
|
||||||
leak_tracker:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: leak_tracker
|
|
||||||
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "10.0.7"
|
|
||||||
leak_tracker_flutter_testing:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: leak_tracker_flutter_testing
|
|
||||||
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.0.8"
|
|
||||||
leak_tracker_testing:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: leak_tracker_testing
|
|
||||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.0.1"
|
|
||||||
lints:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: lints
|
|
||||||
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.0.0"
|
|
||||||
list_counter:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: list_counter
|
|
||||||
sha256: c447ae3dfcd1c55f0152867090e67e219d42fe6d4f2807db4bbe8b8d69912237
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.2"
|
|
||||||
logging:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: logging
|
|
||||||
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.2.0"
|
|
||||||
matcher:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: matcher
|
|
||||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.12.16+1"
|
|
||||||
material_color_utilities:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: material_color_utilities
|
|
||||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.11.1"
|
|
||||||
meta:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: meta
|
|
||||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.15.0"
|
|
||||||
nested:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: nested
|
|
||||||
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.0"
|
|
||||||
number_pagination:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: number_pagination
|
|
||||||
sha256: "75d3a28616196e7c8df431d0fb7c48e811e462155f4cf3b5b4167b3408421327"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.6"
|
|
||||||
path:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: path
|
|
||||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.9.0"
|
|
||||||
path_parsing:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: path_parsing
|
|
||||||
sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.1"
|
|
||||||
path_provider:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: path_provider
|
|
||||||
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.1.3"
|
|
||||||
path_provider_android:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: path_provider_android
|
|
||||||
sha256: e84c8a53fe1510ef4582f118c7b4bdf15b03002b51d7c2b66983c65843d61193
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.2.8"
|
|
||||||
path_provider_foundation:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: path_provider_foundation
|
|
||||||
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.0"
|
|
||||||
path_provider_linux:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: path_provider_linux
|
|
||||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.2.1"
|
|
||||||
path_provider_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: path_provider_platform_interface
|
|
||||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.1.2"
|
|
||||||
path_provider_windows:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: path_provider_windows
|
|
||||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.3.0"
|
|
||||||
petitparser:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: petitparser
|
|
||||||
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "6.0.2"
|
|
||||||
platform:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: platform
|
|
||||||
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.1.5"
|
|
||||||
plugin_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: plugin_platform_interface
|
|
||||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.1.8"
|
|
||||||
provider:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: provider
|
|
||||||
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "6.1.2"
|
|
||||||
shared_preferences:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: shared_preferences
|
|
||||||
sha256: c3f888ba2d659f3e75f4686112cc1e71f46177f74452d40d8307edc332296ead
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.3.0"
|
|
||||||
shared_preferences_android:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: shared_preferences_android
|
|
||||||
sha256: "041be4d9d2dc6079cf342bc8b761b03787e3b71192d658220a56cac9c04a0294"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.3.0"
|
|
||||||
shared_preferences_foundation:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: shared_preferences_foundation
|
|
||||||
sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.5.0"
|
|
||||||
shared_preferences_linux:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: shared_preferences_linux
|
|
||||||
sha256: "2ba0510d3017f91655b7543e9ee46d48619de2a2af38e5c790423f7007c7ccc1"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.0"
|
|
||||||
shared_preferences_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: shared_preferences_platform_interface
|
|
||||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.1"
|
|
||||||
shared_preferences_web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: shared_preferences_web
|
|
||||||
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.2"
|
|
||||||
shared_preferences_windows:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: shared_preferences_windows
|
|
||||||
sha256: "398084b47b7f92110683cac45c6dc4aae853db47e470e5ddcd52cab7f7196ab2"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.0"
|
|
||||||
sky_engine:
|
|
||||||
dependency: transitive
|
|
||||||
description: flutter
|
|
||||||
source: sdk
|
|
||||||
version: "0.0.0"
|
|
||||||
source_span:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: source_span
|
|
||||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.10.0"
|
|
||||||
sprintf:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: sprintf
|
|
||||||
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "7.0.0"
|
|
||||||
stack_trace:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: stack_trace
|
|
||||||
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.12.0"
|
|
||||||
stream_channel:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: stream_channel
|
|
||||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.1.2"
|
|
||||||
string_scanner:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: string_scanner
|
|
||||||
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.3.0"
|
|
||||||
term_glyph:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: term_glyph
|
|
||||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.2.1"
|
|
||||||
test_api:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: test_api
|
|
||||||
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.7.3"
|
|
||||||
time_picker_spinner:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: time_picker_spinner
|
|
||||||
sha256: "53d824801d108890d22756501e7ade9db48b53dac1ec41580499dd4ebd128e3c"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.0"
|
|
||||||
typed_data:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: typed_data
|
|
||||||
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.3.2"
|
|
||||||
url_launcher:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: url_launcher
|
|
||||||
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "6.3.1"
|
|
||||||
url_launcher_android:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: url_launcher_android
|
|
||||||
sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "6.3.14"
|
|
||||||
url_launcher_ios:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: url_launcher_ios
|
|
||||||
sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "6.3.2"
|
|
||||||
url_launcher_linux:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: url_launcher_linux
|
|
||||||
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.2.1"
|
|
||||||
url_launcher_macos:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: url_launcher_macos
|
|
||||||
sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.2.2"
|
|
||||||
url_launcher_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: url_launcher_platform_interface
|
|
||||||
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.3.2"
|
|
||||||
url_launcher_web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: url_launcher_web
|
|
||||||
sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.0"
|
|
||||||
url_launcher_windows:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: url_launcher_windows
|
|
||||||
sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.1.4"
|
|
||||||
uuid:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: uuid
|
|
||||||
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "4.5.1"
|
|
||||||
vector_graphics:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: vector_graphics
|
|
||||||
sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.11+1"
|
|
||||||
vector_graphics_codec:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: vector_graphics_codec
|
|
||||||
sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.11+1"
|
|
||||||
vector_graphics_compiler:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: vector_graphics_compiler
|
|
||||||
sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.11+1"
|
|
||||||
vector_math:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: vector_math
|
|
||||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.1.4"
|
|
||||||
vm_service:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: vm_service
|
|
||||||
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "14.3.0"
|
|
||||||
web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: web
|
|
||||||
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.1.0"
|
|
||||||
win32:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: win32
|
|
||||||
sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "5.5.1"
|
|
||||||
xdg_directories:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: xdg_directories
|
|
||||||
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.4"
|
|
||||||
xml:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: xml
|
|
||||||
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "6.5.0"
|
|
||||||
sdks:
|
|
||||||
dart: ">=3.6.0 <4.0.0"
|
|
||||||
flutter: ">=3.27.0"
|
|
Reference in New Issue
Block a user