mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-15 17:47:53 +00:00
Compare commits
56 Commits
SP-1597-FE
...
dev-ci/cd-
Author | SHA1 | Date | |
---|---|---|---|
3e32968209 | |||
beb5239c4f | |||
ad8e06ac40 | |||
5f8eb9de06 | |||
8e8fdf0fc6 | |||
0d0d51463d | |||
8827f571f4 | |||
7472aff704 | |||
575ba2aed2 | |||
eb708edc83 | |||
e86c25c74a | |||
c2c58e6a7a | |||
0135b6711e | |||
46feb0ea28 | |||
74ae9d7ce1 | |||
710f316f8d | |||
7cc46d464f | |||
0c82a19a1d | |||
d1df33b31e | |||
6a36405530 | |||
3c98365338 | |||
88a7607395 | |||
f58ddf76da | |||
a71a66034c | |||
b06a23cc60 | |||
5595bb7f25 | |||
8e11749ed7 | |||
7bc9079212 | |||
97801872e0 | |||
fa9210f387 | |||
57b6f01177 | |||
77d39bfc53 | |||
3bd2bd114b | |||
f98636a2e5 | |||
19548e99ab | |||
b60c674496 | |||
6f3dfb607e | |||
62dabf1ce2 | |||
066f967cd1 | |||
e28f3c3c03 | |||
2be15e648a | |||
2e12d73151 | |||
c50ed693ae | |||
8dc7d2b3d0 | |||
accafb150e | |||
736e0c3d9c | |||
455d9c1f01 | |||
4479ed04b7 | |||
286dea3f51 | |||
44c4648941 | |||
ca1feb9600 | |||
7b31914e1c | |||
10f35d3747 | |||
1998a629b6 | |||
5940e52826 | |||
7c55e8bbf9 |
8
.github/pull_request_template.md
vendored
8
.github/pull_request_template.md
vendored
@ -7,11 +7,7 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
## Jira Ticket
|
## Jira Ticket
|
||||||
<!-- Add your Jira ticket number as a link (e.g., [PROJ-123](https://jira.company.com/browse/PROJ-123)) -->
|
[SP-0000](https://syncrow.atlassian.net/browse/SP-0000)
|
||||||
|
|
||||||
## Status
|
|
||||||
|
|
||||||
**READY/IN DEVELOPMENT/HOLD**
|
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
@ -27,4 +23,4 @@
|
|||||||
- [ ] 🧹 Code refactor
|
- [ ] 🧹 Code refactor
|
||||||
- [ ] ✅ Build configuration change
|
- [ ] ✅ Build configuration change
|
||||||
- [ ] 📝 Documentation
|
- [ ] 📝 Documentation
|
||||||
- [ ] 🗑️ Chore
|
- [ ] 🗑️ 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
|
24
assets/icons/settings_button.svg
Normal file
24
assets/icons/settings_button.svg
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<svg width="40" height="26" viewBox="0 0 40 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g filter="url(#filter0_d_9292_1537)">
|
||||||
|
<rect x="3" y="3" width="34" height="20" rx="10" fill="#F5F6F7"/>
|
||||||
|
<g clip-path="url(#clip0_9292_1537)">
|
||||||
|
<path d="M20.4393 20H19.5607C18.85 20 18.2718 19.4218 18.2718 18.7112V18.414C17.9697 18.3174 17.6762 18.1956 17.3942 18.0497L17.1835 18.2603C16.6733 18.7711 15.8561 18.7562 15.3607 18.2601L14.7397 17.6391C14.2434 17.1434 14.2291 16.3264 14.7398 15.8163L14.9503 15.6058C14.8044 15.3238 14.6826 15.0303 14.586 14.7281H14.2888C13.5782 14.7281 13 14.15 13 13.4393V12.5607C13 11.85 13.5782 11.2719 14.2888 11.2719H14.586C14.6826 10.9697 14.8044 10.6762 14.9503 10.3942L14.7397 10.1836C14.2293 9.67374 14.2434 8.85666 14.7399 8.36072L15.3609 7.73969C15.8574 7.24247 16.6745 7.23006 17.1838 7.73986L17.3942 7.95032C17.6762 7.80441 17.9698 7.68257 18.2719 7.58602V7.28879C18.2719 6.57816 18.85 6 19.5607 6H20.4393C21.15 6 21.7281 6.57816 21.7281 7.28879V7.58605C22.0302 7.68257 22.3238 7.80441 22.6058 7.95035L22.8164 7.73969C23.3266 7.22886 24.1439 7.24384 24.6393 7.73988L25.2603 8.36086C25.7566 8.85657 25.7708 9.67358 25.2601 10.1837L25.0497 10.3942C25.1956 10.6762 25.3174 10.9697 25.414 11.2719H25.7112C26.4218 11.2719 27 11.85 27 12.5607V13.4393C27 14.15 26.4218 14.7281 25.7112 14.7281H25.414C25.3174 15.0303 25.1956 15.3238 25.0497 15.6058L25.2603 15.8164C25.7707 16.3263 25.7566 17.1434 25.2601 17.6393L24.6391 18.2603C24.1426 18.7576 23.3255 18.77 22.8162 18.2602L22.6058 18.0497C22.3238 18.1956 22.0302 18.3175 21.7281 18.414V18.7113C21.7281 19.4218 21.15 20 20.4393 20ZM17.5313 17.1882C17.9231 17.4199 18.3447 17.595 18.7845 17.7085C18.9656 17.7552 19.0922 17.9185 19.0922 18.1056V18.7112C19.0922 18.9695 19.3024 19.1797 19.5607 19.1797H20.4393C20.6976 19.1797 20.9078 18.9695 20.9078 18.7112V18.1056C20.9078 17.9185 21.0344 17.7552 21.2155 17.7085C21.6553 17.595 22.0769 17.4199 22.4687 17.1882C22.6299 17.0929 22.8351 17.1188 22.9675 17.2513L23.3965 17.6803C23.5815 17.8654 23.8785 17.8611 24.0589 17.6805L24.6803 17.059C24.8603 16.8793 24.8663 16.5822 24.6805 16.3966L24.2513 15.9675C24.1189 15.8351 24.093 15.6298 24.1883 15.4687C24.42 15.0769 24.595 14.6553 24.7085 14.2155C24.7552 14.0344 24.9186 13.9078 25.1056 13.9078H25.7112C25.9695 13.9078 26.1797 13.6977 26.1797 13.4394V12.5607C26.1797 12.3024 25.9695 12.0922 25.7112 12.0922H25.1056C24.9186 12.0922 24.7552 11.9657 24.7085 11.7846C24.595 11.3447 24.42 10.9231 24.1883 10.5314C24.093 10.3702 24.1189 10.165 24.2513 10.0326L24.6803 9.60358C24.8658 9.41835 24.8609 9.1214 24.6805 8.94118L24.0591 8.31979C23.879 8.13943 23.5819 8.13415 23.3967 8.31962L22.9676 8.74879C22.8352 8.88121 22.6299 8.90713 22.4687 8.81181C22.077 8.58013 21.6553 8.4051 21.2155 8.2916C21.0344 8.24487 20.9079 8.08152 20.9079 7.89446V7.28879C20.9079 7.03048 20.6977 6.82031 20.4394 6.82031H19.5607C19.3024 6.82031 19.0922 7.03048 19.0922 7.28879V7.8944C19.0922 8.08146 18.9657 8.24481 18.7845 8.29154C18.3447 8.40505 17.9231 8.58007 17.5314 8.81176C17.3701 8.90705 17.1649 8.88113 17.0325 8.74873L16.6036 8.31973C16.4186 8.13456 16.1216 8.13886 15.9412 8.31954L15.3197 8.94096C15.1398 9.12071 15.1337 9.41775 15.3196 9.60336L15.7487 10.0325C15.8811 10.1649 15.9071 10.3702 15.8118 10.5313C15.5801 10.9231 15.4051 11.3447 15.2916 11.7845C15.2448 11.9656 15.0815 12.0922 14.8944 12.0922H14.2888C14.0305 12.0922 13.8203 12.3024 13.8203 12.5607V13.4393C13.8203 13.6976 14.0305 13.9078 14.2888 13.9078H14.8944C15.0815 13.9078 15.2448 14.0344 15.2915 14.2155C15.405 14.6553 15.5801 15.0769 15.8117 15.4686C15.907 15.6298 15.8811 15.8351 15.7487 15.9675L15.3197 16.3965C15.1343 16.5817 15.1391 16.8786 15.3195 17.0589L15.9409 17.6802C16.121 17.8606 16.4181 17.8659 16.6033 17.6804L17.0325 17.2512C17.13 17.1537 17.333 17.0709 17.5313 17.1882Z" fill="#023DFE" fill-opacity="0.7"/>
|
||||||
|
<path d="M19.9992 16.0461C18.3196 16.0461 16.9531 14.6796 16.9531 13C16.9531 11.3204 18.3196 9.95391 19.9992 9.95391C21.6789 9.95391 23.0453 11.3204 23.0453 13C23.0453 14.6796 21.6789 16.0461 19.9992 16.0461ZM19.9992 10.7742C18.7719 10.7742 17.7734 11.7727 17.7734 13C17.7734 14.2273 18.7719 15.2258 19.9992 15.2258C21.2265 15.2258 22.225 14.2273 22.225 13C22.225 11.7727 21.2265 10.7742 19.9992 10.7742Z" fill="#023DFE" fill-opacity="0.7"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_d_9292_1537" x="0" y="0" width="40" height="26" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset/>
|
||||||
|
<feGaussianBlur stdDeviation="1.5"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="out"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_9292_1537"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_9292_1537" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
<clipPath id="clip0_9292_1537">
|
||||||
|
<rect width="14" height="14" fill="white" transform="translate(13 6)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 5.0 KiB |
57
lib/pages/analytics/models/air_quality_data_model.dart
Normal file
57
lib/pages/analytics/models/air_quality_data_model.dart
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class AirQualityDataModel extends Equatable {
|
||||||
|
const AirQualityDataModel({
|
||||||
|
required this.date,
|
||||||
|
required this.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DateTime date;
|
||||||
|
final List<AirQualityPercentageData> data;
|
||||||
|
|
||||||
|
factory AirQualityDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AirQualityDataModel(
|
||||||
|
date: DateTime.parse(json['date'] as String),
|
||||||
|
data: (json['data'] as List<dynamic>)
|
||||||
|
.map((e) => AirQualityPercentageData.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final Map<String, Color> metricColors = {
|
||||||
|
'good': ColorsManager.goodGreen.withValues(alpha: 0.7),
|
||||||
|
'moderate': ColorsManager.moderateYellow.withValues(alpha: 0.7),
|
||||||
|
'poor': ColorsManager.poorOrange.withValues(alpha: 0.7),
|
||||||
|
'unhealthy': ColorsManager.unhealthyRed.withValues(alpha: 0.7),
|
||||||
|
'severe': ColorsManager.severePink.withValues(alpha: 0.7),
|
||||||
|
'hazardous': ColorsManager.hazardousPurple.withValues(alpha: 0.7),
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [date, data];
|
||||||
|
}
|
||||||
|
|
||||||
|
class AirQualityPercentageData extends Equatable {
|
||||||
|
const AirQualityPercentageData({
|
||||||
|
required this.type,
|
||||||
|
required this.name,
|
||||||
|
required this.percentage,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String type;
|
||||||
|
final String name;
|
||||||
|
final double percentage;
|
||||||
|
|
||||||
|
factory AirQualityPercentageData.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AirQualityPercentageData(
|
||||||
|
type: json['type'] as String? ?? '',
|
||||||
|
name: json['name'] as String? ?? '',
|
||||||
|
percentage: (json['percentage'] as num?)?.toDouble() ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [type, name, percentage];
|
||||||
|
}
|
@ -23,17 +23,18 @@ class AnalyticsDevice {
|
|||||||
return AnalyticsDevice(
|
return AnalyticsDevice(
|
||||||
uuid: json['uuid'] as String,
|
uuid: json['uuid'] as String,
|
||||||
name: json['name'] as String,
|
name: json['name'] as String,
|
||||||
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null,
|
createdAt: json['createdAt'] != null
|
||||||
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null,
|
? DateTime.parse(json['createdAt'] as String)
|
||||||
|
: null,
|
||||||
|
updatedAt: json['updatedAt'] != null
|
||||||
|
? DateTime.parse(json['updatedAt'] as String)
|
||||||
|
: null,
|
||||||
deviceTuyaUuid: json['deviceTuyaUuid'] as String?,
|
deviceTuyaUuid: json['deviceTuyaUuid'] as String?,
|
||||||
isActive: json['isActive'] as bool?,
|
isActive: json['isActive'] as bool?,
|
||||||
productDevice: json['productDevice'] != null
|
productDevice: json['productDevice'] != null
|
||||||
? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>)
|
? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>)
|
||||||
: null,
|
: null,
|
||||||
spaceUuid: (json['spaces'] as List<dynamic>?)
|
spaceUuid: json['spaceUuid'] as String?,
|
||||||
?.map((e) => e['uuid'])
|
|
||||||
.firstOrNull
|
|
||||||
?.toString(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,8 +61,12 @@ class ProductDevice {
|
|||||||
factory ProductDevice.fromJson(Map<String, dynamic> json) {
|
factory ProductDevice.fromJson(Map<String, dynamic> json) {
|
||||||
return ProductDevice(
|
return ProductDevice(
|
||||||
uuid: json['uuid'] as String?,
|
uuid: json['uuid'] as String?,
|
||||||
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null,
|
createdAt: json['createdAt'] != null
|
||||||
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null,
|
? DateTime.parse(json['createdAt'] as String)
|
||||||
|
: null,
|
||||||
|
updatedAt: json['updatedAt'] != null
|
||||||
|
? DateTime.parse(json['updatedAt'] as String)
|
||||||
|
: null,
|
||||||
catName: json['catName'] as String?,
|
catName: json['catName'] as String?,
|
||||||
prodId: json['prodId'] as String?,
|
prodId: json['prodId'] as String?,
|
||||||
name: json['name'] as String?,
|
name: json['name'] as String?,
|
||||||
|
@ -1,18 +1,49 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
class RangeOfAqi extends Equatable {
|
class RangeOfAqi extends Equatable {
|
||||||
final double min;
|
|
||||||
final double avg;
|
|
||||||
final double max;
|
|
||||||
final DateTime date;
|
final DateTime date;
|
||||||
|
final List<RangeOfAqiValue> data;
|
||||||
|
|
||||||
const RangeOfAqi({
|
const RangeOfAqi({
|
||||||
required this.min,
|
required this.data,
|
||||||
required this.avg,
|
|
||||||
required this.max,
|
|
||||||
required this.date,
|
required this.date,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
factory RangeOfAqi.fromJson(Map<String, dynamic> json) {
|
||||||
|
return RangeOfAqi(
|
||||||
|
date: DateTime.parse(json['date'] as String),
|
||||||
|
data: (json['data'] as List<dynamic>)
|
||||||
|
.map((e) => RangeOfAqiValue.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [min, avg, max, date];
|
List<Object?> get props => [data, date];
|
||||||
|
}
|
||||||
|
|
||||||
|
class RangeOfAqiValue extends Equatable {
|
||||||
|
final String type;
|
||||||
|
final double min;
|
||||||
|
final double average;
|
||||||
|
final double max;
|
||||||
|
|
||||||
|
const RangeOfAqiValue({
|
||||||
|
required this.type,
|
||||||
|
required this.min,
|
||||||
|
required this.average,
|
||||||
|
required this.max,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory RangeOfAqiValue.fromJson(Map<String, dynamic> json) {
|
||||||
|
return RangeOfAqiValue(
|
||||||
|
type: json['type'] as String,
|
||||||
|
min: (json['min'] as num).toDouble(),
|
||||||
|
average: (json['average'] as num).toDouble(),
|
||||||
|
max: (json['max'] as num).toDouble(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [type, min, average, max];
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart';
|
||||||
|
|
||||||
|
part 'air_quality_distribution_event.dart';
|
||||||
|
part 'air_quality_distribution_state.dart';
|
||||||
|
|
||||||
|
class AirQualityDistributionBloc
|
||||||
|
extends Bloc<AirQualityDistributionEvent, AirQualityDistributionState> {
|
||||||
|
final AirQualityDistributionService _aqiDistributionService;
|
||||||
|
|
||||||
|
AirQualityDistributionBloc(
|
||||||
|
this._aqiDistributionService,
|
||||||
|
) : super(const AirQualityDistributionState()) {
|
||||||
|
on<LoadAirQualityDistribution>(_onLoadAirQualityDistribution);
|
||||||
|
on<ClearAirQualityDistribution>(_onClearAirQualityDistribution);
|
||||||
|
on<UpdateAqiTypeEvent>(_onUpdateAqiTypeEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onLoadAirQualityDistribution(
|
||||||
|
LoadAirQualityDistribution event,
|
||||||
|
Emitter<AirQualityDistributionState> emit,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
emit(state.copyWith(status: AirQualityDistributionStatus.loading));
|
||||||
|
final result = await _aqiDistributionService.getAirQualityDistribution(
|
||||||
|
event.param,
|
||||||
|
);
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: AirQualityDistributionStatus.success,
|
||||||
|
chartData: result,
|
||||||
|
filteredChartData: _arrangeChartDataByType(result, state.selectedAqiType),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
AirQualityDistributionState(
|
||||||
|
status: AirQualityDistributionStatus.failure,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
selectedAqiType: state.selectedAqiType,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onClearAirQualityDistribution(
|
||||||
|
ClearAirQualityDistribution event,
|
||||||
|
Emitter<AirQualityDistributionState> emit,
|
||||||
|
) async {
|
||||||
|
emit(const AirQualityDistributionState());
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onUpdateAqiTypeEvent(
|
||||||
|
UpdateAqiTypeEvent event,
|
||||||
|
Emitter<AirQualityDistributionState> emit,
|
||||||
|
) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
selectedAqiType: event.aqiType,
|
||||||
|
filteredChartData: _arrangeChartDataByType(state.chartData, event.aqiType),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AirQualityDataModel> _arrangeChartDataByType(
|
||||||
|
List<AirQualityDataModel> data,
|
||||||
|
AqiType aqiType,
|
||||||
|
) {
|
||||||
|
final filteredData = data.map(
|
||||||
|
(data) => AirQualityDataModel(
|
||||||
|
date: data.date,
|
||||||
|
data: data.data.where((value) => value.type == aqiType.code).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return filteredData.toList();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
part of 'air_quality_distribution_bloc.dart';
|
||||||
|
|
||||||
|
sealed class AirQualityDistributionEvent extends Equatable {
|
||||||
|
const AirQualityDistributionEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class LoadAirQualityDistribution extends AirQualityDistributionEvent {
|
||||||
|
final GetAirQualityDistributionParam param;
|
||||||
|
|
||||||
|
const LoadAirQualityDistribution(this.param);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [param];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class UpdateAqiTypeEvent extends AirQualityDistributionEvent {
|
||||||
|
const UpdateAqiTypeEvent(this.aqiType);
|
||||||
|
|
||||||
|
final AqiType aqiType;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [aqiType];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ClearAirQualityDistribution extends AirQualityDistributionEvent {
|
||||||
|
const ClearAirQualityDistribution();
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
part of 'air_quality_distribution_bloc.dart';
|
||||||
|
|
||||||
|
enum AirQualityDistributionStatus {
|
||||||
|
initial,
|
||||||
|
loading,
|
||||||
|
success,
|
||||||
|
failure,
|
||||||
|
}
|
||||||
|
|
||||||
|
class AirQualityDistributionState extends Equatable {
|
||||||
|
const AirQualityDistributionState({
|
||||||
|
this.status = AirQualityDistributionStatus.initial,
|
||||||
|
this.chartData = const [],
|
||||||
|
this.filteredChartData = const [],
|
||||||
|
this.errorMessage,
|
||||||
|
this.selectedAqiType = AqiType.aqi,
|
||||||
|
});
|
||||||
|
|
||||||
|
final AirQualityDistributionStatus status;
|
||||||
|
final List<AirQualityDataModel> chartData;
|
||||||
|
final List<AirQualityDataModel> filteredChartData;
|
||||||
|
final String? errorMessage;
|
||||||
|
final AqiType selectedAqiType;
|
||||||
|
|
||||||
|
AirQualityDistributionState copyWith({
|
||||||
|
AirQualityDistributionStatus? status,
|
||||||
|
List<AirQualityDataModel>? chartData,
|
||||||
|
List<AirQualityDataModel>? filteredChartData,
|
||||||
|
String? errorMessage,
|
||||||
|
AqiType? selectedAqiType,
|
||||||
|
}) {
|
||||||
|
return AirQualityDistributionState(
|
||||||
|
status: status ?? this.status,
|
||||||
|
chartData: chartData ?? this.chartData,
|
||||||
|
filteredChartData: filteredChartData ?? this.filteredChartData,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
selectedAqiType: selectedAqiType ?? this.selectedAqiType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [status, chartData, errorMessage, selectedAqiType];
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
|
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart';
|
||||||
|
|
||||||
@ -11,6 +12,7 @@ class RangeOfAqiBloc extends Bloc<RangeOfAqiEvent, RangeOfAqiState> {
|
|||||||
RangeOfAqiBloc(this._rangeOfAqiService) : super(const RangeOfAqiState()) {
|
RangeOfAqiBloc(this._rangeOfAqiService) : super(const RangeOfAqiState()) {
|
||||||
on<LoadRangeOfAqiEvent>(_onLoadRangeOfAqiEvent);
|
on<LoadRangeOfAqiEvent>(_onLoadRangeOfAqiEvent);
|
||||||
on<ClearRangeOfAqiEvent>(_onClearRangeOfAqiEvent);
|
on<ClearRangeOfAqiEvent>(_onClearRangeOfAqiEvent);
|
||||||
|
on<UpdateAqiTypeEvent>(_onUpdateAqiTypeEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
final RangeOfAqiService _rangeOfAqiService;
|
final RangeOfAqiService _rangeOfAqiService;
|
||||||
@ -20,19 +22,55 @@ class RangeOfAqiBloc extends Bloc<RangeOfAqiEvent, RangeOfAqiState> {
|
|||||||
Emitter<RangeOfAqiState> emit,
|
Emitter<RangeOfAqiState> emit,
|
||||||
) async {
|
) async {
|
||||||
emit(
|
emit(
|
||||||
RangeOfAqiState(
|
state.copyWith(status: RangeOfAqiStatus.loading),
|
||||||
status: RangeOfAqiStatus.loading,
|
|
||||||
rangeOfAqi: state.rangeOfAqi,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
final rangeOfAqi = await _rangeOfAqiService.load(event.param);
|
final rangeOfAqi = await _rangeOfAqiService.load(event.param);
|
||||||
emit(RangeOfAqiState(status: RangeOfAqiStatus.loaded, rangeOfAqi: rangeOfAqi));
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: RangeOfAqiStatus.loaded,
|
||||||
|
rangeOfAqi: rangeOfAqi,
|
||||||
|
filteredRangeOfAqi: _arrangeChartDataByType(
|
||||||
|
rangeOfAqi,
|
||||||
|
state.selectedAqiType,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(RangeOfAqiState(status: RangeOfAqiStatus.failure, errorMessage: '$e'));
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: RangeOfAqiStatus.failure,
|
||||||
|
errorMessage: '$e',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onUpdateAqiTypeEvent(
|
||||||
|
UpdateAqiTypeEvent event,
|
||||||
|
Emitter<RangeOfAqiState> emit,
|
||||||
|
) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
selectedAqiType: event.aqiType,
|
||||||
|
filteredRangeOfAqi: _arrangeChartDataByType(state.rangeOfAqi, event.aqiType),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RangeOfAqi> _arrangeChartDataByType(
|
||||||
|
List<RangeOfAqi> rangeOfAqi,
|
||||||
|
AqiType aqiType,
|
||||||
|
) {
|
||||||
|
final filteredRangeOfAqi = rangeOfAqi.map(
|
||||||
|
(data) => RangeOfAqi(
|
||||||
|
date: data.date,
|
||||||
|
data: data.data.where((value) => value.type == aqiType.code).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return filteredRangeOfAqi.toList();
|
||||||
|
}
|
||||||
|
|
||||||
void _onClearRangeOfAqiEvent(
|
void _onClearRangeOfAqiEvent(
|
||||||
ClearRangeOfAqiEvent event,
|
ClearRangeOfAqiEvent event,
|
||||||
Emitter<RangeOfAqiState> emit,
|
Emitter<RangeOfAqiState> emit,
|
||||||
|
@ -16,6 +16,15 @@ class LoadRangeOfAqiEvent extends RangeOfAqiEvent {
|
|||||||
List<Object> get props => [param];
|
List<Object> get props => [param];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UpdateAqiTypeEvent extends RangeOfAqiEvent {
|
||||||
|
const UpdateAqiTypeEvent(this.aqiType);
|
||||||
|
|
||||||
|
final AqiType aqiType;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [aqiType];
|
||||||
|
}
|
||||||
|
|
||||||
class ClearRangeOfAqiEvent extends RangeOfAqiEvent {
|
class ClearRangeOfAqiEvent extends RangeOfAqiEvent {
|
||||||
const ClearRangeOfAqiEvent();
|
const ClearRangeOfAqiEvent();
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,35 @@ enum RangeOfAqiStatus { initial, loading, loaded, failure }
|
|||||||
final class RangeOfAqiState extends Equatable {
|
final class RangeOfAqiState extends Equatable {
|
||||||
const RangeOfAqiState({
|
const RangeOfAqiState({
|
||||||
this.rangeOfAqi = const [],
|
this.rangeOfAqi = const [],
|
||||||
|
this.filteredRangeOfAqi = const [],
|
||||||
this.status = RangeOfAqiStatus.initial,
|
this.status = RangeOfAqiStatus.initial,
|
||||||
this.errorMessage,
|
this.errorMessage,
|
||||||
|
this.selectedAqiType = AqiType.aqi,
|
||||||
});
|
});
|
||||||
|
|
||||||
final RangeOfAqiStatus status;
|
final RangeOfAqiStatus status;
|
||||||
final List<RangeOfAqi> rangeOfAqi;
|
final List<RangeOfAqi> rangeOfAqi;
|
||||||
|
final List<RangeOfAqi> filteredRangeOfAqi;
|
||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
|
final AqiType selectedAqiType;
|
||||||
|
|
||||||
|
RangeOfAqiState copyWith({
|
||||||
|
RangeOfAqiStatus? status,
|
||||||
|
List<RangeOfAqi>? rangeOfAqi,
|
||||||
|
List<RangeOfAqi>? filteredRangeOfAqi,
|
||||||
|
String? errorMessage,
|
||||||
|
AqiType? selectedAqiType,
|
||||||
|
}) {
|
||||||
|
return RangeOfAqiState(
|
||||||
|
status: status ?? this.status,
|
||||||
|
rangeOfAqi: rangeOfAqi ?? this.rangeOfAqi,
|
||||||
|
filteredRangeOfAqi: filteredRangeOfAqi ?? this.filteredRangeOfAqi,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
selectedAqiType: selectedAqiType ?? this.selectedAqiType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [status, rangeOfAqi, errorMessage];
|
List<Object?> get props =>
|
||||||
|
[status, rangeOfAqi, filteredRangeOfAqi, errorMessage, selectedAqiType];
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
||||||
|
|
||||||
@ -13,8 +14,10 @@ abstract final class FetchAirQualityDataHelper {
|
|||||||
|
|
||||||
static void loadAirQualityData(
|
static void loadAirQualityData(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
|
required DateTime date,
|
||||||
required String communityUuid,
|
required String communityUuid,
|
||||||
required String spaceUuid,
|
required String spaceUuid,
|
||||||
|
bool shouldFetchAnalyticsDevices = true,
|
||||||
}) {
|
}) {
|
||||||
final date = context.read<AnalyticsDatePickerBloc>().state.monthlyDate;
|
final date = context.read<AnalyticsDatePickerBloc>().state.monthlyDate;
|
||||||
loadAnalyticsDevices(
|
loadAnalyticsDevices(
|
||||||
@ -26,7 +29,11 @@ abstract final class FetchAirQualityDataHelper {
|
|||||||
context,
|
context,
|
||||||
spaceUuid: spaceUuid,
|
spaceUuid: spaceUuid,
|
||||||
date: date,
|
date: date,
|
||||||
aqiType: AqiType.aqi,
|
);
|
||||||
|
loadAirQualityDistribution(
|
||||||
|
context,
|
||||||
|
spaceUuid: spaceUuid,
|
||||||
|
date: date,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +44,9 @@ abstract final class FetchAirQualityDataHelper {
|
|||||||
context.read<RealtimeDeviceChangesBloc>().add(
|
context.read<RealtimeDeviceChangesBloc>().add(
|
||||||
const RealtimeDeviceChangesClosed(),
|
const RealtimeDeviceChangesClosed(),
|
||||||
);
|
);
|
||||||
|
context.read<AirQualityDistributionBloc>().add(
|
||||||
|
const ClearAirQualityDistribution(),
|
||||||
|
);
|
||||||
context.read<RangeOfAqiBloc>().add(const ClearRangeOfAqiEvent());
|
context.read<RangeOfAqiBloc>().add(const ClearRangeOfAqiEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,16 +76,26 @@ abstract final class FetchAirQualityDataHelper {
|
|||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required String spaceUuid,
|
required String spaceUuid,
|
||||||
required DateTime date,
|
required DateTime date,
|
||||||
required AqiType aqiType,
|
|
||||||
}) {
|
}) {
|
||||||
context.read<RangeOfAqiBloc>().add(
|
context.read<RangeOfAqiBloc>().add(
|
||||||
LoadRangeOfAqiEvent(
|
LoadRangeOfAqiEvent(
|
||||||
GetRangeOfAqiParam(
|
GetRangeOfAqiParam(
|
||||||
date: date,
|
date: date,
|
||||||
spaceUuid: spaceUuid,
|
spaceUuid: spaceUuid,
|
||||||
aqiType: aqiType,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void loadAirQualityDistribution(
|
||||||
|
BuildContext context, {
|
||||||
|
required String spaceUuid,
|
||||||
|
required DateTime date,
|
||||||
|
}) {
|
||||||
|
context.read<AirQualityDistributionBloc>().add(
|
||||||
|
LoadAirQualityDistribution(
|
||||||
|
GetAirQualityDistributionParam(spaceUuid: spaceUuid, date: date),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_box.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart';
|
||||||
|
|
||||||
class AirQualityView extends StatelessWidget {
|
class AirQualityView extends StatelessWidget {
|
||||||
@ -23,8 +24,14 @@ class AirQualityView extends StatelessWidget {
|
|||||||
height: height * 1.2,
|
height: height * 1.2,
|
||||||
child: const AirQualityEndSideWidget(),
|
child: const AirQualityEndSideWidget(),
|
||||||
),
|
),
|
||||||
SizedBox(height: height * 0.5, child: const RangeOfAqiChartBox()),
|
SizedBox(
|
||||||
SizedBox(height: height * 0.5, child: const Placeholder()),
|
height: height * 0.5,
|
||||||
|
child: const RangeOfAqiChartBox(),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: height * 0.5,
|
||||||
|
child: const AqiDistributionChartBox(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -46,7 +53,7 @@ class AirQualityView extends StatelessWidget {
|
|||||||
spacing: 20,
|
spacing: 20,
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: RangeOfAqiChartBox()),
|
Expanded(child: RangeOfAqiChartBox()),
|
||||||
Expanded(child: Placeholder()),
|
Expanded(child: AqiDistributionChartBox()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -0,0 +1,174 @@
|
|||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/air_quality_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';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class AqiDistributionChart extends StatelessWidget {
|
||||||
|
const AqiDistributionChart({super.key, required this.chartData});
|
||||||
|
final List<AirQualityDataModel> chartData;
|
||||||
|
|
||||||
|
static const _rodStackItemsSpacing = 0.4;
|
||||||
|
static const _barWidth = 13.0;
|
||||||
|
static final _barBorderRadius = BorderRadius.circular(22);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final sortedData = List<AirQualityDataModel>.from(chartData)
|
||||||
|
..sort(
|
||||||
|
(a, b) => a.date.compareTo(b.date),
|
||||||
|
);
|
||||||
|
|
||||||
|
return BarChart(
|
||||||
|
BarChartData(
|
||||||
|
maxY: 100.1,
|
||||||
|
gridData: EnergyManagementChartsHelper.gridData(
|
||||||
|
horizontalInterval: 20,
|
||||||
|
),
|
||||||
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
|
barTouchData: _barTouchData(context),
|
||||||
|
titlesData: _titlesData(context),
|
||||||
|
barGroups: _buildBarGroups(sortedData),
|
||||||
|
),
|
||||||
|
duration: Duration.zero,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BarChartGroupData> _buildBarGroups(List<AirQualityDataModel> sortedData) {
|
||||||
|
return List.generate(sortedData.length, (index) {
|
||||||
|
final data = sortedData[index];
|
||||||
|
final stackItems = <BarChartRodData>[];
|
||||||
|
double currentY = 0;
|
||||||
|
bool isFirstElement = true;
|
||||||
|
|
||||||
|
// Sort data by type to ensure consistent order
|
||||||
|
final sortedPercentageData = List<AirQualityPercentageData>.from(data.data)
|
||||||
|
..sort((a, b) => a.type.compareTo(b.type));
|
||||||
|
|
||||||
|
for (final percentageData in sortedPercentageData) {
|
||||||
|
stackItems.add(
|
||||||
|
BarChartRodData(
|
||||||
|
fromY: currentY,
|
||||||
|
toY: currentY + percentageData.percentage ,
|
||||||
|
color: AirQualityDataModel.metricColors[percentageData.name]!,
|
||||||
|
borderRadius: isFirstElement
|
||||||
|
? const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(22),
|
||||||
|
topRight: Radius.circular(22),
|
||||||
|
)
|
||||||
|
: _barBorderRadius,
|
||||||
|
width: _barWidth,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
currentY += percentageData.percentage + _rodStackItemsSpacing;
|
||||||
|
isFirstElement = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BarChartGroupData(
|
||||||
|
x: index,
|
||||||
|
barRods: stackItems,
|
||||||
|
groupVertically: true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
BarTouchData _barTouchData(BuildContext context) {
|
||||||
|
return BarTouchData(
|
||||||
|
touchTooltipData: BarTouchTooltipData(
|
||||||
|
getTooltipColor: (_) => ColorsManager.whiteColors,
|
||||||
|
tooltipBorder: const BorderSide(
|
||||||
|
color: ColorsManager.semiTransparentBlack,
|
||||||
|
),
|
||||||
|
tooltipRoundedRadius: 16,
|
||||||
|
tooltipPadding: const EdgeInsets.all(8),
|
||||||
|
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
||||||
|
final data = chartData[group.x.toInt()];
|
||||||
|
|
||||||
|
final List<TextSpan> children = [];
|
||||||
|
|
||||||
|
final textStyle = context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontSize: 12,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sort data by type to ensure consistent order
|
||||||
|
final sortedPercentageData = List<AirQualityPercentageData>.from(data.data)
|
||||||
|
..sort((a, b) => a.type.compareTo(b.type));
|
||||||
|
|
||||||
|
for (final percentageData in sortedPercentageData) {
|
||||||
|
children.add(TextSpan(
|
||||||
|
text:
|
||||||
|
'\n${percentageData.type.toUpperCase()}: ${percentageData.percentage.toStringAsFixed(1)}%',
|
||||||
|
style: textStyle,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return BarTooltipItem(
|
||||||
|
DateFormat('dd/MM/yyyy').format(data.date),
|
||||||
|
context.textTheme.bodyMedium!.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FlTitlesData _titlesData(BuildContext context) {
|
||||||
|
final titlesData = EnergyManagementChartsHelper.titlesData(
|
||||||
|
context,
|
||||||
|
leftTitlesInterval: 20,
|
||||||
|
);
|
||||||
|
|
||||||
|
final leftTitles = titlesData.leftTitles.copyWith(
|
||||||
|
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
||||||
|
reservedSize: 70,
|
||||||
|
interval: 20,
|
||||||
|
maxIncluded: false,
|
||||||
|
minIncluded: true,
|
||||||
|
getTitlesWidget: (value, meta) => Padding(
|
||||||
|
padding: const EdgeInsetsDirectional.only(end: 12),
|
||||||
|
child: FittedBox(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text(
|
||||||
|
'${value.toStringAsFixed(0)}%',
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
color: ColorsManager.lightGreyColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final bottomTitles = AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
getTitlesWidget: (value, _) => FittedBox(
|
||||||
|
alignment: AlignmentDirectional.bottomCenter,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text(
|
||||||
|
chartData[value.toInt()].date.day.toString(),
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.lightGreyColor,
|
||||||
|
fontSize: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
reservedSize: 36,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return titlesData.copyWith(
|
||||||
|
leftTitles: leftTitles,
|
||||||
|
bottomTitles: bottomTitles,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
||||||
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
|
class AqiDistributionChartBox extends StatelessWidget {
|
||||||
|
const AqiDistributionChartBox({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<AirQualityDistributionBloc, AirQualityDistributionState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsetsDirectional.all(30),
|
||||||
|
decoration: subSectionContainerDecoration.copyWith(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (state.errorMessage != null) ...[
|
||||||
|
AnalyticsErrorWidget(state.errorMessage),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
],
|
||||||
|
AqiDistributionChartTitle(
|
||||||
|
isLoading: state.status == AirQualityDistributionStatus.loading,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
const Divider(),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Expanded(
|
||||||
|
child: AqiDistributionChart(chartData: state.filteredChartData),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.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';
|
||||||
|
|
||||||
|
class AqiDistributionChartTitle extends StatelessWidget {
|
||||||
|
const AqiDistributionChartTitle({required this.isLoading, super.key});
|
||||||
|
|
||||||
|
final bool isLoading;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
ChartsLoadingWidget(isLoading: isLoading),
|
||||||
|
const Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
child: ChartTitle(
|
||||||
|
title: Text('Distribution over Air Quality Index'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FittedBox(
|
||||||
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: AqiTypeDropdown(
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
context
|
||||||
|
.read<AirQualityDistributionBloc>()
|
||||||
|
.add(UpdateAqiTypeEvent(value));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -3,17 +3,18 @@ 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';
|
||||||
|
|
||||||
enum AqiType {
|
enum AqiType {
|
||||||
aqi('AQI', ''),
|
aqi('AQI', '', 'aqi'),
|
||||||
pm25('PM2.5', 'µg/m³'),
|
pm25('PM2.5', 'µg/m³', 'pm25'),
|
||||||
pm10('PM10', 'µg/m³'),
|
pm10('PM10', 'µg/m³', 'pm10'),
|
||||||
hcho('HCHO', 'mg/m³'),
|
hcho('HCHO', 'mg/m³', 'hcho'),
|
||||||
tvoc('TVOC', 'µg/m³'),
|
tvoc('TVOC', 'µg/m³', 'tvoc'),
|
||||||
co2('CO2', 'ppm');
|
co2('CO2', 'ppm', 'co2');
|
||||||
|
|
||||||
const AqiType(this.value, this.unit);
|
const AqiType(this.value, this.unit, this.code);
|
||||||
|
|
||||||
final String value;
|
final String value;
|
||||||
final String unit;
|
final String unit;
|
||||||
|
final String code;
|
||||||
}
|
}
|
||||||
|
|
||||||
class AqiTypeDropdown extends StatefulWidget {
|
class AqiTypeDropdown extends StatefulWidget {
|
||||||
|
@ -13,23 +13,37 @@ class RangeOfAqiChart extends StatelessWidget {
|
|||||||
required this.chartData,
|
required this.chartData,
|
||||||
});
|
});
|
||||||
|
|
||||||
List<(List<double> values, Color color, Color? dotColor)> get _lines => [
|
List<(List<double> values, Color color, Color? dotColor)> get _lines {
|
||||||
(
|
final sortedData = List<RangeOfAqi>.from(chartData)
|
||||||
chartData.map((e) => e.max).toList(),
|
..sort((a, b) => a.date.compareTo(b.date));
|
||||||
ColorsManager.maxPurple,
|
|
||||||
ColorsManager.maxPurpleDot,
|
return [
|
||||||
),
|
(
|
||||||
(
|
sortedData.map((e) {
|
||||||
chartData.map((e) => e.avg).toList(),
|
final value = e.data.firstOrNull;
|
||||||
Colors.white,
|
return value?.max ?? 0;
|
||||||
null,
|
}).toList(),
|
||||||
),
|
ColorsManager.maxPurple,
|
||||||
(
|
ColorsManager.maxPurpleDot,
|
||||||
chartData.map((e) => e.min).toList(),
|
),
|
||||||
ColorsManager.minBlue,
|
(
|
||||||
ColorsManager.minBlueDot,
|
sortedData.map((e) {
|
||||||
),
|
final value = e.data.firstOrNull;
|
||||||
];
|
return value?.average ?? 0;
|
||||||
|
}).toList(),
|
||||||
|
Colors.white,
|
||||||
|
null,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
sortedData.map((e) {
|
||||||
|
final value = e.data.firstOrNull;
|
||||||
|
return value?.min ?? 0;
|
||||||
|
}).toList(),
|
||||||
|
ColorsManager.minBlue,
|
||||||
|
ColorsManager.minBlueDot,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -32,7 +32,7 @@ class RangeOfAqiChartBox extends StatelessWidget {
|
|||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Expanded(child: RangeOfAqiChart(chartData: state.rangeOfAqi)),
|
Expanded(child: RangeOfAqiChart(chartData: state.filteredRangeOfAqi)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.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/chart_informative_cell.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/chart_informative_cell.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
|
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||||
|
|
||||||
class RangeOfAqiChartTitle extends StatelessWidget {
|
class RangeOfAqiChartTitle extends StatelessWidget {
|
||||||
const RangeOfAqiChartTitle({required this.isLoading, super.key});
|
const RangeOfAqiChartTitle({
|
||||||
|
required this.isLoading,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
|
|
||||||
static const List<(Color color, String title, bool hasBorder)> _colors = [
|
static const List<(Color color, String title, bool hasBorder)> _colors = [
|
||||||
@ -66,12 +69,9 @@ class RangeOfAqiChartTitle extends StatelessWidget {
|
|||||||
|
|
||||||
if (spaceUuid == null) return;
|
if (spaceUuid == null) return;
|
||||||
|
|
||||||
FetchAirQualityDataHelper.loadRangeOfAqi(
|
if (value != null) {
|
||||||
context,
|
context.read<RangeOfAqiBloc>().add(UpdateAqiTypeEvent(value));
|
||||||
spaceUuid: spaceUuid,
|
}
|
||||||
date: context.read<AnalyticsDatePickerBloc>().state.monthlyDate,
|
|
||||||
aqiType: value ?? AqiType.aqi,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.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/strategies/analytics_data_loading_strategy.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
||||||
@ -39,6 +40,7 @@ final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
|||||||
context,
|
context,
|
||||||
communityUuid: community.uuid,
|
communityUuid: community.uuid,
|
||||||
spaceUuid: space.uuid ?? '',
|
spaceUuid: space.uuid ?? '',
|
||||||
|
date: context.read<AnalyticsDatePickerBloc>().state.monthlyDate,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
@ -13,6 +14,7 @@ import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/real
|
|||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/fake_air_quality_distribution_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart';
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart';
|
||||||
@ -101,6 +103,11 @@ class _AnalyticsPageState extends State<AnalyticsPage> {
|
|||||||
FakeRangeOfAqiService(),
|
FakeRangeOfAqiService(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => AirQualityDistributionBloc(
|
||||||
|
FakeAirQualityDistributionService(),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: const AnalyticsPageForm(),
|
child: const AnalyticsPageForm(),
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
|
||||||
@ -56,33 +57,16 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
|||||||
const Spacer(),
|
const Spacer(),
|
||||||
Visibility(
|
Visibility(
|
||||||
key: ValueKey(selectedTab),
|
key: ValueKey(selectedTab),
|
||||||
visible: selectedTab == AnalyticsPageTab.energyManagement,
|
visible: selectedTab == AnalyticsPageTab.energyManagement ||
|
||||||
|
selectedTab == AnalyticsPageTab.airQuality,
|
||||||
child: Expanded(
|
child: Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
alignment: AlignmentDirectional.centerEnd,
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
child: AnalyticsDateFilterButton(
|
child: AnalyticsDateFilterButton(
|
||||||
onDateSelected: (DateTime value) {
|
onDateSelected: (value) {
|
||||||
context.read<AnalyticsDatePickerBloc>().add(
|
_onDateChanged(context, value, selectedTab);
|
||||||
UpdateAnalyticsDatePickerEvent(montlyDate: value),
|
|
||||||
);
|
|
||||||
|
|
||||||
final spaceTreeState =
|
|
||||||
context.read<SpaceTreeBloc>().state;
|
|
||||||
if (spaceTreeState.selectedSpaces.isNotEmpty) {
|
|
||||||
FetchEnergyManagementDataHelper
|
|
||||||
.loadEnergyManagementData(
|
|
||||||
context,
|
|
||||||
shouldFetchAnalyticsDevices: false,
|
|
||||||
selectedDate: value,
|
|
||||||
communityId:
|
|
||||||
spaceTreeState.selectedCommunities.firstOrNull ??
|
|
||||||
'',
|
|
||||||
spaceId:
|
|
||||||
spaceTreeState.selectedSpaces.firstOrNull ?? '',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
selectedDate: context
|
selectedDate: context
|
||||||
.watch<AnalyticsDatePickerBloc>()
|
.watch<AnalyticsDatePickerBloc>()
|
||||||
@ -112,4 +96,73 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
|||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onDateChanged(
|
||||||
|
BuildContext context,
|
||||||
|
DateTime date,
|
||||||
|
AnalyticsPageTab selectedTab,
|
||||||
|
) {
|
||||||
|
context.read<AnalyticsDatePickerBloc>().add(
|
||||||
|
UpdateAnalyticsDatePickerEvent(montlyDate: date),
|
||||||
|
);
|
||||||
|
|
||||||
|
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||||
|
final communities = spaceTreeState.selectedCommunities;
|
||||||
|
final spaces = spaceTreeState.selectedSpaces;
|
||||||
|
if (spaceTreeState.selectedSpaces.isNotEmpty) {
|
||||||
|
switch (selectedTab) {
|
||||||
|
case AnalyticsPageTab.energyManagement:
|
||||||
|
_onEnergyManagementDateChanged(
|
||||||
|
context,
|
||||||
|
date: date,
|
||||||
|
communityUuid: communities.firstOrNull ?? '',
|
||||||
|
spaceUuid: spaces.firstOrNull ?? '',
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case AnalyticsPageTab.airQuality:
|
||||||
|
_onAirQualityDateChanged(
|
||||||
|
context,
|
||||||
|
date: date,
|
||||||
|
communityUuid: communities.firstOrNull ?? '',
|
||||||
|
spaceUuid: spaces.firstOrNull ?? '',
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onEnergyManagementDateChanged(
|
||||||
|
BuildContext context, {
|
||||||
|
required DateTime date,
|
||||||
|
required String communityUuid,
|
||||||
|
required String spaceUuid,
|
||||||
|
}) {
|
||||||
|
context.read<AnalyticsDatePickerBloc>().add(
|
||||||
|
UpdateAnalyticsDatePickerEvent(montlyDate: date),
|
||||||
|
);
|
||||||
|
|
||||||
|
FetchEnergyManagementDataHelper.loadEnergyManagementData(
|
||||||
|
context,
|
||||||
|
shouldFetchAnalyticsDevices: false,
|
||||||
|
selectedDate: date,
|
||||||
|
communityId: communityUuid,
|
||||||
|
spaceId: spaceUuid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onAirQualityDateChanged(
|
||||||
|
BuildContext context, {
|
||||||
|
required DateTime date,
|
||||||
|
required String communityUuid,
|
||||||
|
required String spaceUuid,
|
||||||
|
}) {
|
||||||
|
FetchAirQualityDataHelper.loadAirQualityData(
|
||||||
|
context,
|
||||||
|
date: date,
|
||||||
|
communityUuid: communityUuid,
|
||||||
|
spaceUuid: spaceUuid,
|
||||||
|
shouldFetchAnalyticsDevices: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ abstract final class EnergyManagementChartsHelper {
|
|||||||
sideTitles: SideTitles(
|
sideTitles: SideTitles(
|
||||||
showTitles: true,
|
showTitles: true,
|
||||||
maxIncluded: false,
|
maxIncluded: false,
|
||||||
minIncluded: false,
|
minIncluded: true,
|
||||||
interval: leftTitlesInterval,
|
interval: leftTitlesInterval,
|
||||||
reservedSize: 110,
|
reservedSize: 110,
|
||||||
getTitlesWidget: (value, meta) => Padding(
|
getTitlesWidget: (value, meta) => Padding(
|
||||||
|
@ -16,7 +16,6 @@ import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_
|
|||||||
abstract final class FetchEnergyManagementDataHelper {
|
abstract final class FetchEnergyManagementDataHelper {
|
||||||
const FetchEnergyManagementDataHelper._();
|
const FetchEnergyManagementDataHelper._();
|
||||||
|
|
||||||
// static const String _powerClampId = 'cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa';
|
|
||||||
static AnalyticsDevice? getSelectedDevice(BuildContext context) {
|
static AnalyticsDevice? getSelectedDevice(BuildContext context) {
|
||||||
return context.read<AnalyticsDevicesBloc>().state.selectedDevice;
|
return context.read<AnalyticsDevicesBloc>().state.selectedDevice;
|
||||||
}
|
}
|
||||||
@ -48,7 +47,6 @@ abstract final class FetchEnergyManagementDataHelper {
|
|||||||
loadTotalEnergyConsumption(
|
loadTotalEnergyConsumption(
|
||||||
context,
|
context,
|
||||||
selectedDate: selectedDate0,
|
selectedDate: selectedDate0,
|
||||||
communityId: communityId,
|
|
||||||
spaceId: spaceId,
|
spaceId: spaceId,
|
||||||
);
|
);
|
||||||
final selectedDevice = getSelectedDevice(context);
|
final selectedDevice = getSelectedDevice(context);
|
||||||
@ -61,7 +59,6 @@ abstract final class FetchEnergyManagementDataHelper {
|
|||||||
}
|
}
|
||||||
loadEnergyConsumptionPerDevice(
|
loadEnergyConsumptionPerDevice(
|
||||||
context,
|
context,
|
||||||
communityId: communityId,
|
|
||||||
spaceId: spaceId,
|
spaceId: spaceId,
|
||||||
selectedDate: selectedDate0,
|
selectedDate: selectedDate0,
|
||||||
);
|
);
|
||||||
@ -84,12 +81,10 @@ abstract final class FetchEnergyManagementDataHelper {
|
|||||||
static void loadTotalEnergyConsumption(
|
static void loadTotalEnergyConsumption(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
DateTime? selectedDate,
|
DateTime? selectedDate,
|
||||||
required String communityId,
|
|
||||||
required String spaceId,
|
required String spaceId,
|
||||||
}) {
|
}) {
|
||||||
final param = GetTotalEnergyConsumptionParam(
|
final param = GetTotalEnergyConsumptionParam(
|
||||||
spaceId: spaceId,
|
spaceId: spaceId,
|
||||||
communityId: communityId,
|
|
||||||
monthDate: selectedDate,
|
monthDate: selectedDate,
|
||||||
);
|
);
|
||||||
context.read<TotalEnergyConsumptionBloc>().add(
|
context.read<TotalEnergyConsumptionBloc>().add(
|
||||||
@ -100,12 +95,10 @@ abstract final class FetchEnergyManagementDataHelper {
|
|||||||
static void loadEnergyConsumptionPerDevice(
|
static void loadEnergyConsumptionPerDevice(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
DateTime? selectedDate,
|
DateTime? selectedDate,
|
||||||
required String communityId,
|
|
||||||
required String spaceId,
|
required String spaceId,
|
||||||
}) {
|
}) {
|
||||||
final param = GetEnergyConsumptionPerDeviceParam(
|
final param = GetEnergyConsumptionPerDeviceParam(
|
||||||
spaceId: spaceId,
|
spaceId: spaceId,
|
||||||
communityId: communityId,
|
|
||||||
monthDate: selectedDate,
|
monthDate: selectedDate,
|
||||||
);
|
);
|
||||||
context.read<EnergyConsumptionPerDeviceBloc>().add(
|
context.read<EnergyConsumptionPerDeviceBloc>().add(
|
||||||
|
@ -23,7 +23,6 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
padding: const EdgeInsets.all(30),
|
padding: const EdgeInsets.all(30),
|
||||||
child: Column(
|
child: Column(
|
||||||
spacing: 20,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
AnalyticsErrorWidget(state.errorMessage),
|
AnalyticsErrorWidget(state.errorMessage),
|
||||||
@ -52,7 +51,9 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
const Divider(height: 0),
|
const Divider(height: 0),
|
||||||
|
const SizedBox(height: 20),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: EnergyConsumptionPerDeviceChart(chartData: state.chartData),
|
child: EnergyConsumptionPerDeviceChart(chartData: state.chartData),
|
||||||
),
|
),
|
||||||
|
@ -41,7 +41,7 @@ class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget {
|
|||||||
.color;
|
.color;
|
||||||
|
|
||||||
return Tooltip(
|
return Tooltip(
|
||||||
message: '${device.name}\n${device.productDevice?.uuid ?? ''}',
|
message: '${device.name}\n${device.spaceUuid ?? ''}',
|
||||||
child: ChartInformativeCell(title: Text(device.name), color: deviceColor),
|
child: ChartInformativeCell(title: Text(device.name), color: deviceColor),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
|
|||||||
AnalyticsErrorWidget(state.errorMessage),
|
AnalyticsErrorWidget(state.errorMessage),
|
||||||
AnalyticsSidebarHeader(
|
AnalyticsSidebarHeader(
|
||||||
title: 'Smart Power Clamp',
|
title: 'Smart Power Clamp',
|
||||||
showSpaceUuid: true,
|
showSpaceUuidInDevicesDropdown: true,
|
||||||
onChanged: (device) {
|
onChanged: (device) {
|
||||||
FetchEnergyManagementDataHelper.loadEnergyConsumptionByPhases(
|
FetchEnergyManagementDataHelper.loadEnergyConsumptionByPhases(
|
||||||
context,
|
context,
|
||||||
|
@ -19,7 +19,6 @@ class TotalEnergyConsumptionChartBox extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
padding: const EdgeInsets.all(30),
|
padding: const EdgeInsets.all(30),
|
||||||
child: Column(
|
child: Column(
|
||||||
spacing: 20,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
AnalyticsErrorWidget(state.errorMessage),
|
AnalyticsErrorWidget(state.errorMessage),
|
||||||
@ -39,7 +38,9 @@ class TotalEnergyConsumptionChartBox extends StatelessWidget {
|
|||||||
const Spacer(flex: 4),
|
const Spacer(flex: 4),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
const SizedBox(height: 20),
|
||||||
TotalEnergyConsumptionChart(chartData: state.chartData),
|
TotalEnergyConsumptionChart(chartData: state.chartData),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -16,7 +16,7 @@ class OccupancyChart extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BarChart(
|
return BarChart(
|
||||||
BarChartData(
|
BarChartData(
|
||||||
maxY: 100.0,
|
maxY: 100.001,
|
||||||
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
||||||
checkToShowHorizontalLine: (value) => true,
|
checkToShowHorizontalLine: (value) => true,
|
||||||
horizontalInterval: 20,
|
horizontalInterval: 20,
|
||||||
@ -134,7 +134,7 @@ class OccupancyChart extends StatelessWidget {
|
|||||||
alignment: AlignmentDirectional.bottomCenter,
|
alignment: AlignmentDirectional.bottomCenter,
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
child: Text(
|
child: Text(
|
||||||
(value + 1).toString(),
|
chartData[value.toInt()].date.day.toString(),
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
color: ColorsManager.greyColor,
|
color: ColorsManager.greyColor,
|
||||||
fontSize: 8,
|
fontSize: 8,
|
||||||
|
@ -22,7 +22,6 @@ class OccupancyChartBox extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.all(30),
|
padding: const EdgeInsets.all(30),
|
||||||
decoration: containerWhiteDecoration,
|
decoration: containerWhiteDecoration,
|
||||||
child: Column(
|
child: Column(
|
||||||
spacing: 20,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -65,7 +64,9 @@ class OccupancyChartBox extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const Divider(height: 0),
|
const SizedBox(height: 20),
|
||||||
|
const Divider(),
|
||||||
|
const SizedBox(height: 20),
|
||||||
Expanded(child: OccupancyChart(chartData: state.chartData)),
|
Expanded(child: OccupancyChart(chartData: state.chartData)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -22,7 +22,6 @@ class OccupancyHeatMapBox extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.all(30),
|
padding: const EdgeInsets.all(30),
|
||||||
decoration: containerWhiteDecoration,
|
decoration: containerWhiteDecoration,
|
||||||
child: Column(
|
child: Column(
|
||||||
spacing: 20,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -66,7 +65,9 @@ class OccupancyHeatMapBox extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const Divider(height: 0),
|
const SizedBox(height: 20),
|
||||||
|
const Divider(),
|
||||||
|
const SizedBox(height: 20),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: OccupancyHeatMap(
|
child: OccupancyHeatMap(
|
||||||
heatMapData: state.heatMapData.asMap().map(
|
heatMapData: state.heatMapData.asMap().map(
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
class GetAirQualityDistributionParam {
|
||||||
|
final DateTime date;
|
||||||
|
final String spaceUuid;
|
||||||
|
|
||||||
|
const GetAirQualityDistributionParam({
|
||||||
|
required this.date,
|
||||||
|
required this.spaceUuid,
|
||||||
|
});
|
||||||
|
}
|
@ -2,18 +2,15 @@ class GetEnergyConsumptionPerDeviceParam {
|
|||||||
const GetEnergyConsumptionPerDeviceParam({
|
const GetEnergyConsumptionPerDeviceParam({
|
||||||
this.monthDate,
|
this.monthDate,
|
||||||
this.spaceId,
|
this.spaceId,
|
||||||
this.communityId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final DateTime? monthDate;
|
final DateTime? monthDate;
|
||||||
final String? spaceId;
|
final String? spaceId;
|
||||||
final String? communityId;
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
'monthDate':
|
'monthDate':
|
||||||
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
|
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
|
||||||
if (spaceId == null || spaceId == null) 'spaceUuid': spaceId,
|
if (spaceId == null || spaceId == null) 'spaceUuid': spaceId,
|
||||||
'communityUuid': communityId,
|
|
||||||
'groupByDevice': true,
|
'groupByDevice': true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
|
||||||
|
|
||||||
class GetRangeOfAqiParam extends Equatable {
|
class GetRangeOfAqiParam extends Equatable {
|
||||||
final DateTime date;
|
final DateTime date;
|
||||||
final String spaceUuid;
|
final String spaceUuid;
|
||||||
final AqiType aqiType;
|
|
||||||
|
|
||||||
const GetRangeOfAqiParam(
|
const GetRangeOfAqiParam({
|
||||||
{
|
|
||||||
required this.date,
|
required this.date,
|
||||||
required this.spaceUuid,
|
required this.spaceUuid,
|
||||||
required this.aqiType,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
class GetTotalEnergyConsumptionParam {
|
class GetTotalEnergyConsumptionParam {
|
||||||
final DateTime? monthDate;
|
final DateTime? monthDate;
|
||||||
final String? spaceId;
|
final String? spaceId;
|
||||||
final String? communityId;
|
|
||||||
|
|
||||||
const GetTotalEnergyConsumptionParam({
|
const GetTotalEnergyConsumptionParam({
|
||||||
this.monthDate,
|
this.monthDate,
|
||||||
this.spaceId,
|
this.spaceId,
|
||||||
this.communityId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
@ -14,7 +12,6 @@ class GetTotalEnergyConsumptionParam {
|
|||||||
'monthDate':
|
'monthDate':
|
||||||
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
|
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
|
||||||
if (spaceId == null || spaceId == null) 'spaceUuid': spaceId,
|
if (spaceId == null || spaceId == null) 'spaceUuid': spaceId,
|
||||||
'communityUuid': communityId,
|
|
||||||
'groupByDevice': false,
|
'groupByDevice': false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
||||||
|
|
||||||
|
abstract interface class AirQualityDistributionService {
|
||||||
|
Future<List<AirQualityDataModel>> getAirQualityDistribution(
|
||||||
|
GetAirQualityDistributionParam param,
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart';
|
||||||
|
|
||||||
|
class FakeAirQualityDistributionService implements AirQualityDistributionService {
|
||||||
|
final _random = Random();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<AirQualityDataModel>> getAirQualityDistribution(
|
||||||
|
GetAirQualityDistributionParam param,
|
||||||
|
) async {
|
||||||
|
return Future.delayed(
|
||||||
|
const Duration(milliseconds: 400),
|
||||||
|
() => List.generate(30, (index) {
|
||||||
|
final date = DateTime(2025, 5, 1).add(Duration(days: index));
|
||||||
|
|
||||||
|
final values = _generateRandomPercentages();
|
||||||
|
final nullMask = List.generate(6, (_) => _shouldBeNull());
|
||||||
|
|
||||||
|
if (nullMask.every((isNull) => isNull)) {
|
||||||
|
nullMask[_random.nextInt(6)] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final nonNullValues = _redistributePercentages(values, nullMask);
|
||||||
|
|
||||||
|
return AirQualityDataModel(
|
||||||
|
date: date,
|
||||||
|
data: [
|
||||||
|
AirQualityPercentageData(
|
||||||
|
type: AqiType.aqi.code,
|
||||||
|
percentage: nonNullValues[0],
|
||||||
|
name: 'good',
|
||||||
|
),
|
||||||
|
AirQualityPercentageData(
|
||||||
|
name: 'moderate',
|
||||||
|
type: AqiType.co2.code,
|
||||||
|
percentage: nonNullValues[1],
|
||||||
|
),
|
||||||
|
AirQualityPercentageData(
|
||||||
|
name: 'poor',
|
||||||
|
percentage: nonNullValues[2],
|
||||||
|
type: AqiType.hcho.code,
|
||||||
|
|
||||||
|
),
|
||||||
|
AirQualityPercentageData(
|
||||||
|
name: 'unhealthy',
|
||||||
|
percentage: nonNullValues[3],
|
||||||
|
type: AqiType.pm10.code,
|
||||||
|
),
|
||||||
|
AirQualityPercentageData(
|
||||||
|
name: 'severe',
|
||||||
|
type: AqiType.pm25.code,
|
||||||
|
percentage: nonNullValues[4],
|
||||||
|
),
|
||||||
|
AirQualityPercentageData(
|
||||||
|
name: 'hazardous',
|
||||||
|
percentage: nonNullValues[5],
|
||||||
|
type: AqiType.co2.code,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<double> _redistributePercentages(
|
||||||
|
List<double> originalValues,
|
||||||
|
List<bool> nullMask,
|
||||||
|
) {
|
||||||
|
double nonNullSum = 0;
|
||||||
|
for (int i = 0; i < originalValues.length; i++) {
|
||||||
|
if (!nullMask[i]) {
|
||||||
|
nonNullSum += originalValues[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return List.generate(originalValues.length, (i) {
|
||||||
|
if (nullMask[i]) return 0;
|
||||||
|
return (originalValues[i] / nonNullSum * 100).roundToDouble();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _shouldBeNull() => _random.nextDouble() < 0.6;
|
||||||
|
|
||||||
|
List<double> _generateRandomPercentages() {
|
||||||
|
final values = List.generate(6, (_) => _random.nextDouble());
|
||||||
|
|
||||||
|
final sum = values.reduce((a, b) => a + b);
|
||||||
|
|
||||||
|
return values.map((value) => (value / sum * 100).roundToDouble()).toList();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
|
class RemoteAirQualityDistributionService implements AirQualityDistributionService {
|
||||||
|
RemoteAirQualityDistributionService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<AirQualityDataModel>> getAirQualityDistribution(
|
||||||
|
GetAirQualityDistributionParam param,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: 'endpoint',
|
||||||
|
queryParameters: {
|
||||||
|
'spaceUuid': param.spaceUuid,
|
||||||
|
'date': param.date.toIso8601String(),
|
||||||
|
},
|
||||||
|
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 AirQualityDataModel.fromJson(jsonData);
|
||||||
|
}).toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load energy consumption per phase: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
|
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart';
|
||||||
|
|
||||||
@ -18,10 +19,15 @@ class FakeRangeOfAqiService implements RangeOfAqiService {
|
|||||||
final avg = (min + avgDelta).clamp(0.0, 301.0);
|
final avg = (min + avgDelta).clamp(0.0, 301.0);
|
||||||
final max = (avg + maxDelta).clamp(0.0, 301.0);
|
final max = (avg + maxDelta).clamp(0.0, 301.0);
|
||||||
|
|
||||||
return RangeOfAqi(
|
return RangeOfAqi(
|
||||||
min: min,
|
data: [
|
||||||
avg: avg,
|
RangeOfAqiValue(type: AqiType.aqi.code, min: min, average: avg, max: max),
|
||||||
max: max,
|
RangeOfAqiValue(type: AqiType.pm25.code, min: min, average: avg, max: max),
|
||||||
|
RangeOfAqiValue(type: AqiType.pm10.code, min: min, average: avg, max: max),
|
||||||
|
RangeOfAqiValue(type: AqiType.hcho.code, min: min, average: avg, max: max),
|
||||||
|
RangeOfAqiValue(type: AqiType.tvoc.code, min: min, average: avg, max: max),
|
||||||
|
RangeOfAqiValue(type: AqiType.co2.code, min: min, average: avg, max: max),
|
||||||
|
],
|
||||||
date: date,
|
date: date,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
|
final class RemoteRangeOfAqiService implements RangeOfAqiService {
|
||||||
|
const RemoteRangeOfAqiService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<RangeOfAqi>> load(GetRangeOfAqiParam param) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: 'endpoint',
|
||||||
|
queryParameters: {
|
||||||
|
'spaceUuid': param.spaceUuid,
|
||||||
|
'date': param.date.toIso8601String(),
|
||||||
|
},
|
||||||
|
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 RangeOfAqi.fromJson(jsonData);
|
||||||
|
}).toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load energy consumption per phase: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,14 +11,17 @@ class AnalyticsErrorWidget extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Visibility(
|
return Visibility(
|
||||||
visible: errorMessage != null || (errorMessage?.isNotEmpty ?? false),
|
visible: errorMessage != null || (errorMessage?.isNotEmpty ?? false),
|
||||||
child: Text(
|
child: Padding(
|
||||||
errorMessage ?? 'Something went wrong',
|
padding: const EdgeInsetsDirectional.only(bottom: 10),
|
||||||
maxLines: 1,
|
child: Text(
|
||||||
overflow: TextOverflow.ellipsis,
|
errorMessage ?? 'Something went wrong',
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
maxLines: 1,
|
||||||
color: ColorsManager.red,
|
overflow: TextOverflow.ellipsis,
|
||||||
fontWeight: FontWeight.w400,
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
fontSize: 8,
|
color: ColorsManager.red,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 8,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -10,13 +10,13 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
|||||||
class AnalyticsSidebarHeader extends StatelessWidget {
|
class AnalyticsSidebarHeader extends StatelessWidget {
|
||||||
const AnalyticsSidebarHeader({
|
const AnalyticsSidebarHeader({
|
||||||
required this.title,
|
required this.title,
|
||||||
this.showSpaceUuid = false,
|
this.showSpaceUuidInDevicesDropdown = false,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final bool showSpaceUuid;
|
final bool showSpaceUuidInDevicesDropdown;
|
||||||
final void Function(AnalyticsDevice device)? onChanged;
|
final void Function(AnalyticsDevice device)? onChanged;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -49,6 +49,7 @@ class AnalyticsSidebarHeader extends StatelessWidget {
|
|||||||
alignment: AlignmentDirectional.centerEnd,
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
child: AnalyticsDeviceDropdown(
|
child: AnalyticsDeviceDropdown(
|
||||||
|
showSpaceUuid: showSpaceUuidInDevicesDropdown,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
context.read<AnalyticsDevicesBloc>().add(
|
context.read<AnalyticsDevicesBloc>().add(
|
||||||
SelectAnalyticsDeviceEvent(value),
|
SelectAnalyticsDeviceEvent(value),
|
||||||
|
@ -13,6 +13,7 @@ import 'package:syncrow_web/pages/common/bloc/project_manager.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/space_tree/bloc/space_tree_bloc.dart';
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
||||||
|
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||||
import 'package:syncrow_web/services/auth_api.dart';
|
import 'package:syncrow_web/services/auth_api.dart';
|
||||||
import 'package:syncrow_web/utils/constants/strings_manager.dart';
|
import 'package:syncrow_web/utils/constants/strings_manager.dart';
|
||||||
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
|
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
|
||||||
@ -99,7 +100,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> changePassword(ChangePasswordEvent event, Emitter<AuthState> emit) async {
|
Future<void> changePassword(
|
||||||
|
ChangePasswordEvent event, Emitter<AuthState> emit) async {
|
||||||
emit(LoadingForgetState());
|
emit(LoadingForgetState());
|
||||||
try {
|
try {
|
||||||
var response = await AuthenticationAPI.verifyOtp(
|
var response = await AuthenticationAPI.verifyOtp(
|
||||||
@ -113,14 +115,14 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
||||||
emit(SuccessForgetState());
|
emit(SuccessForgetState());
|
||||||
}
|
}
|
||||||
} on DioException catch (e) {
|
} on APIException catch (e) {
|
||||||
final errorData = e.response!.data;
|
final errorMessage = e.message;
|
||||||
String errorMessage = errorData['error']['message'] ?? 'something went wrong';
|
|
||||||
validate = errorMessage;
|
validate = errorMessage;
|
||||||
emit(AuthInitialState());
|
emit(AuthInitialState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
String? validateCode(String? value) {
|
String? validateCode(String? value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
return 'Code is required';
|
return 'Code is required';
|
||||||
@ -149,6 +151,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
static UserModel? user;
|
static UserModel? user;
|
||||||
bool showValidationMessage = false;
|
bool showValidationMessage = false;
|
||||||
|
|
||||||
|
|
||||||
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
|
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
|
||||||
emit(AuthLoading());
|
emit(AuthLoading());
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
@ -165,21 +168,20 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
password: event.password,
|
password: event.password,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} on DioException catch (e) {
|
} on APIException catch (e) {
|
||||||
final errorData = e.response!.data;
|
validate = e.message;
|
||||||
String errorMessage = errorData['error']['message'];
|
emit(LoginInitial());
|
||||||
if (errorMessage == "Access denied for web platform") {
|
return;
|
||||||
validate = errorMessage;
|
} catch (e) {
|
||||||
} else {
|
validate = 'Something went wrong';
|
||||||
validate = 'Invalid Credentials!';
|
|
||||||
}
|
|
||||||
emit(LoginInitial());
|
emit(LoginInitial());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token.accessTokenIsNotEmpty) {
|
if (token.accessTokenIsNotEmpty) {
|
||||||
FlutterSecureStorage storage = const FlutterSecureStorage();
|
FlutterSecureStorage storage = const FlutterSecureStorage();
|
||||||
await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken);
|
await storage.write(
|
||||||
|
key: Token.loginAccessTokenKey, value: token.accessToken);
|
||||||
const FlutterSecureStorage().write(
|
const FlutterSecureStorage().write(
|
||||||
key: UserModel.userUuidKey,
|
key: UserModel.userUuidKey,
|
||||||
value: Token.decodeToken(token.accessToken)['uuid'].toString());
|
value: Token.decodeToken(token.accessToken)['uuid'].toString());
|
||||||
@ -195,6 +197,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
checkBoxToggle(
|
checkBoxToggle(
|
||||||
CheckBoxEvent event,
|
CheckBoxEvent event,
|
||||||
Emitter<AuthState> emit,
|
Emitter<AuthState> emit,
|
||||||
|
@ -162,31 +162,34 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
controller: _horizontalBodyScrollController,
|
controller: _horizontalBodyScrollController,
|
||||||
child: SizedBox(
|
child: Container(
|
||||||
width: widget.size.width,
|
color: ColorsManager.whiteColors,
|
||||||
child: widget.isEmpty
|
child: SizedBox(
|
||||||
? _buildEmptyState()
|
width: widget.size.width,
|
||||||
: Column(
|
child: widget.isEmpty
|
||||||
children:
|
? _buildEmptyState()
|
||||||
List.generate(widget.data.length, (rowIndex) {
|
: Column(
|
||||||
final row = widget.data[rowIndex];
|
children: List.generate(widget.data.length,
|
||||||
return Row(
|
(rowIndex) {
|
||||||
children: [
|
final row = widget.data[rowIndex];
|
||||||
if (widget.withCheckBox)
|
return Row(
|
||||||
_buildRowCheckbox(
|
children: [
|
||||||
rowIndex, widget.size.height * 0.08),
|
if (widget.withCheckBox)
|
||||||
...row.asMap().entries.map((entry) {
|
_buildRowCheckbox(rowIndex,
|
||||||
return _buildTableCell(
|
widget.size.height * 0.08),
|
||||||
entry.value.toString(),
|
...row.asMap().entries.map((entry) {
|
||||||
widget.size.height * 0.08,
|
return _buildTableCell(
|
||||||
rowIndex: rowIndex,
|
entry.value.toString(),
|
||||||
columnIndex: entry.key,
|
widget.size.height * 0.08,
|
||||||
);
|
rowIndex: rowIndex,
|
||||||
}).toList(),
|
columnIndex: entry.key,
|
||||||
],
|
);
|
||||||
);
|
}).toList(),
|
||||||
}),
|
],
|
||||||
),
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -211,7 +214,6 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
onChanged: widget.withSelectAll && widget.data.isNotEmpty
|
onChanged: widget.withSelectAll && widget.data.isNotEmpty
|
||||||
? _toggleSelectAll
|
? _toggleSelectAll
|
||||||
: null,
|
: null,
|
||||||
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -282,7 +284,6 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
horizontal: index == widget.headers.length - 1 ? 12 : 8.0,
|
horizontal: index == widget.headers.length - 1 ? 12 : 8.0,
|
||||||
vertical: 4),
|
vertical: 4),
|
||||||
|
|
||||||
child: Text(
|
child: Text(
|
||||||
title,
|
title,
|
||||||
style: context.textTheme.titleSmall!.copyWith(
|
style: context.textTheme.titleSmall!.copyWith(
|
||||||
@ -303,7 +304,6 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
required int rowIndex,
|
required int rowIndex,
|
||||||
required int columnIndex,
|
required int columnIndex,
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
bool isBatteryLevel = content.endsWith('%');
|
bool isBatteryLevel = content.endsWith('%');
|
||||||
double? batteryLevel;
|
double? batteryLevel;
|
||||||
|
|
||||||
@ -313,9 +313,13 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
bool isSettingsColumn = widget.headers[columnIndex] == 'Settings';
|
bool isSettingsColumn = widget.headers[columnIndex] == 'Settings';
|
||||||
|
|
||||||
if (isSettingsColumn) {
|
if (isSettingsColumn) {
|
||||||
return _buildSettingsIcon(rowIndex, size);
|
return buildSettingsIcon(
|
||||||
|
width: 120,
|
||||||
|
height: 60,
|
||||||
|
iconSize: 40,
|
||||||
|
onTap: () => widget.onSettingsPressed?.call(rowIndex),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Color? statusColor;
|
Color? statusColor;
|
||||||
switch (content) {
|
switch (content) {
|
||||||
@ -368,22 +372,63 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSettingsIcon(int rowIndex, double size) {
|
Widget buildSettingsIcon(
|
||||||
return Container(
|
{double width = 120,
|
||||||
height: size,
|
double height = 60,
|
||||||
width: 120,
|
double iconSize = 40,
|
||||||
padding: const EdgeInsets.all(5.0),
|
VoidCallback? onTap}) {
|
||||||
decoration: const BoxDecoration(
|
return Column(
|
||||||
border: Border(
|
children: [
|
||||||
bottom: BorderSide(color: ColorsManager.boxDivider, width: 1.0),
|
Container(
|
||||||
|
padding: const EdgeInsets.only(top: 10, bottom: 15, left: 10),
|
||||||
|
margin: const EdgeInsets.only(right: 15),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: ColorsManager.boxDivider,
|
||||||
|
width: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
width: width,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
right: 16.0,
|
||||||
|
left: 17.0,
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
width: 50,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFF7F8FA),
|
||||||
|
borderRadius: BorderRadius.circular(height / 2),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.17),
|
||||||
|
blurRadius: 14,
|
||||||
|
offset: const Offset(0, 4),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Center(
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
Assets.settings, // ضع المسار الصحيح هنا
|
||||||
|
width: 40,
|
||||||
|
height: 22,
|
||||||
|
color: ColorsManager
|
||||||
|
.primaryColor, // نفس لون الأيقونة في الصورة
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
color: Colors.white,
|
],
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: IconButton(
|
|
||||||
icon: SvgPicture.asset(Assets.settings),
|
|
||||||
onPressed: () => widget.onSettingsPressed?.call(rowIndex),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,27 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:firebase_database/firebase_database.dart';
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart';
|
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
|
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
|
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||||
|
import 'package:syncrow_web/services/control_device_service.dart';
|
||||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||||
|
|
||||||
class AcBloc extends Bloc<AcsEvent, AcsState> {
|
class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||||
late AcStatusModel deviceStatus;
|
late AcStatusModel deviceStatus;
|
||||||
final String deviceId;
|
final String deviceId;
|
||||||
Timer? _timer;
|
final ControlDeviceService controlDeviceService;
|
||||||
|
final BatchControlDevicesService batchControlDevicesService;
|
||||||
Timer? _countdownTimer;
|
Timer? _countdownTimer;
|
||||||
|
|
||||||
AcBloc({required this.deviceId}) : super(AcsInitialState()) {
|
AcBloc({
|
||||||
|
required this.deviceId,
|
||||||
|
required this.controlDeviceService,
|
||||||
|
required this.batchControlDevicesService,
|
||||||
|
}) : super(AcsInitialState()) {
|
||||||
on<AcFetchDeviceStatusEvent>(_onFetchAcStatus);
|
on<AcFetchDeviceStatusEvent>(_onFetchAcStatus);
|
||||||
on<AcFetchBatchStatusEvent>(_onFetchAcBatchStatus);
|
on<AcFetchBatchStatusEvent>(_onFetchAcBatchStatus);
|
||||||
on<AcControlEvent>(_onAcControl);
|
on<AcControlEvent>(_onAcControl);
|
||||||
@ -34,14 +40,14 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
int scheduledMinutes = 0;
|
int scheduledMinutes = 0;
|
||||||
|
|
||||||
FutureOr<void> _onFetchAcStatus(
|
FutureOr<void> _onFetchAcStatus(
|
||||||
AcFetchDeviceStatusEvent event, Emitter<AcsState> emit) async {
|
AcFetchDeviceStatusEvent event,
|
||||||
|
Emitter<AcsState> emit,
|
||||||
|
) async {
|
||||||
emit(AcsLoadingState());
|
emit(AcsLoadingState());
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
|
||||||
deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status);
|
deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status);
|
||||||
if (deviceStatus.countdown1 != 0) {
|
if (deviceStatus.countdown1 != 0) {
|
||||||
// Convert API value to minutes
|
|
||||||
final totalMinutes = deviceStatus.countdown1 * 6;
|
final totalMinutes = deviceStatus.countdown1 * 6;
|
||||||
scheduledHours = totalMinutes ~/ 60;
|
scheduledHours = totalMinutes ~/ 60;
|
||||||
scheduledMinutes = totalMinutes % 60;
|
scheduledMinutes = totalMinutes % 60;
|
||||||
@ -62,30 +68,24 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_listenToChanges(deviceId) {
|
void _listenToChanges(deviceId) {
|
||||||
try {
|
try {
|
||||||
DatabaseReference ref =
|
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
final stream = ref.onValue;
|
||||||
Stream<DatabaseEvent> stream = ref.onValue;
|
|
||||||
|
|
||||||
stream.listen((DatabaseEvent event) async {
|
stream.listen((DatabaseEvent event) async {
|
||||||
if (event.snapshot.value == null) return;
|
if (event.snapshot.value == null) return;
|
||||||
|
|
||||||
if (_timer != null) {
|
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
|
||||||
}
|
|
||||||
Map<dynamic, dynamic> usersMap =
|
Map<dynamic, dynamic> usersMap =
|
||||||
event.snapshot.value as Map<dynamic, dynamic>;
|
event.snapshot.value as Map<dynamic, dynamic>;
|
||||||
|
|
||||||
List<Status> statusList = [];
|
List<Status> statusList = [];
|
||||||
|
|
||||||
usersMap['status'].forEach((element) {
|
usersMap['status'].forEach((element) {
|
||||||
statusList
|
statusList.add(Status(code: element['code'], value: element['value']));
|
||||||
.add(Status(code: element['code'], value: element['value']));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
deviceStatus =
|
deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList);
|
||||||
AcStatusModel.fromJson(usersMap['productUuid'], statusList);
|
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(AcStatusUpdated(deviceStatus));
|
add(AcStatusUpdated(deviceStatus));
|
||||||
}
|
}
|
||||||
@ -93,146 +93,44 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAcStatusUpdated(AcStatusUpdated event, Emitter<AcsState> emit) {
|
void _onAcStatusUpdated(
|
||||||
|
AcStatusUpdated event,
|
||||||
|
Emitter<AcsState> emit,
|
||||||
|
) {
|
||||||
deviceStatus = event.deviceStatus;
|
deviceStatus = event.deviceStatus;
|
||||||
emit(ACStatusLoaded(status: deviceStatus));
|
emit(ACStatusLoaded(status: deviceStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onAcControl(
|
FutureOr<void> _onAcControl(
|
||||||
AcControlEvent event, Emitter<AcsState> emit) async {
|
AcControlEvent event,
|
||||||
final oldValue = _getValueByCode(event.code);
|
Emitter<AcsState> emit,
|
||||||
|
) async {
|
||||||
_updateLocalValue(event.code, event.value, emit);
|
emit(AcsLoadingState());
|
||||||
|
_updateDeviceFunctionFromCode(event.code, event.value);
|
||||||
emit(ACStatusLoaded(status: deviceStatus));
|
emit(ACStatusLoaded(status: deviceStatus));
|
||||||
|
|
||||||
await _runDebounce(
|
try {
|
||||||
isBatch: false,
|
final success = await controlDeviceService.controlDevice(
|
||||||
deviceId: event.deviceId,
|
deviceUuid: event.deviceId,
|
||||||
code: event.code,
|
status: Status(code: event.code, value: event.value),
|
||||||
value: event.value,
|
);
|
||||||
oldValue: oldValue,
|
|
||||||
emit: emit,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _runDebounce({
|
if (!success) {
|
||||||
required dynamic deviceId,
|
emit(const AcsFailedState(error: 'Failed to control device'));
|
||||||
required String code,
|
|
||||||
required dynamic value,
|
|
||||||
required dynamic oldValue,
|
|
||||||
required Emitter<AcsState> emit,
|
|
||||||
required bool isBatch,
|
|
||||||
}) async {
|
|
||||||
late String id;
|
|
||||||
|
|
||||||
if (deviceId is List) {
|
|
||||||
id = deviceId.first;
|
|
||||||
} else {
|
|
||||||
id = deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_timer != null) {
|
|
||||||
_timer!.cancel();
|
|
||||||
}
|
|
||||||
_timer = Timer(const Duration(seconds: 1), () async {
|
|
||||||
try {
|
|
||||||
late bool response;
|
|
||||||
if (isBatch) {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceBatchControl(deviceId, code, value);
|
|
||||||
} else {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceControl(deviceId, Status(code: code, value: value));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response) {
|
|
||||||
_revertValueAndEmit(id, code, oldValue, emit);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (e is DioException && e.response != null) {
|
|
||||||
debugPrint('Error response: ${e.response?.data}');
|
|
||||||
}
|
|
||||||
_revertValueAndEmit(id, code, oldValue, emit);
|
|
||||||
}
|
}
|
||||||
});
|
} catch (e) {
|
||||||
}
|
emit(AcsFailedState(error: e.toString()));
|
||||||
|
|
||||||
void _revertValueAndEmit(
|
|
||||||
String deviceId, String code, dynamic oldValue, Emitter<AcsState> emit) {
|
|
||||||
_updateLocalValue(code, oldValue, emit);
|
|
||||||
emit(ACStatusLoaded(status: deviceStatus));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateLocalValue(String code, dynamic value, Emitter<AcsState> emit) {
|
|
||||||
switch (code) {
|
|
||||||
case 'switch':
|
|
||||||
if (value is bool) {
|
|
||||||
deviceStatus = deviceStatus.copyWith(acSwitch: value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'temp_set':
|
|
||||||
if (value is int) {
|
|
||||||
deviceStatus = deviceStatus.copyWith(tempSet: value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'mode':
|
|
||||||
if (value is String) {
|
|
||||||
deviceStatus = deviceStatus.copyWith(
|
|
||||||
modeString: value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'level':
|
|
||||||
if (value is String) {
|
|
||||||
deviceStatus = deviceStatus.copyWith(
|
|
||||||
fanSpeedsString: value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'child_lock':
|
|
||||||
if (value is bool) {
|
|
||||||
deviceStatus = deviceStatus.copyWith(childLock: value);
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'countdown_time':
|
|
||||||
if (value is int) {
|
|
||||||
deviceStatus = deviceStatus.copyWith(countdown1: value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
emit(ACStatusLoaded(status: deviceStatus));
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamic _getValueByCode(String code) {
|
|
||||||
switch (code) {
|
|
||||||
case 'switch':
|
|
||||||
return deviceStatus.acSwitch;
|
|
||||||
case 'temp_set':
|
|
||||||
return deviceStatus.tempSet;
|
|
||||||
case 'mode':
|
|
||||||
return deviceStatus.modeString;
|
|
||||||
case 'level':
|
|
||||||
return deviceStatus.fanSpeedsString;
|
|
||||||
case 'child_lock':
|
|
||||||
return deviceStatus.childLock;
|
|
||||||
case 'countdown_time':
|
|
||||||
return deviceStatus.countdown1;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onFetchAcBatchStatus(
|
FutureOr<void> _onFetchAcBatchStatus(
|
||||||
AcFetchBatchStatusEvent event, Emitter<AcsState> emit) async {
|
AcFetchBatchStatusEvent event,
|
||||||
|
Emitter<AcsState> emit,
|
||||||
|
) async {
|
||||||
emit(AcsLoadingState());
|
emit(AcsLoadingState());
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
deviceStatus = AcStatusModel.fromJson(event.devicesIds.first, status.status);
|
||||||
deviceStatus =
|
|
||||||
AcStatusModel.fromJson(event.devicesIds.first, status.status);
|
|
||||||
emit(ACStatusLoaded(status: deviceStatus));
|
emit(ACStatusLoaded(status: deviceStatus));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(AcsFailedState(error: e.toString()));
|
emit(AcsFailedState(error: e.toString()));
|
||||||
@ -240,25 +138,32 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onAcBatchControl(
|
FutureOr<void> _onAcBatchControl(
|
||||||
AcBatchControlEvent event, Emitter<AcsState> emit) async {
|
AcBatchControlEvent event,
|
||||||
final oldValue = _getValueByCode(event.code);
|
Emitter<AcsState> emit,
|
||||||
|
) async {
|
||||||
_updateLocalValue(event.code, event.value, emit);
|
emit(AcsLoadingState());
|
||||||
|
_updateDeviceFunctionFromCode(event.code, event.value);
|
||||||
emit(ACStatusLoaded(status: deviceStatus));
|
emit(ACStatusLoaded(status: deviceStatus));
|
||||||
|
|
||||||
await _runDebounce(
|
try {
|
||||||
isBatch: true,
|
final success = await batchControlDevicesService.batchControlDevices(
|
||||||
deviceId: event.devicesIds,
|
uuids: event.devicesIds,
|
||||||
code: event.code,
|
code: event.code,
|
||||||
value: event.value,
|
value: event.value,
|
||||||
oldValue: oldValue,
|
);
|
||||||
emit: emit,
|
|
||||||
);
|
if (!success) {
|
||||||
|
emit(const AcsFailedState(error: 'Failed to control devices'));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(AcsFailedState(error: e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onFactoryReset(
|
Future<void> _onFactoryReset(
|
||||||
AcFactoryResetEvent event, Emitter<AcsState> emit) async {
|
AcFactoryResetEvent event,
|
||||||
|
Emitter<AcsState> emit,
|
||||||
|
) async {
|
||||||
emit(AcsLoadingState());
|
emit(AcsLoadingState());
|
||||||
try {
|
try {
|
||||||
final response = await DevicesManagementApi().factoryReset(
|
final response = await DevicesManagementApi().factoryReset(
|
||||||
@ -275,9 +180,11 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onClose(OnClose event, Emitter<AcsState> emit) {
|
void _onClose(
|
||||||
|
OnClose event,
|
||||||
|
Emitter<AcsState> emit,
|
||||||
|
) {
|
||||||
_countdownTimer?.cancel();
|
_countdownTimer?.cancel();
|
||||||
_timer?.cancel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleIncreaseTime(IncreaseTimeEvent event, Emitter<AcsState> emit) {
|
void _handleIncreaseTime(IncreaseTimeEvent event, Emitter<AcsState> emit) {
|
||||||
@ -300,7 +207,10 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleDecreaseTime(DecreaseTimeEvent event, Emitter<AcsState> emit) {
|
void _handleDecreaseTime(
|
||||||
|
DecreaseTimeEvent event,
|
||||||
|
Emitter<AcsState> emit,
|
||||||
|
) {
|
||||||
if (state is! ACStatusLoaded) return;
|
if (state is! ACStatusLoaded) return;
|
||||||
final currentState = state as ACStatusLoaded;
|
final currentState = state as ACStatusLoaded;
|
||||||
int totalMinutes = (scheduledHours * 60) + scheduledMinutes;
|
int totalMinutes = (scheduledHours * 60) + scheduledMinutes;
|
||||||
@ -315,7 +225,9 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleToggleTimer(
|
Future<void> _handleToggleTimer(
|
||||||
ToggleScheduleEvent event, Emitter<AcsState> emit) async {
|
ToggleScheduleEvent event,
|
||||||
|
Emitter<AcsState> emit,
|
||||||
|
) async {
|
||||||
if (state is! ACStatusLoaded) return;
|
if (state is! ACStatusLoaded) return;
|
||||||
final currentState = state as ACStatusLoaded;
|
final currentState = state as ACStatusLoaded;
|
||||||
|
|
||||||
@ -331,37 +243,44 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final scaledValue = totalMinutes ~/ 6;
|
final scaledValue = totalMinutes ~/ 6;
|
||||||
await _runDebounce(
|
final success = await controlDeviceService.controlDevice(
|
||||||
isBatch: false,
|
deviceUuid: deviceId,
|
||||||
deviceId: deviceId,
|
status: Status(code: 'countdown_time', value: scaledValue),
|
||||||
code: 'countdown_time',
|
|
||||||
value: scaledValue,
|
|
||||||
oldValue: scaledValue,
|
|
||||||
emit: emit,
|
|
||||||
);
|
);
|
||||||
_startCountdownTimer(emit);
|
|
||||||
emit(currentState.copyWith(isTimerActive: timerActive));
|
if (success) {
|
||||||
|
_startCountdownTimer(emit);
|
||||||
|
emit(currentState.copyWith(isTimerActive: timerActive));
|
||||||
|
} else {
|
||||||
|
timerActive = false;
|
||||||
|
emit(const AcsFailedState(error: 'Failed to set timer'));
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
timerActive = false;
|
timerActive = false;
|
||||||
emit(AcsFailedState(error: e.toString()));
|
emit(AcsFailedState(error: e.toString()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await _runDebounce(
|
try {
|
||||||
isBatch: false,
|
final success = await controlDeviceService.controlDevice(
|
||||||
deviceId: deviceId,
|
deviceUuid: deviceId,
|
||||||
code: 'countdown_time',
|
status: Status(code: 'countdown_time', value: 0),
|
||||||
value: 0,
|
);
|
||||||
oldValue: 0,
|
|
||||||
emit: emit,
|
if (success) {
|
||||||
);
|
_countdownTimer?.cancel();
|
||||||
_countdownTimer?.cancel();
|
scheduledHours = 0;
|
||||||
scheduledHours = 0;
|
scheduledMinutes = 0;
|
||||||
scheduledMinutes = 0;
|
emit(currentState.copyWith(
|
||||||
emit(currentState.copyWith(
|
isTimerActive: timerActive,
|
||||||
isTimerActive: timerActive,
|
scheduledHours: 0,
|
||||||
scheduledHours: 0,
|
scheduledMinutes: 0,
|
||||||
scheduledMinutes: 0,
|
));
|
||||||
));
|
} else {
|
||||||
|
emit(const AcsFailedState(error: 'Failed to stop timer'));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(AcsFailedState(error: e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,7 +304,10 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleUpdateTimer(UpdateTimerEvent event, Emitter<AcsState> emit) {
|
void _handleUpdateTimer(
|
||||||
|
UpdateTimerEvent event,
|
||||||
|
Emitter<AcsState> emit,
|
||||||
|
) {
|
||||||
if (state is ACStatusLoaded) {
|
if (state is ACStatusLoaded) {
|
||||||
final currentState = state as ACStatusLoaded;
|
final currentState = state as ACStatusLoaded;
|
||||||
emit(currentState.copyWith(
|
emit(currentState.copyWith(
|
||||||
@ -400,7 +322,6 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
ApiCountdownValueEvent event, Emitter<AcsState> emit) {
|
ApiCountdownValueEvent event, Emitter<AcsState> emit) {
|
||||||
if (state is ACStatusLoaded) {
|
if (state is ACStatusLoaded) {
|
||||||
final totalMinutes = event.apiValue * 6;
|
final totalMinutes = event.apiValue * 6;
|
||||||
final scheduledHours = totalMinutes ~/ 60;
|
|
||||||
scheduledMinutes = totalMinutes % 60;
|
scheduledMinutes = totalMinutes % 60;
|
||||||
_startCountdownTimer(
|
_startCountdownTimer(
|
||||||
emit,
|
emit,
|
||||||
@ -409,6 +330,43 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _updateDeviceFunctionFromCode(String code, dynamic value) {
|
||||||
|
switch (code) {
|
||||||
|
case 'switch':
|
||||||
|
if (value is bool) {
|
||||||
|
deviceStatus = deviceStatus.copyWith(acSwitch: value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'temp_set':
|
||||||
|
if (value is int) {
|
||||||
|
deviceStatus = deviceStatus.copyWith(tempSet: value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'mode':
|
||||||
|
if (value is String) {
|
||||||
|
deviceStatus = deviceStatus.copyWith(modeString: value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'level':
|
||||||
|
if (value is String) {
|
||||||
|
deviceStatus = deviceStatus.copyWith(fanSpeedsString: value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'child_lock':
|
||||||
|
if (value is bool) {
|
||||||
|
deviceStatus = deviceStatus.copyWith(childLock: value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'countdown_time':
|
||||||
|
if (value is int) {
|
||||||
|
deviceStatus = deviceStatus.copyWith(countdown1: value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() {
|
Future<void> close() {
|
||||||
add(OnClose());
|
add(OnClose());
|
||||||
|
18
lib/pages/device_managment/ac/factories/ac_bloc_factory.dart
Normal file
18
lib/pages/device_managment/ac/factories/ac_bloc_factory.dart
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||||
|
|
||||||
|
abstract final class AcBlocFactory {
|
||||||
|
const AcBlocFactory._();
|
||||||
|
|
||||||
|
static AcBloc create({
|
||||||
|
required String deviceId,
|
||||||
|
}) {
|
||||||
|
return AcBloc(
|
||||||
|
deviceId: deviceId,
|
||||||
|
controlDeviceService:
|
||||||
|
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||||
|
batchControlDevicesService:
|
||||||
|
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -3,12 +3,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart';
|
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/ac/factories/ac_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart';
|
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart';
|
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart';
|
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.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/shared/batch_control/factory_reset.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||||
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
@ -26,8 +26,9 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
|
|||||||
final isLarge = isLargeScreenSize(context);
|
final isLarge = isLargeScreenSize(context);
|
||||||
final isMedium = isMediumScreenSize(context);
|
final isMedium = isMediumScreenSize(context);
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) =>
|
create: (context) => AcBlocFactory.create(
|
||||||
AcBloc(deviceId: devicesIds.first)..add(AcFetchBatchStatusEvent(devicesIds)),
|
deviceId: devicesIds.first,
|
||||||
|
)..add(AcFetchBatchStatusEvent(devicesIds)),
|
||||||
child: BlocBuilder<AcBloc, AcsState>(
|
child: BlocBuilder<AcBloc, AcsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is ACStatusLoaded) {
|
if (state is ACStatusLoaded) {
|
||||||
|
@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart';
|
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/ac/factories/ac_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/ac_mode.dart';
|
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/ac_mode.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/current_temp.dart';
|
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/current_temp.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/fan_speed.dart';
|
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/fan_speed.dart';
|
||||||
@ -24,8 +25,9 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
|
|||||||
final isMedium = isMediumScreenSize(context);
|
final isMedium = isMediumScreenSize(context);
|
||||||
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => AcBloc(deviceId: device.uuid!)
|
create: (context) => AcBlocFactory.create(
|
||||||
..add(AcFetchDeviceStatusEvent(device.uuid!)),
|
deviceId: device.uuid!,
|
||||||
|
)..add(AcFetchDeviceStatusEvent(device.uuid!)),
|
||||||
child: BlocBuilder<AcBloc, AcsState>(
|
child: BlocBuilder<AcBloc, AcsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final acBloc = BlocProvider.of<AcBloc>(context);
|
final acBloc = BlocProvider.of<AcBloc>(context);
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:firebase_database/firebase_database.dart';
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
@ -7,14 +5,21 @@ import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_e
|
|||||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart';
|
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
|
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/help_description.dart';
|
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/help_description.dart';
|
||||||
|
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||||
|
import 'package:syncrow_web/services/control_device_service.dart';
|
||||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||||
|
|
||||||
class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
|
class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
|
||||||
final String deviceId;
|
final String deviceId;
|
||||||
|
final ControlDeviceService controlDeviceService;
|
||||||
|
final BatchControlDevicesService batchControlDevicesService;
|
||||||
late CeilingSensorModel deviceStatus;
|
late CeilingSensorModel deviceStatus;
|
||||||
Timer? _timer;
|
|
||||||
|
|
||||||
CeilingSensorBloc({required this.deviceId}) : super(CeilingInitialState()) {
|
CeilingSensorBloc({
|
||||||
|
required this.deviceId,
|
||||||
|
required this.controlDeviceService,
|
||||||
|
required this.batchControlDevicesService,
|
||||||
|
}) : super(CeilingInitialState()) {
|
||||||
on<CeilingInitialEvent>(_fetchCeilingSensorStatus);
|
on<CeilingInitialEvent>(_fetchCeilingSensorStatus);
|
||||||
on<CeilingFetchDeviceStatusEvent>(_fetchCeilingSensorBatchControl);
|
on<CeilingFetchDeviceStatusEvent>(_fetchCeilingSensorBatchControl);
|
||||||
on<CeilingChangeValueEvent>(_changeValue);
|
on<CeilingChangeValueEvent>(_changeValue);
|
||||||
@ -26,35 +31,34 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
|
|||||||
on<StatusUpdated>(_onStatusUpdated);
|
on<StatusUpdated>(_onStatusUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _fetchCeilingSensorStatus(
|
Future<void> _fetchCeilingSensorStatus(
|
||||||
CeilingInitialEvent event, Emitter<CeilingSensorState> emit) async {
|
CeilingInitialEvent event,
|
||||||
|
Emitter<CeilingSensorState> emit,
|
||||||
|
) async {
|
||||||
emit(CeilingLoadingInitialState());
|
emit(CeilingLoadingInitialState());
|
||||||
try {
|
try {
|
||||||
var response =
|
final response = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
|
||||||
deviceStatus = CeilingSensorModel.fromJson(response.status);
|
deviceStatus = CeilingSensorModel.fromJson(response.status);
|
||||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||||
_listenToChanges(event.deviceId);
|
_listenToChanges(event.deviceId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(CeilingFailedState(error: e.toString()));
|
emit(CeilingFailedState(error: e.toString()));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_listenToChanges(deviceId) {
|
void _listenToChanges(String deviceId) {
|
||||||
try {
|
try {
|
||||||
DatabaseReference ref =
|
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
final stream = ref.onValue;
|
||||||
Stream<DatabaseEvent> stream = ref.onValue;
|
|
||||||
|
|
||||||
stream.listen((DatabaseEvent event) {
|
stream.listen((DatabaseEvent event) {
|
||||||
Map<dynamic, dynamic> usersMap =
|
if (event.snapshot.value == null) return;
|
||||||
event.snapshot.value as Map<dynamic, dynamic>;
|
|
||||||
|
final usersMap = event.snapshot.value as Map<dynamic, dynamic>;
|
||||||
|
final statusList = <Status>[];
|
||||||
|
|
||||||
List<Status> statusList = [];
|
|
||||||
usersMap['status'].forEach((element) {
|
usersMap['status'].forEach((element) {
|
||||||
statusList
|
statusList.add(Status(code: element['code'], value: element['value']));
|
||||||
.add(Status(code: element['code'], value: element['value']));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
deviceStatus = CeilingSensorModel.fromJson(statusList);
|
deviceStatus = CeilingSensorModel.fromJson(statusList);
|
||||||
@ -65,149 +69,127 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
|
|||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onStatusUpdated(StatusUpdated event, Emitter<CeilingSensorState> emit) {
|
void _onStatusUpdated(
|
||||||
|
StatusUpdated event,
|
||||||
|
Emitter<CeilingSensorState> emit,
|
||||||
|
) {
|
||||||
deviceStatus = event.deviceStatus;
|
deviceStatus = event.deviceStatus;
|
||||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _changeValue(
|
Future<void> _changeValue(
|
||||||
CeilingChangeValueEvent event, Emitter<CeilingSensorState> emit) async {
|
CeilingChangeValueEvent event,
|
||||||
|
Emitter<CeilingSensorState> emit,
|
||||||
|
) async {
|
||||||
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
|
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
|
||||||
if (event.code == 'sensitivity') {
|
_updateDeviceFunctionFromCode(event.code, event.value);
|
||||||
deviceStatus.sensitivity = event.value;
|
|
||||||
} else if (event.code == 'none_body_time') {
|
|
||||||
deviceStatus.noBodyTime = event.value;
|
|
||||||
} else if (event.code == 'moving_max_dis') {
|
|
||||||
deviceStatus.maxDistance = event.value;
|
|
||||||
} else if (event.code == 'scene') {
|
|
||||||
deviceStatus.spaceType = getSpaceType(event.value);
|
|
||||||
}
|
|
||||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||||
await _runDeBouncer(
|
|
||||||
deviceId: deviceId,
|
try {
|
||||||
code: event.code,
|
await controlDeviceService.controlDevice(
|
||||||
value: event.value,
|
deviceUuid: deviceId,
|
||||||
emit: emit,
|
status: Status(code: event.code, value: event.value),
|
||||||
isBatch: false,
|
);
|
||||||
);
|
} catch (e) {
|
||||||
|
emit(CeilingFailedState(error: e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onBatchControl(
|
Future<void> _onBatchControl(
|
||||||
CeilingBatchControlEvent event, Emitter<CeilingSensorState> emit) async {
|
CeilingBatchControlEvent event,
|
||||||
|
Emitter<CeilingSensorState> emit,
|
||||||
|
) async {
|
||||||
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
|
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
|
||||||
if (event.code == 'sensitivity') {
|
_updateDeviceFunctionFromCode(event.code, event.value);
|
||||||
deviceStatus.sensitivity = event.value;
|
|
||||||
} else if (event.code == 'none_body_time') {
|
|
||||||
deviceStatus.noBodyTime = event.value;
|
|
||||||
} else if (event.code == 'moving_max_dis') {
|
|
||||||
deviceStatus.maxDistance = event.value;
|
|
||||||
} else if (event.code == 'scene') {
|
|
||||||
deviceStatus.spaceType = getSpaceType(event.value);
|
|
||||||
}
|
|
||||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||||
await _runDeBouncer(
|
|
||||||
deviceId: event.deviceIds,
|
|
||||||
code: event.code,
|
|
||||||
value: event.value,
|
|
||||||
emit: emit,
|
|
||||||
isBatch: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_runDeBouncer({
|
try {
|
||||||
required dynamic deviceId,
|
final success = await batchControlDevicesService.batchControlDevices(
|
||||||
required String code,
|
uuids: event.deviceIds,
|
||||||
required dynamic value,
|
code: event.code,
|
||||||
required Emitter<CeilingSensorState> emit,
|
value: event.value,
|
||||||
required bool isBatch,
|
);
|
||||||
}) {
|
|
||||||
late String id;
|
|
||||||
|
|
||||||
if (deviceId is List) {
|
if (!success) {
|
||||||
id = deviceId.first;
|
emit(const CeilingFailedState(error: 'Failed to control devices'));
|
||||||
} else {
|
|
||||||
id = deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_timer != null) {
|
|
||||||
_timer!.cancel();
|
|
||||||
}
|
|
||||||
_timer = Timer(const Duration(seconds: 1), () async {
|
|
||||||
try {
|
|
||||||
late bool response;
|
|
||||||
if (isBatch) {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceBatchControl(deviceId, code, value);
|
|
||||||
} else {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceControl(deviceId, Status(code: code, value: value));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response) {
|
|
||||||
add(CeilingInitialEvent(id));
|
|
||||||
}
|
|
||||||
if (response == true && code == 'scene') {
|
|
||||||
emit(CeilingLoadingInitialState());
|
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
|
||||||
add(CeilingInitialEvent(id));
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
|
||||||
add(CeilingInitialEvent(id));
|
|
||||||
}
|
}
|
||||||
});
|
} catch (e) {
|
||||||
|
emit(CeilingFailedState(error: e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _getDeviceReports(GetCeilingDeviceReportsEvent event,
|
void _updateDeviceFunctionFromCode(String code, dynamic value) {
|
||||||
Emitter<CeilingSensorState> emit) async {
|
switch (code) {
|
||||||
|
case 'sensitivity':
|
||||||
|
deviceStatus.sensitivity = value;
|
||||||
|
break;
|
||||||
|
case 'none_body_time':
|
||||||
|
deviceStatus.noBodyTime = value;
|
||||||
|
break;
|
||||||
|
case 'moving_max_dis':
|
||||||
|
deviceStatus.maxDistance = value;
|
||||||
|
break;
|
||||||
|
case 'scene':
|
||||||
|
deviceStatus.spaceType = getSpaceType(value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _getDeviceReports(
|
||||||
|
GetCeilingDeviceReportsEvent event,
|
||||||
|
Emitter<CeilingSensorState> emit,
|
||||||
|
) async {
|
||||||
if (event.code.isEmpty) {
|
if (event.code.isEmpty) {
|
||||||
emit(ShowCeilingDescriptionState(description: reportString));
|
emit(ShowCeilingDescriptionState(description: reportString));
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
emit(CeilingReportsLoadingState());
|
|
||||||
// final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch;
|
|
||||||
// final to = DateTime.now().millisecondsSinceEpoch;
|
|
||||||
|
|
||||||
try {
|
emit(CeilingReportsLoadingState());
|
||||||
// await DevicesManagementApi.getDeviceReportsByDate(deviceId, event.code, from.toString(), to.toString())
|
try {
|
||||||
await DevicesManagementApi.getDeviceReports(deviceId, event.code)
|
final value = await DevicesManagementApi.getDeviceReports(
|
||||||
.then((value) {
|
deviceId,
|
||||||
emit(CeilingReportsState(deviceReport: value));
|
event.code,
|
||||||
});
|
);
|
||||||
} catch (e) {
|
emit(CeilingReportsState(deviceReport: value));
|
||||||
emit(CeilingReportsFailedState(error: e.toString()));
|
} catch (e) {
|
||||||
return;
|
emit(CeilingReportsFailedState(error: e.toString()));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showDescription(
|
void _showDescription(
|
||||||
ShowCeilingDescriptionEvent event, Emitter<CeilingSensorState> emit) {
|
ShowCeilingDescriptionEvent event,
|
||||||
|
Emitter<CeilingSensorState> emit,
|
||||||
|
) {
|
||||||
emit(ShowCeilingDescriptionState(description: event.description));
|
emit(ShowCeilingDescriptionState(description: event.description));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _backToGridView(
|
void _backToGridView(
|
||||||
BackToCeilingGridViewEvent event, Emitter<CeilingSensorState> emit) {
|
BackToCeilingGridViewEvent event,
|
||||||
|
Emitter<CeilingSensorState> emit,
|
||||||
|
) {
|
||||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _fetchCeilingSensorBatchControl(
|
Future<void> _fetchCeilingSensorBatchControl(
|
||||||
CeilingFetchDeviceStatusEvent event,
|
CeilingFetchDeviceStatusEvent event,
|
||||||
Emitter<CeilingSensorState> emit) async {
|
Emitter<CeilingSensorState> emit,
|
||||||
|
) async {
|
||||||
emit(CeilingLoadingInitialState());
|
emit(CeilingLoadingInitialState());
|
||||||
try {
|
try {
|
||||||
var response =
|
final response = await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
|
||||||
deviceStatus = CeilingSensorModel.fromJson(response.status);
|
deviceStatus = CeilingSensorModel.fromJson(response.status);
|
||||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(CeilingFailedState(error: e.toString()));
|
emit(CeilingFailedState(error: e.toString()));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onFactoryReset(
|
Future<void> _onFactoryReset(
|
||||||
CeilingFactoryResetEvent event, Emitter<CeilingSensorState> emit) async {
|
CeilingFactoryResetEvent event,
|
||||||
|
Emitter<CeilingSensorState> emit,
|
||||||
|
) async {
|
||||||
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
|
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
|
||||||
try {
|
try {
|
||||||
final response = await DevicesManagementApi().factoryReset(
|
final response = await DevicesManagementApi().factoryReset(
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||||
|
|
||||||
|
abstract final class CeilingSensorBlocFactory {
|
||||||
|
const CeilingSensorBlocFactory._();
|
||||||
|
|
||||||
|
static CeilingSensorBloc create({
|
||||||
|
required String deviceId,
|
||||||
|
}) {
|
||||||
|
return CeilingSensorBloc(
|
||||||
|
deviceId: deviceId,
|
||||||
|
controlDeviceService:
|
||||||
|
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||||
|
batchControlDevicesService:
|
||||||
|
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -4,9 +4,9 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_re
|
|||||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart';
|
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart';
|
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/factories/ceiling_sensor_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
|
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||||
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_space_type.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_space_type.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart';
|
||||||
@ -23,8 +23,9 @@ class CeilingSensorBatchControlView extends StatelessWidget with HelperResponsiv
|
|||||||
final isLarge = isLargeScreenSize(context);
|
final isLarge = isLargeScreenSize(context);
|
||||||
final isMedium = isMediumScreenSize(context);
|
final isMedium = isMediumScreenSize(context);
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => CeilingSensorBloc(deviceId: devicesIds.first)
|
create: (context) => CeilingSensorBlocFactory.create(
|
||||||
..add(CeilingFetchDeviceStatusEvent(devicesIds)),
|
deviceId: devicesIds.first,
|
||||||
|
)..add(CeilingFetchDeviceStatusEvent(devicesIds)),
|
||||||
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) {
|
||||||
@ -110,7 +111,6 @@ class CeilingSensorBatchControlView extends StatelessWidget with HelperResponsiv
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// FirmwareUpdateWidget(deviceId: devicesIds.first, version: 4),
|
|
||||||
FactoryResetWidget(
|
FactoryResetWidget(
|
||||||
callFactoryReset: () {
|
callFactoryReset: () {
|
||||||
context.read<CeilingSensorBloc>().add(
|
context.read<CeilingSensorBloc>().add(
|
||||||
|
@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
|
|||||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart';
|
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart';
|
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/factories/ceiling_sensor_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
|
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_display_data.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_display_data.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_space_type.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_space_type.dart';
|
||||||
@ -28,8 +29,9 @@ class CeilingSensorControlsView extends StatelessWidget
|
|||||||
final isLarge = isLargeScreenSize(context);
|
final isLarge = isLargeScreenSize(context);
|
||||||
final isMedium = isMediumScreenSize(context);
|
final isMedium = isMediumScreenSize(context);
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => CeilingSensorBloc(deviceId: device.uuid ?? '')
|
create: (context) => CeilingSensorBlocFactory.create(
|
||||||
..add(CeilingInitialEvent(device.uuid ?? '')),
|
deviceId: device.uuid ?? '',
|
||||||
|
)..add(CeilingInitialEvent(device.uuid ?? '')),
|
||||||
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
|
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is CeilingLoadingInitialState ||
|
if (state is CeilingLoadingInitialState ||
|
||||||
|
@ -1,17 +1,25 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:firebase_database/firebase_database.dart';
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart';
|
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart';
|
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart';
|
||||||
|
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||||
|
import 'package:syncrow_web/services/control_device_service.dart';
|
||||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||||
|
|
||||||
class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
|
class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
|
||||||
late bool deviceStatus;
|
late bool deviceStatus;
|
||||||
final String deviceId;
|
final String deviceId;
|
||||||
Timer? _timer;
|
final ControlDeviceService controlDeviceService;
|
||||||
|
final BatchControlDevicesService batchControlDevicesService;
|
||||||
|
|
||||||
CurtainBloc({required this.deviceId}) : super(CurtainInitial()) {
|
CurtainBloc({
|
||||||
|
required this.deviceId,
|
||||||
|
required this.controlDeviceService,
|
||||||
|
required this.batchControlDevicesService,
|
||||||
|
}) : super(CurtainInitial()) {
|
||||||
on<CurtainFetchDeviceStatus>(_onFetchDeviceStatus);
|
on<CurtainFetchDeviceStatus>(_onFetchDeviceStatus);
|
||||||
on<CurtainFetchBatchStatus>(_onFetchBatchStatus);
|
on<CurtainFetchBatchStatus>(_onFetchBatchStatus);
|
||||||
on<CurtainControl>(_onCurtainControl);
|
on<CurtainControl>(_onCurtainControl);
|
||||||
@ -20,32 +28,31 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
|
|||||||
on<StatusUpdated>(_onStatusUpdated);
|
on<StatusUpdated>(_onStatusUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onFetchDeviceStatus(
|
Future<void> _onFetchDeviceStatus(
|
||||||
CurtainFetchDeviceStatus event, Emitter<CurtainState> emit) async {
|
CurtainFetchDeviceStatus event,
|
||||||
|
Emitter<CurtainState> emit,
|
||||||
|
) async {
|
||||||
emit(CurtainStatusLoading());
|
emit(CurtainStatusLoading());
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
_listenToChanges(event.deviceId, emit);
|
||||||
_listenToChanges(event.deviceId);
|
|
||||||
deviceStatus = _checkStatus(status.status[0].value);
|
deviceStatus = _checkStatus(status.status[0].value);
|
||||||
|
|
||||||
emit(CurtainStatusLoaded(deviceStatus));
|
emit(CurtainStatusLoaded(deviceStatus));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(CurtainError(e.toString()));
|
emit(CurtainError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenToChanges(String deviceId) {
|
void _listenToChanges(String deviceId, Emitter<CurtainState> emit) {
|
||||||
try {
|
try {
|
||||||
DatabaseReference ref =
|
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
final stream = ref.onValue;
|
||||||
Stream<DatabaseEvent> stream = ref.onValue;
|
|
||||||
|
|
||||||
stream.listen((DatabaseEvent event) {
|
stream.listen((DatabaseEvent event) {
|
||||||
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
||||||
if (data == null) return;
|
if (data == null) return;
|
||||||
|
|
||||||
List<Status> statusList = [];
|
final statusList = <Status>[];
|
||||||
if (data['status'] != null) {
|
if (data['status'] != null) {
|
||||||
for (var element in data['status']) {
|
for (var element in data['status']) {
|
||||||
statusList.add(
|
statusList.add(
|
||||||
@ -57,7 +64,7 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (statusList.isNotEmpty) {
|
if (statusList.isNotEmpty) {
|
||||||
bool newStatus = _checkStatus(statusList[0].value);
|
final newStatus = _checkStatus(statusList[0].value);
|
||||||
if (newStatus != deviceStatus) {
|
if (newStatus != deviceStatus) {
|
||||||
deviceStatus = newStatus;
|
deviceStatus = newStatus;
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
@ -71,76 +78,32 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onStatusUpdated(StatusUpdated event, Emitter<CurtainState> emit) {
|
void _onStatusUpdated(
|
||||||
|
StatusUpdated event,
|
||||||
|
Emitter<CurtainState> emit,
|
||||||
|
) {
|
||||||
emit(CurtainStatusLoading());
|
emit(CurtainStatusLoading());
|
||||||
deviceStatus = event.deviceStatus;
|
deviceStatus = event.deviceStatus;
|
||||||
emit(CurtainStatusLoaded(deviceStatus));
|
emit(CurtainStatusLoaded(deviceStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onCurtainControl(
|
Future<void> _onCurtainControl(
|
||||||
CurtainControl event, Emitter<CurtainState> emit) async {
|
CurtainControl event,
|
||||||
final oldValue = deviceStatus;
|
Emitter<CurtainState> emit,
|
||||||
|
) async {
|
||||||
|
emit(CurtainStatusLoading());
|
||||||
_updateLocalValue(event.value, emit);
|
_updateLocalValue(event.value, emit);
|
||||||
|
|
||||||
emit(CurtainStatusLoaded(deviceStatus));
|
try {
|
||||||
|
final controlValue = event.value ? 'open' : 'close';
|
||||||
await _runDebounce(
|
await controlDeviceService.controlDevice(
|
||||||
deviceId: event.deviceId,
|
deviceUuid: event.deviceId,
|
||||||
code: event.code,
|
status: Status(code: event.code, value: controlValue),
|
||||||
value: event.value,
|
);
|
||||||
oldValue: oldValue,
|
} catch (e) {
|
||||||
emit: emit,
|
_updateLocalValue(!event.value, emit);
|
||||||
isBatch: false,
|
emit(CurtainControlError(e.toString()));
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _runDebounce({
|
|
||||||
required dynamic deviceId,
|
|
||||||
required String code,
|
|
||||||
required bool value,
|
|
||||||
required bool oldValue,
|
|
||||||
required Emitter<CurtainState> emit,
|
|
||||||
required bool isBatch,
|
|
||||||
}) async {
|
|
||||||
late String id;
|
|
||||||
|
|
||||||
if (deviceId is List) {
|
|
||||||
id = deviceId.first;
|
|
||||||
} else {
|
|
||||||
id = deviceId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_timer != null) {
|
|
||||||
_timer!.cancel();
|
|
||||||
}
|
|
||||||
_timer = Timer(const Duration(seconds: 1), () async {
|
|
||||||
try {
|
|
||||||
final controlValue = value ? 'open' : 'close';
|
|
||||||
|
|
||||||
late bool response;
|
|
||||||
if (isBatch) {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceBatchControl(deviceId, code, controlValue);
|
|
||||||
} else {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceControl(deviceId, Status(code: code, value: controlValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response) {
|
|
||||||
_revertValueAndEmit(id, oldValue, emit);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
_revertValueAndEmit(id, oldValue, emit);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _revertValueAndEmit(
|
|
||||||
String deviceId, bool oldValue, Emitter<CurtainState> emit) {
|
|
||||||
_updateLocalValue(oldValue, emit);
|
|
||||||
emit(CurtainStatusLoaded(deviceStatus));
|
|
||||||
emit(const CurtainControlError('Failed to control the device.'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateLocalValue(bool value, Emitter<CurtainState> emit) {
|
void _updateLocalValue(bool value, Emitter<CurtainState> emit) {
|
||||||
@ -152,41 +115,44 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
|
|||||||
return command.toLowerCase() == 'open';
|
return command.toLowerCase() == 'open';
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onFetchBatchStatus(
|
Future<void> _onFetchBatchStatus(
|
||||||
CurtainFetchBatchStatus event, Emitter<CurtainState> emit) async {
|
CurtainFetchBatchStatus event,
|
||||||
|
Emitter<CurtainState> emit,
|
||||||
|
) async {
|
||||||
emit(CurtainStatusLoading());
|
emit(CurtainStatusLoading());
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
|
||||||
|
|
||||||
deviceStatus = _checkStatus(status.status[0].value);
|
deviceStatus = _checkStatus(status.status[0].value);
|
||||||
|
|
||||||
emit(CurtainStatusLoaded(deviceStatus));
|
emit(CurtainStatusLoaded(deviceStatus));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(CurtainError(e.toString()));
|
emit(CurtainError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onCurtainBatchControl(
|
Future<void> _onCurtainBatchControl(
|
||||||
CurtainBatchControl event, Emitter<CurtainState> emit) async {
|
CurtainBatchControl event,
|
||||||
final oldValue = deviceStatus;
|
Emitter<CurtainState> emit,
|
||||||
|
) async {
|
||||||
|
emit(CurtainStatusLoading());
|
||||||
_updateLocalValue(event.value, emit);
|
_updateLocalValue(event.value, emit);
|
||||||
|
|
||||||
emit(CurtainStatusLoaded(deviceStatus));
|
try {
|
||||||
|
final controlValue = event.value ? 'open' : 'stop';
|
||||||
await _runDebounce(
|
await batchControlDevicesService.batchControlDevices(
|
||||||
deviceId: event.devicesIds,
|
uuids: event.devicesIds,
|
||||||
code: event.code,
|
code: event.code,
|
||||||
value: event.value,
|
value: controlValue,
|
||||||
oldValue: oldValue,
|
);
|
||||||
emit: emit,
|
} catch (e) {
|
||||||
isBatch: true,
|
_updateLocalValue(!event.value, emit);
|
||||||
);
|
emit(CurtainControlError(e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onFactoryReset(
|
Future<void> _onFactoryReset(
|
||||||
CurtainFactoryReset event, Emitter<CurtainState> emit) async {
|
CurtainFactoryReset event,
|
||||||
|
Emitter<CurtainState> emit,
|
||||||
|
) async {
|
||||||
emit(CurtainStatusLoading());
|
emit(CurtainStatusLoading());
|
||||||
try {
|
try {
|
||||||
final response = await DevicesManagementApi().factoryReset(
|
final response = await DevicesManagementApi().factoryReset(
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||||
|
|
||||||
|
abstract final class CurtainBlocFactory {
|
||||||
|
const CurtainBlocFactory._();
|
||||||
|
|
||||||
|
static CurtainBloc create({
|
||||||
|
required String deviceId,
|
||||||
|
}) {
|
||||||
|
return CurtainBloc(
|
||||||
|
deviceId: deviceId,
|
||||||
|
controlDeviceService:
|
||||||
|
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||||
|
batchControlDevicesService:
|
||||||
|
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_re
|
|||||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart';
|
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart';
|
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/curtain/factories/curtain_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||||
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.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';
|
||||||
@ -18,7 +19,7 @@ class CurtainBatchStatusView extends StatelessWidget with HelperResponsiveLayout
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) =>
|
create: (context) =>
|
||||||
CurtainBloc(deviceId: devicesIds.first)..add(CurtainFetchBatchStatus(devicesIds)),
|
CurtainBlocFactory.create(deviceId: devicesIds.first)..add(CurtainFetchBatchStatus(devicesIds)),
|
||||||
child: BlocBuilder<CurtainBloc, CurtainState>(
|
child: BlocBuilder<CurtainBloc, CurtainState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is CurtainStatusLoading) {
|
if (state is CurtainStatusLoading) {
|
||||||
|
@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/common/curtain_toggle.dart';
|
|||||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart';
|
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart';
|
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/curtain/factories/curtain_bloc_factory.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 CurtainStatusControlsView extends StatelessWidget
|
class CurtainStatusControlsView extends StatelessWidget
|
||||||
@ -15,7 +16,7 @@ class CurtainStatusControlsView extends StatelessWidget
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => CurtainBloc(deviceId: deviceId)
|
create: (context) => CurtainBlocFactory.create(deviceId: deviceId)
|
||||||
..add(CurtainFetchDeviceStatus(deviceId)),
|
..add(CurtainFetchDeviceStatus(deviceId)),
|
||||||
child: BlocBuilder<CurtainBloc, CurtainState>(
|
child: BlocBuilder<CurtainBloc, CurtainState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||||
|
import 'package:syncrow_web/services/control_device_service.dart';
|
||||||
|
|
||||||
|
abstract final class DeviceBlocDependenciesFactory {
|
||||||
|
const DeviceBlocDependenciesFactory._();
|
||||||
|
|
||||||
|
static ControlDeviceService createControlDeviceService() {
|
||||||
|
return DebouncedControlDeviceService(
|
||||||
|
decoratee: RemoteControlDeviceService(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BatchControlDevicesService createBatchControlDevicesService() {
|
||||||
|
return DebouncedBatchControlDevicesService(
|
||||||
|
decoratee: RemoteBatchControlDevicesService(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
|
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart';
|
||||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
|
||||||
import 'package:syncrow_web/services/control_device_service.dart';
|
|
||||||
|
|
||||||
abstract final class FlushMountedPresenceSensorBlocFactory {
|
abstract final class FlushMountedPresenceSensorBlocFactory {
|
||||||
const FlushMountedPresenceSensorBlocFactory._();
|
const FlushMountedPresenceSensorBlocFactory._();
|
||||||
@ -10,12 +9,8 @@ abstract final class FlushMountedPresenceSensorBlocFactory {
|
|||||||
}) {
|
}) {
|
||||||
return FlushMountedPresenceSensorBloc(
|
return FlushMountedPresenceSensorBloc(
|
||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
controlDeviceService: DebouncedControlDeviceService(
|
controlDeviceService: DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||||
decoratee: RemoteControlDeviceService(),
|
batchControlDevicesService: DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||||
),
|
|
||||||
batchControlDevicesService: DebouncedBatchControlDevicesService(
|
|
||||||
decoratee: RemoteBatchControlDevicesService(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
|
||||||
import 'package:firebase_database/firebase_database.dart';
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
import 'package:syncrow_web/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/one_g_glass_switch/models/once_gang_glass_status_model.dart';
|
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart';
|
||||||
|
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||||
|
import 'package:syncrow_web/services/control_device_service.dart';
|
||||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||||
|
|
||||||
part 'one_gang_glass_switch_event.dart';
|
part 'one_gang_glass_switch_event.dart';
|
||||||
@ -13,13 +15,16 @@ part 'one_gang_glass_switch_state.dart';
|
|||||||
|
|
||||||
class OneGangGlassSwitchBloc
|
class OneGangGlassSwitchBloc
|
||||||
extends Bloc<OneGangGlassSwitchEvent, OneGangGlassSwitchState> {
|
extends Bloc<OneGangGlassSwitchEvent, OneGangGlassSwitchState> {
|
||||||
OneGangGlassStatusModel deviceStatus;
|
late OneGangGlassStatusModel deviceStatus;
|
||||||
Timer? _timer;
|
final String deviceId;
|
||||||
|
final ControlDeviceService controlDeviceService;
|
||||||
|
final BatchControlDevicesService batchControlDevicesService;
|
||||||
|
|
||||||
OneGangGlassSwitchBloc({required String deviceId})
|
OneGangGlassSwitchBloc({
|
||||||
: deviceStatus = OneGangGlassStatusModel(
|
required this.deviceId,
|
||||||
uuid: deviceId, switch1: false, countDown: 0),
|
required this.controlDeviceService,
|
||||||
super(OneGangGlassSwitchInitial()) {
|
required this.batchControlDevicesService,
|
||||||
|
}) : super(OneGangGlassSwitchInitial()) {
|
||||||
on<OneGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
on<OneGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
||||||
on<OneGangGlassSwitchControl>(_onControl);
|
on<OneGangGlassSwitchControl>(_onControl);
|
||||||
on<OneGangGlassSwitchBatchControl>(_onBatchControl);
|
on<OneGangGlassSwitchBatchControl>(_onBatchControl);
|
||||||
@ -28,160 +33,140 @@ class OneGangGlassSwitchBloc
|
|||||||
on<StatusUpdated>(_onStatusUpdated);
|
on<StatusUpdated>(_onStatusUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onFetchDeviceStatus(OneGangGlassSwitchFetchDeviceEvent event,
|
Future<void> _onFetchDeviceStatus(
|
||||||
Emitter<OneGangGlassSwitchState> emit) async {
|
OneGangGlassSwitchFetchDeviceEvent event,
|
||||||
|
Emitter<OneGangGlassSwitchState> emit,
|
||||||
|
) async {
|
||||||
emit(OneGangGlassSwitchLoading());
|
emit(OneGangGlassSwitchLoading());
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
_listenToChanges(event.deviceId, emit);
|
||||||
_listenToChanges(event.deviceId);
|
deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceId, status.status);
|
||||||
deviceStatus =
|
|
||||||
OneGangGlassStatusModel.fromJson(event.deviceId, status.status);
|
|
||||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(OneGangGlassSwitchError(e.toString()));
|
emit(OneGangGlassSwitchError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_listenToChanges(deviceId) {
|
void _listenToChanges(
|
||||||
|
String deviceId,
|
||||||
|
Emitter<OneGangGlassSwitchState> emit,
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
DatabaseReference ref =
|
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
final stream = ref.onValue;
|
||||||
Stream<DatabaseEvent> stream = ref.onValue;
|
|
||||||
|
|
||||||
stream.listen((DatabaseEvent event) {
|
stream.listen((DatabaseEvent event) {
|
||||||
Map<dynamic, dynamic> usersMap =
|
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
||||||
event.snapshot.value as Map<dynamic, dynamic>;
|
if (data == null) return;
|
||||||
|
|
||||||
List<Status> statusList = [];
|
final statusList = <Status>[];
|
||||||
usersMap['status'].forEach((element) {
|
if (data['status'] != null) {
|
||||||
statusList
|
for (var element in data['status']) {
|
||||||
.add(Status(code: element['code'], value: element['value']));
|
statusList.add(
|
||||||
});
|
Status(
|
||||||
|
code: element['code'].toString(),
|
||||||
deviceStatus = OneGangGlassStatusModel.fromJson(
|
value: element['value'].toString(),
|
||||||
usersMap['productUuid'], statusList);
|
),
|
||||||
if (!isClosed) {
|
);
|
||||||
add(StatusUpdated(deviceStatus));
|
}
|
||||||
|
}
|
||||||
|
if (statusList.isNotEmpty) {
|
||||||
|
final newStatus = OneGangGlassStatusModel.fromJson(deviceId, statusList);
|
||||||
|
if (newStatus != deviceStatus) {
|
||||||
|
deviceStatus = newStatus;
|
||||||
|
if (!isClosed) {
|
||||||
|
add(StatusUpdated(deviceStatus));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (_) {}
|
} catch (e) {
|
||||||
|
emit(OneGangGlassSwitchError('Failed to listen to changes: $e'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onStatusUpdated(
|
void _onStatusUpdated(
|
||||||
StatusUpdated event, Emitter<OneGangGlassSwitchState> emit) {
|
StatusUpdated event,
|
||||||
|
Emitter<OneGangGlassSwitchState> emit,
|
||||||
|
) {
|
||||||
|
emit(OneGangGlassSwitchLoading());
|
||||||
deviceStatus = event.deviceStatus;
|
deviceStatus = event.deviceStatus;
|
||||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onControl(OneGangGlassSwitchControl event,
|
Future<void> _onControl(
|
||||||
Emitter<OneGangGlassSwitchState> emit) async {
|
OneGangGlassSwitchControl event,
|
||||||
final oldValue = _getValueByCode(event.code);
|
Emitter<OneGangGlassSwitchState> emit,
|
||||||
|
) async {
|
||||||
|
emit(OneGangGlassSwitchLoading());
|
||||||
_updateLocalValue(event.code, event.value);
|
_updateLocalValue(event.code, event.value);
|
||||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
||||||
|
|
||||||
await _runDebounce(
|
|
||||||
deviceId: event.deviceId,
|
|
||||||
code: event.code,
|
|
||||||
value: event.value,
|
|
||||||
oldValue: oldValue,
|
|
||||||
emit: emit,
|
|
||||||
isBatch: false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onFactoryReset(OneGangGlassFactoryResetEvent event,
|
|
||||||
Emitter<OneGangGlassSwitchState> emit) async {
|
|
||||||
emit(OneGangGlassSwitchLoading());
|
|
||||||
try {
|
try {
|
||||||
final response = await DevicesManagementApi()
|
await controlDeviceService.controlDevice(
|
||||||
.factoryReset(event.factoryReset, event.deviceId);
|
deviceUuid: event.deviceId,
|
||||||
if (!response) {
|
status: Status(code: event.code, value: event.value),
|
||||||
emit(OneGangGlassSwitchError('Failed to reset device'));
|
);
|
||||||
} else {
|
|
||||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
_updateLocalValue(event.code, !event.value);
|
||||||
emit(OneGangGlassSwitchError(e.toString()));
|
emit(OneGangGlassSwitchError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onBatchControl(OneGangGlassSwitchBatchControl event,
|
Future<void> _onBatchControl(
|
||||||
Emitter<OneGangGlassSwitchState> emit) async {
|
OneGangGlassSwitchBatchControl event,
|
||||||
final oldValue = _getValueByCode(event.code);
|
Emitter<OneGangGlassSwitchState> emit,
|
||||||
|
) async {
|
||||||
|
emit(OneGangGlassSwitchLoading());
|
||||||
_updateLocalValue(event.code, event.value);
|
_updateLocalValue(event.code, event.value);
|
||||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
||||||
|
|
||||||
await _runDebounce(
|
try {
|
||||||
deviceId: event.deviceIds,
|
await batchControlDevicesService.batchControlDevices(
|
||||||
code: event.code,
|
uuids: event.deviceIds,
|
||||||
value: event.value,
|
code: event.code,
|
||||||
oldValue: oldValue,
|
value: event.value,
|
||||||
emit: emit,
|
);
|
||||||
isBatch: true,
|
} catch (e) {
|
||||||
);
|
_updateLocalValue(event.code, !event.value);
|
||||||
|
emit(OneGangGlassSwitchError(e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onFetchBatchStatus(
|
Future<void> _onFetchBatchStatus(
|
||||||
OneGangGlassSwitchFetchBatchStatusEvent event,
|
OneGangGlassSwitchFetchBatchStatusEvent event,
|
||||||
Emitter<OneGangGlassSwitchState> emit) async {
|
Emitter<OneGangGlassSwitchState> emit,
|
||||||
|
) async {
|
||||||
emit(OneGangGlassSwitchLoading());
|
emit(OneGangGlassSwitchLoading());
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
|
||||||
await DevicesManagementApi().getBatchStatus(event.deviceIds);
|
deviceStatus =
|
||||||
deviceStatus = OneGangGlassStatusModel.fromJson(
|
OneGangGlassStatusModel.fromJson(event.deviceIds.first, status.status);
|
||||||
event.deviceIds.first, status.status);
|
|
||||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(OneGangGlassSwitchError(e.toString()));
|
emit(OneGangGlassSwitchError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _runDebounce({
|
Future<void> _onFactoryReset(
|
||||||
required dynamic deviceId,
|
OneGangGlassFactoryResetEvent event,
|
||||||
required String code,
|
Emitter<OneGangGlassSwitchState> emit,
|
||||||
required bool value,
|
) async {
|
||||||
required bool oldValue,
|
emit(OneGangGlassSwitchLoading());
|
||||||
required Emitter<OneGangGlassSwitchState> emit,
|
try {
|
||||||
required bool isBatch,
|
final response = await DevicesManagementApi().factoryReset(
|
||||||
}) async {
|
event.factoryReset,
|
||||||
late String id;
|
event.deviceId,
|
||||||
if (deviceId is List) {
|
);
|
||||||
id = deviceId.first;
|
if (!response) {
|
||||||
} else {
|
emit(OneGangGlassSwitchError('Failed to reset device'));
|
||||||
id = deviceId;
|
} else {
|
||||||
}
|
add(OneGangGlassSwitchFetchDeviceEvent(event.deviceId));
|
||||||
|
|
||||||
if (_timer != null) {
|
|
||||||
_timer!.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
_timer = Timer(const Duration(milliseconds: 500), () async {
|
|
||||||
try {
|
|
||||||
late bool response;
|
|
||||||
if (isBatch) {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceBatchControl(deviceId, code, value);
|
|
||||||
} else {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceControl(deviceId, Status(code: code, value: value));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response) {
|
|
||||||
_revertValueAndEmit(id, code, oldValue, emit);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
_revertValueAndEmit(id, code, oldValue, emit);
|
|
||||||
}
|
}
|
||||||
});
|
} catch (e) {
|
||||||
}
|
emit(OneGangGlassSwitchError(e.toString()));
|
||||||
|
}
|
||||||
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
|
|
||||||
Emitter<OneGangGlassSwitchState> emit) {
|
|
||||||
_updateLocalValue(code, oldValue);
|
|
||||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateLocalValue(String code, bool value) {
|
void _updateLocalValue(String code, bool value) {
|
||||||
@ -189,19 +174,4 @@ class OneGangGlassSwitchBloc
|
|||||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _getValueByCode(String code) {
|
|
||||||
switch (code) {
|
|
||||||
case 'switch_1':
|
|
||||||
return deviceStatus.switch1;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() {
|
|
||||||
_timer?.cancel();
|
|
||||||
return super.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart';
|
||||||
|
|
||||||
|
abstract final class OneGangGlassSwitchBlocFactory {
|
||||||
|
const OneGangGlassSwitchBlocFactory._();
|
||||||
|
|
||||||
|
static OneGangGlassSwitchBloc create({
|
||||||
|
required String deviceId,
|
||||||
|
}) {
|
||||||
|
return OneGangGlassSwitchBloc(
|
||||||
|
deviceId: deviceId,
|
||||||
|
controlDeviceService:
|
||||||
|
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||||
|
batchControlDevicesService:
|
||||||
|
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.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/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/factories/one_gang_glass_switch_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart';
|
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||||
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.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';
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ class OneGangGlassSwitchBatchControlView extends StatelessWidget with HelperResp
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => OneGangGlassSwitchBloc(deviceId: deviceIds.first)
|
create: (context) => OneGangGlassSwitchBlocFactory.create(deviceId: deviceIds.first)
|
||||||
..add(OneGangGlassSwitchFetchBatchStatusEvent(deviceIds)),
|
..add(OneGangGlassSwitchFetchBatchStatusEvent(deviceIds)),
|
||||||
child: BlocBuilder<OneGangGlassSwitchBloc, OneGangGlassSwitchState>(
|
child: BlocBuilder<OneGangGlassSwitchBloc, OneGangGlassSwitchState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/factories/one_gang_glass_switch_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart';
|
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
@ -9,13 +10,13 @@ import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_la
|
|||||||
class OneGangGlassSwitchControlView extends StatelessWidget with HelperResponsiveLayout {
|
class OneGangGlassSwitchControlView extends StatelessWidget with HelperResponsiveLayout {
|
||||||
final String deviceId;
|
final String deviceId;
|
||||||
|
|
||||||
const OneGangGlassSwitchControlView({required this.deviceId, Key? key}) : super(key: key);
|
const OneGangGlassSwitchControlView({required this.deviceId, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) =>
|
create: (context) =>
|
||||||
OneGangGlassSwitchBloc(deviceId: deviceId)..add(OneGangGlassSwitchFetchDeviceEvent(deviceId)),
|
OneGangGlassSwitchBlocFactory.create(deviceId: deviceId)..add(OneGangGlassSwitchFetchDeviceEvent(deviceId)),
|
||||||
child: BlocBuilder<OneGangGlassSwitchBloc, OneGangGlassSwitchState>(
|
child: BlocBuilder<OneGangGlassSwitchBloc, OneGangGlassSwitchState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is OneGangGlassSwitchLoading) {
|
if (state is OneGangGlassSwitchLoading) {
|
||||||
|
@ -6,12 +6,21 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_sta
|
|||||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart';
|
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart';
|
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
|
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
|
||||||
|
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||||
|
import 'package:syncrow_web/services/control_device_service.dart';
|
||||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||||
|
|
||||||
class WallLightSwitchBloc
|
class WallLightSwitchBloc extends Bloc<WallLightSwitchEvent, WallLightSwitchState> {
|
||||||
extends Bloc<WallLightSwitchEvent, WallLightSwitchState> {
|
late WallLightStatusModel deviceStatus;
|
||||||
WallLightSwitchBloc({required this.deviceId})
|
final String deviceId;
|
||||||
: super(WallLightSwitchInitial()) {
|
final ControlDeviceService controlDeviceService;
|
||||||
|
final BatchControlDevicesService batchControlDevicesService;
|
||||||
|
|
||||||
|
WallLightSwitchBloc({
|
||||||
|
required this.deviceId,
|
||||||
|
required this.controlDeviceService,
|
||||||
|
required this.batchControlDevicesService,
|
||||||
|
}) : super(WallLightSwitchInitial()) {
|
||||||
on<WallLightSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
on<WallLightSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
||||||
on<WallLightSwitchControl>(_onControl);
|
on<WallLightSwitchControl>(_onControl);
|
||||||
on<WallLightSwitchFetchBatchEvent>(_onFetchBatchStatus);
|
on<WallLightSwitchFetchBatchEvent>(_onFetchBatchStatus);
|
||||||
@ -20,143 +29,114 @@ class WallLightSwitchBloc
|
|||||||
on<StatusUpdated>(_onStatusUpdated);
|
on<StatusUpdated>(_onStatusUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
late WallLightStatusModel deviceStatus;
|
Future<void> _onFetchDeviceStatus(
|
||||||
final String deviceId;
|
WallLightSwitchFetchDeviceEvent event,
|
||||||
Timer? _timer;
|
Emitter<WallLightSwitchState> emit,
|
||||||
|
) async {
|
||||||
FutureOr<void> _onFetchDeviceStatus(WallLightSwitchFetchDeviceEvent event,
|
|
||||||
Emitter<WallLightSwitchState> emit) async {
|
|
||||||
emit(WallLightSwitchLoading());
|
emit(WallLightSwitchLoading());
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
_listenToChanges(event.deviceId, emit);
|
||||||
|
deviceStatus = WallLightStatusModel.fromJson(event.deviceId, status.status);
|
||||||
deviceStatus =
|
|
||||||
WallLightStatusModel.fromJson(event.deviceId, status.status);
|
|
||||||
_listenToChanges(event.deviceId);
|
|
||||||
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(WallLightSwitchError(e.toString()));
|
emit(WallLightSwitchError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_listenToChanges(deviceId) {
|
void _listenToChanges(
|
||||||
|
String deviceId,
|
||||||
|
Emitter<WallLightSwitchState> emit,
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
DatabaseReference ref =
|
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
final stream = ref.onValue;
|
||||||
Stream<DatabaseEvent> stream = ref.onValue;
|
|
||||||
|
|
||||||
stream.listen((DatabaseEvent event) {
|
stream.listen((DatabaseEvent event) {
|
||||||
Map<dynamic, dynamic> usersMap =
|
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
||||||
event.snapshot.value as Map<dynamic, dynamic>;
|
if (data == null) return;
|
||||||
|
|
||||||
List<Status> statusList = [];
|
final statusList = <Status>[];
|
||||||
usersMap['status'].forEach((element) {
|
if (data['status'] != null) {
|
||||||
statusList
|
for (var element in data['status']) {
|
||||||
.add(Status(code: element['code'], value: element['value']));
|
statusList.add(
|
||||||
});
|
Status(
|
||||||
|
code: element['code'].toString(),
|
||||||
deviceStatus =
|
value: element['value'].toString(),
|
||||||
WallLightStatusModel.fromJson(usersMap['productUuid'], statusList);
|
),
|
||||||
if (!isClosed) {
|
);
|
||||||
add(StatusUpdated(deviceStatus));
|
}
|
||||||
|
}
|
||||||
|
if (statusList.isNotEmpty) {
|
||||||
|
final newStatus = WallLightStatusModel.fromJson(deviceId, statusList);
|
||||||
|
if (newStatus != deviceStatus) {
|
||||||
|
deviceStatus = newStatus;
|
||||||
|
if (!isClosed) {
|
||||||
|
add(StatusUpdated(deviceStatus));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (_) {}
|
} catch (e) {
|
||||||
|
emit(WallLightSwitchError('Failed to listen to changes: $e'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onStatusUpdated(
|
void _onStatusUpdated(
|
||||||
StatusUpdated event, Emitter<WallLightSwitchState> emit) {
|
StatusUpdated event,
|
||||||
|
Emitter<WallLightSwitchState> emit,
|
||||||
|
) {
|
||||||
|
emit(WallLightSwitchLoading());
|
||||||
deviceStatus = event.deviceStatus;
|
deviceStatus = event.deviceStatus;
|
||||||
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onControl(
|
Future<void> _onControl(
|
||||||
WallLightSwitchControl event, Emitter<WallLightSwitchState> emit) async {
|
WallLightSwitchControl event,
|
||||||
final oldValue = _getValueByCode(event.code);
|
Emitter<WallLightSwitchState> emit,
|
||||||
|
) async {
|
||||||
|
emit(WallLightSwitchLoading());
|
||||||
_updateLocalValue(event.code, event.value);
|
_updateLocalValue(event.code, event.value);
|
||||||
|
|
||||||
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
||||||
|
|
||||||
await _runDebounce(
|
try {
|
||||||
deviceId: event.deviceId,
|
await controlDeviceService.controlDevice(
|
||||||
code: event.code,
|
deviceUuid: event.deviceId,
|
||||||
value: event.value,
|
status: Status(code: event.code, value: event.value),
|
||||||
oldValue: oldValue,
|
);
|
||||||
emit: emit,
|
} catch (e) {
|
||||||
isBatch: false,
|
_updateLocalValue(event.code, !event.value);
|
||||||
);
|
emit(WallLightSwitchError(e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _runDebounce({
|
Future<void> _onBatchControl(
|
||||||
required dynamic deviceId,
|
WallLightSwitchBatchControl event,
|
||||||
required String code,
|
Emitter<WallLightSwitchState> emit,
|
||||||
required bool value,
|
) async {
|
||||||
required bool oldValue,
|
emit(WallLightSwitchLoading());
|
||||||
required Emitter<WallLightSwitchState> emit,
|
_updateLocalValue(event.code, event.value);
|
||||||
required bool isBatch,
|
|
||||||
}) async {
|
|
||||||
late String id;
|
|
||||||
|
|
||||||
if (deviceId is List) {
|
|
||||||
id = deviceId.first;
|
|
||||||
} else {
|
|
||||||
id = deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_timer != null) {
|
|
||||||
_timer!.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
_timer = Timer(const Duration(milliseconds: 500), () async {
|
|
||||||
try {
|
|
||||||
late bool response;
|
|
||||||
|
|
||||||
if (isBatch) {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceBatchControl(deviceId, code, value);
|
|
||||||
} else {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceControl(deviceId, Status(code: code, value: value));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response) {
|
|
||||||
_revertValueAndEmit(id, code, oldValue, emit);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
_revertValueAndEmit(id, code, oldValue, emit);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
|
|
||||||
Emitter<WallLightSwitchState> emit) {
|
|
||||||
_updateLocalValue(code, oldValue);
|
|
||||||
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
||||||
}
|
|
||||||
|
|
||||||
void _updateLocalValue(String code, bool value) {
|
try {
|
||||||
if (code == 'switch_1') {
|
await batchControlDevicesService.batchControlDevices(
|
||||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
uuids: event.devicesIds,
|
||||||
|
code: event.code,
|
||||||
|
value: event.value,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
_updateLocalValue(event.code, !event.value);
|
||||||
|
emit(WallLightSwitchError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _getValueByCode(String code) {
|
Future<void> _onFetchBatchStatus(
|
||||||
switch (code) {
|
WallLightSwitchFetchBatchEvent event,
|
||||||
case 'switch_1':
|
Emitter<WallLightSwitchState> emit,
|
||||||
return deviceStatus.switch1;
|
) async {
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onFetchBatchStatus(WallLightSwitchFetchBatchEvent event,
|
|
||||||
Emitter<WallLightSwitchState> emit) async {
|
|
||||||
emit(WallLightSwitchLoading());
|
emit(WallLightSwitchLoading());
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
|
||||||
deviceStatus =
|
deviceStatus =
|
||||||
WallLightStatusModel.fromJson(event.devicesIds.first, status.status);
|
WallLightStatusModel.fromJson(event.devicesIds.first, status.status);
|
||||||
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
||||||
@ -165,32 +145,10 @@ class WallLightSwitchBloc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Future<void> _onFactoryReset(
|
||||||
Future<void> close() {
|
WallLightFactoryReset event,
|
||||||
_timer?.cancel();
|
Emitter<WallLightSwitchState> emit,
|
||||||
return super.close();
|
) async {
|
||||||
}
|
|
||||||
|
|
||||||
FutureOr<void> _onBatchControl(WallLightSwitchBatchControl event,
|
|
||||||
Emitter<WallLightSwitchState> emit) async {
|
|
||||||
final oldValue = _getValueByCode(event.code);
|
|
||||||
|
|
||||||
_updateLocalValue(event.code, event.value);
|
|
||||||
|
|
||||||
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
|
||||||
|
|
||||||
await _runDebounce(
|
|
||||||
deviceId: event.devicesIds,
|
|
||||||
code: event.code,
|
|
||||||
value: event.value,
|
|
||||||
oldValue: oldValue,
|
|
||||||
emit: emit,
|
|
||||||
isBatch: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
FutureOr<void> _onFactoryReset(
|
|
||||||
WallLightFactoryReset event, Emitter<WallLightSwitchState> emit) async {
|
|
||||||
emit(WallLightSwitchLoading());
|
emit(WallLightSwitchLoading());
|
||||||
try {
|
try {
|
||||||
final response = await DevicesManagementApi().factoryReset(
|
final response = await DevicesManagementApi().factoryReset(
|
||||||
@ -198,12 +156,18 @@ class WallLightSwitchBloc
|
|||||||
event.deviceId,
|
event.deviceId,
|
||||||
);
|
);
|
||||||
if (!response) {
|
if (!response) {
|
||||||
emit(WallLightSwitchError('Failed'));
|
emit(WallLightSwitchError('Failed to reset device'));
|
||||||
} else {
|
} else {
|
||||||
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
add(WallLightSwitchFetchDeviceEvent(event.deviceId));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(WallLightSwitchError(e.toString()));
|
emit(WallLightSwitchError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _updateLocalValue(String code, bool value) {
|
||||||
|
if (code == 'switch_1') {
|
||||||
|
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart';
|
||||||
|
|
||||||
|
abstract final class WallLightSwitchBlocFactory {
|
||||||
|
const WallLightSwitchBlocFactory._();
|
||||||
|
|
||||||
|
static WallLightSwitchBloc create({
|
||||||
|
required String deviceId,
|
||||||
|
}) {
|
||||||
|
return WallLightSwitchBloc(
|
||||||
|
deviceId: deviceId,
|
||||||
|
controlDeviceService:
|
||||||
|
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||||
|
batchControlDevicesService:
|
||||||
|
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -4,9 +4,9 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_re
|
|||||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart';
|
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart';
|
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/one_gang_switch/factories/wall_light_switch_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
|
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||||
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.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';
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ class WallLightBatchControlView extends StatelessWidget with HelperResponsiveLay
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => WallLightSwitchBloc(deviceId: deviceIds.first)
|
create: (context) => WallLightSwitchBlocFactory.create(deviceId: deviceIds.first)
|
||||||
..add(WallLightSwitchFetchBatchEvent(deviceIds)),
|
..add(WallLightSwitchFetchBatchEvent(deviceIds)),
|
||||||
child: BlocBuilder<WallLightSwitchBloc, WallLightSwitchState>(
|
child: BlocBuilder<WallLightSwitchBloc, WallLightSwitchState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart';
|
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart';
|
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/one_gang_switch/factories/wall_light_switch_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
|
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.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';
|
||||||
@ -15,7 +16,7 @@ class WallLightDeviceControl extends StatelessWidget
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => WallLightSwitchBloc(deviceId: deviceId)
|
create: (context) => WallLightSwitchBlocFactory.create(deviceId: deviceId)
|
||||||
..add(WallLightSwitchFetchDeviceEvent(deviceId)),
|
..add(WallLightSwitchFetchDeviceEvent(deviceId)),
|
||||||
child: BlocBuilder<WallLightSwitchBloc, WallLightSwitchState>(
|
child: BlocBuilder<WallLightSwitchBloc, WallLightSwitchState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
@ -157,7 +157,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Text(
|
SelectableText(
|
||||||
value,
|
value,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:firebase_database/firebase_database.dart';
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
import 'package:syncrow_web/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/three_g_glass_switch/models/three_gang_glass_switch.dart';
|
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/models/three_gang_glass_switch.dart';
|
||||||
|
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||||
|
import 'package:syncrow_web/services/control_device_service.dart';
|
||||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||||
|
|
||||||
part 'three_gang_glass_switch_event.dart';
|
part 'three_gang_glass_switch_event.dart';
|
||||||
@ -13,19 +16,16 @@ part 'three_gang_glass_switch_state.dart';
|
|||||||
|
|
||||||
class ThreeGangGlassSwitchBloc
|
class ThreeGangGlassSwitchBloc
|
||||||
extends Bloc<ThreeGangGlassSwitchEvent, ThreeGangGlassSwitchState> {
|
extends Bloc<ThreeGangGlassSwitchEvent, ThreeGangGlassSwitchState> {
|
||||||
ThreeGangGlassStatusModel deviceStatus;
|
late ThreeGangGlassStatusModel deviceStatus;
|
||||||
Timer? _timer;
|
final String deviceId;
|
||||||
|
final ControlDeviceService controlDeviceService;
|
||||||
|
final BatchControlDevicesService batchControlDevicesService;
|
||||||
|
|
||||||
ThreeGangGlassSwitchBloc({required String deviceId})
|
ThreeGangGlassSwitchBloc({
|
||||||
: deviceStatus = ThreeGangGlassStatusModel(
|
required this.deviceId,
|
||||||
uuid: deviceId,
|
required this.controlDeviceService,
|
||||||
switch1: false,
|
required this.batchControlDevicesService,
|
||||||
countDown1: 0,
|
}) : super(ThreeGangGlassSwitchInitial()) {
|
||||||
switch2: false,
|
|
||||||
countDown2: 0,
|
|
||||||
switch3: false,
|
|
||||||
countDown3: 0),
|
|
||||||
super(ThreeGangGlassSwitchInitial()) {
|
|
||||||
on<ThreeGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
on<ThreeGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
||||||
on<ThreeGangGlassSwitchControl>(_onControl);
|
on<ThreeGangGlassSwitchControl>(_onControl);
|
||||||
on<ThreeGangGlassSwitchBatchControl>(_onBatchControl);
|
on<ThreeGangGlassSwitchBatchControl>(_onBatchControl);
|
||||||
@ -34,188 +34,154 @@ class ThreeGangGlassSwitchBloc
|
|||||||
on<StatusUpdated>(_onStatusUpdated);
|
on<StatusUpdated>(_onStatusUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onFetchDeviceStatus(ThreeGangGlassSwitchFetchDeviceEvent event,
|
Future<void> _onFetchDeviceStatus(
|
||||||
Emitter<ThreeGangGlassSwitchState> emit) async {
|
ThreeGangGlassSwitchFetchDeviceEvent event,
|
||||||
|
Emitter<ThreeGangGlassSwitchState> emit,
|
||||||
|
) async {
|
||||||
emit(ThreeGangGlassSwitchLoading());
|
emit(ThreeGangGlassSwitchLoading());
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
_listenToChanges(event.deviceId, emit);
|
||||||
deviceStatus =
|
deviceStatus =
|
||||||
ThreeGangGlassStatusModel.fromJson(event.deviceId, status.status);
|
ThreeGangGlassStatusModel.fromJson(event.deviceId, status.status);
|
||||||
_listenToChanges(event.deviceId);
|
|
||||||
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
|
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(ThreeGangGlassSwitchError(e.toString()));
|
emit(ThreeGangGlassSwitchError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_listenToChanges(deviceId) {
|
void _listenToChanges(
|
||||||
|
String deviceId,
|
||||||
|
Emitter<ThreeGangGlassSwitchState> emit,
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
DatabaseReference ref =
|
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
final stream = ref.onValue;
|
||||||
Stream<DatabaseEvent> stream = ref.onValue;
|
|
||||||
|
|
||||||
stream.listen((DatabaseEvent event) {
|
stream.listen((DatabaseEvent event) {
|
||||||
Map<dynamic, dynamic> usersMap =
|
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
||||||
event.snapshot.value as Map<dynamic, dynamic>;
|
if (data == null) return;
|
||||||
|
|
||||||
List<Status> statusList = [];
|
final statusList = <Status>[];
|
||||||
usersMap['status'].forEach((element) {
|
if (data['status'] != null) {
|
||||||
statusList
|
for (var element in data['status']) {
|
||||||
.add(Status(code: element['code'], value: element['value']));
|
statusList.add(
|
||||||
});
|
Status(
|
||||||
|
code: element['code'].toString(),
|
||||||
deviceStatus = ThreeGangGlassStatusModel.fromJson(
|
value: element['value'].toString(),
|
||||||
usersMap['productUuid'], statusList);
|
),
|
||||||
if (!isClosed) {
|
);
|
||||||
add(StatusUpdated(deviceStatus));
|
}
|
||||||
|
}
|
||||||
|
if (statusList.isNotEmpty) {
|
||||||
|
final newStatus = ThreeGangGlassStatusModel.fromJson(deviceId, statusList);
|
||||||
|
if (newStatus != deviceStatus) {
|
||||||
|
deviceStatus = newStatus;
|
||||||
|
if (!isClosed) {
|
||||||
|
add(StatusUpdated(deviceStatus));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (_) {}
|
} catch (e) {
|
||||||
|
emit(ThreeGangGlassSwitchError('Failed to listen to changes: $e'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onStatusUpdated(
|
void _onStatusUpdated(
|
||||||
StatusUpdated event, Emitter<ThreeGangGlassSwitchState> emit) {
|
StatusUpdated event,
|
||||||
|
Emitter<ThreeGangGlassSwitchState> emit,
|
||||||
|
) {
|
||||||
|
emit(ThreeGangGlassSwitchLoading());
|
||||||
deviceStatus = event.deviceStatus;
|
deviceStatus = event.deviceStatus;
|
||||||
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
|
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onControl(ThreeGangGlassSwitchControl event,
|
Future<void> _onControl(
|
||||||
Emitter<ThreeGangGlassSwitchState> emit) async {
|
ThreeGangGlassSwitchControl event,
|
||||||
final oldValue = _getValueByCode(event.code);
|
Emitter<ThreeGangGlassSwitchState> emit,
|
||||||
|
) async {
|
||||||
|
emit(ThreeGangGlassSwitchLoading());
|
||||||
_updateLocalValue(event.code, event.value);
|
_updateLocalValue(event.code, event.value);
|
||||||
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
|
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
|
||||||
|
|
||||||
await _runDebounce(
|
try {
|
||||||
deviceId: event.deviceId,
|
await controlDeviceService.controlDevice(
|
||||||
code: event.code,
|
deviceUuid: event.deviceId,
|
||||||
value: event.value,
|
status: Status(code: event.code, value: event.value),
|
||||||
oldValue: oldValue,
|
);
|
||||||
emit: emit,
|
} catch (e) {
|
||||||
isBatch: false,
|
_updateLocalValue(event.code, !event.value);
|
||||||
);
|
emit(ThreeGangGlassSwitchError(e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onBatchControl(ThreeGangGlassSwitchBatchControl event,
|
Future<void> _onBatchControl(
|
||||||
Emitter<ThreeGangGlassSwitchState> emit) async {
|
ThreeGangGlassSwitchBatchControl event,
|
||||||
final oldValue = _getValueByCode(event.code);
|
Emitter<ThreeGangGlassSwitchState> emit,
|
||||||
|
) async {
|
||||||
|
emit(ThreeGangGlassSwitchLoading());
|
||||||
_updateLocalValue(event.code, event.value);
|
_updateLocalValue(event.code, event.value);
|
||||||
emit(ThreeGangGlassSwitchBatchStatusLoaded(deviceStatus));
|
emit(ThreeGangGlassSwitchBatchStatusLoaded(deviceStatus));
|
||||||
|
|
||||||
await _runDebounce(
|
try {
|
||||||
deviceId: event.deviceIds,
|
await batchControlDevicesService.batchControlDevices(
|
||||||
code: event.code,
|
uuids: event.deviceIds,
|
||||||
value: event.value,
|
code: event.code,
|
||||||
oldValue: oldValue,
|
value: event.value,
|
||||||
emit: emit,
|
);
|
||||||
isBatch: true,
|
} catch (e) {
|
||||||
);
|
_updateLocalValue(event.code, !event.value);
|
||||||
|
emit(ThreeGangGlassSwitchError(e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onFetchBatchStatus(
|
Future<void> _onFetchBatchStatus(
|
||||||
ThreeGangGlassSwitchFetchBatchStatusEvent event,
|
ThreeGangGlassSwitchFetchBatchStatusEvent event,
|
||||||
Emitter<ThreeGangGlassSwitchState> emit) async {
|
Emitter<ThreeGangGlassSwitchState> emit,
|
||||||
|
) async {
|
||||||
emit(ThreeGangGlassSwitchLoading());
|
emit(ThreeGangGlassSwitchLoading());
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
|
||||||
await DevicesManagementApi().getBatchStatus(event.deviceIds);
|
deviceStatus =
|
||||||
deviceStatus = ThreeGangGlassStatusModel.fromJson(
|
ThreeGangGlassStatusModel.fromJson(event.deviceIds.first, status.status);
|
||||||
event.deviceIds.first, status.status);
|
|
||||||
emit(ThreeGangGlassSwitchBatchStatusLoaded(deviceStatus));
|
emit(ThreeGangGlassSwitchBatchStatusLoaded(deviceStatus));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(ThreeGangGlassSwitchError(e.toString()));
|
emit(ThreeGangGlassSwitchError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onFactoryReset(ThreeGangGlassFactoryReset event,
|
Future<void> _onFactoryReset(
|
||||||
Emitter<ThreeGangGlassSwitchState> emit) async {
|
ThreeGangGlassFactoryReset event,
|
||||||
|
Emitter<ThreeGangGlassSwitchState> emit,
|
||||||
|
) async {
|
||||||
emit(ThreeGangGlassSwitchLoading());
|
emit(ThreeGangGlassSwitchLoading());
|
||||||
try {
|
try {
|
||||||
final response = await DevicesManagementApi()
|
final response = await DevicesManagementApi().factoryReset(
|
||||||
.factoryReset(event.factoryReset, event.deviceId);
|
event.factoryReset,
|
||||||
|
event.deviceId,
|
||||||
|
);
|
||||||
if (!response) {
|
if (!response) {
|
||||||
emit(ThreeGangGlassSwitchError('Failed'));
|
emit(ThreeGangGlassSwitchError('Failed to reset device'));
|
||||||
} else {
|
} else {
|
||||||
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
|
add(ThreeGangGlassSwitchFetchDeviceEvent(event.deviceId));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(ThreeGangGlassSwitchError(e.toString()));
|
emit(ThreeGangGlassSwitchError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _runDebounce({
|
|
||||||
required dynamic deviceId,
|
|
||||||
required String code,
|
|
||||||
required bool value,
|
|
||||||
required bool oldValue,
|
|
||||||
required Emitter<ThreeGangGlassSwitchState> emit,
|
|
||||||
required bool isBatch,
|
|
||||||
}) async {
|
|
||||||
late String id;
|
|
||||||
if (deviceId is List) {
|
|
||||||
id = deviceId.first;
|
|
||||||
} else {
|
|
||||||
id = deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_timer != null) {
|
|
||||||
_timer!.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
_timer = Timer(const Duration(milliseconds: 500), () async {
|
|
||||||
try {
|
|
||||||
late bool response;
|
|
||||||
if (isBatch) {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceBatchControl(deviceId, code, value);
|
|
||||||
} else {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceControl(deviceId, Status(code: code, value: value));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response) {
|
|
||||||
_revertValueAndEmit(id, code, oldValue, emit);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
_revertValueAndEmit(id, code, oldValue, emit);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
|
|
||||||
Emitter<ThreeGangGlassSwitchState> emit) {
|
|
||||||
_updateLocalValue(code, oldValue);
|
|
||||||
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateLocalValue(String code, bool value) {
|
void _updateLocalValue(String code, bool value) {
|
||||||
if (code == 'switch_1') {
|
|
||||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
|
||||||
} else if (code == 'switch_2') {
|
|
||||||
deviceStatus = deviceStatus.copyWith(switch2: value);
|
|
||||||
} else if (code == 'switch_3') {
|
|
||||||
deviceStatus = deviceStatus.copyWith(switch3: value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _getValueByCode(String code) {
|
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 'switch_1':
|
case 'switch_1':
|
||||||
return deviceStatus.switch1;
|
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||||
|
break;
|
||||||
case 'switch_2':
|
case 'switch_2':
|
||||||
return deviceStatus.switch2;
|
deviceStatus = deviceStatus.copyWith(switch2: value);
|
||||||
|
break;
|
||||||
case 'switch_3':
|
case 'switch_3':
|
||||||
return deviceStatus.switch3;
|
deviceStatus = deviceStatus.copyWith(switch3: value);
|
||||||
default:
|
break;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() {
|
|
||||||
_timer?.cancel();
|
|
||||||
return super.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
part of 'three_gang_glass_switch_bloc.dart';
|
part of 'three_gang_glass_switch_bloc.dart';
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
abstract class ThreeGangGlassSwitchEvent {}
|
abstract class ThreeGangGlassSwitchEvent extends Equatable {
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
class ThreeGangGlassSwitchFetchDeviceEvent extends ThreeGangGlassSwitchEvent {
|
class ThreeGangGlassSwitchFetchDeviceEvent extends ThreeGangGlassSwitchEvent {
|
||||||
final String deviceId;
|
final String deviceId;
|
||||||
@ -19,6 +22,9 @@ class ThreeGangGlassSwitchControl extends ThreeGangGlassSwitchEvent {
|
|||||||
required this.code,
|
required this.code,
|
||||||
required this.value,
|
required this.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [deviceId, code, value];
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThreeGangGlassSwitchBatchControl extends ThreeGangGlassSwitchEvent {
|
class ThreeGangGlassSwitchBatchControl extends ThreeGangGlassSwitchEvent {
|
||||||
@ -31,6 +37,9 @@ class ThreeGangGlassSwitchBatchControl extends ThreeGangGlassSwitchEvent {
|
|||||||
required this.code,
|
required this.code,
|
||||||
required this.value,
|
required this.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [deviceIds, code, value];
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThreeGangGlassSwitchFetchBatchStatusEvent
|
class ThreeGangGlassSwitchFetchBatchStatusEvent
|
||||||
@ -38,6 +47,9 @@ class ThreeGangGlassSwitchFetchBatchStatusEvent
|
|||||||
final List<String> deviceIds;
|
final List<String> deviceIds;
|
||||||
|
|
||||||
ThreeGangGlassSwitchFetchBatchStatusEvent(this.deviceIds);
|
ThreeGangGlassSwitchFetchBatchStatusEvent(this.deviceIds);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [deviceIds];
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThreeGangGlassFactoryReset extends ThreeGangGlassSwitchEvent {
|
class ThreeGangGlassFactoryReset extends ThreeGangGlassSwitchEvent {
|
||||||
@ -48,6 +60,9 @@ class ThreeGangGlassFactoryReset extends ThreeGangGlassSwitchEvent {
|
|||||||
required this.deviceId,
|
required this.deviceId,
|
||||||
required this.factoryReset,
|
required this.factoryReset,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [deviceId, factoryReset];
|
||||||
}
|
}
|
||||||
|
|
||||||
class StatusUpdated extends ThreeGangGlassSwitchEvent {
|
class StatusUpdated extends ThreeGangGlassSwitchEvent {
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart';
|
||||||
|
|
||||||
|
abstract final class ThreeGangGlassSwitchBlocFactory {
|
||||||
|
const ThreeGangGlassSwitchBlocFactory._();
|
||||||
|
|
||||||
|
static ThreeGangGlassSwitchBloc create({
|
||||||
|
required String deviceId,
|
||||||
|
}) {
|
||||||
|
return ThreeGangGlassSwitchBloc(
|
||||||
|
deviceId: deviceId,
|
||||||
|
controlDeviceService:
|
||||||
|
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||||
|
batchControlDevicesService:
|
||||||
|
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_
|
|||||||
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/factories/three_gang_glass_switch_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/models/three_gang_glass_switch.dart';
|
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/models/three_gang_glass_switch.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';
|
||||||
|
|
||||||
@ -16,7 +17,7 @@ class ThreeGangGlassSwitchBatchControlView extends StatelessWidget with HelperRe
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => ThreeGangGlassSwitchBloc(deviceId: deviceIds.first)
|
create: (context) => ThreeGangGlassSwitchBlocFactory.create(deviceId: deviceIds.first)
|
||||||
..add(ThreeGangGlassSwitchFetchBatchStatusEvent(deviceIds)),
|
..add(ThreeGangGlassSwitchFetchBatchStatusEvent(deviceIds)),
|
||||||
child: BlocBuilder<ThreeGangGlassSwitchBloc, ThreeGangGlassSwitchState>(
|
child: BlocBuilder<ThreeGangGlassSwitchBloc, ThreeGangGlassSwitchState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/factories/three_gang_glass_switch_bloc_factory.dart';
|
||||||
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';
|
||||||
|
|
||||||
@ -16,7 +17,7 @@ class ThreeGangGlassSwitchControlView extends StatelessWidget with HelperRespons
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) =>
|
create: (context) =>
|
||||||
ThreeGangGlassSwitchBloc(deviceId: deviceId)..add(ThreeGangGlassSwitchFetchDeviceEvent(deviceId)),
|
ThreeGangGlassSwitchBlocFactory.create(deviceId: deviceId)..add(ThreeGangGlassSwitchFetchDeviceEvent(deviceId)),
|
||||||
child: BlocBuilder<ThreeGangGlassSwitchBloc, ThreeGangGlassSwitchState>(
|
child: BlocBuilder<ThreeGangGlassSwitchBloc, ThreeGangGlassSwitchState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is ThreeGangGlassSwitchLoading) {
|
if (state is ThreeGangGlassSwitchLoading) {
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
// ignore_for_file: invalid_use_of_visible_for_testing_member
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:bloc/bloc.dart';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:firebase_database/firebase_database.dart';
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
import 'package:syncrow_web/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/three_gang_switch/models/living_room_model.dart';
|
import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart';
|
||||||
|
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||||
|
import 'package:syncrow_web/services/control_device_service.dart';
|
||||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||||
|
|
||||||
part 'living_room_event.dart';
|
part 'living_room_event.dart';
|
||||||
@ -15,9 +17,14 @@ part 'living_room_state.dart';
|
|||||||
class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
|
class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
|
||||||
late LivingRoomStatusModel deviceStatus;
|
late LivingRoomStatusModel deviceStatus;
|
||||||
final String deviceId;
|
final String deviceId;
|
||||||
Timer? _timer;
|
final ControlDeviceService controlDeviceService;
|
||||||
|
final BatchControlDevicesService batchControlDevicesService;
|
||||||
|
|
||||||
LivingRoomBloc({required this.deviceId}) : super(LivingRoomInitial()) {
|
LivingRoomBloc({
|
||||||
|
required this.deviceId,
|
||||||
|
required this.controlDeviceService,
|
||||||
|
required this.batchControlDevicesService,
|
||||||
|
}) : super(LivingRoomInitial()) {
|
||||||
on<LivingRoomFetchDeviceStatusEvent>(_onFetchDeviceStatus);
|
on<LivingRoomFetchDeviceStatusEvent>(_onFetchDeviceStatus);
|
||||||
on<LivingRoomControl>(_livingRoomControl);
|
on<LivingRoomControl>(_livingRoomControl);
|
||||||
on<LivingRoomBatchControl>(_livingRoomBatchControl);
|
on<LivingRoomBatchControl>(_livingRoomBatchControl);
|
||||||
@ -26,156 +33,108 @@ class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
|
|||||||
on<StatusUpdated>(_onStatusUpdated);
|
on<StatusUpdated>(_onStatusUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onFetchDeviceStatus(LivingRoomFetchDeviceStatusEvent event,
|
Future<void> _onFetchDeviceStatus(
|
||||||
Emitter<LivingRoomState> emit) async {
|
LivingRoomFetchDeviceStatusEvent event,
|
||||||
|
Emitter<LivingRoomState> emit,
|
||||||
|
) async {
|
||||||
emit(LivingRoomDeviceStatusLoading());
|
emit(LivingRoomDeviceStatusLoading());
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
|
||||||
deviceStatus =
|
|
||||||
LivingRoomStatusModel.fromJson(event.deviceId, status.status);
|
|
||||||
_listenToChanges(deviceId);
|
_listenToChanges(deviceId);
|
||||||
|
deviceStatus = LivingRoomStatusModel.fromJson(event.deviceId, status.status);
|
||||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(LivingRoomDeviceManagementError(e.toString()));
|
emit(LivingRoomDeviceManagementError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _livingRoomControl(
|
void _listenToChanges(String deviceId) {
|
||||||
LivingRoomControl event, Emitter<LivingRoomState> emit) async {
|
try {
|
||||||
final oldValue = _getValueByCode(event.code);
|
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||||
|
ref.onValue.listen((event) {
|
||||||
|
final eventsMap = event.snapshot.value as Map<dynamic, dynamic>;
|
||||||
|
|
||||||
|
List<Status> statusList = [];
|
||||||
|
eventsMap['status'].forEach((element) {
|
||||||
|
statusList.add(
|
||||||
|
Status(code: element['code'], value: element['value']),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
deviceStatus = LivingRoomStatusModel.fromJson(deviceId, statusList);
|
||||||
|
add(StatusUpdated(deviceStatus));
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
log('Error listening to changes');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onStatusUpdated(
|
||||||
|
StatusUpdated event,
|
||||||
|
Emitter<LivingRoomState> emit,
|
||||||
|
) {
|
||||||
|
deviceStatus = event.deviceStatus;
|
||||||
|
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _livingRoomControl(
|
||||||
|
LivingRoomControl event,
|
||||||
|
Emitter<LivingRoomState> emit,
|
||||||
|
) async {
|
||||||
|
emit(LivingRoomDeviceStatusLoading());
|
||||||
_updateLocalValue(event.code, event.value);
|
_updateLocalValue(event.code, event.value);
|
||||||
|
|
||||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
||||||
|
|
||||||
await _runDebounce(
|
try {
|
||||||
deviceId: event.deviceId,
|
await controlDeviceService.controlDevice(
|
||||||
code: event.code,
|
deviceUuid: event.deviceId,
|
||||||
value: event.value,
|
status: Status(code: event.code, value: event.value),
|
||||||
oldValue: oldValue,
|
);
|
||||||
emit: emit,
|
} catch (e) {
|
||||||
isBatch: false,
|
_updateLocalValue(event.code, !event.value);
|
||||||
);
|
emit(LivingRoomDeviceManagementError(e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _runDebounce({
|
Future<void> _livingRoomBatchControl(
|
||||||
required dynamic deviceId,
|
LivingRoomBatchControl event,
|
||||||
required String code,
|
Emitter<LivingRoomState> emit,
|
||||||
required dynamic value,
|
) async {
|
||||||
required dynamic oldValue,
|
emit(LivingRoomDeviceStatusLoading());
|
||||||
required Emitter<LivingRoomState> emit,
|
_updateLocalValue(event.code, event.value);
|
||||||
required bool isBatch,
|
|
||||||
}) async {
|
|
||||||
late String id;
|
|
||||||
|
|
||||||
if (deviceId is List) {
|
|
||||||
id = deviceId.first;
|
|
||||||
} else {
|
|
||||||
id = deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_timer != null) {
|
|
||||||
_timer!.cancel();
|
|
||||||
}
|
|
||||||
_timer = Timer(const Duration(seconds: 1), () async {
|
|
||||||
try {
|
|
||||||
late bool response;
|
|
||||||
if (isBatch) {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceBatchControl(deviceId, code, value);
|
|
||||||
} else {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceControl(deviceId, Status(code: code, value: value));
|
|
||||||
}
|
|
||||||
if (!response) {
|
|
||||||
_revertValueAndEmit(id, code, oldValue, emit);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
_revertValueAndEmit(id, code, oldValue, emit);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _revertValueAndEmit(String deviceId, String code, dynamic oldValue,
|
|
||||||
Emitter<LivingRoomState> emit) {
|
|
||||||
_updateLocalValue(code, oldValue);
|
|
||||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
||||||
}
|
|
||||||
|
|
||||||
void _updateLocalValue(String code, dynamic value) {
|
try {
|
||||||
switch (code) {
|
await batchControlDevicesService.batchControlDevices(
|
||||||
case 'switch_1':
|
uuids: event.devicesIds,
|
||||||
if (value is bool) {
|
code: event.code,
|
||||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
value: event.value,
|
||||||
}
|
);
|
||||||
break;
|
} catch (e) {
|
||||||
case 'switch_2':
|
_updateLocalValue(event.code, !event.value);
|
||||||
if (value is bool) {
|
emit(LivingRoomDeviceManagementError(e.toString()));
|
||||||
deviceStatus = deviceStatus.copyWith(switch2: value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'switch_3':
|
|
||||||
if (value is bool) {
|
|
||||||
deviceStatus = deviceStatus.copyWith(switch3: value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamic _getValueByCode(String code) {
|
|
||||||
switch (code) {
|
|
||||||
case 'switch_1':
|
|
||||||
return deviceStatus.switch1;
|
|
||||||
case 'switch_2':
|
|
||||||
return deviceStatus.switch2;
|
|
||||||
case 'switch_3':
|
|
||||||
return deviceStatus.switch3;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _livingRoomFetchBatchControl(
|
Future<void> _livingRoomFetchBatchControl(
|
||||||
LivingRoomFetchBatchEvent event, Emitter<LivingRoomState> emit) async {
|
LivingRoomFetchBatchEvent event,
|
||||||
|
Emitter<LivingRoomState> emit,
|
||||||
|
) async {
|
||||||
emit(LivingRoomDeviceStatusLoading());
|
emit(LivingRoomDeviceStatusLoading());
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
|
||||||
deviceStatus =
|
deviceStatus =
|
||||||
LivingRoomStatusModel.fromJson(event.devicesIds.first, status.status);
|
LivingRoomStatusModel.fromJson(event.devicesIds.first, status.status);
|
||||||
// for (var deviceId in event.devicesIds) {
|
|
||||||
// _listenToChanges(deviceId);
|
|
||||||
// }
|
|
||||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(LivingRoomDeviceManagementError(e.toString()));
|
emit(LivingRoomDeviceManagementError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _livingRoomBatchControl(
|
Future<void> _livingRoomFactoryReset(
|
||||||
LivingRoomBatchControl event, Emitter<LivingRoomState> emit) async {
|
LivingRoomFactoryResetEvent event,
|
||||||
final oldValue = _getValueByCode(event.code);
|
Emitter<LivingRoomState> emit,
|
||||||
|
) async {
|
||||||
_updateLocalValue(event.code, event.value);
|
|
||||||
|
|
||||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
|
||||||
|
|
||||||
await _runDebounce(
|
|
||||||
deviceId: event.devicesIds,
|
|
||||||
code: event.code,
|
|
||||||
value: event.value,
|
|
||||||
oldValue: oldValue,
|
|
||||||
emit: emit,
|
|
||||||
isBatch: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
FutureOr<void> _livingRoomFactoryReset(
|
|
||||||
LivingRoomFactoryResetEvent event, Emitter<LivingRoomState> emit) async {
|
|
||||||
emit(LivingRoomDeviceStatusLoading());
|
emit(LivingRoomDeviceStatusLoading());
|
||||||
try {
|
try {
|
||||||
final response = await DevicesManagementApi().factoryReset(
|
final response = await DevicesManagementApi().factoryReset(
|
||||||
@ -183,42 +142,28 @@ class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
|
|||||||
event.uuid,
|
event.uuid,
|
||||||
);
|
);
|
||||||
if (!response) {
|
if (!response) {
|
||||||
emit(const LivingRoomDeviceManagementError('Failed'));
|
emit(const LivingRoomDeviceManagementError('Failed to reset device'));
|
||||||
} else {
|
} else {
|
||||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
add(LivingRoomFetchDeviceStatusEvent(event.uuid));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(LivingRoomDeviceManagementError(e.toString()));
|
emit(LivingRoomDeviceManagementError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_listenToChanges(deviceId) {
|
void _updateLocalValue(String code, dynamic value) {
|
||||||
try {
|
if (value is! bool) return;
|
||||||
DatabaseReference ref =
|
|
||||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
|
||||||
Stream<DatabaseEvent> stream = ref.onValue;
|
|
||||||
|
|
||||||
stream.listen((DatabaseEvent event) {
|
switch (code) {
|
||||||
Map<dynamic, dynamic> usersMap =
|
case 'switch_1':
|
||||||
event.snapshot.value as Map<dynamic, dynamic>;
|
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||||
|
break;
|
||||||
List<Status> statusList = [];
|
case 'switch_2':
|
||||||
usersMap['status'].forEach((element) {
|
deviceStatus = deviceStatus.copyWith(switch2: value);
|
||||||
statusList
|
break;
|
||||||
.add(Status(code: element['code'], value: element['value']));
|
case 'switch_3':
|
||||||
});
|
deviceStatus = deviceStatus.copyWith(switch3: value);
|
||||||
|
break;
|
||||||
deviceStatus =
|
}
|
||||||
LivingRoomStatusModel.fromJson(usersMap['productUuid'], statusList);
|
|
||||||
if (!isClosed) {
|
|
||||||
add(StatusUpdated(deviceStatus));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onStatusUpdated(StatusUpdated event, Emitter<LivingRoomState> emit) {
|
|
||||||
deviceStatus = event.deviceStatus;
|
|
||||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart';
|
||||||
|
|
||||||
|
abstract final class LivingRoomBlocFactory {
|
||||||
|
const LivingRoomBlocFactory._();
|
||||||
|
|
||||||
|
static LivingRoomBloc create({
|
||||||
|
required String deviceId,
|
||||||
|
}) {
|
||||||
|
return LivingRoomBloc(
|
||||||
|
deviceId: deviceId,
|
||||||
|
controlDeviceService:
|
||||||
|
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||||
|
batchControlDevicesService:
|
||||||
|
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_re
|
|||||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||||
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/three_gang_switch/factories/living_room_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart';
|
import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.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';
|
||||||
@ -17,7 +18,7 @@ class LivingRoomBatchControlsView extends StatelessWidget with HelperResponsiveL
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) =>
|
create: (context) =>
|
||||||
LivingRoomBloc(deviceId: deviceIds.first)..add(LivingRoomFetchBatchEvent(deviceIds)),
|
LivingRoomBlocFactory.create(deviceId: deviceIds.first)..add(LivingRoomFetchBatchEvent(deviceIds)),
|
||||||
child: BlocBuilder<LivingRoomBloc, LivingRoomState>(
|
child: BlocBuilder<LivingRoomBloc, LivingRoomState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is LivingRoomDeviceStatusLoading) {
|
if (state is LivingRoomDeviceStatusLoading) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/three_gang_switch/factories/living_room_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart';
|
import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.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';
|
||||||
@ -14,7 +15,7 @@ class LivingRoomDeviceControlsView extends StatelessWidget
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => LivingRoomBloc(deviceId: deviceId)
|
create: (context) => LivingRoomBlocFactory.create(deviceId: deviceId)
|
||||||
..add(LivingRoomFetchDeviceStatusEvent(deviceId)),
|
..add(LivingRoomFetchDeviceStatusEvent(deviceId)),
|
||||||
child: BlocBuilder<LivingRoomBloc, LivingRoomState>(
|
child: BlocBuilder<LivingRoomBloc, LivingRoomState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
@ -1,26 +1,33 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:firebase_database/firebase_database.dart';
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
import 'package:syncrow_web/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/two_g_glass_switch/models/two_gang_glass_status_model.dart';
|
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart';
|
||||||
|
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||||
|
import 'package:syncrow_web/services/control_device_service.dart';
|
||||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||||
|
|
||||||
part 'two_gang_glass_switch_event.dart';
|
part 'two_gang_glass_switch_event.dart';
|
||||||
part 'two_gang_glass_switch_state.dart';
|
part 'two_gang_glass_switch_state.dart';
|
||||||
|
|
||||||
class TwoGangGlassSwitchBloc
|
class TwoGangGlassSwitchBloc
|
||||||
extends Bloc<TwoGangGlassSwitchEvent, TwoGangGlassSwitchState> {
|
extends Bloc<TwoGangGlassSwitchEvent, TwoGangGlassSwitchState> {
|
||||||
TwoGangGlassStatusModel deviceStatus;
|
final String deviceId;
|
||||||
Timer? _timer;
|
final ControlDeviceService controlDeviceService;
|
||||||
TwoGangGlassSwitchBloc({required String deviceId})
|
final BatchControlDevicesService batchControlDevicesService;
|
||||||
: deviceStatus = TwoGangGlassStatusModel(
|
|
||||||
uuid: deviceId,
|
late TwoGangGlassStatusModel deviceStatus;
|
||||||
switch1: false,
|
|
||||||
countDown1: 0,
|
TwoGangGlassSwitchBloc({
|
||||||
switch2: false,
|
required this.deviceId,
|
||||||
countDown2: 0),
|
required this.controlDeviceService,
|
||||||
super(TwoGangGlassSwitchInitial()) {
|
required this.batchControlDevicesService,
|
||||||
|
}) : super(TwoGangGlassSwitchInitial()) {
|
||||||
on<TwoGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
on<TwoGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
||||||
on<TwoGangGlassSwitchControl>(_onControl);
|
on<TwoGangGlassSwitchControl>(_onControl);
|
||||||
on<TwoGangGlassSwitchBatchControl>(_onBatchControl);
|
on<TwoGangGlassSwitchBatchControl>(_onBatchControl);
|
||||||
@ -29,14 +36,14 @@ class TwoGangGlassSwitchBloc
|
|||||||
on<StatusUpdated>(_onStatusUpdated);
|
on<StatusUpdated>(_onStatusUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onFetchDeviceStatus(TwoGangGlassSwitchFetchDeviceEvent event,
|
Future<void> _onFetchDeviceStatus(
|
||||||
Emitter<TwoGangGlassSwitchState> emit) async {
|
TwoGangGlassSwitchFetchDeviceEvent event,
|
||||||
|
Emitter<TwoGangGlassSwitchState> emit,
|
||||||
|
) async {
|
||||||
emit(TwoGangGlassSwitchLoading());
|
emit(TwoGangGlassSwitchLoading());
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
deviceStatus = TwoGangGlassStatusModel.fromJson(event.deviceId, status.status);
|
||||||
deviceStatus =
|
|
||||||
TwoGangGlassStatusModel.fromJson(event.deviceId, status.status);
|
|
||||||
_listenToChanges(event.deviceId);
|
_listenToChanges(event.deviceId);
|
||||||
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
|
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -46,200 +53,121 @@ class TwoGangGlassSwitchBloc
|
|||||||
|
|
||||||
void _listenToChanges(String deviceId) {
|
void _listenToChanges(String deviceId) {
|
||||||
try {
|
try {
|
||||||
DatabaseReference ref =
|
final ref = FirebaseDatabase.instance.ref(
|
||||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
'device-status/$deviceId',
|
||||||
ref.onValue.listen((DatabaseEvent event) {
|
);
|
||||||
if (event.snapshot.value == null) return;
|
|
||||||
|
ref.onValue.listen((event) {
|
||||||
|
final eventsMap = event.snapshot.value as Map<dynamic, dynamic>;
|
||||||
|
|
||||||
Map<dynamic, dynamic> data =
|
|
||||||
event.snapshot.value as Map<dynamic, dynamic>;
|
|
||||||
List<Status> statusList = [];
|
List<Status> statusList = [];
|
||||||
|
eventsMap['status'].forEach((element) {
|
||||||
data['status'].forEach((element) {
|
statusList.add(Status(code: element['code'], value: element['value']));
|
||||||
statusList
|
|
||||||
.add(Status(code: element['code'], value: element['value']));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Parse the new status and add the event
|
deviceStatus = TwoGangGlassStatusModel.fromJson(deviceId, statusList);
|
||||||
final updatedStatus =
|
add(StatusUpdated(deviceStatus));
|
||||||
TwoGangGlassStatusModel.fromJson(data['productUuid'], statusList);
|
|
||||||
if (!isClosed) {
|
|
||||||
add(StatusUpdated(updatedStatus));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (_) {
|
||||||
// Handle errors and emit an error state if necessary
|
log(
|
||||||
if (!isClosed) {
|
'Error listening to changes',
|
||||||
// add(TwoGangGlassSwitchError('Error listening to updates: $e'));
|
name: 'TwoGangGlassSwitchBloc._listenToChanges',
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onControl(TwoGangGlassSwitchControl event,
|
Future<void> _onControl(
|
||||||
Emitter<TwoGangGlassSwitchState> emit) async {
|
TwoGangGlassSwitchControl event,
|
||||||
final oldValue = _getValueByCode(event.code);
|
Emitter<TwoGangGlassSwitchState> emit,
|
||||||
|
) async {
|
||||||
|
emit(TwoGangGlassSwitchLoading());
|
||||||
_updateLocalValue(event.code, event.value);
|
_updateLocalValue(event.code, event.value);
|
||||||
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
|
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
|
||||||
|
|
||||||
await _runDebounce(
|
try {
|
||||||
deviceId: event.deviceId,
|
await controlDeviceService.controlDevice(
|
||||||
code: event.code,
|
deviceUuid: event.deviceId,
|
||||||
value: event.value,
|
status: Status(code: event.code, value: event.value),
|
||||||
oldValue: oldValue,
|
);
|
||||||
emit: emit,
|
} catch (e) {
|
||||||
isBatch: false,
|
_updateLocalValue(event.code, !event.value);
|
||||||
);
|
emit(TwoGangGlassSwitchError(e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onBatchControl(TwoGangGlassSwitchBatchControl event,
|
Future<void> _onBatchControl(
|
||||||
Emitter<TwoGangGlassSwitchState> emit) async {
|
TwoGangGlassSwitchBatchControl event,
|
||||||
final oldValue = _getValueByCode(event.code);
|
Emitter<TwoGangGlassSwitchState> emit,
|
||||||
|
) async {
|
||||||
|
emit(TwoGangGlassSwitchLoading());
|
||||||
_updateLocalValue(event.code, event.value);
|
_updateLocalValue(event.code, event.value);
|
||||||
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
|
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
|
||||||
|
|
||||||
await _runDebounce(
|
try {
|
||||||
deviceId: event.deviceIds,
|
await batchControlDevicesService.batchControlDevices(
|
||||||
code: event.code,
|
uuids: event.deviceIds,
|
||||||
value: event.value,
|
code: event.code,
|
||||||
oldValue: oldValue,
|
value: event.value,
|
||||||
emit: emit,
|
);
|
||||||
isBatch: true,
|
} catch (e) {
|
||||||
);
|
_updateLocalValue(event.code, !event.value);
|
||||||
|
emit(TwoGangGlassSwitchError(e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onFetchBatchStatus(
|
Future<void> _onFetchBatchStatus(
|
||||||
TwoGangGlassSwitchFetchBatchStatusEvent event,
|
TwoGangGlassSwitchFetchBatchStatusEvent event,
|
||||||
Emitter<TwoGangGlassSwitchState> emit) async {
|
Emitter<TwoGangGlassSwitchState> emit,
|
||||||
|
) async {
|
||||||
emit(TwoGangGlassSwitchLoading());
|
emit(TwoGangGlassSwitchLoading());
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
|
||||||
await DevicesManagementApi().getBatchStatus(event.deviceIds);
|
|
||||||
deviceStatus = TwoGangGlassStatusModel.fromJson(
|
deviceStatus = TwoGangGlassStatusModel.fromJson(
|
||||||
event.deviceIds.first, status.status);
|
event.deviceIds.first,
|
||||||
|
status.status,
|
||||||
|
);
|
||||||
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
|
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(TwoGangGlassSwitchError(e.toString()));
|
emit(TwoGangGlassSwitchError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onFactoryReset(TwoGangGlassFactoryReset event,
|
Future<void> _onFactoryReset(
|
||||||
Emitter<TwoGangGlassSwitchState> emit) async {
|
TwoGangGlassFactoryReset event,
|
||||||
|
Emitter<TwoGangGlassSwitchState> emit,
|
||||||
|
) async {
|
||||||
emit(TwoGangGlassSwitchLoading());
|
emit(TwoGangGlassSwitchLoading());
|
||||||
try {
|
try {
|
||||||
final response = await DevicesManagementApi()
|
final response = await DevicesManagementApi().factoryReset(
|
||||||
.factoryReset(event.factoryReset, event.deviceId);
|
event.factoryReset,
|
||||||
|
event.deviceId,
|
||||||
|
);
|
||||||
if (!response) {
|
if (!response) {
|
||||||
emit(TwoGangGlassSwitchError('Failed'));
|
emit(TwoGangGlassSwitchError('Failed to reset device'));
|
||||||
} else {
|
} else {
|
||||||
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
|
add(TwoGangGlassSwitchFetchDeviceEvent(event.deviceId));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(TwoGangGlassSwitchError(e.toString()));
|
emit(TwoGangGlassSwitchError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _runDebounce({
|
void _onStatusUpdated(
|
||||||
required dynamic deviceId,
|
StatusUpdated event,
|
||||||
required String code,
|
Emitter<TwoGangGlassSwitchState> emit,
|
||||||
required bool value,
|
) {
|
||||||
required bool oldValue,
|
deviceStatus = event.deviceStatus;
|
||||||
required Emitter<TwoGangGlassSwitchState> emit,
|
|
||||||
required bool isBatch,
|
|
||||||
}) async {
|
|
||||||
late String id;
|
|
||||||
if (deviceId is List) {
|
|
||||||
id = deviceId.first;
|
|
||||||
} else {
|
|
||||||
id = deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_timer != null) {
|
|
||||||
_timer!.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
_timer = Timer(const Duration(milliseconds: 500), () async {
|
|
||||||
try {
|
|
||||||
late bool response;
|
|
||||||
if (isBatch) {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceBatchControl(deviceId, code, value);
|
|
||||||
} else {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceControl(deviceId, Status(code: code, value: value));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response) {
|
|
||||||
_revertValueAndEmit(id, code, oldValue, emit);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
_revertValueAndEmit(id, code, oldValue, emit);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
|
|
||||||
Emitter<TwoGangGlassSwitchState> emit) {
|
|
||||||
_updateLocalValue(code, oldValue);
|
|
||||||
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
|
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateLocalValue(String code, bool value) {
|
void _updateLocalValue(String code, bool value) {
|
||||||
if (code == 'switch_1') {
|
|
||||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
|
||||||
} else if (code == 'switch_2') {
|
|
||||||
deviceStatus = deviceStatus.copyWith(switch2: value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _getValueByCode(String code) {
|
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 'switch_1':
|
case 'switch_1':
|
||||||
return deviceStatus.switch1;
|
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||||
|
break;
|
||||||
case 'switch_2':
|
case 'switch_2':
|
||||||
return deviceStatus.switch2;
|
deviceStatus = deviceStatus.copyWith(switch2: value);
|
||||||
default:
|
break;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() {
|
|
||||||
_timer?.cancel();
|
|
||||||
return super.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// _listenToChanges(deviceId) {
|
|
||||||
// try {
|
|
||||||
// DatabaseReference ref =
|
|
||||||
// FirebaseDatabase.instance.ref('device-status/$deviceId');
|
|
||||||
// Stream<DatabaseEvent> stream = ref.onValue;
|
|
||||||
|
|
||||||
// stream.listen((DatabaseEvent event) {
|
|
||||||
// Map<dynamic, dynamic> usersMap =
|
|
||||||
// event.snapshot.value as Map<dynamic, dynamic>;
|
|
||||||
|
|
||||||
// List<Status> statusList = [];
|
|
||||||
// usersMap['status'].forEach((element) {
|
|
||||||
// statusList
|
|
||||||
// .add(Status(code: element['code'], value: element['value']));
|
|
||||||
// });
|
|
||||||
|
|
||||||
// deviceStatus = TwoGangGlassStatusModel.fromJson(
|
|
||||||
// usersMap['productUuid'], statusList);
|
|
||||||
// if (!isClosed) {
|
|
||||||
// add(StatusUpdated(deviceStatus));
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// } catch (_) {}
|
|
||||||
// }
|
|
||||||
|
|
||||||
void _onStatusUpdated(
|
|
||||||
StatusUpdated event, Emitter<TwoGangGlassSwitchState> emit) {
|
|
||||||
// Update the local deviceStatus with the new status from the event
|
|
||||||
deviceStatus = event.deviceStatus;
|
|
||||||
// Emit the new state with the updated status
|
|
||||||
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
part of 'two_gang_glass_switch_bloc.dart';
|
part of 'two_gang_glass_switch_bloc.dart';
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
abstract class TwoGangGlassSwitchEvent {}
|
abstract class TwoGangGlassSwitchEvent extends Equatable {
|
||||||
|
const TwoGangGlassSwitchEvent();
|
||||||
|
}
|
||||||
|
|
||||||
class TwoGangGlassSwitchFetchDeviceEvent extends TwoGangGlassSwitchEvent {
|
class TwoGangGlassSwitchFetchDeviceEvent extends TwoGangGlassSwitchEvent {
|
||||||
final String deviceId;
|
final String deviceId;
|
||||||
|
|
||||||
TwoGangGlassSwitchFetchDeviceEvent(this.deviceId);
|
const TwoGangGlassSwitchFetchDeviceEvent(this.deviceId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [deviceId];
|
||||||
}
|
}
|
||||||
|
|
||||||
class TwoGangGlassSwitchControl extends TwoGangGlassSwitchEvent {
|
class TwoGangGlassSwitchControl extends TwoGangGlassSwitchEvent {
|
||||||
@ -14,11 +19,14 @@ class TwoGangGlassSwitchControl extends TwoGangGlassSwitchEvent {
|
|||||||
final String code;
|
final String code;
|
||||||
final bool value;
|
final bool value;
|
||||||
|
|
||||||
TwoGangGlassSwitchControl({
|
const TwoGangGlassSwitchControl({
|
||||||
required this.deviceId,
|
required this.deviceId,
|
||||||
required this.code,
|
required this.code,
|
||||||
required this.value,
|
required this.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [deviceId, code, value];
|
||||||
}
|
}
|
||||||
|
|
||||||
class TwoGangGlassSwitchBatchControl extends TwoGangGlassSwitchEvent {
|
class TwoGangGlassSwitchBatchControl extends TwoGangGlassSwitchEvent {
|
||||||
@ -26,33 +34,43 @@ class TwoGangGlassSwitchBatchControl extends TwoGangGlassSwitchEvent {
|
|||||||
final String code;
|
final String code;
|
||||||
final bool value;
|
final bool value;
|
||||||
|
|
||||||
TwoGangGlassSwitchBatchControl({
|
const TwoGangGlassSwitchBatchControl({
|
||||||
required this.deviceIds,
|
required this.deviceIds,
|
||||||
required this.code,
|
required this.code,
|
||||||
required this.value,
|
required this.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [deviceIds, code, value];
|
||||||
}
|
}
|
||||||
|
|
||||||
class TwoGangGlassSwitchFetchBatchStatusEvent extends TwoGangGlassSwitchEvent {
|
class TwoGangGlassSwitchFetchBatchStatusEvent extends TwoGangGlassSwitchEvent {
|
||||||
final List<String> deviceIds;
|
final List<String> deviceIds;
|
||||||
|
|
||||||
TwoGangGlassSwitchFetchBatchStatusEvent(this.deviceIds);
|
const TwoGangGlassSwitchFetchBatchStatusEvent(this.deviceIds);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [deviceIds];
|
||||||
}
|
}
|
||||||
|
|
||||||
class TwoGangGlassFactoryReset extends TwoGangGlassSwitchEvent {
|
class TwoGangGlassFactoryReset extends TwoGangGlassSwitchEvent {
|
||||||
final String deviceId;
|
final String deviceId;
|
||||||
final FactoryResetModel factoryReset;
|
final FactoryResetModel factoryReset;
|
||||||
|
|
||||||
TwoGangGlassFactoryReset({
|
const TwoGangGlassFactoryReset({
|
||||||
required this.deviceId,
|
required this.deviceId,
|
||||||
required this.factoryReset,
|
required this.factoryReset,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [deviceId, factoryReset];
|
||||||
}
|
}
|
||||||
|
|
||||||
class StatusUpdated extends TwoGangGlassSwitchEvent {
|
class StatusUpdated extends TwoGangGlassSwitchEvent {
|
||||||
final TwoGangGlassStatusModel deviceStatus;
|
final TwoGangGlassStatusModel deviceStatus;
|
||||||
StatusUpdated(this.deviceStatus);
|
|
||||||
|
const StatusUpdated(this.deviceStatus);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [deviceStatus];
|
List<Object> get props => [deviceStatus];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart';
|
||||||
|
|
||||||
|
abstract final class TwoGangGlassSwitchBlocFactory {
|
||||||
|
const TwoGangGlassSwitchBlocFactory._();
|
||||||
|
|
||||||
|
static TwoGangGlassSwitchBloc create({
|
||||||
|
required String deviceId,
|
||||||
|
}) {
|
||||||
|
return TwoGangGlassSwitchBloc(
|
||||||
|
deviceId: deviceId,
|
||||||
|
controlDeviceService:
|
||||||
|
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||||
|
batchControlDevicesService:
|
||||||
|
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_
|
|||||||
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/factories/two_gang_glass_switch_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart';
|
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.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';
|
||||||
|
|
||||||
@ -16,7 +17,7 @@ class TwoGangGlassSwitchBatchControlView extends StatelessWidget with HelperResp
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => TwoGangGlassSwitchBloc(deviceId: deviceIds.first)
|
create: (context) => TwoGangGlassSwitchBlocFactory.create(deviceId: deviceIds.first)
|
||||||
..add(TwoGangGlassSwitchFetchBatchStatusEvent(deviceIds)),
|
..add(TwoGangGlassSwitchFetchBatchStatusEvent(deviceIds)),
|
||||||
child: BlocBuilder<TwoGangGlassSwitchBloc, TwoGangGlassSwitchState>(
|
child: BlocBuilder<TwoGangGlassSwitchBloc, TwoGangGlassSwitchState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/factories/two_gang_glass_switch_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart';
|
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart';
|
||||||
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';
|
||||||
@ -15,7 +16,7 @@ class TwoGangGlassSwitchControlView extends StatelessWidget
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => TwoGangGlassSwitchBloc(deviceId: deviceId)
|
create: (context) => TwoGangGlassSwitchBlocFactory.create(deviceId: deviceId)
|
||||||
..add(TwoGangGlassSwitchFetchDeviceEvent(deviceId)),
|
..add(TwoGangGlassSwitchFetchDeviceEvent(deviceId)),
|
||||||
child: BlocBuilder<TwoGangGlassSwitchBloc, TwoGangGlassSwitchState>(
|
child: BlocBuilder<TwoGangGlassSwitchBloc, TwoGangGlassSwitchState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:firebase_database/firebase_database.dart';
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@ -6,10 +7,22 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_sta
|
|||||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart';
|
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart';
|
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart';
|
import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart';
|
||||||
|
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||||
|
import 'package:syncrow_web/services/control_device_service.dart';
|
||||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||||
|
|
||||||
class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
|
class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
|
||||||
TwoGangSwitchBloc({required this.deviceId}) : super(TwoGangSwitchInitial()) {
|
final String deviceId;
|
||||||
|
final ControlDeviceService controlDeviceService;
|
||||||
|
final BatchControlDevicesService batchControlDevicesService;
|
||||||
|
|
||||||
|
late TwoGangStatusModel deviceStatus;
|
||||||
|
|
||||||
|
TwoGangSwitchBloc({
|
||||||
|
required this.deviceId,
|
||||||
|
required this.controlDeviceService,
|
||||||
|
required this.batchControlDevicesService,
|
||||||
|
}) : super(TwoGangSwitchInitial()) {
|
||||||
on<TwoGangSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
on<TwoGangSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
||||||
on<TwoGangSwitchControl>(_onControl);
|
on<TwoGangSwitchControl>(_onControl);
|
||||||
on<TwoGangSwitchFetchBatchEvent>(_onFetchBatchStatus);
|
on<TwoGangSwitchFetchBatchEvent>(_onFetchBatchStatus);
|
||||||
@ -18,16 +31,13 @@ class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
|
|||||||
on<StatusUpdated>(_onStatusUpdated);
|
on<StatusUpdated>(_onStatusUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
late TwoGangStatusModel deviceStatus;
|
Future<void> _onFetchDeviceStatus(
|
||||||
final String deviceId;
|
TwoGangSwitchFetchDeviceEvent event,
|
||||||
Timer? _timer;
|
Emitter<TwoGangSwitchState> emit,
|
||||||
|
) async {
|
||||||
FutureOr<void> _onFetchDeviceStatus(TwoGangSwitchFetchDeviceEvent event,
|
|
||||||
Emitter<TwoGangSwitchState> emit) async {
|
|
||||||
emit(TwoGangSwitchLoading());
|
emit(TwoGangSwitchLoading());
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
|
||||||
deviceStatus = TwoGangStatusModel.fromJson(event.deviceId, status.status);
|
deviceStatus = TwoGangStatusModel.fromJson(event.deviceId, status.status);
|
||||||
_listenToChanges(event.deviceId);
|
_listenToChanges(event.deviceId);
|
||||||
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
||||||
@ -36,131 +46,91 @@ class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onControl(
|
void _listenToChanges(String deviceId) {
|
||||||
TwoGangSwitchControl event, Emitter<TwoGangSwitchState> emit) async {
|
try {
|
||||||
final oldValue = _getValueByCode(event.code);
|
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||||
|
|
||||||
|
ref.onValue.listen((event) {
|
||||||
|
final eventsMap = event.snapshot.value as Map<dynamic, dynamic>;
|
||||||
|
|
||||||
|
List<Status> statusList = [];
|
||||||
|
eventsMap['status'].forEach((element) {
|
||||||
|
statusList.add(
|
||||||
|
Status(code: element['code'], value: element['value']),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
deviceStatus = TwoGangStatusModel.fromJson(deviceId, statusList);
|
||||||
|
add(StatusUpdated(deviceStatus));
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
log(
|
||||||
|
'Error listening to changes',
|
||||||
|
name: 'TwoGangSwitchBloc._listenToChanges',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onControl(
|
||||||
|
TwoGangSwitchControl event,
|
||||||
|
Emitter<TwoGangSwitchState> emit,
|
||||||
|
) async {
|
||||||
|
emit(TwoGangSwitchLoading());
|
||||||
_updateLocalValue(event.code, event.value);
|
_updateLocalValue(event.code, event.value);
|
||||||
|
|
||||||
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
||||||
|
|
||||||
await _runDebounce(
|
try {
|
||||||
deviceId: event.deviceId,
|
await controlDeviceService.controlDevice(
|
||||||
code: event.code,
|
deviceUuid: event.deviceId,
|
||||||
value: event.value,
|
status: Status(code: event.code, value: event.value),
|
||||||
oldValue: oldValue,
|
);
|
||||||
emit: emit,
|
} catch (e) {
|
||||||
isBatch: false,
|
_updateLocalValue(event.code, !event.value);
|
||||||
);
|
emit(TwoGangSwitchError(e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _runDebounce({
|
Future<void> _onBatchControl(
|
||||||
required dynamic deviceId,
|
TwoGangSwitchBatchControl event,
|
||||||
required String code,
|
Emitter<TwoGangSwitchState> emit,
|
||||||
required bool value,
|
) async {
|
||||||
required bool oldValue,
|
emit(TwoGangSwitchLoading());
|
||||||
required Emitter<TwoGangSwitchState> emit,
|
_updateLocalValue(event.code, event.value);
|
||||||
required bool isBatch,
|
|
||||||
}) async {
|
|
||||||
late String id;
|
|
||||||
|
|
||||||
if (deviceId is List) {
|
|
||||||
id = deviceId.first;
|
|
||||||
} else {
|
|
||||||
id = deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_timer != null) {
|
|
||||||
_timer!.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
_timer = Timer(const Duration(milliseconds: 500), () async {
|
|
||||||
try {
|
|
||||||
late bool response;
|
|
||||||
if (isBatch) {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceBatchControl(deviceId, code, value);
|
|
||||||
} else {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceControl(deviceId, Status(code: code, value: value));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response) {
|
|
||||||
_revertValueAndEmit(id, code, oldValue, emit);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
_revertValueAndEmit(id, code, oldValue, emit);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
|
|
||||||
Emitter<TwoGangSwitchState> emit) {
|
|
||||||
_updateLocalValue(code, oldValue);
|
|
||||||
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
||||||
}
|
|
||||||
|
|
||||||
void _updateLocalValue(String code, bool value) {
|
try {
|
||||||
if (code == 'switch_1') {
|
await batchControlDevicesService.batchControlDevices(
|
||||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
uuids: event.deviceId,
|
||||||
}
|
code: event.code,
|
||||||
|
value: event.value,
|
||||||
if (code == 'switch_2') {
|
);
|
||||||
deviceStatus = deviceStatus.copyWith(switch2: value);
|
} catch (e) {
|
||||||
|
_updateLocalValue(event.code, !event.value);
|
||||||
|
emit(TwoGangSwitchError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _getValueByCode(String code) {
|
Future<void> _onFetchBatchStatus(
|
||||||
switch (code) {
|
TwoGangSwitchFetchBatchEvent event,
|
||||||
case 'switch_1':
|
Emitter<TwoGangSwitchState> emit,
|
||||||
return deviceStatus.switch1;
|
) async {
|
||||||
case 'switch_2':
|
|
||||||
return deviceStatus.switch2;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onFetchBatchStatus(TwoGangSwitchFetchBatchEvent event,
|
|
||||||
Emitter<TwoGangSwitchState> emit) async {
|
|
||||||
emit(TwoGangSwitchLoading());
|
emit(TwoGangSwitchLoading());
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
deviceStatus = TwoGangStatusModel.fromJson(
|
||||||
deviceStatus =
|
event.devicesIds.first,
|
||||||
TwoGangStatusModel.fromJson(event.devicesIds.first, status.status);
|
status.status,
|
||||||
|
);
|
||||||
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(TwoGangSwitchError(e.toString()));
|
emit(TwoGangSwitchError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Future<void> _onFactoryReset(
|
||||||
Future<void> close() {
|
TwoGangFactoryReset event,
|
||||||
_timer?.cancel();
|
Emitter<TwoGangSwitchState> emit,
|
||||||
return super.close();
|
) async {
|
||||||
}
|
|
||||||
|
|
||||||
FutureOr<void> _onBatchControl(
|
|
||||||
TwoGangSwitchBatchControl event, Emitter<TwoGangSwitchState> emit) async {
|
|
||||||
final oldValue = _getValueByCode(event.code);
|
|
||||||
|
|
||||||
_updateLocalValue(event.code, event.value);
|
|
||||||
|
|
||||||
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
|
||||||
|
|
||||||
await _runDebounce(
|
|
||||||
deviceId: event.deviceId,
|
|
||||||
code: event.code,
|
|
||||||
value: event.value,
|
|
||||||
oldValue: oldValue,
|
|
||||||
emit: emit,
|
|
||||||
isBatch: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
FutureOr<void> _onFactoryReset(
|
|
||||||
TwoGangFactoryReset event, Emitter<TwoGangSwitchState> emit) async {
|
|
||||||
emit(TwoGangSwitchLoading());
|
emit(TwoGangSwitchLoading());
|
||||||
try {
|
try {
|
||||||
final response = await DevicesManagementApi().factoryReset(
|
final response = await DevicesManagementApi().factoryReset(
|
||||||
@ -168,42 +138,31 @@ class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
|
|||||||
event.deviceId,
|
event.deviceId,
|
||||||
);
|
);
|
||||||
if (!response) {
|
if (!response) {
|
||||||
emit(TwoGangSwitchError('Failed'));
|
emit(TwoGangSwitchError('Failed to reset device'));
|
||||||
} else {
|
} else {
|
||||||
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
add(TwoGangSwitchFetchDeviceEvent(event.deviceId));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(TwoGangSwitchError(e.toString()));
|
emit(TwoGangSwitchError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_listenToChanges(deviceId) {
|
void _onStatusUpdated(
|
||||||
try {
|
StatusUpdated event,
|
||||||
DatabaseReference ref =
|
Emitter<TwoGangSwitchState> emit,
|
||||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
) {
|
||||||
Stream<DatabaseEvent> stream = ref.onValue;
|
|
||||||
|
|
||||||
stream.listen((DatabaseEvent event) {
|
|
||||||
Map<dynamic, dynamic> usersMap =
|
|
||||||
event.snapshot.value as Map<dynamic, dynamic>;
|
|
||||||
|
|
||||||
List<Status> statusList = [];
|
|
||||||
usersMap['status'].forEach((element) {
|
|
||||||
statusList
|
|
||||||
.add(Status(code: element['code'], value: element['value']));
|
|
||||||
});
|
|
||||||
|
|
||||||
deviceStatus =
|
|
||||||
TwoGangStatusModel.fromJson(usersMap['productUuid'], statusList);
|
|
||||||
if (!isClosed) {
|
|
||||||
add(StatusUpdated(deviceStatus));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onStatusUpdated(StatusUpdated event, Emitter<TwoGangSwitchState> emit) {
|
|
||||||
deviceStatus = event.deviceStatus;
|
deviceStatus = event.deviceStatus;
|
||||||
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _updateLocalValue(String code, bool value) {
|
||||||
|
switch (code) {
|
||||||
|
case 'switch_1':
|
||||||
|
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||||
|
break;
|
||||||
|
case 'switch_2':
|
||||||
|
deviceStatus = deviceStatus.copyWith(switch2: value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart';
|
||||||
|
|
||||||
|
abstract final class TwoGangSwitchBlocFactory {
|
||||||
|
const TwoGangSwitchBlocFactory._();
|
||||||
|
|
||||||
|
static TwoGangSwitchBloc create({
|
||||||
|
required String deviceId,
|
||||||
|
}) {
|
||||||
|
return TwoGangSwitchBloc(
|
||||||
|
deviceId: deviceId,
|
||||||
|
controlDeviceService:
|
||||||
|
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||||
|
batchControlDevicesService:
|
||||||
|
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -24,16 +24,16 @@ class TwoGangStatusModel {
|
|||||||
for (var status in jsonList) {
|
for (var status in jsonList) {
|
||||||
switch (status.code) {
|
switch (status.code) {
|
||||||
case 'switch_1':
|
case 'switch_1':
|
||||||
switch1 = status.value ?? false;
|
switch1 = bool.tryParse(status.value.toString()) ?? false;
|
||||||
break;
|
break;
|
||||||
case 'countdown_1':
|
case 'countdown_1':
|
||||||
countDown = status.value ?? 0;
|
countDown = int.tryParse(status.value.toString()) ?? 0;
|
||||||
break;
|
break;
|
||||||
case 'switch_2':
|
case 'switch_2':
|
||||||
switch2 = status.value ?? false;
|
switch2 = bool.tryParse(status.value.toString()) ?? false;
|
||||||
break;
|
break;
|
||||||
case 'countdown_2':
|
case 'countdown_2':
|
||||||
countDown2 = status.value ?? 0;
|
countDown2 = int.tryParse(status.value.toString()) ?? 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.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/shared/batch_control/factory_reset.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||||
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart';
|
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart';
|
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/two_gang_switch/factories/two_gang_switch_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart';
|
import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.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';
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ class TwoGangBatchControlView extends StatelessWidget with HelperResponsiveLayou
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => TwoGangSwitchBloc(deviceId: deviceIds.first)
|
create: (context) => TwoGangSwitchBlocFactory.create(deviceId: deviceIds.first)
|
||||||
..add(TwoGangSwitchFetchBatchEvent(deviceIds)),
|
..add(TwoGangSwitchFetchBatchEvent(deviceIds)),
|
||||||
child: BlocBuilder<TwoGangSwitchBloc, TwoGangSwitchState>(
|
child: BlocBuilder<TwoGangSwitchBloc, TwoGangSwitchState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
|||||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart';
|
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart';
|
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/two_gang_switch/factories/two_gang_switch_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart';
|
import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.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';
|
||||||
|
|
||||||
@ -15,7 +16,7 @@ class TwoGangDeviceControlView extends StatelessWidget
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => TwoGangSwitchBloc(deviceId: deviceId)
|
create: (context) => TwoGangSwitchBlocFactory.create(deviceId: deviceId)
|
||||||
..add(TwoGangSwitchFetchDeviceEvent(deviceId)),
|
..add(TwoGangSwitchFetchDeviceEvent(deviceId)),
|
||||||
child: BlocBuilder<TwoGangSwitchBloc, TwoGangSwitchState>(
|
child: BlocBuilder<TwoGangSwitchBloc, TwoGangSwitchState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
@ -1,18 +1,28 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:firebase_database/firebase_database.dart';
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_event.dart';
|
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_event.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_state.dart';
|
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_state.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart';
|
import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart';
|
||||||
|
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||||
|
import 'package:syncrow_web/services/control_device_service.dart';
|
||||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||||
|
|
||||||
class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
||||||
final String deviceId;
|
final String deviceId;
|
||||||
late WallSensorModel deviceStatus;
|
final ControlDeviceService controlDeviceService;
|
||||||
Timer? _timer;
|
final BatchControlDevicesService batchControlDevicesService;
|
||||||
|
|
||||||
WallSensorBloc({required this.deviceId}) : super(WallSensorInitialState()) {
|
late WallSensorModel deviceStatus;
|
||||||
|
|
||||||
|
WallSensorBloc({
|
||||||
|
required this.deviceId,
|
||||||
|
required this.controlDeviceService,
|
||||||
|
required this.batchControlDevicesService,
|
||||||
|
}) : super(WallSensorInitialState()) {
|
||||||
on<WallSensorFetchStatusEvent>(_fetchWallSensorStatus);
|
on<WallSensorFetchStatusEvent>(_fetchWallSensorStatus);
|
||||||
on<WallSensorFetchBatchStatusEvent>(_fetchWallSensorBatchControl);
|
on<WallSensorFetchBatchStatusEvent>(_fetchWallSensorBatchControl);
|
||||||
on<WallSensorChangeValueEvent>(_changeValue);
|
on<WallSensorChangeValueEvent>(_changeValue);
|
||||||
@ -24,28 +34,28 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
|||||||
on<WallSensorRealtimeUpdateEvent>(_onRealtimeUpdate);
|
on<WallSensorRealtimeUpdateEvent>(_onRealtimeUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _fetchWallSensorStatus(
|
Future<void> _fetchWallSensorStatus(
|
||||||
WallSensorFetchStatusEvent event, Emitter<WallSensorState> emit) async {
|
WallSensorFetchStatusEvent event,
|
||||||
|
Emitter<WallSensorState> emit,
|
||||||
|
) async {
|
||||||
emit(WallSensorLoadingInitialState());
|
emit(WallSensorLoadingInitialState());
|
||||||
try {
|
try {
|
||||||
var response = await DevicesManagementApi().getDeviceStatus(deviceId);
|
final response = await DevicesManagementApi().getDeviceStatus(deviceId);
|
||||||
deviceStatus = WallSensorModel.fromJson(response.status);
|
deviceStatus = WallSensorModel.fromJson(response.status);
|
||||||
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
|
||||||
_listenToChanges(deviceId);
|
_listenToChanges(deviceId);
|
||||||
|
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(WallSensorFailedState(error: e.toString()));
|
emit(WallSensorFailedState(error: e.toString()));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch batch status
|
Future<void> _fetchWallSensorBatchControl(
|
||||||
FutureOr<void> _fetchWallSensorBatchControl(
|
WallSensorFetchBatchStatusEvent event,
|
||||||
WallSensorFetchBatchStatusEvent event,
|
Emitter<WallSensorState> emit,
|
||||||
Emitter<WallSensorState> emit) async {
|
) async {
|
||||||
emit(WallSensorLoadingInitialState());
|
emit(WallSensorLoadingInitialState());
|
||||||
try {
|
try {
|
||||||
var response =
|
final response = await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
|
||||||
deviceStatus = WallSensorModel.fromJson(response.status);
|
deviceStatus = WallSensorModel.fromJson(response.status);
|
||||||
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -54,132 +64,105 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _listenToChanges(String deviceId) {
|
void _listenToChanges(String deviceId) {
|
||||||
DatabaseReference ref =
|
try {
|
||||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||||
ref.onValue.listen((DatabaseEvent event) {
|
|
||||||
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
|
||||||
if (data == null) return;
|
|
||||||
|
|
||||||
final statusList = (data['status'] as List?)
|
ref.onValue.listen((event) {
|
||||||
?.map((e) => Status(code: e['code'], value: e['value']))
|
final eventsMap = event.snapshot.value as Map<dynamic, dynamic>;
|
||||||
.toList();
|
|
||||||
|
|
||||||
if (statusList != null) {
|
List<Status> statusList = [];
|
||||||
final updatedDeviceStatus = WallSensorModel.fromJson(statusList);
|
eventsMap['status'].forEach((element) {
|
||||||
|
statusList.add(
|
||||||
|
Status(code: element['code'], value: element['value']),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
deviceStatus = WallSensorModel.fromJson(statusList);
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(WallSensorRealtimeUpdateEvent(updatedDeviceStatus));
|
add(WallSensorRealtimeUpdateEvent(deviceStatus));
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
} catch (_) {
|
||||||
|
log(
|
||||||
|
'Error listening to changes',
|
||||||
|
name: 'WallSensorBloc._listenToChanges',
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _changeValue(
|
||||||
void _changeValue(
|
WallSensorChangeValueEvent event,
|
||||||
WallSensorChangeValueEvent event, Emitter<WallSensorState> emit) async {
|
Emitter<WallSensorState> emit,
|
||||||
|
) async {
|
||||||
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
|
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
|
||||||
if (event.code == 'far_detection') {
|
_updateLocalValue(event.code, event.value);
|
||||||
deviceStatus.farDetection = event.value;
|
|
||||||
} else if (event.code == 'motionless_sensitivity') {
|
|
||||||
deviceStatus.motionlessSensitivity = event.value;
|
|
||||||
} else if (event.code == 'motion_sensitivity_value') {
|
|
||||||
deviceStatus.motionSensitivity = event.value;
|
|
||||||
} else if (event.code == 'no_one_time') {
|
|
||||||
deviceStatus.noBodyTime = event.value;
|
|
||||||
}
|
|
||||||
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
||||||
await _runDeBouncer(
|
|
||||||
deviceId: deviceId,
|
try {
|
||||||
code: event.code,
|
await controlDeviceService.controlDevice(
|
||||||
value: event.value,
|
deviceUuid: deviceId,
|
||||||
isBatch: false,
|
status: Status(code: event.code, value: event.value),
|
||||||
emit: emit,
|
);
|
||||||
);
|
} catch (e) {
|
||||||
|
_updateLocalValue(event.code, event.value == 0 ? 1 : 0);
|
||||||
|
emit(WallSensorFailedState(error: e.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onBatchControl(
|
Future<void> _onBatchControl(
|
||||||
WallSensorBatchControlEvent event, Emitter<WallSensorState> emit) async {
|
WallSensorBatchControlEvent event,
|
||||||
|
Emitter<WallSensorState> emit,
|
||||||
|
) async {
|
||||||
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
|
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
|
||||||
if (event.code == 'far_detection') {
|
_updateLocalValue(event.code, event.value);
|
||||||
deviceStatus.farDetection = event.value;
|
|
||||||
} else if (event.code == 'motionless_sensitivity') {
|
|
||||||
deviceStatus.motionlessSensitivity = event.value;
|
|
||||||
} else if (event.code == 'motion_sensitivity_value') {
|
|
||||||
deviceStatus.motionSensitivity = event.value;
|
|
||||||
} else if (event.code == 'no_one_time') {
|
|
||||||
deviceStatus.noBodyTime = event.value;
|
|
||||||
}
|
|
||||||
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
||||||
await _runDeBouncer(
|
|
||||||
deviceId: event.deviceIds,
|
|
||||||
code: event.code,
|
|
||||||
value: event.value,
|
|
||||||
emit: emit,
|
|
||||||
isBatch: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_runDeBouncer({
|
|
||||||
required dynamic deviceId,
|
|
||||||
required String code,
|
|
||||||
required dynamic value,
|
|
||||||
required Emitter<WallSensorState> emit,
|
|
||||||
required bool isBatch,
|
|
||||||
}) {
|
|
||||||
if (_timer != null) {
|
|
||||||
_timer!.cancel();
|
|
||||||
}
|
|
||||||
_timer = Timer(const Duration(seconds: 1), () async {
|
|
||||||
try {
|
|
||||||
late bool response;
|
|
||||||
if (isBatch) {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceBatchControl(deviceId, code, value);
|
|
||||||
} else {
|
|
||||||
response = await DevicesManagementApi()
|
|
||||||
.deviceControl(deviceId, Status(code: code, value: value));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response) {
|
|
||||||
add(WallSensorFetchStatusEvent());
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
|
||||||
add(WallSensorFetchStatusEvent());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
FutureOr<void> _getDeviceReports(
|
|
||||||
GetDeviceReportsEvent event, Emitter<WallSensorState> emit) async {
|
|
||||||
emit(DeviceReportsLoadingState());
|
|
||||||
// final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch;
|
|
||||||
// final to = DateTime.now().millisecondsSinceEpoch;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// await DevicesManagementApi.getDeviceReportsByDate(
|
await batchControlDevicesService.batchControlDevices(
|
||||||
// deviceId, event.code, from.toString(), to.toString())
|
uuids: event.deviceIds,
|
||||||
await DevicesManagementApi.getDeviceReports(deviceId, event.code)
|
code: event.code,
|
||||||
.then((value) {
|
value: event.value,
|
||||||
emit(DeviceReportsState(deviceReport: value, code: event.code));
|
);
|
||||||
});
|
} catch (e) {
|
||||||
|
_updateLocalValue(event.code, !event.value);
|
||||||
|
emit(WallSensorFailedState(error: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _getDeviceReports(
|
||||||
|
GetDeviceReportsEvent event,
|
||||||
|
Emitter<WallSensorState> emit,
|
||||||
|
) async {
|
||||||
|
emit(DeviceReportsLoadingState());
|
||||||
|
try {
|
||||||
|
final reports = await DevicesManagementApi.getDeviceReports(
|
||||||
|
deviceId,
|
||||||
|
event.code,
|
||||||
|
);
|
||||||
|
emit(DeviceReportsState(deviceReport: reports, code: event.code));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(DeviceReportsFailedState(error: e.toString()));
|
emit(DeviceReportsFailedState(error: e.toString()));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showDescription(
|
void _showDescription(
|
||||||
ShowDescriptionEvent event, Emitter<WallSensorState> emit) {
|
ShowDescriptionEvent event,
|
||||||
|
Emitter<WallSensorState> emit,
|
||||||
|
) {
|
||||||
emit(WallSensorShowDescriptionState(description: event.description));
|
emit(WallSensorShowDescriptionState(description: event.description));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _backToGridView(
|
void _backToGridView(
|
||||||
BackToGridViewEvent event, Emitter<WallSensorState> emit) {
|
BackToGridViewEvent event,
|
||||||
|
Emitter<WallSensorState> emit,
|
||||||
|
) {
|
||||||
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onFactoryReset(
|
Future<void> _onFactoryReset(
|
||||||
WallSensorFactoryResetEvent event, Emitter<WallSensorState> emit) async {
|
WallSensorFactoryResetEvent event,
|
||||||
|
Emitter<WallSensorState> emit,
|
||||||
|
) async {
|
||||||
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
|
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
|
||||||
try {
|
try {
|
||||||
final response = await DevicesManagementApi().factoryReset(
|
final response = await DevicesManagementApi().factoryReset(
|
||||||
@ -187,9 +170,9 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
|||||||
event.deviceId,
|
event.deviceId,
|
||||||
);
|
);
|
||||||
if (!response) {
|
if (!response) {
|
||||||
emit(const WallSensorFailedState(error: 'Failed'));
|
emit(const WallSensorFailedState(error: 'Failed to reset device'));
|
||||||
} else {
|
} else {
|
||||||
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
add(WallSensorFetchStatusEvent());
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(WallSensorFailedState(error: e.toString()));
|
emit(WallSensorFailedState(error: e.toString()));
|
||||||
@ -200,7 +183,23 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
|
|||||||
WallSensorRealtimeUpdateEvent event,
|
WallSensorRealtimeUpdateEvent event,
|
||||||
Emitter<WallSensorState> emit,
|
Emitter<WallSensorState> emit,
|
||||||
) {
|
) {
|
||||||
deviceStatus = event.deviceStatus;
|
emit(WallSensorUpdateState(wallSensorModel: event.deviceStatus));
|
||||||
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
|
}
|
||||||
|
|
||||||
|
void _updateLocalValue(String code, dynamic value) {
|
||||||
|
switch (code) {
|
||||||
|
case 'far_detection':
|
||||||
|
deviceStatus.farDetection = value;
|
||||||
|
break;
|
||||||
|
case 'motionless_sensitivity':
|
||||||
|
deviceStatus.motionlessSensitivity = value;
|
||||||
|
break;
|
||||||
|
case 'motion_sensitivity_value':
|
||||||
|
deviceStatus.motionSensitivity = value;
|
||||||
|
break;
|
||||||
|
case 'no_one_time':
|
||||||
|
deviceStatus.noBodyTime = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_bloc.dart';
|
||||||
|
|
||||||
|
abstract final class WallSensorBlocFactory {
|
||||||
|
const WallSensorBlocFactory._();
|
||||||
|
|
||||||
|
static WallSensorBloc create({
|
||||||
|
required String deviceId,
|
||||||
|
}) {
|
||||||
|
return WallSensorBloc(
|
||||||
|
deviceId: deviceId,
|
||||||
|
controlDeviceService:
|
||||||
|
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||||
|
batchControlDevicesService:
|
||||||
|
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presen
|
|||||||
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_event.dart';
|
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_event.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_state.dart';
|
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_state.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/wall_sensor/factories/wall_sensor_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart';
|
import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.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';
|
||||||
|
|
||||||
@ -21,7 +22,7 @@ class WallSensorBatchControlView extends StatelessWidget with HelperResponsiveLa
|
|||||||
final isLarge = isLargeScreenSize(context);
|
final isLarge = isLargeScreenSize(context);
|
||||||
final isMedium = isMediumScreenSize(context);
|
final isMedium = isMediumScreenSize(context);
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => WallSensorBloc(deviceId: devicesIds.first)
|
create: (context) => WallSensorBlocFactory.create(deviceId: devicesIds.first)
|
||||||
..add(WallSensorFetchBatchStatusEvent(devicesIds)),
|
..add(WallSensorFetchBatchStatusEvent(devicesIds)),
|
||||||
child: BlocBuilder<WallSensorBloc, WallSensorState>(
|
child: BlocBuilder<WallSensorBloc, WallSensorState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presen
|
|||||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_static_widget.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_static_widget.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_status.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_status.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/wall_sensor/factories/wall_sensor_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart';
|
import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart';
|
||||||
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';
|
||||||
@ -26,7 +27,7 @@ class WallSensorControlsView extends StatelessWidget with HelperResponsiveLayout
|
|||||||
final isMedium = isMediumScreenSize(context);
|
final isMedium = isMediumScreenSize(context);
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) =>
|
create: (context) =>
|
||||||
WallSensorBloc(deviceId: device.uuid!)..add(WallSensorFetchStatusEvent()),
|
WallSensorBlocFactory.create(deviceId: device.uuid!)..add(WallSensorFetchStatusEvent()),
|
||||||
child: BlocBuilder<WallSensorBloc, WallSensorState>(
|
child: BlocBuilder<WallSensorBloc, WallSensorState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is WallSensorLoadingInitialState || state is DeviceReportsLoadingState) {
|
if (state is WallSensorLoadingInitialState || state is DeviceReportsLoadingState) {
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
// water_heater_bloc.dart
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
@ -10,6 +8,8 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_sta
|
|||||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
|
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart';
|
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
|
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
|
||||||
|
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||||
|
import 'package:syncrow_web/services/control_device_service.dart';
|
||||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||||
import 'package:syncrow_web/utils/format_date_time.dart';
|
import 'package:syncrow_web/utils/format_date_time.dart';
|
||||||
|
|
||||||
@ -17,7 +17,17 @@ part 'water_heater_event.dart';
|
|||||||
part 'water_heater_state.dart';
|
part 'water_heater_state.dart';
|
||||||
|
|
||||||
class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
||||||
WaterHeaterBloc() : super(WaterHeaterInitial()) {
|
late WaterHeaterStatusModel deviceStatus;
|
||||||
|
final String deviceId;
|
||||||
|
final ControlDeviceService controlDeviceService;
|
||||||
|
final BatchControlDevicesService batchControlDevicesService;
|
||||||
|
Timer? _countdownTimer;
|
||||||
|
|
||||||
|
WaterHeaterBloc({
|
||||||
|
required this.deviceId,
|
||||||
|
required this.controlDeviceService,
|
||||||
|
required this.batchControlDevicesService,
|
||||||
|
}) : super(WaterHeaterInitial()) {
|
||||||
on<WaterHeaterFetchStatusEvent>(_fetchWaterHeaterStatus);
|
on<WaterHeaterFetchStatusEvent>(_fetchWaterHeaterStatus);
|
||||||
on<ToggleWaterHeaterEvent>(_controlWaterHeater);
|
on<ToggleWaterHeaterEvent>(_controlWaterHeater);
|
||||||
on<FetchWaterHeaterBatchStatusEvent>(_batchFetchWaterHeater);
|
on<FetchWaterHeaterBatchStatusEvent>(_batchFetchWaterHeater);
|
||||||
@ -29,7 +39,6 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
on<UpdateSelectedTimeEvent>(_updateSelectedTime);
|
on<UpdateSelectedTimeEvent>(_updateSelectedTime);
|
||||||
on<UpdateSelectedDayEvent>(_updateSelectedDay);
|
on<UpdateSelectedDayEvent>(_updateSelectedDay);
|
||||||
on<UpdateFunctionOnEvent>(_updateFunctionOn);
|
on<UpdateFunctionOnEvent>(_updateFunctionOn);
|
||||||
|
|
||||||
on<GetSchedulesEvent>(_getSchedule);
|
on<GetSchedulesEvent>(_getSchedule);
|
||||||
on<AddScheduleEvent>(_onAddSchedule);
|
on<AddScheduleEvent>(_onAddSchedule);
|
||||||
on<EditWaterHeaterScheduleEvent>(_onEditSchedule);
|
on<EditWaterHeaterScheduleEvent>(_onEditSchedule);
|
||||||
@ -38,11 +47,7 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
on<StatusUpdated>(_onStatusUpdated);
|
on<StatusUpdated>(_onStatusUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
late WaterHeaterStatusModel deviceStatus;
|
void _initializeAddSchedule(
|
||||||
Timer? _countdownTimer;
|
|
||||||
// Timer? _inchingTimer;
|
|
||||||
|
|
||||||
FutureOr<void> _initializeAddSchedule(
|
|
||||||
InitializeAddScheduleEvent event,
|
InitializeAddScheduleEvent event,
|
||||||
Emitter<WaterHeaterState> emit,
|
Emitter<WaterHeaterState> emit,
|
||||||
) {
|
) {
|
||||||
@ -64,7 +69,7 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _updateSelectedTime(
|
void _updateSelectedTime(
|
||||||
UpdateSelectedTimeEvent event,
|
UpdateSelectedTimeEvent event,
|
||||||
Emitter<WaterHeaterState> emit,
|
Emitter<WaterHeaterState> emit,
|
||||||
) {
|
) {
|
||||||
@ -73,7 +78,7 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
emit(currentState.copyWith(selectedTime: event.selectedTime));
|
emit(currentState.copyWith(selectedTime: event.selectedTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _updateSelectedDay(
|
void _updateSelectedDay(
|
||||||
UpdateSelectedDayEvent event,
|
UpdateSelectedDayEvent event,
|
||||||
Emitter<WaterHeaterState> emit,
|
Emitter<WaterHeaterState> emit,
|
||||||
) {
|
) {
|
||||||
@ -84,7 +89,7 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
selectedDays: updatedDays, selectedTime: currentState.selectedTime));
|
selectedDays: updatedDays, selectedTime: currentState.selectedTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _updateFunctionOn(
|
void _updateFunctionOn(
|
||||||
UpdateFunctionOnEvent event,
|
UpdateFunctionOnEvent event,
|
||||||
Emitter<WaterHeaterState> emit,
|
Emitter<WaterHeaterState> emit,
|
||||||
) {
|
) {
|
||||||
@ -93,16 +98,18 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
functionOn: event.isOn, selectedTime: currentState.selectedTime));
|
functionOn: event.isOn, selectedTime: currentState.selectedTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _updateScheduleEvent(
|
Future<void> _updateScheduleEvent(
|
||||||
UpdateScheduleEvent event,
|
UpdateScheduleEvent event,
|
||||||
Emitter<WaterHeaterState> emit,
|
Emitter<WaterHeaterState> emit,
|
||||||
) async {
|
) async {
|
||||||
final currentState = state;
|
final currentState = state;
|
||||||
if (currentState is WaterHeaterDeviceStatusLoaded) {
|
if (currentState is WaterHeaterDeviceStatusLoaded) {
|
||||||
if (event.scheduleMode == ScheduleModes.schedule) {
|
if (event.scheduleMode == ScheduleModes.schedule) {
|
||||||
emit(currentState.copyWith(
|
emit(
|
||||||
scheduleMode: ScheduleModes.schedule,
|
currentState.copyWith(
|
||||||
));
|
scheduleMode: ScheduleModes.schedule,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (event.scheduleMode == ScheduleModes.countdown) {
|
if (event.scheduleMode == ScheduleModes.countdown) {
|
||||||
final countdownRemaining =
|
final countdownRemaining =
|
||||||
@ -116,87 +123,88 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
countdownRemaining: countdownRemaining,
|
countdownRemaining: countdownRemaining,
|
||||||
));
|
));
|
||||||
|
|
||||||
if (!currentState.isCountdownActive! &&
|
if (!currentState.isCountdownActive! && countdownRemaining > Duration.zero) {
|
||||||
countdownRemaining > Duration.zero) {
|
|
||||||
_startCountdownTimer(emit, countdownRemaining);
|
_startCountdownTimer(emit, countdownRemaining);
|
||||||
}
|
}
|
||||||
} else if (event.scheduleMode == ScheduleModes.inching) {
|
} else if (event.scheduleMode == ScheduleModes.inching) {
|
||||||
final inchingDuration =
|
final inchingDuration = Duration(hours: event.hours, minutes: event.minutes);
|
||||||
Duration(hours: event.hours, minutes: event.minutes);
|
|
||||||
|
|
||||||
emit(currentState.copyWith(
|
emit(
|
||||||
scheduleMode: ScheduleModes.inching,
|
currentState.copyWith(
|
||||||
inchingHours: inchingDuration.inHours,
|
scheduleMode: ScheduleModes.inching,
|
||||||
inchingMinutes: inchingDuration.inMinutes % 60,
|
inchingHours: inchingDuration.inHours,
|
||||||
isInchingActive: currentState.isInchingActive,
|
inchingMinutes: inchingDuration.inMinutes % 60,
|
||||||
));
|
isInchingActive: currentState.isInchingActive,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _controlWaterHeater(
|
Future<void> _controlWaterHeater(
|
||||||
ToggleWaterHeaterEvent event,
|
ToggleWaterHeaterEvent event,
|
||||||
Emitter<WaterHeaterState> emit,
|
Emitter<WaterHeaterState> emit,
|
||||||
) async {
|
) async {
|
||||||
if (state is WaterHeaterDeviceStatusLoaded) {
|
if (state is WaterHeaterDeviceStatusLoaded) {
|
||||||
final currentState = state as WaterHeaterDeviceStatusLoaded;
|
final currentState = state as WaterHeaterDeviceStatusLoaded;
|
||||||
|
|
||||||
final oldValue = _getValueByCode(event.code);
|
|
||||||
|
|
||||||
_updateLocalValue(event.code, event.value);
|
_updateLocalValue(event.code, event.value);
|
||||||
|
|
||||||
emit(currentState.copyWith(
|
emit(
|
||||||
status: deviceStatus,
|
currentState.copyWith(
|
||||||
));
|
status: deviceStatus,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final success = await _runDebounce(
|
final success = await controlDeviceService.controlDevice(
|
||||||
deviceId: event.deviceId,
|
deviceUuid: event.deviceId,
|
||||||
code: event.code,
|
status: Status(
|
||||||
value: event.value,
|
code: event.code,
|
||||||
oldValue: oldValue,
|
value: event.value,
|
||||||
emit: emit,
|
),
|
||||||
isBatch: false,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
if (event.code == "countdown_1") {
|
if (event.code == "countdown_1") {
|
||||||
final countdownDuration = Duration(seconds: event.value);
|
final countdownDuration = Duration(seconds: event.value);
|
||||||
|
|
||||||
emit(currentState.copyWith(
|
emit(
|
||||||
countdownHours: countdownDuration.inHours,
|
currentState.copyWith(
|
||||||
countdownMinutes: countdownDuration.inMinutes % 60,
|
countdownHours: countdownDuration.inHours,
|
||||||
countdownRemaining: countdownDuration,
|
countdownMinutes: countdownDuration.inMinutes % 60,
|
||||||
isCountdownActive: true,
|
countdownRemaining: countdownDuration,
|
||||||
));
|
isCountdownActive: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (countdownDuration.inSeconds > 0) {
|
if (countdownDuration.inSeconds > 0) {
|
||||||
_startCountdownTimer(emit, countdownDuration);
|
_startCountdownTimer(emit, countdownDuration);
|
||||||
} else {
|
} else {
|
||||||
_countdownTimer?.cancel();
|
_countdownTimer?.cancel();
|
||||||
emit(currentState.copyWith(
|
emit(
|
||||||
countdownHours: 0,
|
currentState.copyWith(
|
||||||
countdownMinutes: 0,
|
countdownHours: 0,
|
||||||
countdownRemaining: Duration.zero,
|
countdownMinutes: 0,
|
||||||
isCountdownActive: false,
|
countdownRemaining: Duration.zero,
|
||||||
));
|
isCountdownActive: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if (event.code == "switch_inching") {
|
} else if (event.code == "switch_inching") {
|
||||||
final inchingDuration = Duration(seconds: event.value);
|
final inchingDuration = Duration(seconds: event.value);
|
||||||
//if (inchingDuration.inSeconds > 0) {
|
emit(
|
||||||
// _startInchingTimer(emit, inchingDuration);
|
currentState.copyWith(
|
||||||
// } else {
|
inchingHours: inchingDuration.inHours,
|
||||||
emit(currentState.copyWith(
|
inchingMinutes: inchingDuration.inMinutes % 60,
|
||||||
inchingHours: inchingDuration.inHours,
|
isInchingActive: true,
|
||||||
inchingMinutes: inchingDuration.inMinutes % 60,
|
),
|
||||||
isInchingActive: true,
|
);
|
||||||
));
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _stopScheduleEvent(
|
Future<void> _stopScheduleEvent(
|
||||||
StopScheduleEvent event,
|
StopScheduleEvent event,
|
||||||
Emitter<WaterHeaterState> emit,
|
Emitter<WaterHeaterState> emit,
|
||||||
) async {
|
) async {
|
||||||
@ -207,25 +215,28 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
_countdownTimer?.cancel();
|
_countdownTimer?.cancel();
|
||||||
|
|
||||||
if (isCountDown) {
|
if (isCountDown) {
|
||||||
emit(currentState.copyWith(
|
emit(
|
||||||
countdownHours: 0,
|
currentState.copyWith(
|
||||||
countdownMinutes: 0,
|
countdownHours: 0,
|
||||||
countdownRemaining: Duration.zero,
|
countdownMinutes: 0,
|
||||||
isCountdownActive: false,
|
countdownRemaining: Duration.zero,
|
||||||
));
|
isCountdownActive: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
} else if (currentState.scheduleMode == ScheduleModes.inching) {
|
} else if (currentState.scheduleMode == ScheduleModes.inching) {
|
||||||
emit(currentState.copyWith(
|
emit(
|
||||||
inchingHours: 0,
|
currentState.copyWith(
|
||||||
inchingMinutes: 0,
|
inchingHours: 0,
|
||||||
isInchingActive: false,
|
inchingMinutes: 0,
|
||||||
));
|
isInchingActive: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final status = await DevicesManagementApi().deviceControl(
|
final status = await DevicesManagementApi().deviceControl(
|
||||||
event.deviceId,
|
event.deviceId,
|
||||||
Status(
|
Status(code: isCountDown ? 'countdown_1' : 'switch_inching', value: 0),
|
||||||
code: isCountDown ? 'countdown_1' : 'switch_inching', value: 0),
|
|
||||||
);
|
);
|
||||||
if (!status) {
|
if (!status) {
|
||||||
emit(const WaterHeaterFailedState(error: 'Failed to stop schedule.'));
|
emit(const WaterHeaterFailedState(error: 'Failed to stop schedule.'));
|
||||||
@ -236,17 +247,15 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _fetchWaterHeaterStatus(
|
Future<void> _fetchWaterHeaterStatus(
|
||||||
WaterHeaterFetchStatusEvent event,
|
WaterHeaterFetchStatusEvent event,
|
||||||
Emitter<WaterHeaterState> emit,
|
Emitter<WaterHeaterState> emit,
|
||||||
) async {
|
) async {
|
||||||
emit(WaterHeaterLoadingState());
|
emit(WaterHeaterLoadingState());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
deviceStatus = WaterHeaterStatusModel.fromJson(event.deviceId, status.status);
|
||||||
deviceStatus =
|
|
||||||
WaterHeaterStatusModel.fromJson(event.deviceId, status.status);
|
|
||||||
|
|
||||||
if (deviceStatus.scheduleMode == ScheduleModes.countdown) {
|
if (deviceStatus.scheduleMode == ScheduleModes.countdown) {
|
||||||
final countdownRemaining = Duration(
|
final countdownRemaining = Duration(
|
||||||
@ -288,7 +297,6 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
inchingMinutes: deviceStatus.inchingMinutes,
|
inchingMinutes: deviceStatus.inchingMinutes,
|
||||||
isInchingActive: true,
|
isInchingActive: true,
|
||||||
));
|
));
|
||||||
//_startInchingTimer(emit, inchingDuration);
|
|
||||||
} else {
|
} else {
|
||||||
emit(WaterHeaterDeviceStatusLoaded(
|
emit(WaterHeaterDeviceStatusLoaded(
|
||||||
deviceStatus,
|
deviceStatus,
|
||||||
@ -316,7 +324,7 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_listenToChanges(deviceId) {
|
void _listenToChanges(deviceId) {
|
||||||
try {
|
try {
|
||||||
DatabaseReference ref =
|
DatabaseReference ref =
|
||||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||||
@ -328,12 +336,11 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
|
|
||||||
List<Status> statusList = [];
|
List<Status> statusList = [];
|
||||||
usersMap['status'].forEach((element) {
|
usersMap['status'].forEach((element) {
|
||||||
statusList
|
statusList.add(Status(code: element['code'], value: element['value']));
|
||||||
.add(Status(code: element['code'], value: element['value']));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
deviceStatus = WaterHeaterStatusModel.fromJson(
|
deviceStatus =
|
||||||
usersMap['productUuid'], statusList);
|
WaterHeaterStatusModel.fromJson(usersMap['productUuid'], statusList);
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(StatusUpdated(deviceStatus));
|
add(StatusUpdated(deviceStatus));
|
||||||
}
|
}
|
||||||
@ -341,7 +348,10 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onStatusUpdated(StatusUpdated event, Emitter<WaterHeaterState> emit) {
|
void _onStatusUpdated(
|
||||||
|
StatusUpdated event,
|
||||||
|
Emitter<WaterHeaterState> emit,
|
||||||
|
) {
|
||||||
deviceStatus = event.deviceStatus;
|
deviceStatus = event.deviceStatus;
|
||||||
emit(WaterHeaterDeviceStatusLoaded(deviceStatus));
|
emit(WaterHeaterDeviceStatusLoaded(deviceStatus));
|
||||||
}
|
}
|
||||||
@ -352,23 +362,13 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
) {
|
) {
|
||||||
_countdownTimer?.cancel();
|
_countdownTimer?.cancel();
|
||||||
|
|
||||||
_countdownTimer = Timer.periodic(const Duration(minutes: 1), (timer) {
|
_countdownTimer = Timer.periodic(
|
||||||
add(DecrementCountdownEvent());
|
const Duration(minutes: 1),
|
||||||
});
|
(timer) => add(DecrementCountdownEvent()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// void _startInchingTimer(
|
void _onDecrementCountdown(
|
||||||
// Emitter<WaterHeaterState> emit,
|
|
||||||
// Duration inchingDuration,
|
|
||||||
// ) {
|
|
||||||
// _inchingTimer?.cancel();
|
|
||||||
|
|
||||||
// _inchingTimer = Timer.periodic(const Duration(minutes: 1), (timer) {
|
|
||||||
// add(DecrementInchingEvent());
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
_onDecrementCountdown(
|
|
||||||
DecrementCountdownEvent event,
|
DecrementCountdownEvent event,
|
||||||
Emitter<WaterHeaterState> emit,
|
Emitter<WaterHeaterState> emit,
|
||||||
) {
|
) {
|
||||||
@ -382,105 +382,29 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
|
|
||||||
if (newRemaining <= Duration.zero) {
|
if (newRemaining <= Duration.zero) {
|
||||||
_countdownTimer?.cancel();
|
_countdownTimer?.cancel();
|
||||||
emit(currentState.copyWith(
|
emit(
|
||||||
countdownHours: 0,
|
currentState.copyWith(
|
||||||
countdownMinutes: 0,
|
countdownHours: 0,
|
||||||
isCountdownActive: false,
|
countdownMinutes: 0,
|
||||||
countdownRemaining: Duration.zero,
|
isCountdownActive: false,
|
||||||
));
|
countdownRemaining: Duration.zero,
|
||||||
|
),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int totalSeconds = newRemaining.inSeconds;
|
final totalSeconds = newRemaining.inSeconds;
|
||||||
|
final newHours = totalSeconds ~/ 3600;
|
||||||
|
final newMinutes = (totalSeconds % 3600) ~/ 60;
|
||||||
|
|
||||||
int newHours = totalSeconds ~/ 3600;
|
emit(
|
||||||
int newMinutes = (totalSeconds % 3600) ~/ 60;
|
currentState.copyWith(
|
||||||
|
countdownHours: newHours,
|
||||||
emit(currentState.copyWith(
|
countdownMinutes: newMinutes,
|
||||||
countdownHours: newHours,
|
countdownRemaining: newRemaining,
|
||||||
countdownMinutes: newMinutes,
|
),
|
||||||
countdownRemaining: newRemaining,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FutureOr<void> _onDecrementInching(
|
|
||||||
// DecrementInchingEvent event,
|
|
||||||
// Emitter<WaterHeaterState> emit,
|
|
||||||
// ) {
|
|
||||||
// if (state is WaterHeaterDeviceStatusLoaded) {
|
|
||||||
// final currentState = state as WaterHeaterDeviceStatusLoaded;
|
|
||||||
|
|
||||||
// if (currentState.inchingHours > 0 || currentState.inchingMinutes > 0) {
|
|
||||||
// final newRemaining = Duration(
|
|
||||||
// hours: currentState.inchingHours,
|
|
||||||
// minutes: currentState.inchingMinutes,
|
|
||||||
// ) -
|
|
||||||
// const Duration(minutes: 1);
|
|
||||||
|
|
||||||
// if (newRemaining <= Duration.zero) {
|
|
||||||
// _inchingTimer?.cancel();
|
|
||||||
// emit(currentState.copyWith(
|
|
||||||
// inchingHours: 0,
|
|
||||||
// inchingMinutes: 0,
|
|
||||||
// isInchingActive: false,
|
|
||||||
// ));
|
|
||||||
// } else {
|
|
||||||
// emit(currentState.copyWith(
|
|
||||||
// inchingHours: newRemaining.inHours,
|
|
||||||
// inchingMinutes: newRemaining.inMinutes % 60,
|
|
||||||
// ));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
Future<bool> _runDebounce({
|
|
||||||
required dynamic deviceId,
|
|
||||||
required String code,
|
|
||||||
required dynamic value,
|
|
||||||
required dynamic oldValue,
|
|
||||||
required Emitter<WaterHeaterState> emit,
|
|
||||||
required bool isBatch,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
late bool status;
|
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
|
||||||
|
|
||||||
if (isBatch) {
|
|
||||||
status = await DevicesManagementApi().deviceBatchControl(
|
|
||||||
deviceId,
|
|
||||||
code,
|
|
||||||
value,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
status = await DevicesManagementApi().deviceControl(
|
|
||||||
deviceId,
|
|
||||||
Status(code: code, value: value),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!status) {
|
|
||||||
_revertValue(code, oldValue, emit.call);
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
_revertValue(code, oldValue, emit.call);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _revertValue(String code, dynamic oldValue,
|
|
||||||
void Function(WaterHeaterState state) emit) {
|
|
||||||
_updateLocalValue(code, oldValue);
|
|
||||||
if (state is WaterHeaterDeviceStatusLoaded) {
|
|
||||||
final currentState = state as WaterHeaterDeviceStatusLoaded;
|
|
||||||
emit(currentState.copyWith(
|
|
||||||
status: deviceStatus,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -505,14 +429,12 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dynamic _getValueByCode(String code) {
|
dynamic _getValueByCode(String code) {
|
||||||
switch (code) {
|
return switch (code) {
|
||||||
case 'switch_1':
|
'switch_1' => deviceStatus.heaterSwitch,
|
||||||
return deviceStatus.heaterSwitch;
|
'countdown_1' =>
|
||||||
case 'countdown_1':
|
(deviceStatus.countdownHours * 60) + deviceStatus.countdownMinutes,
|
||||||
return deviceStatus.countdownHours * 60 + deviceStatus.countdownMinutes;
|
_ => null,
|
||||||
default:
|
};
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -521,13 +443,17 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _getSchedule(
|
Future<void> _getSchedule(
|
||||||
GetSchedulesEvent event, Emitter<WaterHeaterState> emit) async {
|
GetSchedulesEvent event,
|
||||||
|
Emitter<WaterHeaterState> emit,
|
||||||
|
) async {
|
||||||
emit(ScheduleLoadingState());
|
emit(ScheduleLoadingState());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
List<ScheduleModel> schedules = await DevicesManagementApi()
|
final schedules = await DevicesManagementApi().getDeviceSchedules(
|
||||||
.getDeviceSchedules(deviceStatus.uuid, event.category);
|
deviceStatus.uuid,
|
||||||
|
event.category,
|
||||||
|
);
|
||||||
|
|
||||||
emit(WaterHeaterDeviceStatusLoaded(
|
emit(WaterHeaterDeviceStatusLoaded(
|
||||||
deviceStatus,
|
deviceStatus,
|
||||||
@ -535,7 +461,6 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
scheduleMode: ScheduleModes.schedule,
|
scheduleMode: ScheduleModes.schedule,
|
||||||
));
|
));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
//(const WaterHeaterFailedState(error: 'Failed to fetch schedules.'));
|
|
||||||
emit(WaterHeaterDeviceStatusLoaded(
|
emit(WaterHeaterDeviceStatusLoaded(
|
||||||
deviceStatus,
|
deviceStatus,
|
||||||
schedules: const [],
|
schedules: const [],
|
||||||
@ -543,7 +468,7 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onAddSchedule(
|
Future<void> _onAddSchedule(
|
||||||
AddScheduleEvent event,
|
AddScheduleEvent event,
|
||||||
Emitter<WaterHeaterState> emit,
|
Emitter<WaterHeaterState> emit,
|
||||||
) async {
|
) async {
|
||||||
@ -557,8 +482,6 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
|
days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
|
||||||
);
|
);
|
||||||
|
|
||||||
// emit(ScheduleLoadingState());
|
|
||||||
|
|
||||||
bool success = await DevicesManagementApi()
|
bool success = await DevicesManagementApi()
|
||||||
.addScheduleRecord(newSchedule, currentState.status.uuid);
|
.addScheduleRecord(newSchedule, currentState.status.uuid);
|
||||||
|
|
||||||
@ -566,13 +489,14 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
add(GetSchedulesEvent(category: 'switch_1', uuid: deviceStatus.uuid));
|
add(GetSchedulesEvent(category: 'switch_1', uuid: deviceStatus.uuid));
|
||||||
} else {
|
} else {
|
||||||
emit(currentState);
|
emit(currentState);
|
||||||
//emit(const WaterHeaterFailedState(error: 'Failed to add schedule.'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onEditSchedule(EditWaterHeaterScheduleEvent event,
|
Future<void> _onEditSchedule(
|
||||||
Emitter<WaterHeaterState> emit) async {
|
EditWaterHeaterScheduleEvent event,
|
||||||
|
Emitter<WaterHeaterState> emit,
|
||||||
|
) async {
|
||||||
if (state is WaterHeaterDeviceStatusLoaded) {
|
if (state is WaterHeaterDeviceStatusLoaded) {
|
||||||
final currentState = state as WaterHeaterDeviceStatusLoaded;
|
final currentState = state as WaterHeaterDeviceStatusLoaded;
|
||||||
|
|
||||||
@ -584,8 +508,6 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
|
days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
|
||||||
);
|
);
|
||||||
|
|
||||||
// emit(ScheduleLoadingState());
|
|
||||||
|
|
||||||
bool success = await DevicesManagementApi().editScheduleRecord(
|
bool success = await DevicesManagementApi().editScheduleRecord(
|
||||||
currentState.status.uuid,
|
currentState.status.uuid,
|
||||||
newSchedule,
|
newSchedule,
|
||||||
@ -595,12 +517,11 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
add(GetSchedulesEvent(category: 'switch_1', uuid: deviceStatus.uuid));
|
add(GetSchedulesEvent(category: 'switch_1', uuid: deviceStatus.uuid));
|
||||||
} else {
|
} else {
|
||||||
emit(currentState);
|
emit(currentState);
|
||||||
//emit(const WaterHeaterFailedState(error: 'Failed to add schedule.'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onUpdateSchedule(
|
Future<void> _onUpdateSchedule(
|
||||||
UpdateScheduleEntryEvent event,
|
UpdateScheduleEntryEvent event,
|
||||||
Emitter<WaterHeaterState> emit,
|
Emitter<WaterHeaterState> emit,
|
||||||
) async {
|
) async {
|
||||||
@ -627,20 +548,17 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
emit(currentState.copyWith(schedules: updatedSchedules));
|
emit(currentState.copyWith(schedules: updatedSchedules));
|
||||||
} else {
|
} else {
|
||||||
emit(currentState);
|
emit(currentState);
|
||||||
// emit(const WaterHeaterFailedState(error: 'Failed to update schedule.'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onDeleteSchedule(
|
Future<void> _onDeleteSchedule(
|
||||||
DeleteScheduleEvent event,
|
DeleteScheduleEvent event,
|
||||||
Emitter<WaterHeaterState> emit,
|
Emitter<WaterHeaterState> emit,
|
||||||
) async {
|
) async {
|
||||||
if (state is WaterHeaterDeviceStatusLoaded) {
|
if (state is WaterHeaterDeviceStatusLoaded) {
|
||||||
final currentState = state as WaterHeaterDeviceStatusLoaded;
|
final currentState = state as WaterHeaterDeviceStatusLoaded;
|
||||||
|
|
||||||
// emit(ScheduleLoadingState());
|
|
||||||
|
|
||||||
bool success = await DevicesManagementApi()
|
bool success = await DevicesManagementApi()
|
||||||
.deleteScheduleRecord(currentState.status.uuid, event.scheduleId);
|
.deleteScheduleRecord(currentState.status.uuid, event.scheduleId);
|
||||||
|
|
||||||
@ -652,20 +570,22 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
emit(currentState.copyWith(schedules: updatedSchedules));
|
emit(currentState.copyWith(schedules: updatedSchedules));
|
||||||
} else {
|
} else {
|
||||||
emit(currentState);
|
emit(currentState);
|
||||||
// emit(const WaterHeaterFailedState(error: 'Failed to delete schedule.'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _batchFetchWaterHeater(FetchWaterHeaterBatchStatusEvent event,
|
Future<void> _batchFetchWaterHeater(
|
||||||
Emitter<WaterHeaterState> emit) async {
|
FetchWaterHeaterBatchStatusEvent event,
|
||||||
|
Emitter<WaterHeaterState> emit,
|
||||||
|
) async {
|
||||||
emit(WaterHeaterLoadingState());
|
emit(WaterHeaterLoadingState());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final status =
|
final status = await DevicesManagementApi().getBatchStatus(
|
||||||
await DevicesManagementApi().getBatchStatus(event.devicesUuid);
|
event.devicesUuid,
|
||||||
deviceStatus = WaterHeaterStatusModel.fromJson(
|
);
|
||||||
event.devicesUuid.first, status.status);
|
deviceStatus =
|
||||||
|
WaterHeaterStatusModel.fromJson(event.devicesUuid.first, status.status);
|
||||||
|
|
||||||
emit(WaterHeaterDeviceStatusLoaded(deviceStatus));
|
emit(WaterHeaterDeviceStatusLoaded(deviceStatus));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -673,8 +593,8 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _batchControlWaterHeater(ControlWaterHeaterBatchEvent event,
|
Future<void> _batchControlWaterHeater(
|
||||||
Emitter<WaterHeaterState> emit) async {
|
ControlWaterHeaterBatchEvent event, Emitter<WaterHeaterState> emit) async {
|
||||||
if (state is WaterHeaterDeviceStatusLoaded) {
|
if (state is WaterHeaterDeviceStatusLoaded) {
|
||||||
final currentState = state as WaterHeaterDeviceStatusLoaded;
|
final currentState = state as WaterHeaterDeviceStatusLoaded;
|
||||||
|
|
||||||
@ -686,13 +606,10 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
|
|||||||
status: deviceStatus,
|
status: deviceStatus,
|
||||||
));
|
));
|
||||||
|
|
||||||
final success = await _runDebounce(
|
final success = await batchControlDevicesService.batchControlDevices(
|
||||||
deviceId: event.devicesUuid,
|
uuids: event.devicesUuid,
|
||||||
code: event.code,
|
code: event.code,
|
||||||
value: event.value,
|
value: event.value,
|
||||||
oldValue: oldValue,
|
|
||||||
emit: emit,
|
|
||||||
isBatch: true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
// water_heater_state.dart
|
|
||||||
|
|
||||||
part of 'water_heater_bloc.dart';
|
part of 'water_heater_bloc.dart';
|
||||||
|
|
||||||
sealed class WaterHeaterState extends Equatable {
|
sealed class WaterHeaterState extends Equatable {
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart';
|
||||||
|
|
||||||
|
abstract final class WaterHeaterBlocFactory {
|
||||||
|
const WaterHeaterBlocFactory._();
|
||||||
|
|
||||||
|
static WaterHeaterBloc create({
|
||||||
|
required String deviceId,
|
||||||
|
}) {
|
||||||
|
return WaterHeaterBloc(
|
||||||
|
deviceId: deviceId,
|
||||||
|
controlDeviceService:
|
||||||
|
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||||
|
batchControlDevicesService:
|
||||||
|
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,16 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||||
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/water_heater/factories/water_heater_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
|
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
|
||||||
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 WaterHEaterBatchControlView extends StatelessWidget with HelperResponsiveLayout {
|
class WaterHEaterBatchControlView extends StatelessWidget
|
||||||
|
with HelperResponsiveLayout {
|
||||||
const WaterHEaterBatchControlView({super.key, required this.deviceIds});
|
const WaterHEaterBatchControlView({super.key, required this.deviceIds});
|
||||||
|
|
||||||
final List<String> deviceIds;
|
final List<String> deviceIds;
|
||||||
@ -17,8 +18,9 @@ class WaterHEaterBatchControlView extends StatelessWidget with HelperResponsiveL
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) =>
|
create: (context) => WaterHeaterBlocFactory.create(
|
||||||
WaterHeaterBloc()..add(FetchWaterHeaterBatchStatusEvent(devicesUuid: deviceIds)),
|
deviceId: deviceIds.first,
|
||||||
|
)..add(FetchWaterHeaterBatchStatusEvent(devicesUuid: deviceIds)),
|
||||||
child: BlocBuilder<WaterHeaterBloc, WaterHeaterState>(
|
child: BlocBuilder<WaterHeaterBloc, WaterHeaterState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is WaterHeaterLoadingState) {
|
if (state is WaterHeaterLoadingState) {
|
||||||
|
@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
|
|||||||
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/water_heater/factories/water_heater_bloc_factory.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
|
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/water_heater/widgets/schedual_view.dart';
|
import 'package:syncrow_web/pages/device_managment/water_heater/widgets/schedual_view.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
@ -21,8 +22,9 @@ class WaterHeaterDeviceControlView extends StatelessWidget
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) =>
|
create: (context) => WaterHeaterBlocFactory.create(
|
||||||
WaterHeaterBloc()..add(WaterHeaterFetchStatusEvent(device.uuid!)),
|
deviceId: device.uuid ?? '',
|
||||||
|
)..add(WaterHeaterFetchStatusEvent(device.uuid!)),
|
||||||
child: BlocBuilder<WaterHeaterBloc, WaterHeaterState>(
|
child: BlocBuilder<WaterHeaterBloc, WaterHeaterState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is WaterHeaterLoadingState) {
|
if (state is WaterHeaterLoadingState) {
|
||||||
@ -33,8 +35,7 @@ class WaterHeaterDeviceControlView extends StatelessWidget
|
|||||||
state is WaterHeaterBatchFailedState) {
|
state is WaterHeaterBatchFailedState) {
|
||||||
return const Center(child: Text('Error fetching status'));
|
return const Center(child: Text('Error fetching status'));
|
||||||
} else {
|
} else {
|
||||||
return const SizedBox(
|
return const SizedBox(height: 200, child: Center(child: SizedBox()));
|
||||||
height: 200, child: Center(child: SizedBox()));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:dio/dio.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:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||||
@ -15,6 +16,7 @@ import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
|||||||
import 'package:syncrow_web/pages/routines/models/routine_details_model.dart';
|
import 'package:syncrow_web/pages/routines/models/routine_details_model.dart';
|
||||||
import 'package:syncrow_web/pages/routines/models/routine_model.dart';
|
import 'package:syncrow_web/pages/routines/models/routine_model.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||||
|
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||||
import 'package:syncrow_web/services/routines_api.dart';
|
import 'package:syncrow_web/services/routines_api.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
@ -64,7 +66,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
TriggerSwitchTabsEvent event,
|
TriggerSwitchTabsEvent event,
|
||||||
Emitter<RoutineState> emit,
|
Emitter<RoutineState> emit,
|
||||||
) {
|
) {
|
||||||
emit(state.copyWith(routineTab: event.isRoutineTab, createRoutineView: false));
|
emit(state.copyWith(
|
||||||
|
routineTab: event.isRoutineTab, createRoutineView: false));
|
||||||
add(ResetRoutineState());
|
add(ResetRoutineState());
|
||||||
if (event.isRoutineTab) {
|
if (event.isRoutineTab) {
|
||||||
add(const LoadScenes());
|
add(const LoadScenes());
|
||||||
@ -90,8 +93,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.indexWhere(
|
||||||
updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
(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,18 +103,21 @@ 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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAddToThenContainer(AddToThenContainer event, Emitter<RoutineState> emit) {
|
void _onAddToThenContainer(
|
||||||
|
AddToThenContainer event, Emitter<RoutineState> emit) {
|
||||||
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.indexWhere(
|
||||||
currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
(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 +128,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;
|
||||||
|
|
||||||
@ -157,7 +164,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
// currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions);
|
// currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions);
|
currentSelectedFunctions[event.uniqueCustomId] =
|
||||||
|
List.from(event.functions);
|
||||||
|
|
||||||
emit(state.copyWith(selectedFunctions: currentSelectedFunctions));
|
emit(state.copyWith(selectedFunctions: currentSelectedFunctions));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -165,24 +173,30 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onLoadScenes(LoadScenes event, Emitter<RoutineState> emit) async {
|
Future<void> _onLoadScenes(
|
||||||
|
LoadScenes event, Emitter<RoutineState> emit) async {
|
||||||
emit(state.copyWith(isLoading: true, errorMessage: null));
|
emit(state.copyWith(isLoading: true, errorMessage: null));
|
||||||
List<ScenesModel> scenes = [];
|
List<ScenesModel> scenes = [];
|
||||||
try {
|
try {
|
||||||
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.selectedCommunityId, projectUuid));
|
createRoutineBloc.selectedSpaceId,
|
||||||
|
createRoutineBloc.selectedCommunityId,
|
||||||
|
projectUuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
@ -199,7 +213,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 +222,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 +253,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 +276,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)) {
|
||||||
@ -267,7 +290,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
|
|
||||||
if (_isLastActionDelay(state.thenItems)) {
|
if (_isLastActionDelay(state.thenItems)) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
errorMessage: 'A delay condition cannot be the only or the last action',
|
errorMessage:
|
||||||
|
'A delay condition cannot be the only or the last action',
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
));
|
));
|
||||||
return;
|
return;
|
||||||
@ -335,15 +359,18 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
errorMessage: 'Something went wrong',
|
errorMessage: 'Something went wrong',
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on APIException catch (e) {
|
||||||
|
final errorData = e.message;
|
||||||
|
String errorMessage = errorData;
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errorMessage: 'Something went wrong',
|
errorMessage: errorMessage,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@ -365,7 +392,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
|
|
||||||
if (_isLastActionDelay(state.thenItems)) {
|
if (_isLastActionDelay(state.thenItems)) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
errorMessage: 'A delay condition cannot be the only or the last action',
|
errorMessage:
|
||||||
|
'A delay condition cannot be the only or the last action',
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
));
|
));
|
||||||
CustomSnackBar.redSnackBar('Cannot have delay as the last action');
|
CustomSnackBar.redSnackBar('Cannot have delay as the last action');
|
||||||
@ -456,7 +484,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());
|
||||||
@ -468,26 +497,32 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
));
|
));
|
||||||
CustomSnackBar.redSnackBar('Something went wrong');
|
CustomSnackBar.redSnackBar('Something went wrong');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on APIException catch (e) {
|
||||||
|
final errorData = e.message;
|
||||||
|
String errorMessage = errorData;
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errorMessage: 'Something went wrong',
|
errorMessage: errorMessage,
|
||||||
));
|
));
|
||||||
CustomSnackBar.redSnackBar('Something went wrong');
|
CustomSnackBar.redSnackBar(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
@ -498,7 +533,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
isAutomation: false,
|
isAutomation: false,
|
||||||
isTabToRun: false));
|
isTabToRun: false));
|
||||||
} else {
|
} else {
|
||||||
emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions));
|
emit(state.copyWith(
|
||||||
|
ifItems: ifItems, selectedFunctions: selectedFunctions));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -510,11 +546,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 +679,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,
|
||||||
@ -689,10 +728,12 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
// if (!deviceCards.containsKey(deviceId)) {
|
// if (!deviceCards.containsKey(deviceId)) {
|
||||||
deviceCards[deviceId] = {
|
deviceCards[deviceId] = {
|
||||||
'entityId': action.entityId,
|
'entityId': action.entityId,
|
||||||
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
|
'deviceId':
|
||||||
'uniqueCustomId': action.type == 'automation' || action.actionExecutor == 'delay'
|
action.actionExecutor == 'delay' ? 'delay' : action.entityId,
|
||||||
? action.entityId
|
'uniqueCustomId':
|
||||||
: const Uuid().v4(),
|
action.type == 'automation' || action.actionExecutor == 'delay'
|
||||||
|
? action.entityId
|
||||||
|
: const Uuid().v4(),
|
||||||
'title': action.actionExecutor == 'delay'
|
'title': action.actionExecutor == 'delay'
|
||||||
? 'Delay'
|
? 'Delay'
|
||||||
: action.type == 'automation'
|
: action.type == 'automation'
|
||||||
@ -732,7 +773,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 +840,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: [],
|
||||||
@ -822,7 +865,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
createRoutineView: false));
|
createRoutineView: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _deleteScene(DeleteScene event, Emitter<RoutineState> emit) async {
|
FutureOr<void> _deleteScene(
|
||||||
|
DeleteScene event, Emitter<RoutineState> emit) async {
|
||||||
try {
|
try {
|
||||||
final projectId = await ProjectManager.getProjectUUID() ?? '';
|
final projectId = await ProjectManager.getProjectUUID() ?? '';
|
||||||
|
|
||||||
@ -831,7 +875,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],
|
||||||
@ -854,11 +899,14 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
add(const LoadAutomation());
|
add(const LoadAutomation());
|
||||||
add(ResetRoutineState());
|
add(ResetRoutineState());
|
||||||
emit(state.copyWith(isLoading: false, createRoutineView: false));
|
emit(state.copyWith(isLoading: false, createRoutineView: false));
|
||||||
} catch (e) {
|
} on APIException catch (e) {
|
||||||
|
final errorData = e.message;
|
||||||
|
String errorMessage = errorData;
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errorMessage: 'Failed to delete scene',
|
errorMessage: errorMessage,
|
||||||
));
|
));
|
||||||
|
CustomSnackBar.redSnackBar(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -876,7 +924,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 +934,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 +957,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)) {
|
||||||
@ -918,7 +972,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
|
|
||||||
if (_isLastActionDelay(state.thenItems)) {
|
if (_isLastActionDelay(state.thenItems)) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
errorMessage: 'A delay condition cannot be the only or the last action',
|
errorMessage:
|
||||||
|
'A delay condition cannot be the only or the last action',
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
));
|
));
|
||||||
return;
|
return;
|
||||||
@ -971,7 +1026,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 +1046,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(
|
||||||
@ -1114,10 +1171,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
errorMessage: result['message'],
|
errorMessage: result['message'],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on APIException catch (e) {
|
||||||
|
final errorData = e.message;
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errorMessage: 'Something went wrong',
|
errorMessage: errorData,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1214,7 +1272,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
// if (!deviceThenCards.containsKey(deviceId)) {
|
// if (!deviceThenCards.containsKey(deviceId)) {
|
||||||
deviceThenCards[deviceId] = {
|
deviceThenCards[deviceId] = {
|
||||||
'entityId': action.entityId,
|
'entityId': action.entityId,
|
||||||
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
|
'deviceId':
|
||||||
|
action.actionExecutor == 'delay' ? 'delay' : action.entityId,
|
||||||
'uniqueCustomId': const Uuid().v4(),
|
'uniqueCustomId': const Uuid().v4(),
|
||||||
'title': action.actionExecutor == 'delay'
|
'title': action.actionExecutor == 'delay'
|
||||||
? 'Delay'
|
? 'Delay'
|
||||||
@ -1249,7 +1308,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
updatedFunctions[uniqueCustomId] = [];
|
updatedFunctions[uniqueCustomId] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.executorProperty != null && action.actionExecutor != 'delay') {
|
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 (var function in functions) {
|
for (var function in functions) {
|
||||||
@ -1291,10 +1351,14 @@ 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 +1380,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 {
|
||||||
@ -1358,24 +1423,29 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
final updatedAutomations = await SceneApi.getAutomationByUnitId(
|
final updatedAutomations = await SceneApi.getAutomationByUnitId(
|
||||||
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()}',
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user