mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-15 17:47:53 +00:00
Compare commits
169 Commits
flush-pres
...
ci/cd-enha
Author | SHA1 | Date | |
---|---|---|---|
f19120c754 | |||
6b3eca23af | |||
4f4f11c330 | |||
8a25fa798c | |||
6612e91430 | |||
56c613fb0c | |||
53222bee81 | |||
bfb9158652 | |||
7f03222c12 | |||
5e6c14efeb | |||
9bbf3e75fa | |||
303b0236f1 | |||
4e3e63723e | |||
38ff20f86a | |||
d539e6266e | |||
7467f8d0ea | |||
a11e20147e | |||
55a6974bdc | |||
f8f58a24b8 | |||
682e69e65f | |||
59a59231ec | |||
ad41a2a87e | |||
974aa8f2a4 | |||
428cd34492 | |||
1a6121c452 | |||
e8f9ae944c | |||
7e37aed026 | |||
d89e227599 | |||
5a68b22f0c | |||
38184ca8b2 | |||
4d5de7bc05 | |||
1a3006fa43 | |||
490ca2057e | |||
06637a16bb | |||
696978a78d | |||
818e4e4d51 | |||
af877d7839 | |||
a33b1e3f49 | |||
c3cce334ab | |||
947e9e404c | |||
cd8264b6ce | |||
7467be6980 | |||
0353c73dac | |||
a050792f32 | |||
464f7b7347 | |||
cd54574279 | |||
18acae3e85 | |||
f081a7fc2d | |||
5996ff3928 | |||
a0d1cb988a | |||
c3ec9000d4 | |||
3d6a60b406 | |||
69c9240641 | |||
098013e5c8 | |||
11fb9e4894 | |||
390da9213d | |||
cae8b029fe | |||
6b883c8bb3 | |||
08c99bcbcb | |||
f6448d3eff | |||
a657a9a25e | |||
f55fa25bdf | |||
7242218b2f | |||
e43de3f64c | |||
9c250986b2 | |||
d8faafd1c0 | |||
24c30ddcb5 | |||
bafd2b4d13 | |||
56f9b1fc9a | |||
a9cc92ff86 | |||
3c7edae88a | |||
56c2d11535 | |||
3aa5bff758 | |||
28d1e5a5a7 | |||
fe036a8190 | |||
82e145de9d | |||
ebeb514a5b | |||
6b7e02ee53 | |||
b01136b6e9 | |||
97f8c6c8c9 | |||
6e527503c1 | |||
d6ef06c1b3 | |||
c9aaf2580f | |||
d9cd5d0438 | |||
3eb87dfde1 | |||
f29ff2551f | |||
67dd59ee9c | |||
bb3c3906d1 | |||
3873deca90 | |||
9431dd4500 | |||
63718185e7 | |||
1f4e82d567 | |||
9f68d171ff | |||
6eba640037 | |||
7a088074e3 | |||
d8f40badc0 | |||
fdd5d0feed | |||
fb1f79c7bb | |||
1923ac7014 | |||
c114161357 | |||
8d2d9dd0bb | |||
fe1dbb66ac | |||
b4de07de2f | |||
acefe6b433 | |||
63bc7a56de | |||
7b3635deae | |||
58755eafe1 | |||
ce225818fb | |||
8762a7aaa8 | |||
8dc833b2c3 | |||
13cef151aa | |||
ab23be9828 | |||
687b68ab22 | |||
25614c3dd0 | |||
7cbe20ae88 | |||
349fe6c555 | |||
9779f3783c | |||
fe3db663b6 | |||
888d444752 | |||
bab5e06968 | |||
d7b6174dee | |||
6ef0b2f9d1 | |||
3ceb03826e | |||
52608b1f35 | |||
ac2996629e | |||
51c52c66cb | |||
0f56273d99 | |||
34d4d892d9 | |||
3193fd26fe | |||
43802a9fad | |||
6e0b1775f0 | |||
233fb2ee2c | |||
b26928b3d5 | |||
6fc35a7b9a | |||
756457927c | |||
f30d7c0117 | |||
976d6e385a | |||
ff07e7509d | |||
17a582ab99 | |||
09fb604acc | |||
2068df173d | |||
bfc2a381d2 | |||
778257644d | |||
c8e540e938 | |||
ba20998067 | |||
75b0b24543 | |||
c03b8f290c | |||
2c684a9495 | |||
fbc45b465f | |||
d1bb7b129f | |||
cfc68f1568 | |||
02e08ad92f | |||
d7899a24f5 | |||
800c0ba47f | |||
fe4e775902 | |||
5247856cb4 | |||
4a8b8a32ba | |||
2abce77eb5 | |||
7efd1c3c87 | |||
7a0d9aefb7 | |||
21cc25cfc4 | |||
e2ec4bbf31 | |||
51b46ae197 | |||
36ee22603a | |||
b0abd42b0c | |||
ba4da78846 | |||
dc20d69f20 | |||
cf6ec231dc | |||
d0530f7fc3 |
26
.github/pull_request_template.md
vendored
Normal file
26
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<!--
|
||||||
|
Thanks for contributing!
|
||||||
|
|
||||||
|
Provide a description of your changes below and a general summary in the title
|
||||||
|
|
||||||
|
Please look at the following checklist to ensure that your PR can be accepted quickly:
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Jira Ticket
|
||||||
|
[SP-0000](https://syncrow.atlassian.net/browse/SP-0000)
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
<!--- Describe your changes in detail -->
|
||||||
|
|
||||||
|
## Type of Change
|
||||||
|
|
||||||
|
<!--- Put an `x` in all the boxes that apply: -->
|
||||||
|
|
||||||
|
- [ ] ✨ New feature (non-breaking change which adds functionality)
|
||||||
|
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
|
||||||
|
- [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change)
|
||||||
|
- [ ] 🧹 Code refactor
|
||||||
|
- [ ] ✅ Build configuration change
|
||||||
|
- [ ] 📝 Documentation
|
||||||
|
- [ ] 🗑️ Chore
|
@ -4,10 +4,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize, reopened, closed]
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_deploy_job:
|
build_and_deploy_job:
|
||||||
|
@ -4,18 +4,12 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize, reopened, closed]
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_deploy_job:
|
build_and_deploy_job:
|
||||||
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Build and Deploy Job
|
name: Build and Deploy Job
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
|
29
.github/workflows/pr-check.yml
vendored
Normal file
29
.github/workflows/pr-check.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
name: Pull Request Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
setup_flutter:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Setup Flutter and Dependencies
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
lfs: false
|
||||||
|
|
||||||
|
- name: Set up Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: '3.27.3'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Run Flutter Build
|
||||||
|
run: flutter build web --web-renderer canvaskit -t lib/main_dev.dart
|
@ -26,6 +26,7 @@ linter:
|
|||||||
rules:
|
rules:
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
prefer_const_constructors: true
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
# Additional information about this file can be found at
|
||||||
# https://dart.dev/guides/language/analysis-options
|
# https://dart.dev/guides/language/analysis-options
|
||||||
|
3
assets/icons/blank_calendar.svg
Normal file
3
assets/icons/blank_calendar.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M16.25 2.1875H14.6875V1.875C14.6875 1.62636 14.5887 1.3879 14.4129 1.21209C14.2371 1.03627 13.9986 0.9375 13.75 0.9375C13.5014 0.9375 13.2629 1.03627 13.0871 1.21209C12.9113 1.3879 12.8125 1.62636 12.8125 1.875V2.1875H7.1875V1.875C7.1875 1.62636 7.08873 1.3879 6.91291 1.21209C6.7371 1.03627 6.49864 0.9375 6.25 0.9375C6.00136 0.9375 5.7629 1.03627 5.58709 1.21209C5.41127 1.3879 5.3125 1.62636 5.3125 1.875V2.1875H3.75C3.3356 2.1875 2.93817 2.35212 2.64515 2.64515C2.35212 2.93817 2.1875 3.3356 2.1875 3.75V16.25C2.1875 16.6644 2.35212 17.0618 2.64515 17.3549C2.93817 17.6479 3.3356 17.8125 3.75 17.8125H16.25C16.6644 17.8125 17.0618 17.6479 17.3549 17.3549C17.6479 17.0618 17.8125 16.6644 17.8125 16.25V3.75C17.8125 3.3356 17.6479 2.93817 17.3549 2.64515C17.0618 2.35212 16.6644 2.1875 16.25 2.1875ZM5.3125 4.0625C5.3125 4.31114 5.41127 4.5496 5.58709 4.72541C5.7629 4.90123 6.00136 5 6.25 5C6.49864 5 6.7371 4.90123 6.91291 4.72541C7.08873 4.5496 7.1875 4.31114 7.1875 4.0625H12.8125C12.8125 4.31114 12.9113 4.5496 13.0871 4.72541C13.2629 4.90123 13.5014 5 13.75 5C13.9986 5 14.2371 4.90123 14.4129 4.72541C14.5887 4.5496 14.6875 4.31114 14.6875 4.0625H15.9375V5.9375H4.0625V4.0625H5.3125ZM4.0625 15.9375V7.8125H15.9375V15.9375H4.0625Z" fill="#475569"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
6
lib/pages/analytics/helpers/format_number_to_kwh.dart
Normal file
6
lib/pages/analytics/helpers/format_number_to_kwh.dart
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
extension FormatNumberToKwh on num {
|
||||||
|
String get formatNumberToKwh {
|
||||||
|
final regExp = RegExp(r'(\d)(?=(\d{3})+$)');
|
||||||
|
return '${toStringAsFixed(0).replaceAllMapped(regExp, (match) => '${match[1]},')} kWh';
|
||||||
|
}
|
||||||
|
}
|
19
lib/pages/analytics/helpers/get_month_name_from_int.dart
Normal file
19
lib/pages/analytics/helpers/get_month_name_from_int.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
extension GetMonthNameFromNumber on num {
|
||||||
|
String get getMonthName {
|
||||||
|
return switch (this) {
|
||||||
|
1 => 'JAN',
|
||||||
|
2 => 'FEB',
|
||||||
|
3 => 'MAR',
|
||||||
|
4 => 'APR',
|
||||||
|
5 => 'MAY',
|
||||||
|
6 => 'JUN',
|
||||||
|
7 => 'JUL',
|
||||||
|
8 => 'AUG',
|
||||||
|
9 => 'SEP',
|
||||||
|
10 => 'OCT',
|
||||||
|
11 => 'NOV',
|
||||||
|
12 => 'DEC',
|
||||||
|
_ => 'N/A'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
32
lib/pages/analytics/models/device_energy_data_model.dart
Normal file
32
lib/pages/analytics/models/device_energy_data_model.dart
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
||||||
|
|
||||||
|
class DeviceEnergyDataModel extends Equatable {
|
||||||
|
const DeviceEnergyDataModel({
|
||||||
|
required this.energy,
|
||||||
|
required this.deviceName,
|
||||||
|
required this.deviceId,
|
||||||
|
required this.color,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<EnergyDataModel> energy;
|
||||||
|
final String deviceName;
|
||||||
|
final String deviceId;
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [energy, deviceName, deviceId];
|
||||||
|
|
||||||
|
factory DeviceEnergyDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
final energy = (json['energy'] as List<dynamic>? ?? [])
|
||||||
|
.map((e) => EnergyDataModel.fromJson(e))
|
||||||
|
.toList();
|
||||||
|
return DeviceEnergyDataModel(
|
||||||
|
energy: energy,
|
||||||
|
deviceName: json['device_name'] as String? ?? '',
|
||||||
|
deviceId: json['device_id'] as String? ?? '',
|
||||||
|
color: Color(int.parse(json['color'] as String? ?? '0xFF000000')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
21
lib/pages/analytics/models/energy_data_model.dart
Normal file
21
lib/pages/analytics/models/energy_data_model.dart
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
class EnergyDataModel extends Equatable {
|
||||||
|
const EnergyDataModel({
|
||||||
|
required this.date,
|
||||||
|
required this.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DateTime date;
|
||||||
|
final double value;
|
||||||
|
|
||||||
|
factory EnergyDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return EnergyDataModel(
|
||||||
|
date: DateTime.parse(json['date'] as String),
|
||||||
|
value: (json['value'] as num).toDouble(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [date, value];
|
||||||
|
}
|
27
lib/pages/analytics/models/phases_energy_consumption.dart
Normal file
27
lib/pages/analytics/models/phases_energy_consumption.dart
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
class PhasesEnergyConsumption extends Equatable {
|
||||||
|
final int month;
|
||||||
|
final double phaseA;
|
||||||
|
final double phaseB;
|
||||||
|
final double phaseC;
|
||||||
|
|
||||||
|
const PhasesEnergyConsumption({
|
||||||
|
required this.month,
|
||||||
|
required this.phaseA,
|
||||||
|
required this.phaseB,
|
||||||
|
required this.phaseC,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [month, phaseA, phaseB, phaseC];
|
||||||
|
|
||||||
|
factory PhasesEnergyConsumption.fromJson(Map<String, dynamic> json) {
|
||||||
|
return PhasesEnergyConsumption(
|
||||||
|
month: json['month'] as int,
|
||||||
|
phaseA: (json['phaseA'] as num).toDouble(),
|
||||||
|
phaseB: (json['phaseB'] as num).toDouble(),
|
||||||
|
phaseC: (json['phaseC'] as num).toDouble(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
13
lib/pages/analytics/models/power_clamp_energy_status.dart
Normal file
13
lib/pages/analytics/models/power_clamp_energy_status.dart
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
class PowerClampEnergyStatus {
|
||||||
|
final String iconPath;
|
||||||
|
final String title;
|
||||||
|
final String value;
|
||||||
|
final String unit;
|
||||||
|
|
||||||
|
const PowerClampEnergyStatus({
|
||||||
|
required this.iconPath,
|
||||||
|
required this.title,
|
||||||
|
required this.value,
|
||||||
|
required this.unit,
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
part 'analytics_date_picker_event.dart';
|
||||||
|
|
||||||
|
class AnalyticsDatePickerBloc extends Bloc<AnalyticsDatePickerEvent, DateTime> {
|
||||||
|
AnalyticsDatePickerBloc() : super(DateTime.now()) {
|
||||||
|
on<UpdateAnalyticsDatePickerEvent>(_onUpdateAnalyticsDatePickerEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onUpdateAnalyticsDatePickerEvent(
|
||||||
|
UpdateAnalyticsDatePickerEvent event,
|
||||||
|
Emitter<DateTime> emit,
|
||||||
|
) {
|
||||||
|
emit(event.date);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
part of 'analytics_date_picker_bloc.dart';
|
||||||
|
|
||||||
|
sealed class AnalyticsDatePickerEvent extends Equatable {
|
||||||
|
const AnalyticsDatePickerEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class UpdateAnalyticsDatePickerEvent extends AnalyticsDatePickerEvent {
|
||||||
|
const UpdateAnalyticsDatePickerEvent(this.date);
|
||||||
|
|
||||||
|
final DateTime date;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [date];
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
|
||||||
|
|
||||||
|
part 'analytics_tab_event.dart';
|
||||||
|
|
||||||
|
class AnalyticsTabBloc extends Bloc<AnalyticsTabEvent, AnalyticsPageTab> {
|
||||||
|
AnalyticsTabBloc() : super(AnalyticsPageTab.energyManagement) {
|
||||||
|
on<UpdateAnalyticsTabEvent>(_onUpdateAnalyticsTabEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onUpdateAnalyticsTabEvent(
|
||||||
|
UpdateAnalyticsTabEvent event,
|
||||||
|
Emitter<AnalyticsPageTab> emit,
|
||||||
|
) {
|
||||||
|
emit(event.analyticsTab);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
part of 'analytics_tab_bloc.dart';
|
||||||
|
|
||||||
|
sealed class AnalyticsTabEvent extends Equatable {
|
||||||
|
const AnalyticsTabEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateAnalyticsTabEvent extends AnalyticsTabEvent {
|
||||||
|
const UpdateAnalyticsTabEvent(this.analyticsTab);
|
||||||
|
|
||||||
|
final AnalyticsPageTab analyticsTab;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [analyticsTab];
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/views/analytics_energy_management_view.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart';
|
||||||
|
|
||||||
|
enum AnalyticsPageTab {
|
||||||
|
energyManagement(
|
||||||
|
title: 'Energy Management',
|
||||||
|
child: AnalyticsEnergyManagementView(),
|
||||||
|
),
|
||||||
|
occupancy(
|
||||||
|
title: 'Occupancy',
|
||||||
|
child: AnalyticsOccupancyView(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const AnalyticsPageTab({
|
||||||
|
required this.title,
|
||||||
|
required this.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
final String title;
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_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_page_tabs_and_children.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/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/total_energy_consumption/total_energy_consumption_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/energy_consumption_per_device/fake_energy_consumption_per_device_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/total_energy_consumption/fake_total_energy_consumption_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
||||||
|
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||||
|
|
||||||
|
class AnalyticsPage extends StatelessWidget {
|
||||||
|
const AnalyticsPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MultiBlocProvider(
|
||||||
|
providers: [
|
||||||
|
BlocProvider<AnalyticsTabBloc>(
|
||||||
|
create: (context) => AnalyticsTabBloc(),
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => TotalEnergyConsumptionBloc(
|
||||||
|
FakeTotalEnergyConsumptionService(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => EnergyConsumptionByPhasesBloc(
|
||||||
|
FakeEnergyConsumptionByPhasesService(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => EnergyConsumptionPerDeviceBloc(
|
||||||
|
FakeEnergyConsumptionPerDeviceService(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => PowerClampInfoBloc(
|
||||||
|
RemotePowerClampInfoService(HTTPService()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BlocProvider<RealtimeDeviceChangesBloc>(
|
||||||
|
create: (context) => RealtimeDeviceChangesBloc(
|
||||||
|
FirebaseRealtimeDeviceService(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: const AnalyticsPageForm(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnalyticsPageForm extends StatelessWidget {
|
||||||
|
const AnalyticsPageForm({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return WebScaffold(
|
||||||
|
rightBody: const NavigateHomeGridView(),
|
||||||
|
appBarTitle: Text(
|
||||||
|
'Syncrow Analytics',
|
||||||
|
style: ResponsiveTextTheme.of(context).deviceManagementTitle,
|
||||||
|
),
|
||||||
|
enableMenuSidebar: false,
|
||||||
|
scaffoldBody: const Row(
|
||||||
|
children: [
|
||||||
|
AnalyticsCommunitiesSidebar(),
|
||||||
|
Expanded(flex: 5, child: AnalyticsPageTabsAndChildren()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_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/helpers/fetch_energy_management_data_helper.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
|
||||||
|
|
||||||
|
class AnalyticsCommunitiesSidebar extends StatelessWidget {
|
||||||
|
const AnalyticsCommunitiesSidebar({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Builder(
|
||||||
|
builder: (context) {
|
||||||
|
return Expanded(
|
||||||
|
child: SpaceTreeView(
|
||||||
|
title: const Text('Communities'),
|
||||||
|
shouldDisableDeselectingChildrenOfSelectedParent: true,
|
||||||
|
onSelect: () {
|
||||||
|
/// Necessary to wait for the state to update before fethcing the data.
|
||||||
|
Future.delayed(
|
||||||
|
const Duration(milliseconds: 100),
|
||||||
|
() {
|
||||||
|
if (context.mounted) {
|
||||||
|
FetchEnergyManagementDataHelper.fetchEnergyManagementData(
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges(
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
context.read<PowerClampInfoBloc>().add(
|
||||||
|
const ClearPowerClampInfoEvent(),
|
||||||
|
);
|
||||||
|
final (selectedCommunities, selectedSpaces) =
|
||||||
|
FetchEnergyManagementDataHelper
|
||||||
|
.getSelectedCommunitiesAndSpaces(context);
|
||||||
|
if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) {
|
||||||
|
context.read<PowerClampInfoBloc>().add(
|
||||||
|
const ClearPowerClampInfoEvent(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
FetchEnergyManagementDataHelper.loadPowerClampInfo(
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isSide: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:intl/intl.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/widgets/month_picker_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
|
||||||
|
class AnalyticsDateFilterButton extends StatefulWidget {
|
||||||
|
const AnalyticsDateFilterButton({super.key});
|
||||||
|
|
||||||
|
static final _color = ColorsManager.blackColor.withValues(alpha: 0.8);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AnalyticsDateFilterButton> createState() =>
|
||||||
|
_AnalyticsDateFilterButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AnalyticsDateFilterButtonState extends State<AnalyticsDateFilterButton> {
|
||||||
|
late final AnalyticsDatePickerBloc _analyticsDatePickerBloc;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_analyticsDatePickerBloc = AnalyticsDatePickerBloc();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_analyticsDatePickerBloc.close();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider.value(
|
||||||
|
value: _analyticsDatePickerBloc,
|
||||||
|
child: Builder(builder: (context) {
|
||||||
|
final selectedDate = context.watch<AnalyticsDatePickerBloc>().state;
|
||||||
|
return TextButton.icon(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: AnalyticsDateFilterButton._color,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
side: const BorderSide(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
backgroundColor: ColorsManager.transparentColor,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 32,
|
||||||
|
vertical: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
icon: SvgPicture.asset(
|
||||||
|
Assets.blankCalendar,
|
||||||
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
colorFilter:
|
||||||
|
ColorFilter.mode(AnalyticsDateFilterButton._color, BlendMode.srcIn),
|
||||||
|
),
|
||||||
|
label: Text(
|
||||||
|
_formatDate(selectedDate),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => MonthPickerWidget(
|
||||||
|
selectedDate: selectedDate,
|
||||||
|
onDateSelected: (value) {
|
||||||
|
_analyticsDatePickerBloc.add(
|
||||||
|
UpdateAnalyticsDatePickerEvent(value),
|
||||||
|
);
|
||||||
|
FetchEnergyManagementDataHelper.fetchEnergyManagementData(
|
||||||
|
context,
|
||||||
|
selectedDate: value,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatDate(DateTime? date) {
|
||||||
|
final formatter = DateFormat('MMMM yyyy');
|
||||||
|
final formattedDate = formatter.format(date ?? DateTime.now());
|
||||||
|
return formattedDate;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_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/enums/analytics_page_tab.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class AnalyticsPageTabButton extends StatelessWidget {
|
||||||
|
const AnalyticsPageTabButton({
|
||||||
|
super.key,
|
||||||
|
required this.tab,
|
||||||
|
required this.isSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
final AnalyticsPageTab tab;
|
||||||
|
final bool isSelected;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TextButton(
|
||||||
|
onPressed: () => context.read<AnalyticsTabBloc>().add(
|
||||||
|
UpdateAnalyticsTabEvent(tab),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
tab.title,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
maxLines: 1,
|
||||||
|
softWrap: false,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w400,
|
||||||
|
fontSize: 16,
|
||||||
|
color:
|
||||||
|
isSelected ? ColorsManager.slidingBlueColor : ColorsManager.textGray,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_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/enums/analytics_page_tab.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/utils/style.dart';
|
||||||
|
|
||||||
|
class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
||||||
|
const AnalyticsPageTabsAndChildren({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<AnalyticsTabBloc, AnalyticsPageTab>(
|
||||||
|
buildWhen: (previous, current) => previous != current,
|
||||||
|
builder: (context, selectedTab) => Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
decoration: subSectionContainerDecoration,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 4,
|
||||||
|
child: FittedBox(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Row(
|
||||||
|
spacing: 32,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
...AnalyticsPageTab.values.map(
|
||||||
|
(tab) => AnimatedSwitcher(
|
||||||
|
switchInCurve: Curves.easeIn,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
child: AnalyticsPageTabButton(
|
||||||
|
key: ValueKey(selectedTab),
|
||||||
|
tab: tab,
|
||||||
|
isSelected: tab == selectedTab,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
const Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
|
child: AnalyticsDateFilterButton(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 8,
|
||||||
|
child: AnimatedSwitcher(
|
||||||
|
switchInCurve: Curves.easeIn,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
child: selectedTab.child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,194 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class MonthPickerWidget extends StatefulWidget {
|
||||||
|
const MonthPickerWidget({
|
||||||
|
super.key,
|
||||||
|
required this.selectedDate,
|
||||||
|
required this.onDateSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DateTime selectedDate;
|
||||||
|
final ValueChanged<DateTime>? onDateSelected;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MonthPickerWidget> createState() => _MonthPickerWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MonthPickerWidgetState extends State<MonthPickerWidget> {
|
||||||
|
late int _currentYear;
|
||||||
|
int? _selectedMonth;
|
||||||
|
|
||||||
|
static const _monthNames = [
|
||||||
|
"January",
|
||||||
|
"February",
|
||||||
|
"March",
|
||||||
|
"April",
|
||||||
|
"May",
|
||||||
|
"June",
|
||||||
|
"July",
|
||||||
|
"August",
|
||||||
|
"September",
|
||||||
|
"October",
|
||||||
|
"November",
|
||||||
|
"December",
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_currentYear = widget.selectedDate.year;
|
||||||
|
_selectedMonth = widget.selectedDate.month - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsetsDirectional.all(20),
|
||||||
|
width: 320,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_buildYearSelector(),
|
||||||
|
_buildMonthsGrid(),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
|
spacing: 12,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
fixedSize: const Size(106, 40),
|
||||||
|
backgroundColor: const Color(0xFFEDF2F7),
|
||||||
|
padding: const EdgeInsetsDirectional.symmetric(
|
||||||
|
vertical: 10,
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Cancel',
|
||||||
|
style: context.textTheme.titleSmall?.copyWith(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: ColorsManager.grey700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
final date = DateTime(
|
||||||
|
_currentYear,
|
||||||
|
_selectedMonth! + 1,
|
||||||
|
);
|
||||||
|
widget.onDateSelected?.call(date);
|
||||||
|
},
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
fixedSize: const Size(106, 40),
|
||||||
|
backgroundColor: ColorsManager.vividBlue.withValues(
|
||||||
|
alpha: 0.7,
|
||||||
|
),
|
||||||
|
padding: const EdgeInsetsDirectional.symmetric(
|
||||||
|
vertical: 10,
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Done',
|
||||||
|
style: context.textTheme.titleSmall?.copyWith(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Row _buildYearSelector() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'$_currentYear',
|
||||||
|
style: context.textTheme.titleSmall?.copyWith(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: ColorsManager.grey700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => setState(() => _currentYear = _currentYear - 1),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.chevron_left,
|
||||||
|
color: ColorsManager.grey700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => setState(() => _currentYear = _currentYear + 1),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.chevron_right,
|
||||||
|
color: ColorsManager.grey700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMonthsGrid() {
|
||||||
|
return GridView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: 12,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 3,
|
||||||
|
childAspectRatio: 2.5,
|
||||||
|
mainAxisSpacing: 8,
|
||||||
|
mainAxisExtent: 30,
|
||||||
|
),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final isSelected = _selectedMonth == index;
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => setState(() => _selectedMonth = index),
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected
|
||||||
|
? ColorsManager.vividBlue.withValues(alpha: 0.7)
|
||||||
|
: 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
|
||||||
|
: ColorsManager.blackColor.withValues(alpha: 0.8),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
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';
|
||||||
|
|
||||||
|
part 'energy_consumption_by_phases_event.dart';
|
||||||
|
part 'energy_consumption_by_phases_state.dart';
|
||||||
|
|
||||||
|
class EnergyConsumptionByPhasesBloc
|
||||||
|
extends Bloc<EnergyConsumptionByPhasesEvent, EnergyConsumptionByPhasesState> {
|
||||||
|
EnergyConsumptionByPhasesBloc(
|
||||||
|
this._energyConsumptionByPhasesService,
|
||||||
|
) : super(const EnergyConsumptionByPhasesState()) {
|
||||||
|
on<LoadEnergyConsumptionByPhasesEvent>(_onLoadEnergyConsumptionByPhasesEvent);
|
||||||
|
on<ClearEnergyConsumptionByPhasesEvent>(_onClearEnergyConsumptionByPhasesEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
final EnergyConsumptionByPhasesService _energyConsumptionByPhasesService;
|
||||||
|
|
||||||
|
Future<void> _onLoadEnergyConsumptionByPhasesEvent(
|
||||||
|
LoadEnergyConsumptionByPhasesEvent event,
|
||||||
|
Emitter<EnergyConsumptionByPhasesState> emit,
|
||||||
|
) async {
|
||||||
|
emit(state.copyWith(status: EnergyConsumptionByPhasesStatus.loading));
|
||||||
|
try {
|
||||||
|
final chartData = await _energyConsumptionByPhasesService.load(event.param);
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: EnergyConsumptionByPhasesStatus.loaded,
|
||||||
|
chartData: chartData,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: EnergyConsumptionByPhasesStatus.failure,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onClearEnergyConsumptionByPhasesEvent(
|
||||||
|
ClearEnergyConsumptionByPhasesEvent event,
|
||||||
|
Emitter<EnergyConsumptionByPhasesState> emit,
|
||||||
|
) async {
|
||||||
|
emit(const EnergyConsumptionByPhasesState());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
part of 'energy_consumption_by_phases_bloc.dart';
|
||||||
|
|
||||||
|
sealed class EnergyConsumptionByPhasesEvent extends Equatable {
|
||||||
|
const EnergyConsumptionByPhasesEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadEnergyConsumptionByPhasesEvent extends EnergyConsumptionByPhasesEvent {
|
||||||
|
const LoadEnergyConsumptionByPhasesEvent({
|
||||||
|
required this.param,
|
||||||
|
});
|
||||||
|
|
||||||
|
final GetEnergyConsumptionByPhasesParam param;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [param];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ClearEnergyConsumptionByPhasesEvent extends EnergyConsumptionByPhasesEvent {
|
||||||
|
const ClearEnergyConsumptionByPhasesEvent();
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
part of 'energy_consumption_by_phases_bloc.dart';
|
||||||
|
|
||||||
|
enum EnergyConsumptionByPhasesStatus {
|
||||||
|
initial,
|
||||||
|
loading,
|
||||||
|
loaded,
|
||||||
|
failure,
|
||||||
|
}
|
||||||
|
|
||||||
|
final class EnergyConsumptionByPhasesState extends Equatable {
|
||||||
|
const EnergyConsumptionByPhasesState({
|
||||||
|
this.status = EnergyConsumptionByPhasesStatus.initial,
|
||||||
|
this.chartData = const <PhasesEnergyConsumption>[],
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<PhasesEnergyConsumption> chartData;
|
||||||
|
final EnergyConsumptionByPhasesStatus status;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
EnergyConsumptionByPhasesState copyWith({
|
||||||
|
List<PhasesEnergyConsumption>? chartData,
|
||||||
|
EnergyConsumptionByPhasesStatus? status,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return EnergyConsumptionByPhasesState(
|
||||||
|
chartData: chartData ?? this.chartData,
|
||||||
|
status: status ?? this.status,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [chartData, status, errorMessage];
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/device_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';
|
||||||
|
|
||||||
|
part 'energy_consumption_per_device_event.dart';
|
||||||
|
part 'energy_consumption_per_device_state.dart';
|
||||||
|
|
||||||
|
class EnergyConsumptionPerDeviceBloc
|
||||||
|
extends Bloc<EnergyConsumptionPerDeviceEvent, EnergyConsumptionPerDeviceState> {
|
||||||
|
EnergyConsumptionPerDeviceBloc(
|
||||||
|
this._energyConsumptionPerDeviceService,
|
||||||
|
) : super(const EnergyConsumptionPerDeviceState()) {
|
||||||
|
on<LoadEnergyConsumptionPerDeviceEvent>(_onLoadEnergyConsumptionPerDeviceEvent);
|
||||||
|
on<ClearEnergyConsumptionPerDeviceEvent>(_onClearEnergyConsumptionPerDeviceEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
final EnergyConsumptionPerDeviceService _energyConsumptionPerDeviceService;
|
||||||
|
|
||||||
|
Future<void> _onLoadEnergyConsumptionPerDeviceEvent(
|
||||||
|
LoadEnergyConsumptionPerDeviceEvent event,
|
||||||
|
Emitter<EnergyConsumptionPerDeviceState> emit,
|
||||||
|
) async {
|
||||||
|
emit(state.copyWith(status: EnergyConsumptionPerDeviceStatus.loading));
|
||||||
|
try {
|
||||||
|
final chartData = await _energyConsumptionPerDeviceService.load(event.param);
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: EnergyConsumptionPerDeviceStatus.loaded,
|
||||||
|
chartData: chartData,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: EnergyConsumptionPerDeviceStatus.failure,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onClearEnergyConsumptionPerDeviceEvent(
|
||||||
|
ClearEnergyConsumptionPerDeviceEvent event,
|
||||||
|
Emitter<EnergyConsumptionPerDeviceState> emit,
|
||||||
|
) async {
|
||||||
|
emit(const EnergyConsumptionPerDeviceState());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
part of 'energy_consumption_per_device_bloc.dart';
|
||||||
|
|
||||||
|
sealed class EnergyConsumptionPerDeviceEvent extends Equatable {
|
||||||
|
const EnergyConsumptionPerDeviceEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class LoadEnergyConsumptionPerDeviceEvent
|
||||||
|
extends EnergyConsumptionPerDeviceEvent {
|
||||||
|
const LoadEnergyConsumptionPerDeviceEvent(this.param);
|
||||||
|
|
||||||
|
final GetEnergyConsumptionPerDeviceParam param;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [param];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ClearEnergyConsumptionPerDeviceEvent
|
||||||
|
extends EnergyConsumptionPerDeviceEvent {
|
||||||
|
const ClearEnergyConsumptionPerDeviceEvent();
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
part of 'energy_consumption_per_device_bloc.dart';
|
||||||
|
|
||||||
|
enum EnergyConsumptionPerDeviceStatus { initial, loading, loaded, failure }
|
||||||
|
|
||||||
|
final class EnergyConsumptionPerDeviceState extends Equatable {
|
||||||
|
const EnergyConsumptionPerDeviceState({
|
||||||
|
this.status = EnergyConsumptionPerDeviceStatus.initial,
|
||||||
|
this.chartData = const <DeviceEnergyDataModel>[],
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<DeviceEnergyDataModel> chartData;
|
||||||
|
final EnergyConsumptionPerDeviceStatus status;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
EnergyConsumptionPerDeviceState copyWith({
|
||||||
|
List<DeviceEnergyDataModel>? chartData,
|
||||||
|
EnergyConsumptionPerDeviceStatus? status,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return EnergyConsumptionPerDeviceState(
|
||||||
|
chartData: chartData ?? this.chartData,
|
||||||
|
status: status ?? this.status,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [chartData, status, errorMessage];
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/power_clamp_info_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
|
||||||
|
|
||||||
|
part 'power_clamp_info_event.dart';
|
||||||
|
part 'power_clamp_info_state.dart';
|
||||||
|
|
||||||
|
class PowerClampInfoBloc extends Bloc<PowerClampInfoEvent, PowerClampInfoState> {
|
||||||
|
PowerClampInfoBloc(
|
||||||
|
this._powerClampInfoService,
|
||||||
|
) : super(const PowerClampInfoState()) {
|
||||||
|
on<LoadPowerClampInfoEvent>(_onLoadPowerClampInfoEvent);
|
||||||
|
on<UpdatePowerClampStatusEvent>(_onUpdatePowerClampStatusEvent);
|
||||||
|
on<ClearPowerClampInfoEvent>(_onClearPowerClampInfoEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
final PowerClampInfoService _powerClampInfoService;
|
||||||
|
|
||||||
|
Future<void> _onLoadPowerClampInfoEvent(
|
||||||
|
LoadPowerClampInfoEvent event,
|
||||||
|
Emitter<PowerClampInfoState> emit,
|
||||||
|
) async {
|
||||||
|
emit(state.copyWith(status: PowerClampInfoStatus.loading));
|
||||||
|
try {
|
||||||
|
final powerClampModel = await _powerClampInfoService.getInfo(event.deviceId);
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: PowerClampInfoStatus.loaded,
|
||||||
|
powerClampModel: powerClampModel,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: PowerClampInfoStatus.error,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onUpdatePowerClampStatusEvent(
|
||||||
|
UpdatePowerClampStatusEvent event,
|
||||||
|
Emitter<PowerClampInfoState> emit,
|
||||||
|
) async {
|
||||||
|
final currentModel = state.powerClampModel;
|
||||||
|
if (currentModel == null) return;
|
||||||
|
|
||||||
|
final updatedStatus = PowerStatus.fromStatusList(event.statusList);
|
||||||
|
final updatedModel = currentModel.copyWith(statusPower: updatedStatus);
|
||||||
|
|
||||||
|
emit(state.copyWith(powerClampModel: updatedModel));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onClearPowerClampInfoEvent(
|
||||||
|
ClearPowerClampInfoEvent event,
|
||||||
|
Emitter<PowerClampInfoState> emit,
|
||||||
|
) {
|
||||||
|
emit(const PowerClampInfoState());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
part of 'power_clamp_info_bloc.dart';
|
||||||
|
|
||||||
|
sealed class PowerClampInfoEvent extends Equatable {
|
||||||
|
const PowerClampInfoEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class LoadPowerClampInfoEvent extends PowerClampInfoEvent {
|
||||||
|
const LoadPowerClampInfoEvent(this.deviceId);
|
||||||
|
|
||||||
|
final String deviceId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [deviceId];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final class UpdatePowerClampStatusEvent extends PowerClampInfoEvent {
|
||||||
|
const UpdatePowerClampStatusEvent(this.statusList);
|
||||||
|
|
||||||
|
final List<Status> statusList;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [statusList];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ClearPowerClampInfoEvent extends PowerClampInfoEvent {
|
||||||
|
const ClearPowerClampInfoEvent();
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
part of 'power_clamp_info_bloc.dart';
|
||||||
|
|
||||||
|
enum PowerClampInfoStatus { initial, loading, loaded, error }
|
||||||
|
|
||||||
|
final class PowerClampInfoState extends Equatable {
|
||||||
|
const PowerClampInfoState({
|
||||||
|
this.status = PowerClampInfoStatus.initial,
|
||||||
|
this.powerClampModel,
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
final PowerClampInfoStatus status;
|
||||||
|
final PowerClampModel? powerClampModel;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
PowerClampInfoState copyWith({
|
||||||
|
PowerClampInfoStatus? status,
|
||||||
|
PowerClampModel? powerClampModel,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return PowerClampInfoState(
|
||||||
|
status: status ?? this.status,
|
||||||
|
powerClampModel: powerClampModel ?? this.powerClampModel,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [status, powerClampModel, errorMessage];
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/realtime_device_service/realtime_device_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
|
|
||||||
|
part 'realtime_device_changes_event.dart';
|
||||||
|
part 'realtime_device_changes_state.dart';
|
||||||
|
|
||||||
|
class RealtimeDeviceChangesBloc
|
||||||
|
extends Bloc<RealtimeDeviceChangesEvent, RealtimeDeviceChangesState> {
|
||||||
|
RealtimeDeviceChangesBloc(
|
||||||
|
this._realtimeDeviceService,
|
||||||
|
) : super(const RealtimeDeviceChangesState()) {
|
||||||
|
on<RealtimeDeviceChangesStarted>(_onRealtimeDeviceChangesStarted);
|
||||||
|
on<RealtimeDeviceChangesClosed>(_onRealtimeDeviceChangesClosed);
|
||||||
|
on<_RealtimeDeviceChangesUpdated>(_onRealtimeDeviceChangesUpdated);
|
||||||
|
}
|
||||||
|
|
||||||
|
final RealtimeDeviceService _realtimeDeviceService;
|
||||||
|
StreamSubscription<List<Status>>? _subscription;
|
||||||
|
|
||||||
|
Future<void> _onRealtimeDeviceChangesStarted(
|
||||||
|
RealtimeDeviceChangesStarted event,
|
||||||
|
Emitter<RealtimeDeviceChangesState> emit,
|
||||||
|
) async {
|
||||||
|
await _subscription?.cancel();
|
||||||
|
_subscription = _realtimeDeviceService.subscribe(event.deviceId).listen(
|
||||||
|
(data) {
|
||||||
|
add(_RealtimeDeviceChangesUpdated(data));
|
||||||
|
},
|
||||||
|
onError: (error) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: RealtimeDeviceChangesStatus.failure,
|
||||||
|
errorMessage: '$error',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onRealtimeDeviceChangesClosed(
|
||||||
|
RealtimeDeviceChangesClosed event,
|
||||||
|
Emitter<RealtimeDeviceChangesState> emit,
|
||||||
|
) async {
|
||||||
|
add(const _RealtimeDeviceChangesUpdated([]));
|
||||||
|
await _subscription?.cancel();
|
||||||
|
_subscription = null;
|
||||||
|
emit(const RealtimeDeviceChangesState());
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onRealtimeDeviceChangesUpdated(
|
||||||
|
_RealtimeDeviceChangesUpdated event,
|
||||||
|
Emitter<RealtimeDeviceChangesState> emit,
|
||||||
|
) {
|
||||||
|
final currentState = state;
|
||||||
|
final updatedList = [
|
||||||
|
...currentState.deviceStatusList.where(
|
||||||
|
(device) => !event.deviceStatusList
|
||||||
|
.any((newDevice) => newDevice.code == device.code),
|
||||||
|
),
|
||||||
|
...event.deviceStatusList,
|
||||||
|
];
|
||||||
|
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: RealtimeDeviceChangesStatus.loaded,
|
||||||
|
deviceStatusList: updatedList,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
await _subscription?.cancel();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
part of 'realtime_device_changes_bloc.dart';
|
||||||
|
|
||||||
|
sealed class RealtimeDeviceChangesEvent extends Equatable {
|
||||||
|
const RealtimeDeviceChangesEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class RealtimeDeviceChangesStarted extends RealtimeDeviceChangesEvent {
|
||||||
|
const RealtimeDeviceChangesStarted(this.deviceId);
|
||||||
|
|
||||||
|
final String deviceId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [deviceId];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class RealtimeDeviceChangesClosed extends RealtimeDeviceChangesEvent {
|
||||||
|
const RealtimeDeviceChangesClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RealtimeDeviceChangesUpdated extends RealtimeDeviceChangesEvent {
|
||||||
|
final List<Status> deviceStatusList;
|
||||||
|
|
||||||
|
const _RealtimeDeviceChangesUpdated(this.deviceStatusList);
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
part of 'realtime_device_changes_bloc.dart';
|
||||||
|
|
||||||
|
enum RealtimeDeviceChangesStatus { initial, loaded, failure }
|
||||||
|
|
||||||
|
final class RealtimeDeviceChangesState extends Equatable {
|
||||||
|
const RealtimeDeviceChangesState({
|
||||||
|
this.status = RealtimeDeviceChangesStatus.initial,
|
||||||
|
this.deviceStatusList = const <Status>[],
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
final RealtimeDeviceChangesStatus status;
|
||||||
|
final List<Status> deviceStatusList;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
RealtimeDeviceChangesState copyWith({
|
||||||
|
RealtimeDeviceChangesStatus? status,
|
||||||
|
List<Status>? deviceStatusList,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return RealtimeDeviceChangesState(
|
||||||
|
status: status ?? this.status,
|
||||||
|
deviceStatusList: deviceStatusList ?? this.deviceStatusList,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [status, deviceStatusList, errorMessage];
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/total_energy_consumption_service.dart';
|
||||||
|
|
||||||
|
part 'total_energy_consumption_event.dart';
|
||||||
|
part 'total_energy_consumption_state.dart';
|
||||||
|
|
||||||
|
class TotalEnergyConsumptionBloc
|
||||||
|
extends Bloc<TotalEnergyConsumptionEvent, TotalEnergyConsumptionState> {
|
||||||
|
TotalEnergyConsumptionBloc(
|
||||||
|
this._totalEnergyConsumptionService,
|
||||||
|
) : super(const TotalEnergyConsumptionState()) {
|
||||||
|
on<TotalEnergyConsumptionLoadEvent>(_onTotalEnergyConsumptionLoadEvent);
|
||||||
|
on<ClearTotalEnergyConsumptionEvent>(_onClearTotalEnergyConsumptionEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
final TotalEnergyConsumptionService _totalEnergyConsumptionService;
|
||||||
|
|
||||||
|
Future<void> _onTotalEnergyConsumptionLoadEvent(
|
||||||
|
TotalEnergyConsumptionLoadEvent event,
|
||||||
|
Emitter<TotalEnergyConsumptionState> emit,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
emit(state.copyWith(status: TotalEnergyConsumptionStatus.loading));
|
||||||
|
final chartData = await _totalEnergyConsumptionService.load(event.param);
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
chartData: chartData,
|
||||||
|
status: TotalEnergyConsumptionStatus.loaded,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
status: TotalEnergyConsumptionStatus.failure,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onClearTotalEnergyConsumptionEvent(
|
||||||
|
ClearTotalEnergyConsumptionEvent event,
|
||||||
|
Emitter<TotalEnergyConsumptionState> emit,
|
||||||
|
) async {
|
||||||
|
emit(const TotalEnergyConsumptionState());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
part of 'total_energy_consumption_bloc.dart';
|
||||||
|
|
||||||
|
sealed class TotalEnergyConsumptionEvent extends Equatable {
|
||||||
|
const TotalEnergyConsumptionEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class TotalEnergyConsumptionLoadEvent extends TotalEnergyConsumptionEvent {
|
||||||
|
const TotalEnergyConsumptionLoadEvent({required this.param});
|
||||||
|
|
||||||
|
final GetTotalEnergyConsumptionParam param;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [param];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ClearTotalEnergyConsumptionEvent extends TotalEnergyConsumptionEvent {
|
||||||
|
const ClearTotalEnergyConsumptionEvent();
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
part of 'total_energy_consumption_bloc.dart';
|
||||||
|
|
||||||
|
enum TotalEnergyConsumptionStatus {
|
||||||
|
initial,
|
||||||
|
loading,
|
||||||
|
loaded,
|
||||||
|
failure,
|
||||||
|
}
|
||||||
|
|
||||||
|
final class TotalEnergyConsumptionState extends Equatable {
|
||||||
|
const TotalEnergyConsumptionState({
|
||||||
|
this.status = TotalEnergyConsumptionStatus.initial,
|
||||||
|
this.chartData = const <EnergyDataModel>[],
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<EnergyDataModel> chartData;
|
||||||
|
final TotalEnergyConsumptionStatus status;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
TotalEnergyConsumptionState copyWith({
|
||||||
|
List<EnergyDataModel>? chartData,
|
||||||
|
TotalEnergyConsumptionStatus? status,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return TotalEnergyConsumptionState(
|
||||||
|
chartData: chartData ?? this.chartData,
|
||||||
|
status: status ?? this.status,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [chartData, status, errorMessage];
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
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),
|
||||||
|
];
|
||||||
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/helpers/format_number_to_kwh.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
abstract final class EnergyManagementChartsHelper {
|
||||||
|
const EnergyManagementChartsHelper._();
|
||||||
|
|
||||||
|
static FlTitlesData titlesData(
|
||||||
|
BuildContext context, {
|
||||||
|
double? leftTitlesInterval,
|
||||||
|
}) {
|
||||||
|
const emptyTitle = AxisTitles(sideTitles: SideTitles(showTitles: false));
|
||||||
|
return FlTitlesData(
|
||||||
|
show: true,
|
||||||
|
bottomTitles: AxisTitles(
|
||||||
|
drawBelowEverything: true,
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
interval: 1,
|
||||||
|
reservedSize: 32,
|
||||||
|
showTitles: true,
|
||||||
|
maxIncluded: true,
|
||||||
|
getTitlesWidget: (value, meta) => Padding(
|
||||||
|
padding: const EdgeInsetsDirectional.only(top: 20.0),
|
||||||
|
child: Text(
|
||||||
|
value.toString(),
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leftTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
maxIncluded: true,
|
||||||
|
interval: leftTitlesInterval,
|
||||||
|
reservedSize: 110,
|
||||||
|
getTitlesWidget: (value, meta) => Padding(
|
||||||
|
padding: const EdgeInsetsDirectional.only(end: 12),
|
||||||
|
child: FittedBox(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text(
|
||||||
|
value.formatNumberToKwh,
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
rightTitles: emptyTitle,
|
||||||
|
topTitles: emptyTitle,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getToolTipLabel(num month, double value) {
|
||||||
|
final monthLabel = month.toString();
|
||||||
|
final valueLabel = value.formatNumberToKwh;
|
||||||
|
final labels = [monthLabel, valueLabel];
|
||||||
|
return labels.where((element) => element.isNotEmpty).join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) {
|
||||||
|
return touchedSpots.map((spot) {
|
||||||
|
return LineTooltipItem(
|
||||||
|
getToolTipLabel(spot.x, spot.y),
|
||||||
|
const TextStyle(
|
||||||
|
color: ColorsManager.textPrimaryColor,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static LineTouchTooltipData lineTouchTooltipData() {
|
||||||
|
return LineTouchTooltipData(
|
||||||
|
getTooltipColor: (touchTooltipItem) => ColorsManager.whiteColors,
|
||||||
|
tooltipBorder: const BorderSide(color: ColorsManager.semiTransparentBlack),
|
||||||
|
tooltipRoundedRadius: 16,
|
||||||
|
showOnTopOfTheChartBoxArea: false,
|
||||||
|
tooltipPadding: const EdgeInsets.all(8),
|
||||||
|
getTooltipItems: getTooltipItems,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FlBorderData borderData() {
|
||||||
|
return FlBorderData(
|
||||||
|
show: true,
|
||||||
|
border: const Border.symmetric(
|
||||||
|
horizontal: BorderSide(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
style: BorderStyle.solid,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FlGridData gridData() {
|
||||||
|
return const FlGridData(
|
||||||
|
show: true,
|
||||||
|
drawVerticalLine: false,
|
||||||
|
drawHorizontalLine: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LineTouchData lineTouchData() {
|
||||||
|
return LineTouchData(
|
||||||
|
handleBuiltInTouches: true,
|
||||||
|
touchSpotThreshold: 2,
|
||||||
|
touchTooltipData: EnergyManagementChartsHelper.lineTouchTooltipData(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_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/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/total_energy_consumption/total_energy_consumption_bloc.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_total_energy_consumption_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||||
|
|
||||||
|
abstract final class FetchEnergyManagementDataHelper {
|
||||||
|
const FetchEnergyManagementDataHelper._();
|
||||||
|
|
||||||
|
static void fetchEnergyManagementData(
|
||||||
|
BuildContext context, {
|
||||||
|
DateTime? selectedDate,
|
||||||
|
}) {
|
||||||
|
final (selectedCommunities, selectedSpaces) =
|
||||||
|
getSelectedCommunitiesAndSpaces(context);
|
||||||
|
|
||||||
|
if (selectedCommunities.isEmpty && selectedSpaces.isEmpty) {
|
||||||
|
clearAllData(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadTotalEnergyConsumption(context);
|
||||||
|
loadEnergyConsumptionByPhases(context);
|
||||||
|
loadEnergyConsumptionPerDevice(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static (List<String> selectedCommunities, List<String> selectedSpaces)
|
||||||
|
getSelectedCommunitiesAndSpaces(BuildContext context) {
|
||||||
|
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||||
|
final selectedCommunities = spaceTreeState.selectedCommunities;
|
||||||
|
final selectedSpaces = spaceTreeState.selectedSpaces;
|
||||||
|
|
||||||
|
return (selectedCommunities, selectedSpaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadEnergyConsumptionByPhases(
|
||||||
|
BuildContext context, {
|
||||||
|
DateTime? selectedDate,
|
||||||
|
}) {
|
||||||
|
final param = GetEnergyConsumptionByPhasesParam(
|
||||||
|
startDate: selectedDate,
|
||||||
|
spaceId: '',
|
||||||
|
);
|
||||||
|
context.read<EnergyConsumptionByPhasesBloc>().add(
|
||||||
|
LoadEnergyConsumptionByPhasesEvent(param: param),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadTotalEnergyConsumption(
|
||||||
|
BuildContext context, {
|
||||||
|
DateTime? selectedDate,
|
||||||
|
}) {
|
||||||
|
final (selectedCommunities, selectedSpaces) =
|
||||||
|
getSelectedCommunitiesAndSpaces(context);
|
||||||
|
|
||||||
|
final param = GetTotalEnergyConsumptionParam(
|
||||||
|
spaceId: selectedCommunities.firstOrNull,
|
||||||
|
startDate: selectedDate,
|
||||||
|
);
|
||||||
|
context.read<TotalEnergyConsumptionBloc>().add(
|
||||||
|
TotalEnergyConsumptionLoadEvent(param: param),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadEnergyConsumptionPerDevice(BuildContext context) {
|
||||||
|
const param = GetEnergyConsumptionPerDeviceParam();
|
||||||
|
context.read<EnergyConsumptionPerDeviceBloc>().add(
|
||||||
|
const LoadEnergyConsumptionPerDeviceEvent(param),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadPowerClampInfo(BuildContext context) {
|
||||||
|
context.read<PowerClampInfoBloc>().add(
|
||||||
|
const LoadPowerClampInfoEvent('cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadRealtimeDeviceChanges(BuildContext context) {
|
||||||
|
context.read<RealtimeDeviceChangesBloc>().add(
|
||||||
|
const RealtimeDeviceChangesStarted('cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clearAllData(BuildContext context) {
|
||||||
|
context.read<RealtimeDeviceChangesBloc>().add(
|
||||||
|
const RealtimeDeviceChangesClosed(),
|
||||||
|
);
|
||||||
|
|
||||||
|
context.read<PowerClampInfoBloc>().add(
|
||||||
|
const ClearPowerClampInfoEvent(),
|
||||||
|
);
|
||||||
|
context.read<EnergyConsumptionPerDeviceBloc>().add(
|
||||||
|
const ClearEnergyConsumptionPerDeviceEvent(),
|
||||||
|
);
|
||||||
|
|
||||||
|
context.read<TotalEnergyConsumptionBloc>().add(
|
||||||
|
const ClearTotalEnergyConsumptionEvent(),
|
||||||
|
);
|
||||||
|
|
||||||
|
context.read<EnergyConsumptionByPhasesBloc>().add(
|
||||||
|
const ClearEnergyConsumptionByPhasesEvent(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
import 'package:flutter/material.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/total_energy_consumption_chart_box.dart';
|
||||||
|
|
||||||
|
class AnalyticsEnergyManagementView extends StatelessWidget {
|
||||||
|
const AnalyticsEnergyManagementView({super.key});
|
||||||
|
|
||||||
|
static const _padding = EdgeInsetsDirectional.all(32);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final isMediumOrLess = constraints.maxWidth <= 900;
|
||||||
|
if (isMediumOrLess) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
padding: _padding,
|
||||||
|
child: Column(
|
||||||
|
spacing: 32,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: MediaQuery.sizeOf(context).height * 1.2,
|
||||||
|
child: const PowerClampEnergyDataWidget(),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: MediaQuery.sizeOf(context).height * 0.5,
|
||||||
|
child: const TotalEnergyConsumptionChartBox(),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: MediaQuery.sizeOf(context).height * 0.5,
|
||||||
|
child: const EnergyConsumptionPerDeviceChartBox(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Container(
|
||||||
|
padding: _padding,
|
||||||
|
height: MediaQuery.sizeOf(context).height * 1,
|
||||||
|
child: const Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
spacing: 32,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Column(
|
||||||
|
spacing: 20,
|
||||||
|
children: [
|
||||||
|
Expanded(child: TotalEnergyConsumptionChartBox()),
|
||||||
|
Expanded(child: EnergyConsumptionPerDeviceChartBox()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(child: PowerClampEnergyDataWidget()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class ChartTitle extends StatelessWidget {
|
||||||
|
const ChartTitle({super.key, required this.title});
|
||||||
|
|
||||||
|
final Widget title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DefaultTextStyle(
|
||||||
|
style: context.textTheme.titleLarge!.copyWith(
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
child: title,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,172 @@
|
|||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/helpers/get_month_name_from_int.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/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class EnergyConsumptionByPhasesChart extends StatelessWidget {
|
||||||
|
const EnergyConsumptionByPhasesChart({
|
||||||
|
super.key,
|
||||||
|
required this.energyData,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<PhasesEnergyConsumption> energyData;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BarChart(
|
||||||
|
BarChartData(
|
||||||
|
gridData: EnergyManagementChartsHelper.gridData(),
|
||||||
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
|
barTouchData: _barTouchData(context),
|
||||||
|
titlesData: _titlesData(context),
|
||||||
|
barGroups: energyData.asMap().entries.map((entry) {
|
||||||
|
final index = entry.key;
|
||||||
|
final data = entry.value;
|
||||||
|
|
||||||
|
return BarChartGroupData(
|
||||||
|
x: index,
|
||||||
|
barRods: [
|
||||||
|
BarChartRodData(
|
||||||
|
color: ColorsManager.vividBlue.withValues(alpha: 0.1),
|
||||||
|
toY: data.phaseA + data.phaseB + data.phaseC,
|
||||||
|
rodStackItems: [
|
||||||
|
BarChartRodStackItem(
|
||||||
|
0,
|
||||||
|
data.phaseA,
|
||||||
|
ColorsManager.vividBlue.withValues(alpha: 0.8),
|
||||||
|
),
|
||||||
|
BarChartRodStackItem(
|
||||||
|
data.phaseA,
|
||||||
|
data.phaseA + data.phaseB,
|
||||||
|
ColorsManager.vividBlue.withValues(alpha: 0.4),
|
||||||
|
),
|
||||||
|
BarChartRodStackItem(
|
||||||
|
data.phaseA + data.phaseB,
|
||||||
|
data.phaseA + data.phaseB + data.phaseC,
|
||||||
|
ColorsManager.vividBlue.withValues(alpha: 0.15),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
width: 16,
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(8),
|
||||||
|
topRight: Radius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BarTouchData _barTouchData(BuildContext context) {
|
||||||
|
return BarTouchData(
|
||||||
|
touchTooltipData: BarTouchTooltipData(
|
||||||
|
getTooltipColor: (touchTooltipItem) => ColorsManager.whiteColors,
|
||||||
|
tooltipBorder: const BorderSide(
|
||||||
|
color: ColorsManager.semiTransparentBlack,
|
||||||
|
),
|
||||||
|
tooltipRoundedRadius: 16,
|
||||||
|
tooltipPadding: const EdgeInsets.all(8),
|
||||||
|
getTooltipItem: (group, groupIndex, rod, rodIndex) => getTooltipItem(
|
||||||
|
context: context,
|
||||||
|
group: group,
|
||||||
|
groupIndex: groupIndex,
|
||||||
|
rod: rod,
|
||||||
|
rodIndex: rodIndex,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BarTooltipItem? getTooltipItem({
|
||||||
|
required BuildContext context,
|
||||||
|
required BarChartGroupData group,
|
||||||
|
required int groupIndex,
|
||||||
|
required BarChartRodData rod,
|
||||||
|
required int rodIndex,
|
||||||
|
}) {
|
||||||
|
final data = energyData;
|
||||||
|
|
||||||
|
final month = data[group.x.toInt()].month.getMonthName;
|
||||||
|
final phaseA = data[group.x.toInt()].phaseA;
|
||||||
|
final phaseB = data[group.x.toInt()].phaseB;
|
||||||
|
final phaseC = data[group.x.toInt()].phaseC;
|
||||||
|
|
||||||
|
return BarTooltipItem(
|
||||||
|
'$month\n',
|
||||||
|
context.textTheme.bodyMedium!.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: 'Phase A: $phaseA\n',
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: 'Phase B: $phaseB\n',
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: 'Phase C: $phaseC',
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FlTitlesData _titlesData(BuildContext context) {
|
||||||
|
final titlesData = EnergyManagementChartsHelper.titlesData(
|
||||||
|
context,
|
||||||
|
leftTitlesInterval: 250,
|
||||||
|
);
|
||||||
|
|
||||||
|
final leftTitles = titlesData.leftTitles.copyWith(
|
||||||
|
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
||||||
|
reservedSize: 70,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final bottomTitles = AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
getTitlesWidget: (value, _) {
|
||||||
|
final month = energyData[value.toInt()].month.getMonthName;
|
||||||
|
return FittedBox(
|
||||||
|
alignment: AlignmentDirectional.bottomCenter,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: RotatedBox(
|
||||||
|
quarterTurns: 3,
|
||||||
|
child: Text(
|
||||||
|
month,
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
fontSize: 11,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
reservedSize: 36,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return titlesData.copyWith(
|
||||||
|
leftTitles: leftTitles,
|
||||||
|
bottomTitles: bottomTitles,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_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/widgets/energy_consumption_by_phases_chart.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_title.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
||||||
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
|
class EnergyConsumptionByPhasesChartBox extends StatelessWidget {
|
||||||
|
const EnergyConsumptionByPhasesChartBox({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<EnergyConsumptionByPhasesBloc,
|
||||||
|
EnergyConsumptionByPhasesState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsetsDirectional.all(20),
|
||||||
|
decoration: secondarySection,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 20,
|
||||||
|
children: [
|
||||||
|
AnalyticsErrorWidget(state.errorMessage),
|
||||||
|
EnergyConsumptionByPhasesTitle(isLoading: state.status == EnergyConsumptionByPhasesStatus.loading,),
|
||||||
|
Expanded(
|
||||||
|
child: EnergyConsumptionByPhasesChart(
|
||||||
|
energyData: state.chartData,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class EnergyConsumptionByPhasesTitle extends StatelessWidget {
|
||||||
|
const EnergyConsumptionByPhasesTitle({super.key, required this.isLoading});
|
||||||
|
final bool isLoading;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
ChartsLoadingWidget(isLoading: isLoading),
|
||||||
|
Expanded(
|
||||||
|
flex: 4,
|
||||||
|
child: FittedBox(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: ChartTitle(
|
||||||
|
title: Text(
|
||||||
|
'Energy Consumption by Phases',
|
||||||
|
style: context.textTheme.titleLarge?.copyWith(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: ColorsManager.textPrimaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
...<(String title, double opacity)>[
|
||||||
|
('A', 0.8),
|
||||||
|
('B', 0.4),
|
||||||
|
('C', 0.15),
|
||||||
|
].map((phase) => _buildPhaseCell(context, phase)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPhaseCell(
|
||||||
|
BuildContext context,
|
||||||
|
(String title, double colorOpacity) phase,
|
||||||
|
) {
|
||||||
|
final (title, colorOpacity) = phase;
|
||||||
|
|
||||||
|
return Expanded(
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
backgroundColor: ColorsManager.vividBlue.withValues(
|
||||||
|
alpha: colorOpacity,
|
||||||
|
),
|
||||||
|
radius: 4,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Phase $title',
|
||||||
|
style: context.textTheme.labelSmall?.copyWith(
|
||||||
|
fontSize: 8,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: ColorsManager.lightGreyColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
||||||
|
|
||||||
|
class EnergyConsumptionPerDeviceChart extends StatelessWidget {
|
||||||
|
const EnergyConsumptionPerDeviceChart({super.key, required this.chartData});
|
||||||
|
|
||||||
|
final List<DeviceEnergyDataModel> chartData;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LineChart(
|
||||||
|
LineChartData(
|
||||||
|
titlesData: EnergyManagementChartsHelper.titlesData(
|
||||||
|
context,
|
||||||
|
leftTitlesInterval: 250,
|
||||||
|
),
|
||||||
|
gridData: EnergyManagementChartsHelper.gridData(),
|
||||||
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
|
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
|
||||||
|
lineBarsData: chartData.map((e) {
|
||||||
|
return _buildChartBar(
|
||||||
|
color: e.color,
|
||||||
|
spots: e.energy
|
||||||
|
.map(
|
||||||
|
(energy) => FlSpot(
|
||||||
|
energy.date.day.toDouble(),
|
||||||
|
energy.value,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
duration: Durations.extralong1,
|
||||||
|
curve: Curves.easeIn,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
LineChartBarData _buildChartBar({
|
||||||
|
required Color color,
|
||||||
|
required List<FlSpot> spots,
|
||||||
|
}) {
|
||||||
|
return LineChartBarData(
|
||||||
|
spots: spots,
|
||||||
|
dashArray: [12, 18],
|
||||||
|
isCurved: true,
|
||||||
|
color: color,
|
||||||
|
barWidth: 3,
|
||||||
|
isStrokeCapRound: true,
|
||||||
|
dotData: const FlDotData(show: false),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_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/energy_consumption_per_device_chart.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_devices_list.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
|
||||||
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
|
class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
|
||||||
|
const EnergyConsumptionPerDeviceChartBox({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<EnergyConsumptionPerDeviceBloc,
|
||||||
|
EnergyConsumptionPerDeviceState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Container(
|
||||||
|
decoration: subSectionContainerDecoration.copyWith(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(30),
|
||||||
|
child: Column(
|
||||||
|
spacing: 20,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
AnalyticsErrorWidget(state.errorMessage),
|
||||||
|
Row(
|
||||||
|
spacing: 32,
|
||||||
|
children: [
|
||||||
|
if (state.status == EnergyConsumptionPerDeviceStatus.loading)
|
||||||
|
const ChartsLoadingWidget(isLoading: true),
|
||||||
|
const Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
child: ChartTitle(
|
||||||
|
title: Text('Energy Consumption per Device'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: EnergyConsumptionPerDeviceDevicesList(
|
||||||
|
chartData: state.chartData,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(height: 0),
|
||||||
|
Expanded(
|
||||||
|
child: EnergyConsumptionPerDeviceChart(chartData: state.chartData),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget {
|
||||||
|
const EnergyConsumptionPerDeviceDevicesList({required this.chartData, super.key});
|
||||||
|
|
||||||
|
final List<DeviceEnergyDataModel> chartData;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
spacing: 16,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: chartData.map((e) => _buildDeviceCell(context, e)).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDeviceCell(BuildContext context, DeviceEnergyDataModel device) {
|
||||||
|
return Container(
|
||||||
|
height: MediaQuery.sizeOf(context).height * 0.0365,
|
||||||
|
padding: const EdgeInsetsDirectional.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
horizontal: 12,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadiusDirectional.circular(8),
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Row(
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
radius: 4,
|
||||||
|
backgroundColor: device.color,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
device.deviceName,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class PowerClampEnergyDataDeviceDropdown extends StatelessWidget {
|
||||||
|
const PowerClampEnergyDataDeviceDropdown({super.key});
|
||||||
|
|
||||||
|
static final _color = ColorsManager.blackColor.withValues(alpha: 0.8);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: _color,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
side: const BorderSide(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
backgroundColor: ColorsManager.transparentColor,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 32,
|
||||||
|
vertical: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Device 1',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,142 @@
|
|||||||
|
import 'package:flutter/material.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/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/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_phases_data_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
|
class PowerClampEnergyDataWidget extends StatelessWidget {
|
||||||
|
const PowerClampEnergyDataWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocListener<RealtimeDeviceChangesBloc, RealtimeDeviceChangesState>(
|
||||||
|
listenWhen: (previous, current) =>
|
||||||
|
previous.deviceStatusList != current.deviceStatusList ||
|
||||||
|
previous.status != current.status,
|
||||||
|
listener: (context, state) => context.read<PowerClampInfoBloc>().add(
|
||||||
|
UpdatePowerClampStatusEvent(state.deviceStatusList),
|
||||||
|
),
|
||||||
|
child: BlocBuilder<PowerClampInfoBloc, PowerClampInfoState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final generalDataPoints =
|
||||||
|
state.powerClampModel?.status.general.dataPoints ?? [];
|
||||||
|
return Container(
|
||||||
|
decoration: subSectionContainerDecoration.copyWith(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsetsDirectional.all(32),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
AnalyticsErrorWidget(state.errorMessage),
|
||||||
|
_buildHeader(context),
|
||||||
|
Text(
|
||||||
|
'Device ID:',
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.textPrimaryColor,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
SelectableText(
|
||||||
|
state.powerClampModel?.productUuid ?? 'N/A',
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: PowerClampEnergyStatusWidget(
|
||||||
|
status: [
|
||||||
|
PowerClampEnergyStatus(
|
||||||
|
iconPath: Assets.powerActiveIcon,
|
||||||
|
title: 'Active',
|
||||||
|
value: _valueFromCode('EnergyConsumed', generalDataPoints),
|
||||||
|
unit: 'W',
|
||||||
|
),
|
||||||
|
PowerClampEnergyStatus(
|
||||||
|
iconPath: Assets.voltMeterIcon,
|
||||||
|
title: 'Current',
|
||||||
|
value: _valueFromCode('Current', generalDataPoints),
|
||||||
|
unit: 'A',
|
||||||
|
),
|
||||||
|
PowerClampEnergyStatus(
|
||||||
|
iconPath: Assets.frequencyIcon,
|
||||||
|
title: 'Frequency',
|
||||||
|
value: _valueFromCode('Frequency', generalDataPoints),
|
||||||
|
unit: 'Hz',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
Expanded(
|
||||||
|
flex: 4,
|
||||||
|
child: PowerClampPhasesDataWidget(
|
||||||
|
phaseA: state.powerClampModel?.status.phaseA,
|
||||||
|
phaseB: state.powerClampModel?.status.phaseB,
|
||||||
|
phaseC: state.powerClampModel?.status.phaseC,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
const Expanded(flex: 3, child: EnergyConsumptionByPhasesChartBox()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHeader(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
child: SelectableText(
|
||||||
|
'Smart Power Clamp',
|
||||||
|
style: context.textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: ColorsManager.vividBlue.withValues(alpha: 0.6),
|
||||||
|
fontSize: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
const Expanded(
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
|
child: PowerClampEnergyDataDeviceDropdown(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _valueFromCode(String code, List<DataPoint> points) {
|
||||||
|
return points
|
||||||
|
.firstWhere((e) => e.code == code, orElse: () => DataPoint(value: '--'))
|
||||||
|
.value
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
|
class PowerClampEnergyStatusWidget extends StatelessWidget {
|
||||||
|
const PowerClampEnergyStatusWidget({
|
||||||
|
super.key,
|
||||||
|
required this.status,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<PowerClampEnergyStatus> status;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
decoration: secondarySection.copyWith(boxShadow: const []),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: List.generate(
|
||||||
|
status.length * 2 - 1,
|
||||||
|
(index) => index.isEven
|
||||||
|
? Expanded(child: _buildItem(context, status[index ~/ 2]))
|
||||||
|
: _buildDivider(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildItem(BuildContext context, PowerClampEnergyStatus item) {
|
||||||
|
return Center(
|
||||||
|
child: ListTile(
|
||||||
|
titleAlignment: ListTileTitleAlignment.center,
|
||||||
|
leading: SvgPicture.asset(
|
||||||
|
item.iconPath,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
|
height: 18,
|
||||||
|
width: 18,
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
title: Text(
|
||||||
|
item.title,
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.textPrimaryColor.withValues(alpha: 0.83),
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.textPrimaryColor.withValues(alpha: 0.83),
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
TextSpan(text: '${item.value} '),
|
||||||
|
TextSpan(
|
||||||
|
text: item.unit,
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.textPrimaryColor.withValues(alpha: 0.83),
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDivider() {
|
||||||
|
return Container(
|
||||||
|
height: 1,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Color.fromARGB(20, 0, 0, 0),
|
||||||
|
offset: Offset(0, 1),
|
||||||
|
blurRadius: 1,
|
||||||
|
),
|
||||||
|
BoxShadow(
|
||||||
|
color: Color.fromARGB(30, 0, 0, 0),
|
||||||
|
offset: Offset(0, -2),
|
||||||
|
blurRadius: 3,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
|
class PowerClampPhase extends StatelessWidget {
|
||||||
|
const PowerClampPhase({
|
||||||
|
super.key,
|
||||||
|
required this.iconPath,
|
||||||
|
required this.title,
|
||||||
|
required this.value,
|
||||||
|
this.unit,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String iconPath;
|
||||||
|
final String title;
|
||||||
|
final String value;
|
||||||
|
final String? unit;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Expanded(
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 4),
|
||||||
|
decoration: containerWhiteDecoration.copyWith(boxShadow: const []),
|
||||||
|
padding: const EdgeInsetsDirectional.all(8),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
spacing: 10,
|
||||||
|
children: [
|
||||||
|
_buildIcon(),
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
_buildTitle(context),
|
||||||
|
_buildValue(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildValue(BuildContext context) {
|
||||||
|
final textStyle = context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.textPrimaryColor.withValues(
|
||||||
|
alpha: 0.83,
|
||||||
|
),
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 15,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: FittedBox(
|
||||||
|
alignment: AlignmentDirectional.topCenter,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
style: textStyle,
|
||||||
|
children: [
|
||||||
|
TextSpan(text: '$value '),
|
||||||
|
if (unit != null)
|
||||||
|
TextSpan(
|
||||||
|
text: unit,
|
||||||
|
style: textStyle?.copyWith(
|
||||||
|
color: ColorsManager.textPrimaryColor.withValues(
|
||||||
|
alpha: 0.83,
|
||||||
|
),
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTitle(BuildContext context) {
|
||||||
|
return Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: FittedBox(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: context.textTheme.titleSmall?.copyWith(
|
||||||
|
color: ColorsManager.lightGreyColor,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildIcon() {
|
||||||
|
return Expanded(
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: SvgPicture.asset(iconPath),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,131 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_phase.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
|
class PowerClampPhasesDataWidget extends StatelessWidget {
|
||||||
|
const PowerClampPhasesDataWidget({
|
||||||
|
required this.phaseA,
|
||||||
|
required this.phaseB,
|
||||||
|
required this.phaseC,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Phase? phaseA;
|
||||||
|
final Phase? phaseB;
|
||||||
|
final Phase? phaseC;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final phases = [phaseA, phaseB, phaseC];
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: secondarySection.copyWith(boxShadow: const []),
|
||||||
|
child: Row(
|
||||||
|
children: List.generate(5, (index) {
|
||||||
|
if (index.isOdd) return _buildSeparator();
|
||||||
|
final phaseIndex = index ~/ 2;
|
||||||
|
final phase = phases[phaseIndex];
|
||||||
|
final phaseSuffix = ['A', 'B', 'C'][phaseIndex];
|
||||||
|
|
||||||
|
return Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsetsDirectional.symmetric(horizontal: 14),
|
||||||
|
child: Column(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.center,
|
||||||
|
child: Text(
|
||||||
|
'Phase ${phaseIndex + 1}',
|
||||||
|
style: context.textTheme.titleLarge?.copyWith(
|
||||||
|
color: ColorsManager.textPrimaryColor,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PowerClampPhase(
|
||||||
|
iconPath: Assets.powerActiveIcon,
|
||||||
|
title: 'Active Power',
|
||||||
|
value: _valueFromCode(
|
||||||
|
code: 'ReactivePower$phaseSuffix',
|
||||||
|
points: phase?.dataPoints,
|
||||||
|
),
|
||||||
|
unit: 'W',
|
||||||
|
),
|
||||||
|
PowerClampPhase(
|
||||||
|
iconPath: Assets.voltageIcon,
|
||||||
|
title: 'Voltage',
|
||||||
|
value: _valueFromCode(
|
||||||
|
code: 'Voltage$phaseSuffix',
|
||||||
|
points: phase?.dataPoints,
|
||||||
|
),
|
||||||
|
unit: 'V',
|
||||||
|
),
|
||||||
|
PowerClampPhase(
|
||||||
|
iconPath: Assets.voltMeterIcon,
|
||||||
|
title: 'Current',
|
||||||
|
value: _valueFromCode(
|
||||||
|
code: 'Current$phaseSuffix',
|
||||||
|
points: phase?.dataPoints,
|
||||||
|
),
|
||||||
|
unit: 'A',
|
||||||
|
),
|
||||||
|
PowerClampPhase(
|
||||||
|
iconPath: Assets.speedoMeter,
|
||||||
|
title: 'Power Factor',
|
||||||
|
value: _valueFromCode(
|
||||||
|
code: 'PowerFactor$phaseSuffix',
|
||||||
|
points: phase?.dataPoints,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSeparator() {
|
||||||
|
return Container(
|
||||||
|
height: double.infinity,
|
||||||
|
width: 1,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Color.fromARGB(20, 0, 0, 0),
|
||||||
|
offset: Offset(1, 0),
|
||||||
|
blurRadius: 1,
|
||||||
|
),
|
||||||
|
BoxShadow(
|
||||||
|
color: Color.fromARGB(30, 0, 0, 0),
|
||||||
|
offset: Offset(-2, 0),
|
||||||
|
blurRadius: 1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _valueFromCode({
|
||||||
|
required String code,
|
||||||
|
required List<DataPoint>? points,
|
||||||
|
}) {
|
||||||
|
final element = points?.firstWhere(
|
||||||
|
(e) => e.code == code,
|
||||||
|
orElse: () => DataPoint(value: '--'),
|
||||||
|
);
|
||||||
|
|
||||||
|
return element?.value.toString() ?? '--';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
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/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 {
|
||||||
|
const TotalEnergyConsumptionChart({required this.chartData, super.key});
|
||||||
|
|
||||||
|
final List<EnergyDataModel> chartData;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Expanded(
|
||||||
|
child: LineChart(
|
||||||
|
LineChartData(
|
||||||
|
titlesData: EnergyManagementChartsHelper.titlesData(
|
||||||
|
context,
|
||||||
|
leftTitlesInterval: 5000,
|
||||||
|
),
|
||||||
|
gridData: EnergyManagementChartsHelper.gridData(),
|
||||||
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
|
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
|
||||||
|
lineBarsData: _lineBarsData,
|
||||||
|
),
|
||||||
|
duration: Durations.extralong1,
|
||||||
|
curve: Curves.easeIn,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<LineChartBarData> get _lineBarsData {
|
||||||
|
return [
|
||||||
|
LineChartBarData(
|
||||||
|
preventCurveOvershootingThreshold: 0.1,
|
||||||
|
curveSmoothness: 0.55,
|
||||||
|
preventCurveOverShooting: true,
|
||||||
|
spots: chartData
|
||||||
|
.asMap()
|
||||||
|
.entries
|
||||||
|
.map(
|
||||||
|
(entry) => FlSpot(
|
||||||
|
entry.key.toDouble(),
|
||||||
|
entry.value.value,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
color: ColorsManager.blueColor.withValues(alpha: 0.6),
|
||||||
|
shadow: const Shadow(color: Colors.black12),
|
||||||
|
show: true,
|
||||||
|
isCurved: true,
|
||||||
|
belowBarData: BarAreaData(
|
||||||
|
show: true,
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
ColorsManager.vividBlue.withValues(alpha: 0.3),
|
||||||
|
ColorsManager.vividBlue.withValues(alpha: 0.2),
|
||||||
|
ColorsManager.vividBlue.withValues(alpha: 0.1),
|
||||||
|
Colors.transparent,
|
||||||
|
],
|
||||||
|
begin: Alignment.center,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dotData: const FlDotData(show: false),
|
||||||
|
isStrokeCapRound: true,
|
||||||
|
barWidth: 3,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_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/widgets/chart_title.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
|
||||||
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
|
class TotalEnergyConsumptionChartBox extends StatelessWidget {
|
||||||
|
const TotalEnergyConsumptionChartBox({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<TotalEnergyConsumptionBloc, TotalEnergyConsumptionState>(
|
||||||
|
builder: (context, state) => Container(
|
||||||
|
decoration: subSectionContainerDecoration.copyWith(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(30),
|
||||||
|
child: Column(
|
||||||
|
spacing: 20,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
AnalyticsErrorWidget(state.errorMessage),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
ChartsLoadingWidget(
|
||||||
|
isLoading: state.status == TotalEnergyConsumptionStatus.loading,
|
||||||
|
),
|
||||||
|
const Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: FittedBox(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: ChartTitle(title: Text('Total Energy Consumption')),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(flex: 4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
TotalEnergyConsumptionChart(chartData: state.chartData),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AnalyticsOccupancyView extends StatelessWidget {
|
||||||
|
const AnalyticsOccupancyView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Center(
|
||||||
|
child: Text('AnalyticsOccupancyView is Working!'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
class GetEnergyConsumptionByPhasesParam extends Equatable {
|
||||||
|
final DateTime? startDate;
|
||||||
|
final DateTime? endDate;
|
||||||
|
final String? spaceId;
|
||||||
|
|
||||||
|
const GetEnergyConsumptionByPhasesParam({
|
||||||
|
this.startDate,
|
||||||
|
this.endDate,
|
||||||
|
this.spaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'startDate': startDate?.toIso8601String(),
|
||||||
|
'endDate': endDate?.toIso8601String(),
|
||||||
|
'spaceId': spaceId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [startDate, endDate, spaceId];
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
class GetEnergyConsumptionPerDeviceParam {
|
||||||
|
const GetEnergyConsumptionPerDeviceParam();
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
class GetTotalEnergyConsumptionParam {
|
||||||
|
final DateTime? startDate;
|
||||||
|
final DateTime? endDate;
|
||||||
|
final String? spaceId;
|
||||||
|
|
||||||
|
const GetTotalEnergyConsumptionParam({
|
||||||
|
this.startDate,
|
||||||
|
this.endDate,
|
||||||
|
this.spaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'startDate': startDate?.toIso8601String(),
|
||||||
|
'endDate': endDate?.toIso8601String(),
|
||||||
|
'spaceId': spaceId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
abstract interface class EnergyConsumptionByPhasesService {
|
||||||
|
Future<List<PhasesEnergyConsumption>> load(
|
||||||
|
GetEnergyConsumptionByPhasesParam param,
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
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),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
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';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
|
final class RemoteEnergyConsumptionByPhasesService
|
||||||
|
implements EnergyConsumptionByPhasesService {
|
||||||
|
const RemoteEnergyConsumptionByPhasesService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<PhasesEnergyConsumption>> load(
|
||||||
|
GetEnergyConsumptionByPhasesParam param,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: 'endpoint',
|
||||||
|
showServerMessage: true,
|
||||||
|
expectedResponseModel: (data) {
|
||||||
|
final json = data as Map<String, dynamic>? ?? {};
|
||||||
|
final mappedData = json['data'] as List<dynamic>? ?? [];
|
||||||
|
return mappedData.map((e) {
|
||||||
|
final jsonData = e as Map<String, dynamic>;
|
||||||
|
return PhasesEnergyConsumption.fromJson(jsonData);
|
||||||
|
}).toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load energy consumption per device: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
|
||||||
|
|
||||||
|
abstract interface class EnergyConsumptionPerDeviceService {
|
||||||
|
Future<List<DeviceEnergyDataModel>> load(
|
||||||
|
GetEnergyConsumptionPerDeviceParam param,
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/device_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';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
|
class RemoteEnergyConsumptionPerDeviceService
|
||||||
|
implements EnergyConsumptionPerDeviceService {
|
||||||
|
const RemoteEnergyConsumptionPerDeviceService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<DeviceEnergyDataModel>> load(
|
||||||
|
GetEnergyConsumptionPerDeviceParam param,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: 'endpoint',
|
||||||
|
showServerMessage: true,
|
||||||
|
expectedResponseModel: (data) {
|
||||||
|
final json = data as Map<String, dynamic>? ?? {};
|
||||||
|
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;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load energy consumption per device: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
|
||||||
|
|
||||||
|
abstract interface class PowerClampInfoService {
|
||||||
|
Future<PowerClampModel> getInfo(String deviceId);
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/power_clamp_info_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
|
final class RemotePowerClampInfoService implements PowerClampInfoService {
|
||||||
|
const RemotePowerClampInfoService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PowerClampModel> getInfo(String deviceId) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: '/devices/$deviceId/functions/status',
|
||||||
|
showServerMessage: true,
|
||||||
|
expectedResponseModel: (data) {
|
||||||
|
final json = data as Map<String, Object?>? ?? {};
|
||||||
|
final mappedData = json['data'] as Map<String, Object?>? ?? {};
|
||||||
|
return PowerClampModel.fromJson(mappedData);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to fetch power clamp info: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/realtime_device_service/realtime_device_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
|
|
||||||
|
class FirebaseRealtimeDeviceService implements RealtimeDeviceService {
|
||||||
|
@override
|
||||||
|
Stream<List<Status>> subscribe(String deviceId) {
|
||||||
|
try {
|
||||||
|
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||||
|
|
||||||
|
return ref.onValue.asyncMap((event) {
|
||||||
|
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
||||||
|
|
||||||
|
if (data == null || data['status'] == null) {
|
||||||
|
throw Exception('Invalid data received from Firebase');
|
||||||
|
}
|
||||||
|
|
||||||
|
final statusMap = data['status'] as List<dynamic>;
|
||||||
|
return statusMap.map((status) {
|
||||||
|
if (status is! Map<dynamic, dynamic>) {
|
||||||
|
throw Exception('Invalid status format');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Status(
|
||||||
|
code: status['code']?.toString() ?? '',
|
||||||
|
value: num.tryParse(status['value']?.toString() ?? '0'),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Error subscribing to device status: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
|
|
||||||
|
abstract interface class RealtimeDeviceService {
|
||||||
|
Stream<List<Status>> subscribe(String deviceId);
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/total_energy_consumption_service.dart';
|
||||||
|
|
||||||
|
class FakeTotalEnergyConsumptionService implements TotalEnergyConsumptionService {
|
||||||
|
@override
|
||||||
|
Future<List<EnergyDataModel>> load(
|
||||||
|
GetTotalEnergyConsumptionParam param,
|
||||||
|
) {
|
||||||
|
return Future.value(
|
||||||
|
List.generate(30, (index) {
|
||||||
|
return EnergyDataModel(
|
||||||
|
date: DateTime(2025, 1, index + 1),
|
||||||
|
value: 20000 + (index * 1000) % 5000,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/total_energy_consumption_service.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
|
class RemoteTotalEnergyConsumptionService implements TotalEnergyConsumptionService {
|
||||||
|
const RemoteTotalEnergyConsumptionService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<EnergyDataModel>> load(
|
||||||
|
GetTotalEnergyConsumptionParam param,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: 'endpoint',
|
||||||
|
showServerMessage: true,
|
||||||
|
expectedResponseModel: (data) {
|
||||||
|
final json = data as Map<String, dynamic>? ?? {};
|
||||||
|
final mappedData = json['data'] as List<dynamic>? ?? [];
|
||||||
|
return mappedData.map((e) {
|
||||||
|
final jsonData = e as Map<String, dynamic>;
|
||||||
|
return EnergyDataModel.fromJson(jsonData);
|
||||||
|
}).toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load total energy consumption: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
|
||||||
|
|
||||||
|
abstract interface class TotalEnergyConsumptionService {
|
||||||
|
Future<List<EnergyDataModel>> load(
|
||||||
|
GetTotalEnergyConsumptionParam param,
|
||||||
|
);
|
||||||
|
}
|
26
lib/pages/analytics/widgets/analytics_error_widget.dart
Normal file
26
lib/pages/analytics/widgets/analytics_error_widget.dart
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class AnalyticsErrorWidget extends StatelessWidget {
|
||||||
|
const AnalyticsErrorWidget(this.errorMessage, {super.key});
|
||||||
|
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Visibility(
|
||||||
|
visible: errorMessage != null || (errorMessage?.isNotEmpty ?? false),
|
||||||
|
child: Text(
|
||||||
|
'$errorMessage ?? "Something went wrong"',
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.red,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
25
lib/pages/analytics/widgets/charts_loading_widget.dart
Normal file
25
lib/pages/analytics/widgets/charts_loading_widget.dart
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ChartsLoadingWidget extends StatelessWidget {
|
||||||
|
const ChartsLoadingWidget({
|
||||||
|
required this.isLoading,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
final bool isLoading;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Visibility(
|
||||||
|
visible: isLoading,
|
||||||
|
child: const SizedBox.square(
|
||||||
|
dimension: 16,
|
||||||
|
child: FittedBox(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsetsDirectional.only(end: 8),
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
import 'package:syncrow_web/utils/style.dart';
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
|
|
||||||
class SearchResetButtons extends StatelessWidget {
|
class SearchResetButtons extends StatelessWidget {
|
||||||
const SearchResetButtons({
|
const SearchResetButtons({
|
||||||
@ -17,8 +17,10 @@ class SearchResetButtons extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 25),
|
const SizedBox(height: 25),
|
||||||
|
@ -60,7 +60,15 @@ class _CurrentTempState extends State<CurrentTemp> {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(CurrentTemp oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (oldWidget.tempSet != widget.tempSet) {
|
||||||
|
setState(() {
|
||||||
|
_adjustedValue = _initialAdjustedValue(widget.tempSet);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_debounce?.cancel();
|
_debounce?.cancel();
|
||||||
|
@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_st
|
|||||||
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart';
|
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart';
|
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart';
|
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart';
|
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart';
|
import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_control_view.dart';
|
import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_control_view.dart';
|
||||||
@ -198,6 +199,10 @@ mixin RouteControlsBasedCode {
|
|||||||
return SOSBatchControlView(
|
return SOSBatchControlView(
|
||||||
deviceIds: devices.where((e) => (e.productType == 'SOS')).map((e) => e.uuid!).toList(),
|
deviceIds: devices.where((e) => (e.productType == 'SOS')).map((e) => e.uuid!).toList(),
|
||||||
);
|
);
|
||||||
|
case 'NCPS':
|
||||||
|
return FlushMountedPresenceSensorBatchControlView(
|
||||||
|
devicesIds: devices.where((e) => (e.productType == 'NCPS')).map((e) => e.uuid!).toList(),
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
|
@ -108,7 +108,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
|
|||||||
return DeviceManagementBody(
|
return DeviceManagementBody(
|
||||||
devices: deviceState.filteredDevices);
|
devices: deviceState.filteredDevices);
|
||||||
} else {
|
} else {
|
||||||
return const Center(child: Text('Error fetching Devices'));
|
return const DeviceManagementBody(devices: []);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -72,6 +72,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
|||||||
child: state is DeviceManagementLoading
|
child: state is DeviceManagementLoading
|
||||||
? const Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
: Column(
|
: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: isLargeScreenSize(context)
|
padding: isLargeScreenSize(context)
|
||||||
|
@ -14,29 +14,29 @@ class DeviceSearchFilters extends StatefulWidget {
|
|||||||
|
|
||||||
class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
|
class _DeviceSearchFiltersState extends State<DeviceSearchFilters>
|
||||||
with HelperResponsiveLayout {
|
with HelperResponsiveLayout {
|
||||||
final _unitNameController = TextEditingController();
|
late final TextEditingController _unitNameController;
|
||||||
final _productNameController = TextEditingController();
|
late final TextEditingController _productNameController;
|
||||||
|
|
||||||
List<Widget> get _widgets => [
|
@override
|
||||||
_buildSearchField("Space Name", _unitNameController, 200),
|
void initState() {
|
||||||
_buildSearchField("Device Name / Product Name", _productNameController, 300),
|
_unitNameController = TextEditingController();
|
||||||
_buildSearchResetButtons(),
|
_productNameController = TextEditingController();
|
||||||
];
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (isExtraLargeScreenSize(context)) {
|
|
||||||
return Row(
|
|
||||||
children: _widgets
|
|
||||||
.map((e) => Padding(padding: const EdgeInsets.all(10), child: e))
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Wrap(
|
return Wrap(
|
||||||
|
alignment: WrapAlignment.start,
|
||||||
|
runAlignment: WrapAlignment.start,
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
spacing: 20,
|
spacing: 20,
|
||||||
runSpacing: 10,
|
runSpacing: 10,
|
||||||
children: _widgets,
|
children: [
|
||||||
|
_buildSearchField("Space Name", _unitNameController, 200),
|
||||||
|
_buildSearchField("Device Name / Product Name", _productNameController, 300),
|
||||||
|
_buildSearchResetButtons(),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,8 @@ import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dar
|
|||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||||
|
|
||||||
class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLayout {
|
class CeilingSensorControlsView extends StatelessWidget
|
||||||
|
with HelperResponsiveLayout {
|
||||||
const CeilingSensorControlsView({super.key, required this.device});
|
const CeilingSensorControlsView({super.key, required this.device});
|
||||||
|
|
||||||
final AllDevicesModel device;
|
final AllDevicesModel device;
|
||||||
@ -31,29 +32,35 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
|
|||||||
..add(CeilingInitialEvent(device.uuid ?? '')),
|
..add(CeilingInitialEvent(device.uuid ?? '')),
|
||||||
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
|
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is CeilingLoadingInitialState || state is CeilingReportsLoadingState) {
|
if (state is CeilingLoadingInitialState ||
|
||||||
|
state is CeilingReportsLoadingState) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
} else if (state is CeilingUpdateState) {
|
} else if (state is CeilingUpdateState) {
|
||||||
return _buildGridView(
|
return _buildGridView(context, state.ceilingSensorModel,
|
||||||
context, state.ceilingSensorModel, isExtraLarge, isLarge, isMedium);
|
isExtraLarge, isLarge, isMedium);
|
||||||
} else if (state is CeilingReportsState) {
|
} else if (state is CeilingReportsState) {
|
||||||
return ReportsTable(
|
return ReportsTable(
|
||||||
report: state.deviceReport,
|
report: state.deviceReport,
|
||||||
onRowTap: (index) {},
|
onRowTap: (index) {},
|
||||||
onClose: () {
|
onClose: () {
|
||||||
context.read<CeilingSensorBloc>().add(BackToCeilingGridViewEvent());
|
context
|
||||||
|
.read<CeilingSensorBloc>()
|
||||||
|
.add(BackToCeilingGridViewEvent());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else if (state is ShowCeilingDescriptionState) {
|
} else if (state is ShowCeilingDescriptionState) {
|
||||||
return DescriptionView(
|
return DescriptionView(
|
||||||
description: state.description,
|
description: state.description,
|
||||||
onClose: () {
|
onClose: () {
|
||||||
context.read<CeilingSensorBloc>().add(BackToCeilingGridViewEvent());
|
context
|
||||||
|
.read<CeilingSensorBloc>()
|
||||||
|
.add(BackToCeilingGridViewEvent());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else if (state is CeilingReportsFailedState) {
|
} else if (state is CeilingReportsFailedState) {
|
||||||
final model = context.read<CeilingSensorBloc>().deviceStatus;
|
final model = context.read<CeilingSensorBloc>().deviceStatus;
|
||||||
return _buildGridView(context, model, isExtraLarge, isLarge, isMedium);
|
return _buildGridView(
|
||||||
|
context, model, isExtraLarge, isLarge, isMedium);
|
||||||
}
|
}
|
||||||
return const Center(child: Text('Error fetching status'));
|
return const Center(child: Text('Error fetching status'));
|
||||||
},
|
},
|
||||||
@ -61,8 +68,8 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildGridView(BuildContext context, CeilingSensorModel model, bool isExtraLarge,
|
Widget _buildGridView(BuildContext context, CeilingSensorModel model,
|
||||||
bool isLarge, bool isMedium) {
|
bool isExtraLarge, bool isLarge, bool isMedium) {
|
||||||
return GridView(
|
return GridView(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
@ -143,8 +150,8 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
|
|||||||
),
|
),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.read<CeilingSensorBloc>().add(
|
context.read<CeilingSensorBloc>().add(GetCeilingDeviceReportsEvent(
|
||||||
GetCeilingDeviceReportsEvent(code: 'presence_state', deviceUuid: device.uuid!));
|
code: 'presence_state', deviceUuid: device.uuid!));
|
||||||
},
|
},
|
||||||
child: const PresenceStaticWidget(
|
child: const PresenceStaticWidget(
|
||||||
icon: Assets.illuminanceRecordIcon,
|
icon: Assets.illuminanceRecordIcon,
|
||||||
@ -153,9 +160,8 @@ class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLay
|
|||||||
),
|
),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context
|
context.read<CeilingSensorBloc>().add(GetCeilingDeviceReportsEvent(
|
||||||
.read<CeilingSensorBloc>()
|
code: '', deviceUuid: device.uuid!));
|
||||||
.add(GetCeilingDeviceReportsEvent(code: '', deviceUuid: device.uuid!));
|
|
||||||
},
|
},
|
||||||
child: const PresenceStaticWidget(
|
child: const PresenceStaticWidget(
|
||||||
icon: Assets.helpDescriptionIcon,
|
icon: Assets.helpDescriptionIcon,
|
||||||
|
@ -49,6 +49,9 @@ class FlushMountedPresenceSensorBloc
|
|||||||
on<FlushMountedPresenceSensorFactoryResetEvent>(
|
on<FlushMountedPresenceSensorFactoryResetEvent>(
|
||||||
_onFlushMountedPresenceSensorFactoryResetEvent,
|
_onFlushMountedPresenceSensorFactoryResetEvent,
|
||||||
);
|
);
|
||||||
|
on<FlushMountedPresenceSensorStatusUpdatedEvent>(
|
||||||
|
_onFlushMountedPresenceSensorStatusUpdatedEvent,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onFlushMountedPresenceSensorFetchStatusEvent(
|
void _onFlushMountedPresenceSensorFetchStatusEvent(
|
||||||
@ -60,7 +63,7 @@ class FlushMountedPresenceSensorBloc
|
|||||||
final response = await DevicesManagementApi().getDeviceStatus(deviceId);
|
final response = await DevicesManagementApi().getDeviceStatus(deviceId);
|
||||||
deviceStatus = FlushMountedPresenceSensorModel.fromJson(response.status);
|
deviceStatus = FlushMountedPresenceSensorModel.fromJson(response.status);
|
||||||
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
|
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
|
||||||
_listenToChanges(emit, deviceId);
|
_listenToChanges(deviceId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(FlushMountedPresenceSensorFailedState(error: e.toString()));
|
emit(FlushMountedPresenceSensorFailedState(error: e.toString()));
|
||||||
return;
|
return;
|
||||||
@ -81,31 +84,33 @@ class FlushMountedPresenceSensorBloc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _listenToChanges(
|
void _listenToChanges(String deviceId) {
|
||||||
Emitter<FlushMountedPresenceSensorState> emit,
|
try {
|
||||||
String deviceId,
|
final ref = FirebaseDatabase.instance.ref(
|
||||||
) async {
|
'device-status/$deviceId',
|
||||||
final ref = FirebaseDatabase.instance.ref(
|
);
|
||||||
'device-status/$deviceId',
|
|
||||||
);
|
ref.onValue.listen((event) {
|
||||||
|
final eventsMap = event.snapshot.value as Map<dynamic, dynamic>;
|
||||||
|
|
||||||
await ref.onValue.listen(
|
|
||||||
(DatabaseEvent event) async {
|
|
||||||
Map<dynamic, dynamic> usersMap =
|
|
||||||
event.snapshot.value as Map<dynamic, dynamic>;
|
|
||||||
List<Status> statusList = [];
|
List<Status> statusList = [];
|
||||||
|
eventsMap['status'].forEach((element) {
|
||||||
(usersMap['status'] as List<dynamic>?)?.forEach((element) {
|
statusList.add(
|
||||||
statusList.add(Status(code: element['code'], value: element['value']));
|
Status(code: element['code'], value: element['value']),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
deviceStatus = FlushMountedPresenceSensorModel.fromJson(statusList);
|
deviceStatus = FlushMountedPresenceSensorModel.fromJson(statusList);
|
||||||
if (!emit.isDone) {
|
if (!isClosed) {
|
||||||
emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus));
|
add(FlushMountedPresenceSensorStatusUpdatedEvent(deviceStatus));
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
onError: (error) => log(error.toString(), name: 'FirebaseDatabaseError'),
|
} catch (_) {
|
||||||
).asFuture();
|
log(
|
||||||
|
'Error listening to changes',
|
||||||
|
name: 'FlushMountedPresenceSensorBloc._listenToChanges',
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onFlushMountedPresenceSensorChangeValueEvent(
|
void _onFlushMountedPresenceSensorChangeValueEvent(
|
||||||
@ -234,4 +239,12 @@ class FlushMountedPresenceSensorBloc
|
|||||||
emit(FlushMountedPresenceSensorFailedState(error: e.toString()));
|
emit(FlushMountedPresenceSensorFailedState(error: e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onFlushMountedPresenceSensorStatusUpdatedEvent(
|
||||||
|
FlushMountedPresenceSensorStatusUpdatedEvent event,
|
||||||
|
Emitter<FlushMountedPresenceSensorState> emit,
|
||||||
|
) {
|
||||||
|
deviceStatus = event.model;
|
||||||
|
emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,15 +10,24 @@ sealed class FlushMountedPresenceSensorEvent extends Equatable {
|
|||||||
class FlushMountedPresenceSensorFetchStatusEvent
|
class FlushMountedPresenceSensorFetchStatusEvent
|
||||||
extends FlushMountedPresenceSensorEvent {}
|
extends FlushMountedPresenceSensorEvent {}
|
||||||
|
|
||||||
|
class FlushMountedPresenceSensorStatusUpdatedEvent
|
||||||
|
extends FlushMountedPresenceSensorEvent {
|
||||||
|
const FlushMountedPresenceSensorStatusUpdatedEvent(this.model);
|
||||||
|
|
||||||
|
final FlushMountedPresenceSensorModel model;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [model];
|
||||||
|
}
|
||||||
|
|
||||||
class FlushMountedPresenceSensorChangeValueEvent
|
class FlushMountedPresenceSensorChangeValueEvent
|
||||||
extends FlushMountedPresenceSensorEvent {
|
extends FlushMountedPresenceSensorEvent {
|
||||||
final int value;
|
final int value;
|
||||||
final String code;
|
final String code;
|
||||||
final bool isBatchControl;
|
|
||||||
const FlushMountedPresenceSensorChangeValueEvent({
|
const FlushMountedPresenceSensorChangeValueEvent({
|
||||||
required this.value,
|
required this.value,
|
||||||
required this.code,
|
required this.code,
|
||||||
this.isBatchControl = false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -16,6 +16,6 @@ abstract final class FlushMountedPresenceSensorBlocFactory {
|
|||||||
batchControlDevicesService: DebouncedBatchControlDevicesService(
|
batchControlDevicesService: DebouncedBatchControlDevicesService(
|
||||||
decoratee: RemoteBatchControlDevicesService(),
|
decoratee: RemoteBatchControlDevicesService(),
|
||||||
),
|
),
|
||||||
)..add(FlushMountedPresenceSensorFetchStatusEvent());
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
|
|||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => FlushMountedPresenceSensorBlocFactory.create(
|
create: (context) => FlushMountedPresenceSensorBlocFactory.create(
|
||||||
deviceId: devicesIds.first,
|
deviceId: devicesIds.first,
|
||||||
),
|
)..add(FlushMountedPresenceSensorFetchBatchStatusEvent(devicesIds)),
|
||||||
child: BlocBuilder<FlushMountedPresenceSensorBloc,
|
child: BlocBuilder<FlushMountedPresenceSensorBloc,
|
||||||
FlushMountedPresenceSensorState>(
|
FlushMountedPresenceSensorState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
@ -67,14 +67,15 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
|
|||||||
maxValue: 9,
|
maxValue: 9,
|
||||||
steps: 1,
|
steps: 1,
|
||||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||||
FlushMountedPresenceSensorChangeValueEvent(
|
FlushMountedPresenceSensorBatchControlEvent(
|
||||||
|
deviceIds: devicesIds,
|
||||||
code: FlushMountedPresenceSensorModel.codeSensitivity,
|
code: FlushMountedPresenceSensorModel.codeSensitivity,
|
||||||
value: value,
|
value: value,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PresenceUpdateData(
|
PresenceUpdateData(
|
||||||
value: (model.nearDetection / 100).toDouble(),
|
value: (model.nearDetection / 100).clamp(0.0, double.infinity),
|
||||||
title: 'Nearest Detect Dist:',
|
title: 'Nearest Detect Dist:',
|
||||||
description: 'm',
|
description: 'm',
|
||||||
minValue: 0.0,
|
minValue: 0.0,
|
||||||
@ -83,14 +84,15 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
|
|||||||
valuesPercision: 1,
|
valuesPercision: 1,
|
||||||
action: (double value) =>
|
action: (double value) =>
|
||||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||||
FlushMountedPresenceSensorChangeValueEvent(
|
FlushMountedPresenceSensorBatchControlEvent(
|
||||||
|
deviceIds: devicesIds,
|
||||||
code: FlushMountedPresenceSensorModel.codeNearDetection,
|
code: FlushMountedPresenceSensorModel.codeNearDetection,
|
||||||
value: (value * 100).toInt(),
|
value: (value * 100).toInt(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PresenceUpdateData(
|
PresenceUpdateData(
|
||||||
value: (model.farDetection / 100).toDouble(),
|
value: (model.farDetection / 100).clamp(0.0, double.infinity),
|
||||||
title: 'Max Detect Dist:',
|
title: 'Max Detect Dist:',
|
||||||
description: 'm',
|
description: 'm',
|
||||||
minValue: 0.0,
|
minValue: 0.0,
|
||||||
@ -99,51 +101,57 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
|
|||||||
valuesPercision: 1,
|
valuesPercision: 1,
|
||||||
action: (double value) =>
|
action: (double value) =>
|
||||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||||
FlushMountedPresenceSensorChangeValueEvent(
|
FlushMountedPresenceSensorBatchControlEvent(
|
||||||
|
deviceIds: devicesIds,
|
||||||
code: FlushMountedPresenceSensorModel.codeFarDetection,
|
code: FlushMountedPresenceSensorModel.codeFarDetection,
|
||||||
value: (value * 100).toInt(),
|
value: (value * 100).toInt(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PresenceUpdateData(
|
PresenceUpdateData(
|
||||||
value: model.presenceDelay.toDouble(),
|
value: model.sensiReduce.toDouble(),
|
||||||
title: 'Trigger Level:',
|
title: 'Trigger Level:',
|
||||||
minValue: 0,
|
minValue: 0,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
steps: 1,
|
steps: 1,
|
||||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||||
FlushMountedPresenceSensorChangeValueEvent(
|
FlushMountedPresenceSensorBatchControlEvent(
|
||||||
code: FlushMountedPresenceSensorModel.codePresenceDelay,
|
deviceIds: devicesIds,
|
||||||
|
code: FlushMountedPresenceSensorModel.codeSensiReduce,
|
||||||
value: value,
|
value: value,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PresenceUpdateData(
|
PresenceUpdateData(
|
||||||
value: (model.occurDistReduce.toDouble()),
|
value: model.occurDistReduce.toDouble(),
|
||||||
title: 'Indent Level:',
|
title: 'Indent Level:',
|
||||||
minValue: 0,
|
minValue: 0,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
steps: 1,
|
steps: 1,
|
||||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||||
FlushMountedPresenceSensorChangeValueEvent(
|
FlushMountedPresenceSensorBatchControlEvent(
|
||||||
|
deviceIds: devicesIds,
|
||||||
code: FlushMountedPresenceSensorModel.codeOccurDistReduce,
|
code: FlushMountedPresenceSensorModel.codeOccurDistReduce,
|
||||||
value: value,
|
value: value,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PresenceUpdateData(
|
PresenceUpdateData(
|
||||||
value: (model.sensiReduce.toDouble()),
|
value: (model.presenceDelay / 10).toDouble(),
|
||||||
title: 'Target Confirm Time:',
|
title: 'Target Confirm Time:',
|
||||||
description: 's',
|
description: 's',
|
||||||
minValue: 0,
|
minValue: 0.0,
|
||||||
maxValue: 3,
|
maxValue: 0.5,
|
||||||
steps: 1,
|
steps: 0.1,
|
||||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
valuesPercision: 1,
|
||||||
FlushMountedPresenceSensorChangeValueEvent(
|
action: (double value) =>
|
||||||
code: FlushMountedPresenceSensorModel.codeSensiReduce,
|
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||||
value: value,
|
FlushMountedPresenceSensorBatchControlEvent(
|
||||||
),
|
deviceIds: devicesIds,
|
||||||
),
|
code: FlushMountedPresenceSensorModel.codePresenceDelay,
|
||||||
|
value: (value * 10).toInt(),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
PresenceUpdateData(
|
PresenceUpdateData(
|
||||||
value: ((model.noneDelay / 10).toDouble()),
|
value: ((model.noneDelay / 10).toDouble()),
|
||||||
@ -154,7 +162,8 @@ class FlushMountedPresenceSensorBatchControlView extends StatelessWidget
|
|||||||
steps: 1,
|
steps: 1,
|
||||||
action: (double value) =>
|
action: (double value) =>
|
||||||
context.read<FlushMountedPresenceSensorBloc>().add(
|
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||||
FlushMountedPresenceSensorChangeValueEvent(
|
FlushMountedPresenceSensorBatchControlEvent(
|
||||||
|
deviceIds: devicesIds,
|
||||||
code: FlushMountedPresenceSensorModel.codeNoneDelay,
|
code: FlushMountedPresenceSensorModel.codeNoneDelay,
|
||||||
value: (value * 10).round(),
|
value: (value * 10).round(),
|
||||||
),
|
),
|
||||||
|
@ -15,7 +15,7 @@ import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_la
|
|||||||
|
|
||||||
class FlushMountedPresenceSensorControlView extends StatelessWidget
|
class FlushMountedPresenceSensorControlView extends StatelessWidget
|
||||||
with HelperResponsiveLayout {
|
with HelperResponsiveLayout {
|
||||||
const FlushMountedPresenceSensorControlView({super.key, required this.device});
|
const FlushMountedPresenceSensorControlView({required this.device, super.key});
|
||||||
|
|
||||||
final AllDevicesModel device;
|
final AllDevicesModel device;
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget
|
|||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => FlushMountedPresenceSensorBlocFactory.create(
|
create: (context) => FlushMountedPresenceSensorBlocFactory.create(
|
||||||
deviceId: device.uuid ?? '-1',
|
deviceId: device.uuid ?? '-1',
|
||||||
),
|
)..add(FlushMountedPresenceSensorFetchStatusEvent()),
|
||||||
child: BlocBuilder<FlushMountedPresenceSensorBloc,
|
child: BlocBuilder<FlushMountedPresenceSensorBloc,
|
||||||
FlushMountedPresenceSensorState>(
|
FlushMountedPresenceSensorState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
@ -113,7 +113,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
PresenceUpdateData(
|
PresenceUpdateData(
|
||||||
value: (model.nearDetection / 100).toDouble(),
|
value: (model.nearDetection / 100).clamp(0.0, double.infinity),
|
||||||
title: 'Nearest Detect Dist:',
|
title: 'Nearest Detect Dist:',
|
||||||
description: 'm',
|
description: 'm',
|
||||||
minValue: 0.0,
|
minValue: 0.0,
|
||||||
@ -129,7 +129,7 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
PresenceUpdateData(
|
PresenceUpdateData(
|
||||||
value: (model.farDetection / 100).toDouble(),
|
value: (model.farDetection / 100).clamp(0.0, double.infinity),
|
||||||
title: 'Max Detect Dist:',
|
title: 'Max Detect Dist:',
|
||||||
description: 'm',
|
description: 'm',
|
||||||
minValue: 0.0,
|
minValue: 0.0,
|
||||||
@ -145,20 +145,20 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
PresenceUpdateData(
|
PresenceUpdateData(
|
||||||
value: (model.presenceDelay.toDouble()),
|
value: model.sensiReduce.toDouble(),
|
||||||
title: 'Trigger Level:',
|
title: 'Trigger Level:',
|
||||||
minValue: 0,
|
minValue: 0,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
steps: 1,
|
steps: 1,
|
||||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
||||||
FlushMountedPresenceSensorChangeValueEvent(
|
FlushMountedPresenceSensorChangeValueEvent(
|
||||||
code: FlushMountedPresenceSensorModel.codePresenceDelay,
|
code: FlushMountedPresenceSensorModel.codeSensiReduce,
|
||||||
value: value,
|
value: value,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PresenceUpdateData(
|
PresenceUpdateData(
|
||||||
value: (model.occurDistReduce.toDouble()),
|
value: model.occurDistReduce.toDouble(),
|
||||||
title: 'Indent Level:',
|
title: 'Indent Level:',
|
||||||
minValue: 0,
|
minValue: 0,
|
||||||
maxValue: 3,
|
maxValue: 3,
|
||||||
@ -171,21 +171,23 @@ class FlushMountedPresenceSensorControlView extends StatelessWidget
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
PresenceUpdateData(
|
PresenceUpdateData(
|
||||||
value: (model.sensiReduce.toDouble()),
|
value: (model.presenceDelay / 10).toDouble(),
|
||||||
|
valuesPercision: 1,
|
||||||
title: 'Target Confirm Time:',
|
title: 'Target Confirm Time:',
|
||||||
description: 's',
|
description: 's',
|
||||||
minValue: 0,
|
minValue: 0.0,
|
||||||
maxValue: 3,
|
maxValue: 0.5,
|
||||||
steps: 1,
|
steps: 0.1,
|
||||||
action: (int value) => context.read<FlushMountedPresenceSensorBloc>().add(
|
action: (double value) =>
|
||||||
FlushMountedPresenceSensorChangeValueEvent(
|
context.read<FlushMountedPresenceSensorBloc>().add(
|
||||||
code: FlushMountedPresenceSensorModel.codeSensiReduce,
|
FlushMountedPresenceSensorChangeValueEvent(
|
||||||
value: value,
|
code: FlushMountedPresenceSensorModel.codePresenceDelay,
|
||||||
),
|
value: (value * 10).toInt(),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
PresenceUpdateData(
|
PresenceUpdateData(
|
||||||
value: ((model.noneDelay / 10).toDouble()),
|
value: (model.noneDelay / 10).toDouble(),
|
||||||
description: 's',
|
description: 's',
|
||||||
title: 'Disappe Delay:',
|
title: 'Disappe Delay:',
|
||||||
minValue: 20,
|
minValue: 20,
|
||||||
|
@ -217,29 +217,31 @@ class SmartPowerBloc extends Bloc<SmartPowerEvent, SmartPowerState> {
|
|||||||
try {
|
try {
|
||||||
var status =
|
var status =
|
||||||
await DevicesManagementApi().getPowerClampInfo(event.deviceId);
|
await DevicesManagementApi().getPowerClampInfo(event.deviceId);
|
||||||
deviceStatus = PowerClampModel.fromJson(status);
|
deviceStatus = PowerClampModel.fromJson(status as Map<String, Object?>? ??{});
|
||||||
|
final phaseADataPoints = deviceStatus.status.phaseA.dataPoints;
|
||||||
|
final phaseBDataPoints = deviceStatus.status.phaseB.dataPoints;
|
||||||
|
final phaseCDataPoints = deviceStatus.status.phaseC.dataPoints;
|
||||||
phaseData = [
|
phaseData = [
|
||||||
{
|
{
|
||||||
'name': 'Phase A',
|
'name': 'Phase A',
|
||||||
'voltage': '${deviceStatus.status.phaseA.dataPoints[0].value / 10} V',
|
'voltage': '${(phaseADataPoints.elementAtOrNull(0)?.value as num? ?? 0) / 10} V',
|
||||||
'current': '${deviceStatus.status.phaseA.dataPoints[1].value / 10} A',
|
'current': '${(phaseADataPoints.elementAtOrNull(1)?.value as num? ?? 0) / 10} A',
|
||||||
'activePower': '${deviceStatus.status.phaseA.dataPoints[2].value} W',
|
'activePower': '${phaseADataPoints.elementAtOrNull(2)?.value??'N/A'} W',
|
||||||
'powerFactor': '${deviceStatus.status.phaseA.dataPoints[3].value}',
|
'powerFactor': '${phaseADataPoints.elementAtOrNull(3)?.value??'N/A'}',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Phase B',
|
'name': 'Phase B',
|
||||||
'voltage': '${deviceStatus.status.phaseB.dataPoints[0].value / 10} V',
|
'voltage': '${(phaseBDataPoints .elementAtOrNull(0)?.value as num? ?? 0) / 10} V',
|
||||||
'current': '${deviceStatus.status.phaseB.dataPoints[1].value / 10} A',
|
'current': '${(phaseBDataPoints .elementAtOrNull(1)?.value as num? ?? 0) / 10} A',
|
||||||
'activePower': '${deviceStatus.status.phaseB.dataPoints[2].value} W',
|
'activePower': '${phaseBDataPoints.elementAtOrNull(2)?.value??'N/A'} W',
|
||||||
'powerFactor': '${deviceStatus.status.phaseB.dataPoints[3].value}',
|
'powerFactor': '${phaseBDataPoints.elementAtOrNull(3)?.value??'N/A'}',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'Phase C',
|
'name': 'Phase C',
|
||||||
'voltage': '${deviceStatus.status.phaseC.dataPoints[0].value / 10} V',
|
'voltage': '${(phaseCDataPoints.elementAtOrNull(0)?.value as num? ?? 0) / 10} V',
|
||||||
'current': '${deviceStatus.status.phaseC.dataPoints[1].value / 10} A',
|
'current': '${(phaseCDataPoints.elementAtOrNull(1)?.value as num? ?? 0) / 10} A',
|
||||||
'activePower': '${deviceStatus.status.phaseC.dataPoints[2].value} W',
|
'activePower': '${phaseCDataPoints.elementAtOrNull(2)?.value ?? 'N/A'} W',
|
||||||
'powerFactor': '${deviceStatus.status.phaseC.dataPoints[3].value}',
|
'powerFactor': '${phaseCDataPoints.elementAtOrNull(3)?.value ?? 'N/A'}',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
emit(GetDeviceStatus());
|
emit(GetDeviceStatus());
|
||||||
@ -785,7 +787,7 @@ class SmartPowerBloc extends Bloc<SmartPowerEvent, SmartPowerState> {
|
|||||||
void selectDateRange() async {
|
void selectDateRange() async {
|
||||||
DateTime startDate = dateTime!;
|
DateTime startDate = dateTime!;
|
||||||
DateTime endDate = DateTime(startDate.year, startDate.month + 1, 1)
|
DateTime endDate = DateTime(startDate.year, startDate.month + 1, 1)
|
||||||
.subtract(Duration(days: 1));
|
.subtract(const Duration(days: 1));
|
||||||
String formattedEndDate = DateFormat('dd/MM/yyyy').format(endDate);
|
String formattedEndDate = DateFormat('dd/MM/yyyy').format(endDate);
|
||||||
endChartDate = ' - $formattedEndDate';
|
endChartDate = ' - $formattedEndDate';
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
// PowerClampModel class to represent the response
|
// PowerClampModel class to represent the response
|
||||||
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
|
|
||||||
class PowerClampModel {
|
class PowerClampModel {
|
||||||
String productUuid;
|
String productUuid;
|
||||||
String productType;
|
String productType;
|
||||||
@ -12,9 +14,9 @@ class PowerClampModel {
|
|||||||
|
|
||||||
factory PowerClampModel.fromJson(Map<String, dynamic> json) {
|
factory PowerClampModel.fromJson(Map<String, dynamic> json) {
|
||||||
return PowerClampModel(
|
return PowerClampModel(
|
||||||
productUuid: json['productUuid'],
|
productUuid: json['productUuid'] as String? ?? '',
|
||||||
productType: json['productType'],
|
productType: json['productType'] as String? ?? '',
|
||||||
status: PowerStatus.fromJson(json['status']),
|
status: PowerStatus.fromJson(json['status'] as Map<String, dynamic>? ?? {}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +28,7 @@ class PowerClampModel {
|
|||||||
return PowerClampModel(
|
return PowerClampModel(
|
||||||
productUuid: productUuid ?? this.productUuid,
|
productUuid: productUuid ?? this.productUuid,
|
||||||
productType: productType ?? this.productType,
|
productType: productType ?? this.productType,
|
||||||
status: statusPower ?? this.status,
|
status: statusPower ?? status,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,13 +48,27 @@ class PowerStatus {
|
|||||||
|
|
||||||
factory PowerStatus.fromJson(Map<String, dynamic> json) {
|
factory PowerStatus.fromJson(Map<String, dynamic> json) {
|
||||||
return PowerStatus(
|
return PowerStatus(
|
||||||
phaseA: Phase.fromJson(json['phaseA']),
|
phaseA: Phase.fromJson(json['phaseA'] as List<dynamic>? ?? []),
|
||||||
phaseB: Phase.fromJson(json['phaseB']),
|
phaseB: Phase.fromJson(json['phaseB'] as List<dynamic>? ?? []),
|
||||||
phaseC: Phase.fromJson(json['phaseC']),
|
phaseC: Phase.fromJson(json['phaseC'] as List<dynamic>? ?? []),
|
||||||
general: Phase.fromJson(json['general']
|
general: Phase.fromJson(json['general'] as List<dynamic>? ?? []),
|
||||||
// List<DataPoint>.from(
|
);
|
||||||
// json['general'].map((x) => DataPoint.fromJson(x))),
|
}
|
||||||
));
|
|
||||||
|
factory PowerStatus.fromStatusList(List<Status> statuses) {
|
||||||
|
List<DataPoint> extractPhase(String prefix) {
|
||||||
|
return statuses
|
||||||
|
.where((s) => s.code.endsWith(prefix))
|
||||||
|
.map((s) => DataPoint(code: s.code, value: s.value))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return PowerStatus(
|
||||||
|
phaseA: Phase(dataPoints: extractPhase('A')),
|
||||||
|
phaseB: Phase(dataPoints: extractPhase('B')),
|
||||||
|
phaseC: Phase(dataPoints: extractPhase('C')),
|
||||||
|
general: Phase(dataPoints: extractPhase('')),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,30 +85,30 @@ class Phase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class DataPoint {
|
class DataPoint {
|
||||||
dynamic code;
|
final String? code;
|
||||||
dynamic customName;
|
final String? customName;
|
||||||
dynamic dpId;
|
final int? dpId;
|
||||||
dynamic time;
|
final int? time;
|
||||||
dynamic type;
|
final String? type;
|
||||||
dynamic value;
|
final dynamic value;
|
||||||
|
|
||||||
DataPoint({
|
DataPoint({
|
||||||
required this.code,
|
this.code,
|
||||||
required this.customName,
|
this.customName,
|
||||||
required this.dpId,
|
this.dpId,
|
||||||
required this.time,
|
this.time,
|
||||||
required this.type,
|
this.type,
|
||||||
required this.value,
|
this.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory DataPoint.fromJson(Map<String, dynamic> json) {
|
factory DataPoint.fromJson(Map<String, dynamic> json) {
|
||||||
return DataPoint(
|
return DataPoint(
|
||||||
code: json['code'],
|
code: json['code'] as String?,
|
||||||
customName: json['customName'],
|
customName: json['customName'] as String?,
|
||||||
dpId: json['dpId'],
|
dpId: json['dpId'] as int?,
|
||||||
time: json['time'],
|
time: json['time'] as int?,
|
||||||
type: json['type'],
|
type: json['type'] as String?,
|
||||||
value: json['value'],
|
value: json['value'] as dynamic,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class EnergyConsumptionPage extends StatefulWidget {
|
class EnergyConsumptionPage extends StatefulWidget {
|
||||||
@ -10,7 +10,8 @@ class EnergyConsumptionPage extends StatefulWidget {
|
|||||||
final Widget widget;
|
final Widget widget;
|
||||||
final Function()? onTap;
|
final Function()? onTap;
|
||||||
|
|
||||||
EnergyConsumptionPage({
|
const EnergyConsumptionPage({
|
||||||
|
super.key,
|
||||||
required this.chartData,
|
required this.chartData,
|
||||||
required this.totalConsumption,
|
required this.totalConsumption,
|
||||||
required this.date,
|
required this.date,
|
||||||
@ -91,11 +92,12 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 10),
|
padding: const EdgeInsets.only(top: 10),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: MediaQuery.of(context).size.height * 0.11,
|
height: MediaQuery.sizeOf(context).height * 0.09,
|
||||||
child: LineChart(
|
child: LineChart(
|
||||||
LineChartData(
|
LineChartData(
|
||||||
lineTouchData: LineTouchData(
|
lineTouchData: LineTouchData(
|
||||||
@ -151,7 +153,7 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
|
|||||||
child: RotatedBox(
|
child: RotatedBox(
|
||||||
quarterTurns: -1,
|
quarterTurns: -1,
|
||||||
child: Text(_chartData[index].time,
|
child: Text(_chartData[index].time,
|
||||||
style: TextStyle(fontSize: 10)),
|
style: const TextStyle(fontSize: 10)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -190,8 +192,8 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
|
|||||||
spots: _chartData
|
spots: _chartData
|
||||||
.asMap()
|
.asMap()
|
||||||
.entries
|
.entries
|
||||||
.map((entry) => FlSpot(entry.key.toDouble(),
|
.map((entry) => FlSpot(
|
||||||
entry.value.consumption))
|
entry.key.toDouble(), entry.value.consumption))
|
||||||
.toList(),
|
.toList(),
|
||||||
isCurved: true,
|
isCurved: true,
|
||||||
color: ColorsManager.primaryColor.withOpacity(0.6),
|
color: ColorsManager.primaryColor.withOpacity(0.6),
|
||||||
@ -218,7 +220,7 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
|
|||||||
borderData: FlBorderData(
|
borderData: FlBorderData(
|
||||||
show: false,
|
show: false,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Color(0xff023DFE).withOpacity(0.7),
|
color: const Color(0xff023DFE).withOpacity(0.7),
|
||||||
width: 10,
|
width: 10,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -253,11 +255,9 @@ class _EnergyConsumptionPageState extends State<EnergyConsumptionPage> {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: widget.onTap,
|
onTap: widget.onTap,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: SizedBox(
|
child: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.all(5),
|
||||||
padding: const EdgeInsets.all(5),
|
child: Text(widget.date),
|
||||||
child: Text(widget.date),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -12,8 +12,7 @@ import 'package:syncrow_web/utils/constants/assets.dart';
|
|||||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||||
|
|
||||||
//Smart Power Clamp
|
//Smart Power Clamp
|
||||||
class SmartPowerDeviceControl extends StatelessWidget
|
class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayout {
|
||||||
with HelperResponsiveLayout {
|
|
||||||
final String deviceId;
|
final String deviceId;
|
||||||
|
|
||||||
const SmartPowerDeviceControl({super.key, required this.deviceId});
|
const SmartPowerDeviceControl({super.key, required this.deviceId});
|
||||||
@ -25,27 +24,27 @@ class SmartPowerDeviceControl extends StatelessWidget
|
|||||||
..add(SmartPowerFetchDeviceEvent(deviceId)),
|
..add(SmartPowerFetchDeviceEvent(deviceId)),
|
||||||
child: BlocBuilder<SmartPowerBloc, SmartPowerState>(
|
child: BlocBuilder<SmartPowerBloc, SmartPowerState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final _blocProvider = BlocProvider.of<SmartPowerBloc>(context);
|
final blocProvider = BlocProvider.of<SmartPowerBloc>(context);
|
||||||
|
|
||||||
if (state is SmartPowerLoading) {
|
if (state is SmartPowerLoading) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
} else if (state is FakeState) {
|
} else if (state is FakeState) {
|
||||||
return _buildStatusControls(
|
return _buildStatusControls(
|
||||||
currentPage: _blocProvider.currentPage,
|
currentPage: blocProvider.currentPage,
|
||||||
context: context,
|
context: context,
|
||||||
blocProvider: _blocProvider,
|
blocProvider: blocProvider,
|
||||||
);
|
);
|
||||||
} else if (state is GetDeviceStatus) {
|
} else if (state is GetDeviceStatus) {
|
||||||
return _buildStatusControls(
|
return _buildStatusControls(
|
||||||
currentPage: _blocProvider.currentPage,
|
currentPage: blocProvider.currentPage,
|
||||||
context: context,
|
context: context,
|
||||||
blocProvider: _blocProvider,
|
blocProvider: blocProvider,
|
||||||
);
|
);
|
||||||
} else if (state is FilterRecordsState) {
|
} else if (state is FilterRecordsState) {
|
||||||
return _buildStatusControls(
|
return _buildStatusControls(
|
||||||
currentPage: _blocProvider.currentPage,
|
currentPage: blocProvider.currentPage,
|
||||||
context: context,
|
context: context,
|
||||||
blocProvider: _blocProvider,
|
blocProvider: blocProvider,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
@ -60,7 +59,7 @@ class SmartPowerDeviceControl extends StatelessWidget
|
|||||||
required SmartPowerBloc blocProvider,
|
required SmartPowerBloc blocProvider,
|
||||||
required int currentPage,
|
required int currentPage,
|
||||||
}) {
|
}) {
|
||||||
PageController _pageController = PageController(initialPage: currentPage);
|
PageController pageController = PageController(initialPage: currentPage);
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
padding: const EdgeInsets.symmetric(horizontal: 50),
|
||||||
child: DeviceControlsContainer(
|
child: DeviceControlsContainer(
|
||||||
@ -85,25 +84,31 @@ class SmartPowerDeviceControl extends StatelessWidget
|
|||||||
PowerClampInfoCard(
|
PowerClampInfoCard(
|
||||||
iconPath: Assets.powerActiveIcon,
|
iconPath: Assets.powerActiveIcon,
|
||||||
title: 'Active',
|
title: 'Active',
|
||||||
value: blocProvider
|
value: blocProvider.deviceStatus.status.general.dataPoints
|
||||||
.deviceStatus.status.general.dataPoints[2].value
|
.elementAtOrNull(2)
|
||||||
.toString(),
|
?.value
|
||||||
|
.toString() ??
|
||||||
|
'',
|
||||||
unit: '',
|
unit: '',
|
||||||
),
|
),
|
||||||
PowerClampInfoCard(
|
PowerClampInfoCard(
|
||||||
iconPath: Assets.voltMeterIcon,
|
iconPath: Assets.voltMeterIcon,
|
||||||
title: 'Current',
|
title: 'Current',
|
||||||
value: blocProvider
|
value: blocProvider.deviceStatus.status.general.dataPoints
|
||||||
.deviceStatus.status.general.dataPoints[1].value
|
.elementAtOrNull(1)
|
||||||
.toString(),
|
?.value
|
||||||
|
.toString() ??
|
||||||
|
'',
|
||||||
unit: ' A',
|
unit: ' A',
|
||||||
),
|
),
|
||||||
PowerClampInfoCard(
|
PowerClampInfoCard(
|
||||||
iconPath: Assets.frequencyIcon,
|
iconPath: Assets.frequencyIcon,
|
||||||
title: 'Frequency',
|
title: 'Frequency',
|
||||||
value: blocProvider
|
value: blocProvider.deviceStatus.status.general.dataPoints
|
||||||
.deviceStatus.status.general.dataPoints[4].value
|
.elementAtOrNull(4)
|
||||||
.toString(),
|
?.value
|
||||||
|
.toString() ??
|
||||||
|
'',
|
||||||
unit: ' Hz',
|
unit: ' Hz',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -142,7 +147,7 @@ class SmartPowerDeviceControl extends StatelessWidget
|
|||||||
icon: const Icon(Icons.arrow_left),
|
icon: const Icon(Icons.arrow_left),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
blocProvider.add(SmartPowerArrowPressedEvent(-1));
|
blocProvider.add(SmartPowerArrowPressedEvent(-1));
|
||||||
_pageController.previousPage(
|
pageController.previousPage(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
);
|
);
|
||||||
@ -162,7 +167,7 @@ class SmartPowerDeviceControl extends StatelessWidget
|
|||||||
icon: const Icon(Icons.arrow_right),
|
icon: const Icon(Icons.arrow_right),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
blocProvider.add(SmartPowerArrowPressedEvent(1));
|
blocProvider.add(SmartPowerArrowPressedEvent(1));
|
||||||
_pageController.nextPage(
|
pageController.nextPage(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
);
|
);
|
||||||
@ -177,7 +182,7 @@ class SmartPowerDeviceControl extends StatelessWidget
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: PageView(
|
child: PageView(
|
||||||
controller: _pageController,
|
controller: pageController,
|
||||||
onPageChanged: (int page) {
|
onPageChanged: (int page) {
|
||||||
blocProvider.add(SmartPowerPageChangedEvent(page));
|
blocProvider.add(SmartPowerPageChangedEvent(page));
|
||||||
},
|
},
|
||||||
@ -190,8 +195,8 @@ class SmartPowerDeviceControl extends StatelessWidget
|
|||||||
blocProvider.add(SelectDateEvent(context: context));
|
blocProvider.add(SelectDateEvent(context: context));
|
||||||
blocProvider.add(FilterRecordsByDateEvent(
|
blocProvider.add(FilterRecordsByDateEvent(
|
||||||
selectedDate: blocProvider.dateTime!,
|
selectedDate: blocProvider.dateTime!,
|
||||||
viewType: blocProvider
|
viewType:
|
||||||
.views[blocProvider.currentIndex]));
|
blocProvider.views[blocProvider.currentIndex]));
|
||||||
},
|
},
|
||||||
widget: blocProvider.dateSwitcher(),
|
widget: blocProvider.dateSwitcher(),
|
||||||
chartData: blocProvider.energyDataList.isNotEmpty
|
chartData: blocProvider.energyDataList.isNotEmpty
|
||||||
|
@ -84,6 +84,16 @@ class _PresenceUpdateDataState extends State<PresenceNoBodyTime> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(PresenceNoBodyTime oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (oldWidget.value != widget.value) {
|
||||||
|
setState(() {
|
||||||
|
_currentValue = widget.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DeviceControlsContainer(
|
return DeviceControlsContainer(
|
||||||
|
@ -21,6 +21,7 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
|||||||
on<ShowDescriptionEvent>(_showDescription);
|
on<ShowDescriptionEvent>(_showDescription);
|
||||||
on<BackToGridViewEvent>(_backToGridView);
|
on<BackToGridViewEvent>(_backToGridView);
|
||||||
on<WallSensorFactoryResetEvent>(_onFactoryReset);
|
on<WallSensorFactoryResetEvent>(_onFactoryReset);
|
||||||
|
on<WallSensorRealtimeUpdateEvent>(_onRealtimeUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _fetchWallSensorStatus(
|
void _fetchWallSensorStatus(
|
||||||
@ -30,7 +31,7 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
|||||||
var response = await DevicesManagementApi().getDeviceStatus(deviceId);
|
var response = await DevicesManagementApi().getDeviceStatus(deviceId);
|
||||||
deviceStatus = WallSensorModel.fromJson(response.status);
|
deviceStatus = WallSensorModel.fromJson(response.status);
|
||||||
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
||||||
_listenToChanges(emit, deviceId);
|
_listenToChanges(deviceId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(WallSensorFailedState(error: e.toString()));
|
emit(WallSensorFailedState(error: e.toString()));
|
||||||
return;
|
return;
|
||||||
@ -52,28 +53,27 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_listenToChanges(Emitter<WallSensorState> emit, deviceId) {
|
void _listenToChanges(String deviceId) {
|
||||||
try {
|
DatabaseReference ref =
|
||||||
DatabaseReference ref =
|
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
ref.onValue.listen((DatabaseEvent event) {
|
||||||
Stream<DatabaseEvent> stream = ref.onValue;
|
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
||||||
|
if (data == null) return;
|
||||||
|
|
||||||
stream.listen((DatabaseEvent event) {
|
final statusList = (data['status'] as List?)
|
||||||
Map<dynamic, dynamic> usersMap =
|
?.map((e) => Status(code: e['code'], value: e['value']))
|
||||||
event.snapshot.value as Map<dynamic, dynamic>;
|
.toList();
|
||||||
List<Status> statusList = [];
|
|
||||||
|
|
||||||
usersMap['status'].forEach((element) {
|
if (statusList != null) {
|
||||||
statusList
|
final updatedDeviceStatus = WallSensorModel.fromJson(statusList);
|
||||||
.add(Status(code: element['code'], value: element['value']));
|
if (!isClosed) {
|
||||||
});
|
add(WallSensorRealtimeUpdateEvent(updatedDeviceStatus));
|
||||||
|
}
|
||||||
deviceStatus = WallSensorModel.fromJson(statusList);
|
}
|
||||||
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
|
});
|
||||||
});
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void _changeValue(
|
void _changeValue(
|
||||||
WallSensorChangeValueEvent event, Emitter<WallSensorState> emit) async {
|
WallSensorChangeValueEvent event, Emitter<WallSensorState> emit) async {
|
||||||
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
|
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
|
||||||
@ -195,4 +195,12 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
|||||||
emit(WallSensorFailedState(error: e.toString()));
|
emit(WallSensorFailedState(error: e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onRealtimeUpdate(
|
||||||
|
WallSensorRealtimeUpdateEvent event,
|
||||||
|
Emitter<WallSensorState> emit,
|
||||||
|
) {
|
||||||
|
deviceStatus = event.deviceStatus;
|
||||||
|
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart';
|
||||||
|
|
||||||
abstract class WallSensorEvent extends Equatable {
|
abstract class WallSensorEvent extends Equatable {
|
||||||
const WallSensorEvent();
|
const WallSensorEvent();
|
||||||
@ -70,3 +71,8 @@ class WallSensorFactoryResetEvent extends WallSensorEvent {
|
|||||||
required this.factoryReset,
|
required this.factoryReset,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class WallSensorRealtimeUpdateEvent extends WallSensorEvent {
|
||||||
|
final WallSensorModel deviceStatus;
|
||||||
|
const WallSensorRealtimeUpdateEvent(this.deviceStatus);
|
||||||
|
}
|
||||||
|
@ -142,7 +142,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
|||||||
},
|
},
|
||||||
color: ColorsManager.primaryColor,
|
color: ColorsManager.primaryColor,
|
||||||
),
|
),
|
||||||
|
|
||||||
// HomeItemModel(
|
// HomeItemModel(
|
||||||
// title: 'Move in',
|
// title: 'Move in',
|
||||||
// icon: Assets.moveinIcon,
|
// icon: Assets.moveinIcon,
|
||||||
|
@ -50,8 +50,9 @@ class HomeMobilePage extends StatelessWidget {
|
|||||||
height: size.height * 0.6,
|
height: size.height * 0.6,
|
||||||
width: size.width * 0.68,
|
width: size.width * 0.68,
|
||||||
child: GridView.builder(
|
child: GridView.builder(
|
||||||
itemCount: 3,
|
itemCount: homeItems.length,
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate:
|
||||||
|
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 2,
|
crossAxisCount: 2,
|
||||||
crossAxisSpacing: 20.0,
|
crossAxisSpacing: 20.0,
|
||||||
mainAxisSpacing: 20.0,
|
mainAxisSpacing: 20.0,
|
||||||
@ -60,10 +61,11 @@ class HomeMobilePage extends StatelessWidget {
|
|||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return HomeCard(
|
return HomeCard(
|
||||||
index: index,
|
index: index,
|
||||||
active: homeItems[index]['active'],
|
active: homeBloc.homeItems[index].active!,
|
||||||
name: homeItems[index]['title'],
|
name: homeBloc.homeItems[index].title!,
|
||||||
img: homeItems[index]['icon'],
|
img: homeBloc.homeItems[index].icon!,
|
||||||
onTap: () => homeBloc.homeItems[index].onPress(context),
|
onTap: () =>
|
||||||
|
homeBloc.homeItems[index].onPress(context),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -94,6 +96,11 @@ class HomeMobilePage extends StatelessWidget {
|
|||||||
'icon': Assets.devicesIcon,
|
'icon': Assets.devicesIcon,
|
||||||
'active': true,
|
'active': true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'title': 'Syncrow Analytics',
|
||||||
|
'icon': Assets.iconEdit,
|
||||||
|
'active': true,
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// 'title': 'Move in',
|
// 'title': 'Move in',
|
||||||
// 'icon': Assets.moveinIcon,
|
// 'icon': Assets.moveinIcon,
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
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:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
|
||||||
import 'package:syncrow_web/pages/home/view/agreement_and_privacy_dialog.dart';
|
|
||||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
||||||
import 'package:syncrow_web/pages/home/bloc/home_state.dart';
|
import 'package:syncrow_web/pages/home/bloc/home_state.dart';
|
||||||
|
import 'package:syncrow_web/pages/home/view/agreement_and_privacy_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/home/view/home_card.dart';
|
import 'package:syncrow_web/pages/home/view/home_card.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||||
@ -24,7 +24,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final homeBloc = BlocProvider.of<HomeBloc>(context);
|
final homeBloc = BlocProvider.of<HomeBloc>(context);
|
||||||
homeBloc.add(FetchUserInfo());
|
homeBloc.add(const FetchUserInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -97,7 +97,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
|
|||||||
height: size.height * 0.6,
|
height: size.height * 0.6,
|
||||||
width: size.width * 0.68,
|
width: size.width * 0.68,
|
||||||
child: GridView.builder(
|
child: GridView.builder(
|
||||||
itemCount: 3, // Change this count if needed.
|
itemCount: homeBloc.homeItems.length,
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 3, // Adjust as needed.
|
crossAxisCount: 3, // Adjust as needed.
|
||||||
crossAxisSpacing: 20.0,
|
crossAxisSpacing: 20.0,
|
||||||
|
@ -19,7 +19,6 @@ 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';
|
||||||
|
|
||||||
class UsersPage extends StatelessWidget {
|
class UsersPage extends StatelessWidget {
|
||||||
UsersPage({super.key});
|
UsersPage({super.key});
|
||||||
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/common/bloc/project_manager.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/automation_scene_trigger_bloc/automation_status_update.dart';
|
import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart';
|
||||||
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
|
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
|
||||||
@ -90,8 +89,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
final updatedIfItems = List<Map<String, dynamic>>.from(state.ifItems);
|
final updatedIfItems = List<Map<String, dynamic>>.from(state.ifItems);
|
||||||
|
|
||||||
// Find the index of the item in teh current itemsList
|
// Find the index of the item in teh current itemsList
|
||||||
int index =
|
int index = updatedIfItems
|
||||||
updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
||||||
// Replace the map if the index is valid
|
// Replace the map if the index is valid
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
updatedIfItems[index] = event.item;
|
updatedIfItems[index] = event.item;
|
||||||
@ -100,9 +99,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.isTabToRun) {
|
if (event.isTabToRun) {
|
||||||
emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
|
emit(state.copyWith(
|
||||||
|
ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
|
||||||
} else {
|
} else {
|
||||||
emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
|
emit(state.copyWith(
|
||||||
|
ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,8 +111,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
final currentItems = List<Map<String, dynamic>>.from(state.thenItems);
|
final currentItems = List<Map<String, dynamic>>.from(state.thenItems);
|
||||||
|
|
||||||
// Find the index of the item in teh current itemsList
|
// Find the index of the item in teh current itemsList
|
||||||
int index =
|
int index = currentItems
|
||||||
currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
||||||
// Replace the map if the index is valid
|
// Replace the map if the index is valid
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
currentItems[index] = event.item;
|
currentItems[index] = event.item;
|
||||||
@ -122,7 +123,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
emit(state.copyWith(thenItems: currentItems));
|
emit(state.copyWith(thenItems: currentItems));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter<RoutineState> emit) {
|
void _onAddFunctionsToRoutine(
|
||||||
|
AddFunctionToRoutine event, Emitter<RoutineState> emit) {
|
||||||
try {
|
try {
|
||||||
if (event.functions.isEmpty) return;
|
if (event.functions.isEmpty) return;
|
||||||
|
|
||||||
@ -172,17 +174,20 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
BuildContext context = NavigationService.navigatorKey.currentContext!;
|
BuildContext context = NavigationService.navigatorKey.currentContext!;
|
||||||
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
|
if (createRoutineBloc.selectedSpaceId == '' &&
|
||||||
|
createRoutineBloc.selectedCommunityId == '') {
|
||||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||||
for (var communityId in spaceBloc.state.selectedCommunities) {
|
for (var communityId in spaceBloc.state.selectedCommunities) {
|
||||||
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
List<String> spacesList =
|
||||||
|
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||||
for (var spaceId in spacesList) {
|
for (var spaceId in spacesList) {
|
||||||
scenes.addAll(await SceneApi.getScenes(spaceId, communityId, projectUuid));
|
scenes
|
||||||
|
.addAll(await SceneApi.getScenes(spaceId, communityId, projectUuid));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scenes.addAll(await SceneApi.getScenes(
|
scenes.addAll(await SceneApi.getScenes(createRoutineBloc.selectedSpaceId,
|
||||||
createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectUuid));
|
createRoutineBloc.selectedCommunityId, projectUuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
@ -199,7 +204,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onLoadAutomation(LoadAutomation event, Emitter<RoutineState> emit) async {
|
Future<void> _onLoadAutomation(
|
||||||
|
LoadAutomation event, Emitter<RoutineState> emit) async {
|
||||||
emit(state.copyWith(isLoading: true, errorMessage: null));
|
emit(state.copyWith(isLoading: true, errorMessage: null));
|
||||||
List<ScenesModel> automations = [];
|
List<ScenesModel> automations = [];
|
||||||
final projectId = await ProjectManager.getProjectUUID() ?? '';
|
final projectId = await ProjectManager.getProjectUUID() ?? '';
|
||||||
@ -207,17 +213,22 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
BuildContext context = NavigationService.navigatorKey.currentContext!;
|
BuildContext context = NavigationService.navigatorKey.currentContext!;
|
||||||
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
||||||
try {
|
try {
|
||||||
if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
|
if (createRoutineBloc.selectedSpaceId == '' &&
|
||||||
|
createRoutineBloc.selectedCommunityId == '') {
|
||||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||||
for (var communityId in spaceBloc.state.selectedCommunities) {
|
for (var communityId in spaceBloc.state.selectedCommunities) {
|
||||||
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
List<String> spacesList =
|
||||||
|
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||||
for (var spaceId in spacesList) {
|
for (var spaceId in spacesList) {
|
||||||
automations.addAll(await SceneApi.getAutomation(spaceId, communityId, projectId));
|
automations.addAll(
|
||||||
|
await SceneApi.getAutomation(spaceId, communityId, projectId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
automations.addAll(await SceneApi.getAutomation(
|
automations.addAll(await SceneApi.getAutomation(
|
||||||
createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectId));
|
createRoutineBloc.selectedSpaceId,
|
||||||
|
createRoutineBloc.selectedCommunityId,
|
||||||
|
projectId));
|
||||||
}
|
}
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
automations: automations,
|
automations: automations,
|
||||||
@ -233,14 +244,16 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onSearchRoutines(SearchRoutines event, Emitter<RoutineState> emit) async {
|
FutureOr<void> _onSearchRoutines(
|
||||||
|
SearchRoutines event, Emitter<RoutineState> emit) async {
|
||||||
emit(state.copyWith(isLoading: true, errorMessage: null));
|
emit(state.copyWith(isLoading: true, errorMessage: null));
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
emit(state.copyWith(isLoading: false, errorMessage: null));
|
emit(state.copyWith(isLoading: false, errorMessage: null));
|
||||||
emit(state.copyWith(searchText: event.query));
|
emit(state.copyWith(searchText: event.query));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onAddSelectedIcon(AddSelectedIcon event, Emitter<RoutineState> emit) {
|
FutureOr<void> _onAddSelectedIcon(
|
||||||
|
AddSelectedIcon event, Emitter<RoutineState> emit) {
|
||||||
emit(state.copyWith(selectedIcon: event.icon));
|
emit(state.copyWith(selectedIcon: event.icon));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,7 +267,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
return actions.last['deviceId'] == 'delay';
|
return actions.last['deviceId'] == 'delay';
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onCreateScene(CreateSceneEvent event, Emitter<RoutineState> emit) async {
|
Future<void> _onCreateScene(
|
||||||
|
CreateSceneEvent event, Emitter<RoutineState> emit) async {
|
||||||
try {
|
try {
|
||||||
// Check if first action is delay
|
// Check if first action is delay
|
||||||
// if (_isFirstActionDelay(state.thenItems)) {
|
// if (_isFirstActionDelay(state.thenItems)) {
|
||||||
@ -343,7 +357,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onCreateAutomation(CreateAutomationEvent event, Emitter<RoutineState> emit) async {
|
Future<void> _onCreateAutomation(
|
||||||
|
CreateAutomationEvent event, Emitter<RoutineState> emit) async {
|
||||||
try {
|
try {
|
||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
if (state.routineName == null || state.routineName!.isEmpty) {
|
if (state.routineName == null || state.routineName!.isEmpty) {
|
||||||
@ -456,7 +471,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
actions: actions,
|
actions: actions,
|
||||||
);
|
);
|
||||||
|
|
||||||
final result = await SceneApi.createAutomation(createAutomationModel, projectUuid);
|
final result =
|
||||||
|
await SceneApi.createAutomation(createAutomationModel, projectUuid);
|
||||||
if (result['success']) {
|
if (result['success']) {
|
||||||
add(ResetRoutineState());
|
add(ResetRoutineState());
|
||||||
add(const LoadAutomation());
|
add(const LoadAutomation());
|
||||||
@ -477,17 +493,21 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onRemoveDragCard(RemoveDragCard event, Emitter<RoutineState> emit) {
|
FutureOr<void> _onRemoveDragCard(
|
||||||
|
RemoveDragCard event, Emitter<RoutineState> emit) {
|
||||||
if (event.isFromThen) {
|
if (event.isFromThen) {
|
||||||
final thenItems = List<Map<String, dynamic>>.from(state.thenItems);
|
final thenItems = List<Map<String, dynamic>>.from(state.thenItems);
|
||||||
final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
final selectedFunctions =
|
||||||
|
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
||||||
|
|
||||||
thenItems.removeAt(event.index);
|
thenItems.removeAt(event.index);
|
||||||
selectedFunctions.remove(event.key);
|
selectedFunctions.remove(event.key);
|
||||||
emit(state.copyWith(thenItems: thenItems, selectedFunctions: selectedFunctions));
|
emit(state.copyWith(
|
||||||
|
thenItems: thenItems, selectedFunctions: selectedFunctions));
|
||||||
} else {
|
} else {
|
||||||
final ifItems = List<Map<String, dynamic>>.from(state.ifItems);
|
final ifItems = List<Map<String, dynamic>>.from(state.ifItems);
|
||||||
final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
final selectedFunctions =
|
||||||
|
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
||||||
|
|
||||||
ifItems.removeAt(event.index);
|
ifItems.removeAt(event.index);
|
||||||
selectedFunctions.remove(event.key);
|
selectedFunctions.remove(event.key);
|
||||||
@ -510,11 +530,13 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
|
FutureOr<void> _onEffectiveTimeEvent(
|
||||||
|
EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
|
||||||
emit(state.copyWith(effectiveTime: event.effectiveTime));
|
emit(state.copyWith(effectiveTime: event.effectiveTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onSetRoutineName(SetRoutineName event, Emitter<RoutineState> emit) {
|
FutureOr<void> _onSetRoutineName(
|
||||||
|
SetRoutineName event, Emitter<RoutineState> emit) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
routineName: event.name,
|
routineName: event.name,
|
||||||
));
|
));
|
||||||
@ -641,7 +663,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
// return (thenItems, ifItems, currentFunctions);
|
// return (thenItems, ifItems, currentFunctions);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
Future<void> _onGetSceneDetails(GetSceneDetails event, Emitter<RoutineState> emit) async {
|
Future<void> _onGetSceneDetails(
|
||||||
|
GetSceneDetails event, Emitter<RoutineState> emit) async {
|
||||||
try {
|
try {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
@ -690,9 +713,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
deviceCards[deviceId] = {
|
deviceCards[deviceId] = {
|
||||||
'entityId': action.entityId,
|
'entityId': action.entityId,
|
||||||
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
|
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
|
||||||
'uniqueCustomId': action.type == 'automation' || action.actionExecutor == 'delay'
|
'uniqueCustomId':
|
||||||
? action.entityId
|
action.type == 'automation' || action.actionExecutor == 'delay'
|
||||||
: const Uuid().v4(),
|
? action.entityId
|
||||||
|
: const Uuid().v4(),
|
||||||
'title': action.actionExecutor == 'delay'
|
'title': action.actionExecutor == 'delay'
|
||||||
? 'Delay'
|
? 'Delay'
|
||||||
: action.type == 'automation'
|
: action.type == 'automation'
|
||||||
@ -732,7 +756,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
// emit(state.copyWith(automationActionExecutor: action.actionExecutor));
|
// emit(state.copyWith(automationActionExecutor: action.actionExecutor));
|
||||||
} else if (action.executorProperty != null && action.actionExecutor != 'delay') {
|
} else if (action.executorProperty != null &&
|
||||||
|
action.actionExecutor != 'delay') {
|
||||||
final functions = matchingDevice?.functions ?? [];
|
final functions = matchingDevice?.functions ?? [];
|
||||||
final functionCode = action.executorProperty?.functionCode;
|
final functionCode = action.executorProperty?.functionCode;
|
||||||
for (DeviceFunction function in functions) {
|
for (DeviceFunction function in functions) {
|
||||||
@ -798,7 +823,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onResetRoutineState(ResetRoutineState event, Emitter<RoutineState> emit) {
|
FutureOr<void> _onResetRoutineState(
|
||||||
|
ResetRoutineState event, Emitter<RoutineState> emit) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
ifItems: [],
|
ifItems: [],
|
||||||
thenItems: [],
|
thenItems: [],
|
||||||
@ -831,7 +857,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||||
if (state.isTabToRun) {
|
if (state.isTabToRun) {
|
||||||
await SceneApi.deleteScene(
|
await SceneApi.deleteScene(
|
||||||
unitUuid: spaceBloc.state.selectedSpaces[0], sceneId: state.sceneId ?? '');
|
unitUuid: spaceBloc.state.selectedSpaces[0],
|
||||||
|
sceneId: state.sceneId ?? '');
|
||||||
} else {
|
} else {
|
||||||
await SceneApi.deleteAutomation(
|
await SceneApi.deleteAutomation(
|
||||||
unitUuid: spaceBloc.state.selectedSpaces[0],
|
unitUuid: spaceBloc.state.selectedSpaces[0],
|
||||||
@ -876,7 +903,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
FutureOr<void> _fetchDevices(FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
|
FutureOr<void> _fetchDevices(
|
||||||
|
FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
|
||||||
emit(state.copyWith(isLoading: true));
|
emit(state.copyWith(isLoading: true));
|
||||||
try {
|
try {
|
||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
@ -885,17 +913,21 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
||||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||||
|
|
||||||
if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
|
if (createRoutineBloc.selectedSpaceId == '' &&
|
||||||
|
createRoutineBloc.selectedCommunityId == '') {
|
||||||
for (var communityId in spaceBloc.state.selectedCommunities) {
|
for (var communityId in spaceBloc.state.selectedCommunities) {
|
||||||
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
List<String> spacesList =
|
||||||
|
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||||
for (var spaceId in spacesList) {
|
for (var spaceId in spacesList) {
|
||||||
devices.addAll(
|
devices.addAll(await DevicesManagementApi()
|
||||||
await DevicesManagementApi().fetchDevices(communityId, spaceId, projectUuid));
|
.fetchDevices(communityId, spaceId, projectUuid));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
devices.addAll(await DevicesManagementApi().fetchDevices(
|
devices.addAll(await DevicesManagementApi().fetchDevices(
|
||||||
createRoutineBloc.selectedCommunityId, createRoutineBloc.selectedSpaceId, projectUuid));
|
createRoutineBloc.selectedCommunityId,
|
||||||
|
createRoutineBloc.selectedSpaceId,
|
||||||
|
projectUuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(state.copyWith(isLoading: false, devices: devices));
|
emit(state.copyWith(isLoading: false, devices: devices));
|
||||||
@ -904,7 +936,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onUpdateScene(UpdateScene event, Emitter<RoutineState> emit) async {
|
FutureOr<void> _onUpdateScene(
|
||||||
|
UpdateScene event, Emitter<RoutineState> emit) async {
|
||||||
try {
|
try {
|
||||||
// Check if first action is delay
|
// Check if first action is delay
|
||||||
// if (_isFirstActionDelay(state.thenItems)) {
|
// if (_isFirstActionDelay(state.thenItems)) {
|
||||||
@ -971,7 +1004,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
actions: actions,
|
actions: actions,
|
||||||
);
|
);
|
||||||
|
|
||||||
final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
|
final result =
|
||||||
|
await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
|
||||||
if (result['success']) {
|
if (result['success']) {
|
||||||
add(ResetRoutineState());
|
add(ResetRoutineState());
|
||||||
add(const LoadScenes());
|
add(const LoadScenes());
|
||||||
@ -990,7 +1024,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onUpdateAutomation(UpdateAutomation event, Emitter<RoutineState> emit) async {
|
FutureOr<void> _onUpdateAutomation(
|
||||||
|
UpdateAutomation event, Emitter<RoutineState> emit) async {
|
||||||
try {
|
try {
|
||||||
if (state.routineName == null || state.routineName!.isEmpty) {
|
if (state.routineName == null || state.routineName!.isEmpty) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
@ -1106,8 +1141,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
|
|
||||||
if (result['success']) {
|
if (result['success']) {
|
||||||
add(ResetRoutineState());
|
add(ResetRoutineState());
|
||||||
add(LoadAutomation());
|
add(const LoadAutomation());
|
||||||
add(LoadScenes());
|
add(const LoadScenes());
|
||||||
} else {
|
} else {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
@ -1291,10 +1326,13 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final ifItems = deviceIfCards.values.where((card) => card['type'] == 'condition').toList();
|
final ifItems =
|
||||||
|
deviceIfCards.values.where((card) => card['type'] == 'condition').toList();
|
||||||
final thenItems = deviceThenCards.values
|
final thenItems = deviceThenCards.values
|
||||||
.where((card) =>
|
.where((card) =>
|
||||||
card['type'] == 'action' || card['type'] == 'automation' || card['type'] == 'scene')
|
card['type'] == 'action' ||
|
||||||
|
card['type'] == 'automation' ||
|
||||||
|
card['type'] == 'scene')
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
@ -1316,7 +1354,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSceneTrigger(SceneTrigger event, Emitter<RoutineState> emit) async {
|
Future<void> _onSceneTrigger(
|
||||||
|
SceneTrigger event, Emitter<RoutineState> emit) async {
|
||||||
emit(state.copyWith(loadingSceneId: event.sceneId));
|
emit(state.copyWith(loadingSceneId: event.sceneId));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1361,21 +1400,24 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
event.automationStatusUpdate.spaceUuid, event.communityId, projectId);
|
event.automationStatusUpdate.spaceUuid, event.communityId, projectId);
|
||||||
|
|
||||||
// Remove from loading set safely
|
// Remove from loading set safely
|
||||||
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
|
final updatedLoadingIds = {...state.loadingAutomationIds!}
|
||||||
|
..remove(event.automationId);
|
||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
automations: updatedAutomations,
|
automations: updatedAutomations,
|
||||||
loadingAutomationIds: updatedLoadingIds,
|
loadingAutomationIds: updatedLoadingIds,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
|
final updatedLoadingIds = {...state.loadingAutomationIds!}
|
||||||
|
..remove(event.automationId);
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
loadingAutomationIds: updatedLoadingIds,
|
loadingAutomationIds: updatedLoadingIds,
|
||||||
errorMessage: 'Update failed',
|
errorMessage: 'Update failed',
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
|
final updatedLoadingIds = {...state.loadingAutomationIds!}
|
||||||
|
..remove(event.automationId);
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
loadingAutomationIds: updatedLoadingIds,
|
loadingAutomationIds: updatedLoadingIds,
|
||||||
errorMessage: 'Update error: ${e.toString()}',
|
errorMessage: 'Update error: ${e.toString()}',
|
||||||
|
@ -30,8 +30,8 @@ class _RoutinesViewState extends State<RoutinesView> {
|
|||||||
final spaceId = result['space'];
|
final spaceId = result['space'];
|
||||||
final bloc = BlocProvider.of<CreateRoutineBloc>(context);
|
final bloc = BlocProvider.of<CreateRoutineBloc>(context);
|
||||||
final routineBloc = context.read<RoutineBloc>();
|
final routineBloc = context.read<RoutineBloc>();
|
||||||
bloc.add(
|
bloc.add(SaveCommunityIdAndSpaceIdEvent(
|
||||||
SaveCommunityIdAndSpaceIdEvent(communityID: communityId, spaceID: spaceId));
|
communityID: communityId, spaceID: spaceId));
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
routineBloc.add(const CreateNewRoutineViewEvent(createRoutineView: true));
|
routineBloc.add(const CreateNewRoutineViewEvent(createRoutineView: true));
|
||||||
}
|
}
|
||||||
@ -61,34 +61,38 @@ class _RoutinesViewState extends State<RoutinesView> {
|
|||||||
width: context.screenWidth,
|
width: context.screenWidth,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsetsDirectional.all(16),
|
padding: const EdgeInsetsDirectional.all(16),
|
||||||
child: Column(
|
child: Padding(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
padding: const EdgeInsets.only(left: 20),
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
child: Column(
|
||||||
spacing: 16,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
Text(
|
spacing: 16,
|
||||||
"Create New Routines",
|
children: [
|
||||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
Text(
|
||||||
color: ColorsManager.grayColor,
|
"Create New Routines",
|
||||||
fontWeight: FontWeight.bold,
|
style:
|
||||||
),
|
Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
),
|
color: ColorsManager.grayColor,
|
||||||
RoutineViewCard(
|
fontWeight: FontWeight.bold,
|
||||||
isLoading: false,
|
),
|
||||||
onChanged: (v) {},
|
),
|
||||||
status: '',
|
RoutineViewCard(
|
||||||
spaceId: '',
|
isLoading: false,
|
||||||
automationId: '',
|
onChanged: (v) {},
|
||||||
communityId: '',
|
status: '',
|
||||||
sceneId: '',
|
spaceId: '',
|
||||||
cardType: '',
|
automationId: '',
|
||||||
spaceName: '',
|
communityId: '',
|
||||||
onTap: () => _handleRoutineCreation(context),
|
sceneId: '',
|
||||||
icon: Icons.add,
|
cardType: '',
|
||||||
textString: '',
|
spaceName: '',
|
||||||
),
|
onTap: () => _handleRoutineCreation(context),
|
||||||
const FetchRoutineScenesAutomation(),
|
icon: Icons.add,
|
||||||
],
|
textString: '',
|
||||||
|
),
|
||||||
|
const FetchRoutineScenesAutomation(),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user